# Crystal Vault(晶体库) 把一文件夹的 Markdown 卡片变成一座**能看的知识晶体库**。 **QQ 群:831440768** 截图预览: 桌面模式: Crystal Vault overview 故事线模式: 故事线模式 而它真正的战场是**文献模式**:一边读 PDF / 图片 / Markdown,一边把读到的东西当场立成结构—— 读完一章,那一章的结构也就搭好了。**桌面**把这些摆成一桌能拖能缩的窗, **结构窗**让库的结构跟你正在读的那一页并排待着,不用切走。 卡片就是普通的 Markdown,文件夹就是文件夹。没有任何东西被锁进数据库,也不出你的 vault。 > English: [README.en.md](README.en.md)。 --- ## 主功能:文献模式 · 桌面 阅读器有两种版式:**网格**是一屏摊开 N 页(翻页扫读用),**桌面**是一页一扇窗 ——后者是主力,因为它把「我要同时看几样东西」这件事交回给你自己。 - **一页一扇窗,摆法自己定。** 拖标题栏挪位置、拉右下角改大小, **关掉阅读器再打开,摆法原样还在**。 - **桌上不只有文献页。** 卡片、结构、网页都是同一种窗,共用同一套拖、缩、收。 卡片窗顶上会显示这张卡的**概念**,那行里的公式照常渲染。 - **收纳栏**(顶栏那颗按钮)。左边出来一条竖栏,是这些窗的架子:把窗拖到栏上, 或者按窗上的「收纳」,它就收起来、栏里留一条,点一下再拿出来。 **收着的窗,关掉阅读器也还在库里。** - **外部标签页**(收纳栏最上面那颗 `+`)。填一条网址,网课、文档站就挂在你正读的 那一页旁边,和别的窗一样能拖能收。 > ⚠️ 网页**嵌不嵌得进来由那个站说了算**,不是这个插件说了算:站点可以用响应头 > 拒绝被别的页面嵌(Google 系一律拒绝),这是浏览器的规矩,绕不过去。 > 所以那扇窗里常驻一颗「**用系统浏览器打开**」——嵌得进去当窗用,嵌不进去也始终有出路, > 不会给你一个白框。 - **右栏也能收。** 同一颗「收纳栏」按钮还能把右边「边看边记」那一栏**挤回去**,读宽一点。 ### + 页:Markdown 是按行分页的 顶栏那颗「**+ 页**」每按一次,就把这份文献的**下一段**摆到桌面上。 对 Markdown 来说「一段」是**行数**——接着桌上最后一扇的结束行,往下 40 行。 想改某一扇看的是哪几行,就改它标题栏里那两个框:「**起始行**」和「**结束行**」, **改完按回车**。旁边那颗「共 N 行」是这份文件今天的总行数,挑区间时拿它当尺子。 > 同样的规矩也适用于 PDF / 图片那一档:它标题栏里那个框是**页码**,**也是改完按回车**。 这是和桌面配着用的那一半。 读文献最烦的是「这块东西在库里到底长什么样」——切过去看一眼,回来就得重新找刚才那行。 结构窗把这层结构开成**你桌上的一扇窗**,和正在读的那一页并排: - **看一眼,不是离开。** 切走是「离开」;一扇窗只是瞥一眼。 - **固定看一颗。** 顶栏那颗「晶体:…」换看哪颗,下次打开还是它。 - **顶栏挡着就收起来。** 顶栏最左边那颗 **▲** 是整条栏的开关,**颜色就是状态**: **蓝** = 按钮都摆着;**白** = 全收起来了,屏幕上只剩这一颗。再点一下全回来。 收没收起记着,下次开窗不用重收。 ### 画线:看 / 写 顶栏那颗「**画线:看 / 写**」管的是**拖一根线的后果**: | 档位 | 拖一根线,会发生什么 | | --- | --- | | **看**(默认) | 画一根**金线**。它存在库里,**一个字都不碰你的笔记**。 | | **写** | 往起点那张卡的正文里**真写**一条 `[[目标卡]]`——笔记跟着变。线是**蓝**的。 | 蓝跟金的区别说的就是这件事:**金线是一种看法,蓝线是笔记里真有的东西。** 你亲手拖过连接点的那一根线,**接法会写进这张卡自己的 frontmatter**(字段 `晶体接法`)——所以它跟着笔记文件走:改名、搬到别的文件夹、**压缩发给别人**, 都还在。没过手的那些不写,它们按位置算就够了。 蓝线接在卡片的哪一边,**默认按位置算**:上下叠着的两张走「下→上」,左右摆的走 「右→左」。想改成别的走法,在**写**模式下从你要的那条边拖一次,**那一根就按你拖的走** (手动拖过的永远压过默认)。 在**写**模式下把蓝线框住删掉,删的是**卡片正文里那条 `[[链接]]`**,不是画在屏幕上的东西。 删错了可以撤销一次(和卡片编辑那条撤销同一套)。 ### 右键:三种落点,三种结果 - **对着卡片右键 → 隐藏这张卡片所有的入链和出链。** 一张卡连进连出十几根线的时候,把它整个收干净、只留别的, 一眼就能看清剩下的结构。**再右键一次就放回来**; 只要有东西藏着,顶栏就会出现一颗「**显示全部**」,一下全放回来。 - **右键落在金线上 → 进连线编辑模式。** 顶栏出现「**选框:线**」和「**选框**」两颗, 拖鼠标就能框住几根线;框住之后出现「**删除实线(N)**」。 (右边那颗「选框:线」点一下会变成「框:卡」,那是**整批挪卡片**用的,见下一节。) - **右键空白处(在「写」档下)→ 也出现「选框」,但这一档框的是蓝线。** 框住之后是「**删除蓝线(N)**」——按下去会**从卡片正文里**把对应的那条 `[[链接]]` 删掉。 > 一个细节:**「对着卡片右键」这个入口是由「写」档开的**——在「看」档下, > 右键卡片仍然是老行为(进连接模式)。但**已经藏起来的卡,两种档位下右键它都管用**, > 不然你一切回「看」,藏起来的东西就只剩顶栏那颗按钮能救了。 ### 收纳方框:把一片收干净,但线不算断 画面上那些虚线框是**收纳方框**。**每个子晶体自动一个**,名字就是文件夹名; 框可以**改名**(点名字)也可以**收起**(点左边那个小三角)。 框有两种颜色,含义不一样: | | 是什么 | 是容器吗 | | --- | --- | --- | | **金色**(点线,暖) | **子晶体**——文件夹长出来的 | **是**,见下 | | **蓝色**(虚线,冷) | 你用「**+ 框**」**自己画的** | 不是,卡片随便进出 | **金框一层一个、层层套起来**(1.3.88 起)。一个文件夹的框里,装着它**自己的卡**, 外加它**每个子文件夹各自的框**,所以整棵树的层级一路看得见。框内部横着切: ``` ┌ A ─────────────────────────────────┐ │ [A 自己的卡] ┃ [B1 的卡] ┊ [B2 的卡] │ └────────────────┸────────────┸──────┘ 珊瑚橙竖线 ↑ 金色竖线 ↑ (本级 ↔ 子文件夹) (同级 ↔ 同级) ``` - **珊瑚橙 `#FF6B6B`** 分的是这个文件夹**自己的卡**和它的**子文件夹**; - **金色**分的是两个**同级**子文件夹。 **金框是容器。** 里面的卡拖不进别的金框,也拖不过那条珊瑚橙线到上一级"自己的卡"里 ——**卡片停在边界上**。**蓝框不能移出它所在的那个金框**(拖和拉大小都出不去), 但蓝框本身**不是**容器,卡片照样进出。**拖金框时里面所有东西跟着走**,蓝框也跟。 > **你画的框属于你画它的那一层。** 在「Python/数据分析」里建的框,切到 > 「Python/爬虫」就看不见了——回到那一层它原样还在。以前不是这样:框是全局的, > 换一个文件夹看,别的层画的框还在原地摆着。 点「**+ 框**」建出来的框落在**你正看着的地方**(视口正中),而不是世界的某个固定角落 ——以前框建在固定的 (60,60),视口推远了就得先推回去找它。新建的卡片同理。 **框里任何一块空白都能拖动整框**(1.3.83 起),不只是顶上那条标题栏——按住框的边、 框的空处都行,框和里面的卡一起挪。压在卡片上时拖的仍然是卡片(卡片层级更高), 所以想挪卡片就按卡片,想挪整框就按框的空白处。拖动**按格点走**,见下面 「一格一格地摆」。 收起之后,框里的卡和连着它们的线一起收起来——**但「那边还有东西」这件事不会丢**: - 外面凡是**有蓝线连进去**的卡,右上角点一个**黄色实心圆点**; - 鼠标悬停那张卡,和它有关联的、正收着的框会**绕边闪一圈**。 不这么做的话,收起读起来就是「我的双链丢了」,而你明明没删。 框展开着的时候,里面的卡照常可以往外连蓝线——**跨级连也没问题**。 ### 框:卡 —— 一次挪好几张 结构窗顶栏有两颗挨着的按钮,**各管一件事**,别混: | 按钮 | 它说的是 | | --- | --- | | **选框:线** ⇄ **框:卡** | 框住的**东西是什么** | | **选框** ⇄ **退出选框** | 现在**能不能框** | 默认是「选框:线」:右键进连线编辑模式之后,拖鼠标框住几根线,再点「删除实线 / 删除蓝线」。 点一下「选框:线」,它变成「**框:卡**」——这时拖出来的方框**框的是卡片**: 1. 框住几张卡,它们会**亮起青边**; 2. **按住其中任意一张拖走 = 整批一起动**; 3. 松手时整批一起判归属:**拖进收纳方框就归它,拖到框外就移出来**; 4. 想清掉它们:点顶栏那颗**红框的「删除卡片(n)」**——**连笔记文件一起**进回收站。 少了这一条,把十几张卡收进一个框就只能**一张一张拖**;要清掉一批,就只能切回库里 一张一张走「删除卡片」。 > - 按住**没被框住**的那张,只挪它自己——框选之后顺手拖一张别的, > 意思显然是「我要挪这一张」,不是「顺便把刚才那几张也带上」。 > - **「删除卡片」删的是卡片文件本身**(不是解绑、不是隐藏):它连那张笔记一起进 > **回收站**,能捡回来;那张卡所在的**晶体不动**。它只在真有卡被框住时才出现, > 张数写在按钮上——这扇窗没有确认弹窗,**按钮上的数、悬停那句话、底下那条说明 > 就是全部的告知**。 > - **按 D 在卡档里不删卡**:一个裸按键就能把好几个文件扔进回收站太容易误触 > (那会儿手正放在方向键上挪卡片)。要删就走那颗按钮。 > - 框选本身**不动你的笔记一个字**——「删除实线 / 删除蓝线」那颗按钮在卡档里会 > 自己收起来,因为卡档里没有线可删。 > - 框选完之后想让它们不亮:**点一下空白**,或者按 **Esc**。 ### 卡片的位置会跟着笔记走 **卡片摆在哪儿,写进卡片自己的 frontmatter 里**(字段叫 `晶体坐标`)。所以换一台电脑、 装同一个插件、打开同一个库,**卡片的相对摆放位置和你这台机器上一模一样**—— 视图状态是每台机器各存各的,只有写进文件才跟着走。 字段长这样,单位是「格」(最小单位 = 卡片宽的 1/10 = 21px),**以左下角为准**: ```yaml 晶体坐标: [3, 5] ``` > 字段名带「晶体」前缀是**故意的**:这个插件装在别人的库里,而 `坐标` 是个 > 太通用的名字——谁要是有个自用的同名字段,会被静默顶掉。 > - **金色方框不用单独存**:它的位置和大小就是里面卡片的包围盒,卡片跟着文件走, > 它自然跟着走。 > - **收纳方框也有自己的文件**:每颗晶体一个 `.crystal-boxes.json`,就放在那颗晶体的 > 文件夹里。点开头 → Obsidian 的文件列表里看不见,也**不会变成一张卡片** > (适配层另外只认 `.md`,两道都挡得住)。里面存的是框的**左下角**坐标、 > 名字、大小、**收起状态**和成员。手改前先关掉 Obsidian。 > - ⚠️ **同步通道必须带点开头的文件**。FNS 走文件系统那一层,没问题; > 换成会跳过 `.` 开头的通道(Obsidian 自带的 Sync 就是),这个文件传不过去, > 框就只在你这台机器上——那种情况下你不会收到任何提示。 > - **外来卡(「导入卡片」引过来的)也不写**:它在你这层的落点和它在自己晶体里的 > 位置是两回事,写进去就等于改了它老家的位置。 > - 写盘是**拖完停一小会儿**才做的(你有多端同步,拖几下就写几次会引发同步风暴)。 > 写完之前关掉库也会催一下,不会白摆。 ### 一格一格地摆:卡片的格点与方向键 卡片在故事线里落在一层**格点**上,坐标以**左下角**为准——两张卡并排时,是**下边对齐**。 | 按键 | 一步走多远 | | --- | --- | | **← →** | **42px**(最小单位的 2 倍,5 步正好一张卡宽) | | **↑ ↓** | **21px**(最小单位) | **最小单位 = 卡片宽的 1/10 = 21px。** 就是这么算出来的:每张卡大小一样,拿它当尺子, 整张图自然就对得齐。 拖一张卡,停在哪儿就自动落到最近的格点上;框选之后按方向键,一格一格挪。 > - **收纳方框也走格点**(1.3.83 起):拖动时**框的左下角**吸到格点上,框里的卡按 > **同一段位移整体挪**,它们之间的相对位置一点不变。**大小仍然随你拉**—— > 框是你**画**出来的容器,多大由你定,只有摆在哪儿才需要对齐。 > - **金色方框(文件夹长出来的那个)按整格移动。** 它的位置和大小是从里面的卡片 > **算出来的**,自己没有坐标可吸,所以吸的是**位移**:框和里面的卡一起按整格的 > 整数倍走,同样是整体平移。 > - **已经摆好的旧位置不会被集体挪动。** 格点只在你下一次拖它 / 按方向键时生效 > ——不然那是一次没人按过按钮的破坏。代价是一张从没被碰过的老卡可能停在格外, > 动它一下就归位。 ### 导入卡片:把别的晶体里的卡借过来 一颗晶体只画得下**它自己文件夹里**的卡。可你常常想让 A 晶体的卡去连 B 晶体的卡—— 这时候点结构窗顶栏的「**导入卡片**」,从别的晶体挑一张进来: 1. 它**落在窗的正中间**(不是自动排到某个角落——你看得见,才知道它进来了); 2. 拖它进任意一个收纳方框就归它,**跨级连蓝线**从此成立; 3. 不想要了,点它**右上角那颗 ✕**。 引进来的卡是**点线紫框**,一眼能和本层的卡分开。**它本身一个字都不动**: 还在原来那个文件夹里,别人指向它的双链也都在。拿走再引回来,连你给它摆的位置都还在。 > - 引一张**本来就在这颗晶体里**的卡会被挡下来,并告诉你为什么。 > - 只有**结构窗**里能引;引进来之后,在晶体库的故事线上看同一颗晶体,它也在那儿。 > - 「删除实线 / 删除蓝线」和它无关——**引卡不会删任何东西**。 > - 一颗晶体最多引 500 张(这只是一个防坏存档的上限,正常用不到)。 > - 引来的卡**进不了子晶体框**——那种框的成员是文件夹长出来的。拖进去它会告诉你 > 一句,要给它分组请用「+ 框」建一个手动框。 > - 选择器里点**文件夹名**是展开它(不是"就选这个文件夹")。挑卡片的三档 > (导入 / 删除卡 / 重命名卡)都是这个手感。 ## 边看边记:读到什么,当场立成卡 阅读器右边那一栏。填**卡片名 / 概念 / 来源 / 正文**就能建卡,不用离开这一页。 - **想用真编辑器写**:点「✎ 在编辑器里写」,它先把卡建出来,正文改用 Obsidian 自己的 实时渲染编辑器(边写边渲染、自动存盘)。 - **反悔了**:点「返回」,刚建的那张卡撤掉,正文原封不动转进**草稿纸**。 - **草稿纸**:一块用原生编辑器写的便签,落在你指定的文件夹里,**不建成卡片**。 - **卡片盒**:搜库里的卡片,点一张就摆到桌面上,并在那张卡里留一行**指回这一页**的链接。 - **重命名卡片 / 重命名晶体**就在这一栏。走的是 Obsidian 自己的改名通道, 所以**全库指向它的 `[[双链]]` 会一起跟着改**。 - **挑文件夹那块面板抬头有一颗「取消」**(1.3.83 起)。「将建在」、新建 / 删除 / 重命名晶体、删除 / 重命名卡片、结构窗挑晶体,打开的都是这块面板,**关闭统一走 这一颗**——它只收起面板,什么都不改。 ## 晶体库:不读文献的时候 - **一个文件夹 = 一颗晶体**。卡片目录下的每个子文件夹在环上就是一颗晶体,可以一层层钻进去, 面包屑随时告诉你站在哪儿。 - **双链变成形状**。正文里写 `[[另一张卡]]`,两张卡之间就出现一根线。故事线按连线拓扑 把整颗晶体铺开。A→B 与 B→A **合成一条、两头带箭头**:一个箭头是单向,两个是互相。 - **孤岛是信号,不是错误**。既没链出去也没人链它的卡会被标出来——那是还没接进任何脉络的卡。 - **先想再看**。回忆模式下卡片内容默认遮住,点一下才显示;复习模式全部摊开。 - **就地改**。点面板右上角那颗笔,原地改概念 / 来源 / 标签 / 正文——不跳转、不开新标签页。 - **删除走回收站**。删晶体、删卡片,都按你在「文件与链接 → 删除的文件」里选的那一档,能捡回来。 ## 为什么不直接用图谱? 图谱回答的是「**这张笔记和谁有关系**」——它以笔记为起点、连线是主角,**而文件夹根本不在图里**。 如果你的知识是按科目和章节组织的,图谱会把你所有东西搅成一团。 这正是很多人觉得它「好看但没用」的原因:**他们找不到自己组织知识的那条线。** 晶体库问的是另一个问题:「**这一块知识里有什么、长到什么程度了**」。它以文件夹为起点, **结构本身就是主角**。 它不是更好的图谱,是**不同的起点**。 ## 三种打开方式:全屏 / 浮窗 / 嵌入 点侧边栏图标打开晶体库,默认是**全屏**——铺满整个窗口、盖在笔记上。 另外两档在插件设置的「**打开方式**」里选,库里顶栏那颗按钮也能随时**循环切** (全屏 → 浮窗 → 嵌入,**按钮上写的就是下一档叫什么**): | | 长什么样 | | --- | --- | | **全屏** | 铺满整个窗口,盖在笔记上。老行为,也是出厂默认。 | | **浮窗** | 收成屏幕上一块矩形,**浮在笔记上**——一边看笔记一边用库。 | | **嵌入** | **就长在它那颗 Obsidian 标签页里**,不盖任何东西。可以和别的标签页左右分屏。 | **浮窗**:拖顶栏挪位置、拖右下角改大小;位置和大小**跟着 vault 走**, 换台机器打开也在你放的地方。 > - 三档切换都会**重开一次视图**(阅读器里开着的文献、桌面上的窗会没)。这一档换的是 > 库的几何,原地改总有一块不跟着变。 > - **文献阅读器、卡片面板这些也都待在当前这一档里**——浮窗里点开一张卡, > 面板不会盖住你的笔记;嵌入模式下它也只占这颗标签页。 > - **嵌入模式不锁笔记的滚动**(另外两档会——它们盖在笔记上,背后不该跟着滚)。 > - **dataviewjs 那种用法没有这几档**(它本来就嵌在笔记里),所以那颗按钮不会出现。 ## 第四档:浮到 Obsidian 之外(悬浮伴侣) 上面那三档**都跳不出 Obsidian 的窗口**——它们是这个插件在自己进程里造的浮层。 想让它**压在 Edge、微信、Word 上面**(在浏览器里查资料,边看边记还挂在旁边), 得有一个系统级的窗口,而**这件事插件做不到**:Obsidian 的插件沙箱里没有那条路。 所以有了 **CrystalFloat**——一个**单独的小程序**: | | 大小 | 从哪来 | | --- | --- | --- | | 晶体库(就是这个插件) | ~2 MB | 插件市场,照旧 | | CrystalFloat(悬浮伴侣) | ~107 MB | [另一个仓的 Release](https://github.com/runyao-zhang/crystal-vault-float/releases/latest) | 装完之后**不用填任何路径**——插件会自己找到它。命令面板里多两条: - **晶体库:独立窗口:边看边记** - **晶体库:独立窗口:结构窗** 窗口顶栏有拖拽区,右边是**置顶开关 / 最小化 / 关闭**。 > **没装伴侣的时候,上面所有功能一个不少**,只是没有这两条命令。 > 点它们会给你一句人话加一个下载入口,不会静默失败。 ### 压得住什么,压不住什么 **压得住**:Edge、Chrome、微信、QQ、Word、资源管理器——常规窗口,最大化的也算。 **压不住**(这条清单是诚实的): - 独占全屏的应用(全屏视频、游戏) - **另一个同样置顶的窗口**——同档相撞时谁后激活谁赢。装了 Raycast (它默认置顶)、或者某些截图/录屏工具,就会撞上这条 - UAC 提权弹窗(那在安全桌面上,谁都压不住) ### 三条已知的折价 1. **悬浮窗里编辑卡片正文是纯文本框**,不是 Obsidian 那个实时预览编辑器 ——那个组件跨不了进程。翻 PDF、拖卡片、写笔记都照常,只有编辑体验这一项降级。 2. **悬浮文档里的 `[[双链]]` 和 callout 渲染成纯文本**,Dataview 之类的扩展不生效。 3. **不要同时在两边拖同一张卡**——卡片坐标是防抖写、没有基线比对, 同时动手会有一边被静默覆盖。一边改完再动另一边就没问题。 ### 遇到问题 / 想提意见 **QQ 群:831440768** 装不上、用着哪儿不对劲、想要个什么功能,都可以直接进来说。 (在 GitHub 上开 [Issue](https://github.com/runyao-zhang/crystal-vault/issues) 也一样, 只是群里问一句通常快得多。) ## 安装 在 *设置 → 第三方插件 → 浏览* 里搜 **Crystal Vault**。 手动装:从 [最新 Release](https://github.com/runyao-zhang/crystal-vault/releases/latest) 下载 `main.js` / `manifest.json` / `styles.css`,放进 `<你的 vault>/.obsidian/plugins/crystal-vault/`, 然后在 *设置 → 第三方插件* 里启用。 ## 上手 1. 在插件设置里把**卡片目录**指到你放笔记的那个文件夹。默认是 `cards`—— 还没有的话,在 vault 里建一个叫这个名字的文件夹就行。 设置页会**当场**告诉你这个路径在不在、里面有几颗晶体。 2. **往卡片目录里放一份能读的东西**——你在看的那本书、那门课的讲义、那份论文。 PDF 最好(图片也行)。**别放一个空文件进去**:阅读器只会说一句 「这份文献里没有可显示的内容」。放进去之后它就会出现在阅读器的文献清单里, 第 6 步要用。 3. 点侧边栏那颗宝石图标(或运行命令 **Crystal Vault: Open**)。 4. 在卡片目录里建一个子文件夹——那就是一颗晶体。 5. 按下面的格式写一张卡,再用 `[[双链]]` 把它连出去。 6. 点顶栏的「**文献**」进阅读器,选刚才放进去的那一份,开始读; 切到「**桌面**」,再点「**结构窗**」——这就是它日常的样子。 > **放什么格式**:阅读器认 **PDF / 图片(png、jpg、jpeg、gif、webp、bmp、svg、avif)/ Markdown**。 > **PPTX 不在里面,也加不了**——先自己导出成 PDF 再放进来。 > > ⚠️ 放进卡片目录的 `.md` 会**同时是一张卡片**(子文件夹 = 晶体,`.md` = 卡片), > 也就是说那份 markdown 会一起出现在库里。想图干净就放 PDF。 ## 卡片格式 ```markdown --- 概念: 一句话说清它是什么 来源: 哪本书 / 哪节课 / 哪份文献 tags: [科目, 类型] --- 先用自己的话说清它是什么…… 接着看 [[02-下一张]] 连过去的原因就写在后面 ==高亮== 的部分会被抽成自测点。 ``` | 字段 | 在库里长什么样 | | --- | --- | | (文件名) | 卡片的标题,`卡片-` / `卡片_` 前缀会自动剥掉 | | `概念` | 面板顶部那一行 | | `来源` | 悬停浮层里的「来源」 | | `tags` | 卡面上最多显示前 6 个 | **四条让库活起来的习惯**:至少连一次(不然是孤岛)· 连的时候写下理由 · 用编号前缀控制顺序(`01-` `02-`)· 想自测的地方用 `==高亮==`。 ## 更新记录 新的在上。这一份跟着每一版一起改,**只记用户看得见的东西**。 **1.4.17** · 修:**在 Obsidian 的文件列表里新建文件或文件夹,库里会多出一颗「未命名」,点它却说「不存在」。** 底下的毛病是**增删不对称**——两半用了两套口径: - **加**的那半拿盘上的文件夹清单扫**整棵树**,新建的 `未命名` 进来了 ✓ - **删**的那半只扫「**丢过卡**的文件夹」,而一颗从来没有过卡的文件夹(名字还没起好的 `未命名`、刚建的空文件夹)**永远进不了那张表** 于是库里那颗 `未命名` **只进不出**:你把它改名成 `TCP`,`TCP` 加进来了,`未命名` 还留着——点它当然说不存在。 它跟上一版是连着的:1.4.13 做「在 Obsidian 里新建文件夹,晶体库也跟着看见」时,**只做了「加」这一半**,没配对称的「删」。现在两半共用同一张表,加和删才算同一个口径。 顺带修了改名那条路上的一个**无声**失败:改名原来要先把新文件读出来再报给核心,而核心拿到它**根本不用内容**——它只拿这个信号去"整份对账"。可万一下载/同步正忙、那一次读失败,通知就被**静默丢掉**了,旧路径那张卡永远留着。既然内容用不上,改名现在直接走整份对账,读不读得到都一样。 --- **1.4.16** · 修:**桌面上的卡片(和文献)点「编辑」,界面上写着「原生编辑器没挂上」。** 底下的原因很难看,值得说清楚——**它取决于你当时有没有开着一篇 .md 笔记**,所以一直是"有时候好、有时候坏"。 借 Obsidian 的编辑器组件有两条路:一条是问 `viewRegistry` 里那个**造视图的工厂**,一条是从一篇**活着的**笔记实例上借它的构造器。原来代码先借构造器、拿不到才去问工厂。真机对照实验(同一颗叶子、同一个状态、连 `view.constructor.name` 都一样,**只换造视图那一行**): | 造视图的方式 | 结果 | |---|---| | `creator(leaf)`(工厂) | 编辑器**第 0 帧**就长出来 | | `new ViewCtor(leaf)`(借构造器) | **60 帧都不长**,视图挂上了、编辑器是空的 | 两者造出来的是**同名类**,光看类型分不出来,只有真跑才暴露。**于是症状正好反着直觉走:你开着一篇笔记时反而挂不上**(走了借构造器那条),一篇都不开时倒好了(才轮到工厂)。 现在顺序倒过来了:先工厂,借构造器那条只当备胎。顺带把「随便打开一篇笔记,再点一次『编辑』就行」那句提示删了——**它现在正好是反的**。 --- **1.4.15** · 修:**1.4.14 之后,那个面板还是会被卡片盖住——而且是「点过别的卡之后」才犯。** 1.4.14 只是把面板的 z-index 调大了,那是在**错的层级上比大小**,怎么调都是碰运气。真正的病根是桌面那一层:`position:relative` 配 `z-index:auto`,**它不构成层叠上下文**——于是里面每一扇窗的 z-index 全都穿透它跑出来,直接和面板比。现在给桌面加 `z-index:1`,整层连同里面的窗被**封成一个整体**,对外只有一个编号,里面的窗再涨也出不来。 --- **1.4.14** · 修:**「文件改动」点出来的面板会被桌面上的窗盖住。** 那棵树和新建/重命名那个方框原来分别是 `z-index:6` 和 `7`——够压住「选哪份文献」那一层,但**桌面上的窗**(页窗 / 卡片窗 / 结构窗)的 z-index 是**点一下涨一次**的(点一下抬到最前)。所以用上一会儿它们就爬到了面板上面:表现为「点了删除卡片,树被一张卡挡住、点不着」,而屏幕上看不出被谁挡了。 现在那两个面板拉到 **100000 / 100001**——桌面那边要点一万下才追得上。面板本来就是「临时盖住一切」的东西,桌面窗是「常驻、可以互相叠」的,两者不该比大小。 --- **1.4.13** · 改:**三件事——「将建在」搬到顶栏、改概念结构窗立刻跟上、外面动文件晶体库也跟上。** **一、「将建在」从「更多」里搬出来,落在「换一份」左边。** 卡片名 / 概念 / 来源是"这一张卡才用得上"的,收着是对的;而「将建在」是**跨卡片**的设置——你改一次,后面每张卡都建到那儿去。埋在两层里等于每次建卡都要先开一次盒子确认。显示的是**最后一段**,鼠标悬停看全路径。 **二、在卡片窗里改「概念」,结构窗立刻跟上。** 这条底下其实是**两个毛病叠着**,缺一个都不生效: - 保存走的是宿主原生编辑器那条路,写的是**整份文件**(YAML 也能在里面改),而核心保存后传的是一张**空字段表**——只更新正文,**模型里的概念一直是旧的**。现在改成把 概念/来源/tags **读回来**。 - 而且**没人叫结构窗重画**:整份代码里能让一扇已挂载的结构窗重画的只有 `renderCrystals` 那个漏斗,而这条路一次都没碰到它。现在补上了。 (卡片盒那条路走同一段代码,一起好。) **三、在 Obsidian 自己的文件列表里新建/删除文件夹或文件,晶体库立刻跟上。** 从前这条**压根没修过**——不是漏了,是**有意留白**(代码里写着"`create` 故意不订;外面新增一张卡是另一个特性")。现在做了:订了 `create`,文件夹的所有事件也不再被 `.md` 过滤掉,收到就**整份对账一次**(重读卡片清单 + 重跑 `listFolders`,空文件夹照样算一颗晶体)。带 400ms 防抖,同步拖一批下来不会连着炸。 --- **1.4.12** · 修:**删掉线之后,卡片里的 `晶体接法` 还留着那一条。** 1.4.11 让"删线"顺手把 `晶体接法` 里对应那条也摘掉,可摘不掉——因为 `patchFrontmatter` **根本没有"删字段"这条路**:它只会写 `键: 值`。而清空一个字段时,调用方为了不在卡上留一行空字段,干脆**跳过不写**。两头一凑,结果是: > **删掉一个卡片的最后一条线时,那条 `晶体接法` 永远留在文件里。** 现在 `patchFrontmatter` 认 `null` = **把这个字段整个删掉**(连那两块 `---` 一起收掉,如果整个块都空了的话),`linksides` 在表空时就走这条路。 --- **1.4.11** · 修:**删掉一根蓝线之后,这一对卡再也连不上了。** 根因是用户自己找到的:「首次打开晶体库都能正常连接,连完一条线再删除,这时候**晶体接法**上仍然有刚才的链接」。 链条是这样接起来的: 1. 拖一根线 → 正文里写下 `[[目标卡]]`,**同时**在这张卡的 frontmatter 里记一条 `晶体接法: ["[[目标卡]] 起点 终点"]`。 2. 删掉这根线 → 正文里那条 `[[…]]` 被挖掉了,**但 `晶体接法` 那一条没人清**。 3. 再拖同一对 → 判重那一句用的是**整份文件**(`card.content.indexOf(...)`),于是**命中 frontmatter 里那条残留** → 判定"这两张卡已经连着了" → 不写、不画。**而且永远这样。** 改了两处: - **判重只看正文**(`splitCard(base).body`)。链接住在正文里,`parseLinks` 和删除那条路都在正文上解析切割,判重没有理由用别的口径。 - **删线时把对应的 `晶体接法` 一起摘掉**(走 `queueCardSides` 防抖合流,和坐标那条路一样,免得一次框选删好几根引发同步风暴)。 --- **1.4.10** · 修:**拖蓝线文件里有、屏幕上没有,而且怎么重拖都出不来。** 两个问题,第二个是把你卡死的那一环。 **一、早退那一支不重画。** `writeStoryLink` 发现源卡正文里已经有那条链接时,会说一句「这两张卡已经连着了。接的位置按你拖的改过来了」,然后**直接返回、什么都不重画**。 而那句话本身承诺了接的位置改过来了——它确实刚写进去。于是只要盘上早就有那条链接、屏幕上因为任何原因没有线,你**每拖一次都掉进这一支**,它说一句已经连着了就完事。**屏幕上永远补不上,怎么重拖都没用。**(你说的「现在又好了」,是重开窗触发了整窗重画。) **二、写完之后那几步刷新,任何一步抛了都会把最后的重画一起带走。** 现在它们被单独圈起来:报错留痕,重画照做。 --- **1.4.9** · 修:**1.4.8 那个「存完自动摆出来」根本没跑。** 写错了作用域:`viaHost` 是在一个 `{ }` 块**里面**声明的,而新加的那段在块**外面**读它——`const` 是块级作用域,块外读它是 `ReferenceError`。 更糟的是那一段套着 `try/catch`,**报错被吞掉了**:屏幕上没有卡片窗、控制台里一条错都没有。 顺带:那个 `catch` 现在会 `console.error` 留痕——以后同样的坏法至少看得见。 --- **1.4.8** · 改:**桌面模式下存下一张卡,它就当场摆在你眼前、可以直接接着写。** 从前点「存进晶体库」,卡是建出来了,但屏幕上什么都不动——你得自己回库里把它找出来。 现在,**桌面模式**下存一张卡,它会 - **自动开成一扇窗**,贴右下角(窗的**右边**离桌面右边 5%,**下边**离底边 5%); - 而且**直接进入编辑模式**,光标就在正文里,接着写就行。 只在**桌面模式**下做。网格模式是"一屏摊开 N 页扫读",那时候硬开一扇窗会把人从翻页里拽出来。 --- **1.4.7** · 修:**1.4.6 那三个能力位根本没传到阅读器。** `mount()` 是用**具名参数**收的,而它内部调阅读器那一行是**一张写死的表**。 1.4.6 只把 `storyHost` / `cardHost` / `onCardCreated` 加进了阅读器的参数表, 忘了在 `mount` 这一层列出来——**没列出来的字段直接丢掉**,而且屏幕上、 控制台里一句错都不报。 症状就是伴侣那边"点了没反应 / 卡片出不去":它拿到的还是老行为。 同一个版本里还补了两条漏写的: - 在伴侣的「边看边记」那扇窗里点「结构窗」,从前**什么都不发生**(能力位说 "交给宿主",可宿主那份结构窗只存在于**另一扇**窗里)。现在它是**开/聚焦那扇窗**; 窗已经开着时还能把"换一颗晶体看"推进去。 - 在伴侣的结构窗里点一个节点,从前是**交给 Obsidian 打开那张卡**。现在它 **给那张卡开一扇自己的窗**——那才是用户要的。 ⚠️ 要配 **CrystalFloat 1.0.15**。 --- **1.4.6** · 改:**悬浮伴侣换成「组件直接挂」,并且每张卡自己一扇窗。** 这一版**应用内看不出任何变化**——它改的是伴侣那边要用到的三个"能力位": - **结构窗那扇不再走桌面层。** 以前是把整个桌面层端出来、桌上摆一扇铺满的结构窗; 现在结构窗那个组件**直接挂进窗口**,桌面层根本不参与。 (1.4.3 试过一次,但那次顺手把「边看边记」那扇的导航也收掉了——这次一条不动。) - **每张卡一扇自己的系统窗。** 在卡片盒或结构窗里点一张卡,伴侣会为**它**开一扇 独立窗口:浮在所有页面之上、**不随伴侣窗口一起最小化**、能拖到伴侣窗口外面去。 - **「新建卡片」是一扇自己的窗**:打开是一张空白卡,写完存下去,它就变成那张卡的窗。 ⚠️ 要配 **CrystalFloat 1.0.5**。伴侣的安装包在 [伴侣的 Releases](https://github.com/runyao-zhang/crystal-vault-float/releases/latest) 上。 --- **1.4.5** · 改:**阅读器顶栏重排,「边看边记」那一栏撤掉。** 顶栏现在长这样(括号里是新加的): ``` ‹ 返回 │ 换一份 桌面 + 页 收纳栏 (文件改动) (新建卡片) 卡片盒 草稿纸 故事线 结构窗 收起 └ 桌面模式下:上一屏 / 下一屏 / - / 100% / + 收起来 └ 页阵模式下:+ 页 收起来 ``` **去掉了**:文献名、页码计数、`边看边记` 那颗开关。 文献名和计数说的都是**你刚自己挑的那份文献**——顶栏再念一遍是重复;页码本来就印在每一页上。 **新加的两颗**: - **文件改动** —— 一个下拉,装原来的五颗:新建晶体 / 删除晶体 / 删除卡片 / 重命名卡片 / 重命名晶体。**功能一行没改**,只是从右边那一栏搬到了顶栏。 - **新建卡片** —— 点了在它右边摊开:一个写正文的框 →「更多」(卡片名 / 概念 / 来源 / 建在哪个文件夹)→「存进晶体库」。**卡片名留空就从正文第一行取**, 所以默认用法是「写完直接存」。 **没打开文献也能用**——它落在你上次挑的那个文件夹里(没挑过就落卡片根目录)。 **「边看边记」那一栏整条撤了**,里面的东西一样没丢: | 原来在那儿 | 现在在哪儿 | | --- | --- | | 五颗文件操作 | 顶栏「文件改动」下拉 | | 卡片名 / 概念 / 来源 / 正文 / 存进晶体库 | 顶栏「新建卡片」摊开的框 | | 「将建在」文件夹选择 | 「更多」里面 | | 草稿纸的起名框 | 挪进浮层,**功能照旧** | **删掉的只有一样**:`✎ 在编辑器里写`(连同它那套「返回 = 把刚建出来的卡撤掉」)。 它和「新建卡片」是两条并行的建卡路,而它那条会先在库里留下一张半成品卡。 ⚠️ 这一版**只改应用内**。悬浮伴侣那边这次没动——它的两扇窗怎么分,等定下来再说。 --- **1.4.4** · **回退:悬浮窗回到 1.4.2 那套形态。** 1.4.3 把「结构窗」那扇改成了**组件直接挂**(不经过桌面层),理论上更干净, 但用起来不对,退回 1.4.2 的形态:**两扇窗,结构窗那扇是「桌面层 + 桌上只有 一扇铺满的结构窗」**。 1.4.3 那一条更新记录**留着不改**——它确实发出去过。现在这一版把它撤了。 ⚠️ 要配 **CrystalFloat 1.0.4**。 --- **1.4.3** · 改:**浮出去的是「组件」,不是「整个桌面模式」。** 1.4.2 的「结构窗」那扇窗**做错了**:它开的是整个桌面层,桌上摆一扇铺满的结构窗。 量出来窗里实际有这些东西—— ``` .kb-v13-trigger 976×80 晶体库的入口按钮(跟浮窗毫无关系的库层残渣) .kb-v13-reader-desk 976×778 整个桌面层容器 .kb-v13-desk-win 1378×1180 桌面窗,带自己的顶栏「结构:X 收纳 ✕」和拖拽角 ``` 那不是结构窗,那是把桌面模式整扇端了出来。现在**结构窗那个组件是直接挂的**, 桌面层根本不参与: ``` #float-story-host 906×724 .kb-v13-embedstory 906×724 .kb-v13-stage 906×724 节点画布 .kb-v13-embedbar 304×34 结构窗自己的顶栏 ``` 两扇窗都顺带清掉了**晶体库那颗入口按钮**(在浮窗里点它没有意义,伴侣不承载库那一层)。 「边看边记」那扇也收掉了**晶体库自己的导航**(桌面 / +页 / 收纳栏 / 卡片盒 / 故事线 / 结构窗)——那些是整个库的东西,不是这个组件的。尤其那颗「桌面」: 按下去会把桌面层拉进这扇窗。阅读器自己的控件(返回 / 换一份 / 翻页 / 缩放 / 边看边记 / 收起顶栏)一个不少。 ⚠️ 要配 **CrystalFloat 1.0.3**。 --- **1.4.2** · 改:**两个功能各成一扇窗,并排放。** 1.4.0/1.4.1 那两版是一扇窗切来切去,而且「结构窗」那条命令会**把整个桌面层端出来** (还自动多摆一扇 PDF 窗)。不是当初要的样子。现在: | 命令 | 开出来的窗 | | --- | --- | | **独立窗口:边看边记** | 文献 + 右边那条笔记栏。顶栏多一颗「**只看笔记栏**」,按一下收掉文献区、笔记栏铺满整窗(钉在屏幕一侧当便签用)。 | | **独立窗口:结构窗** | **只有结构窗**。桌面层不再露出来、不再自动多摆 PDF 窗,阅读器自己那条顶栏也收起来了。 | 两条命令**互不影响**:点第二条不会关掉第一扇,也不再"先杀掉重开"。两扇窗各有各的 位置、各有各的状态(你在边看边记那扇收起了阅读区,不会把结构窗那扇也改了)。 顺带修了一个**一直没生效**的东西:悬浮窗读偏好和视图状态的那条路是坏的 (契约要求同步读,而桥是异步的,第一版直接漏了个 Promise 出去)——所以 「在 Obsidian 里布置好再飘出来」这件事**从来没成功过**,现在真的能了。 ⚠️ 这一版要配 **CrystalFloat 1.0.2**。 --- **1.4.1** · 修:**开过一次悬浮窗、关掉之后,那两条命令就再也不响应了。** 1.4.0 发出去当天就撞上的。伴侣在你点 ✕ 关掉窗之后**进程不退**——它变成一个没有 窗口的隐形进程,而它**一直占着单实例锁**。于是之后每一次「独立窗口」命令都在 后台被干净地拒掉(退出码 0、窗一闪都没有),你看到的只是**点了没反应**, 诊断日志里也只有一句「伴侣退出了」——一个字都不会提到锁。 现在关窗就退。另外**自己双击启动伴侣也能用了**:它以前没有 `--bridge` 参数就连不上库,只会干坐着;现在它会去读插件留下的发现文件。 ⚠️ **这一版要重新下一个伴侣安装包**(CrystalFloat 1.0.1)。旧的那个请先卸载或 关掉——它正是占着锁的那个。装完之后如果你还开着旧的,先在任务管理器里 结束 `CrystalFloat.exe` 再试。 --- **1.4.0** · 新:**「边看边记」和「结构窗」现在能浮到 Obsidian 外面去了。** 设置里那三档(全屏 / 浮窗 / 嵌入)**都跳不出 Obsidian 的窗口**——它们是插件在自己 进程里画的浮层。这一版加了**第四档**:一扇真的独立窗口,**压在 Edge、微信、Word 上面**。 它必须是一个**单独的程序**(CrystalFloat,约 107MB),因为插件沙箱里造不出系统级 窗口——这是平台限制,不是偷懒。装了之后**不用填任何路径**,插件会自己找到它; 命令面板里多两条「**独立窗口:边看边记**」和「**独立窗口:结构窗**」。 **没装它的时候,上面所有功能一个不少**,只是没有那两条命令。 三条要提前知道的折价(README 上面有完整清单): - 悬浮窗里**编辑卡片正文是纯文本框**——那个组件跨不了进程。翻页、拖卡、写笔记照常。 - 悬浮文档里的 `[[双链]]` 和 callout 渲染成纯文本。 - **不要同时在两边拖同一张卡**(卡片坐标是防抖写、没有基线比对,同时动手会有一边被静默覆盖)。 --- **1.3.97** · 修:**一篇笔记都没开的时候,桌面卡片点「编辑」会弹一句看不懂的话。** 插件要挂 Obsidian 的**原生编辑器**,而 Obsidian **不公开导出编辑器类** —— 只能从 **一个活着的笔记实例**上把类「借」过来。原来的判据是「必须有一颗 Markdown 笔记开着」, 可这个前提**从来没写出来过**,而你的用法很可能就是**从来不打开 `.md` 标签页** (都在晶体库里读)—— 于是每一次点「编辑」都借不到、都掉进降级那条路, 还顺带弹一句给开发看的报错。 现在借类有**三条路**,依次试,任何一条走通就不再往下: 1. **活着的 Markdown 实例**(原路,判据放宽了) 2. **Obsidian 的视图类型表**(`viewRegistry.viewByType`)—— **不需要任何笔记开着** 3. 上一次借成的那一份,本会话内一直有效 三条都不通时,那句话也换成正常人话了:**说清楚为什么、下一步做什么**, 并点明下面那个输入框**照样能写**(它不是错误,是降级)。 **1.3.96** · 修:**删掉一条蓝线,那条线还在**——关掉 Obsidian 重启也在,可**文件里已经没有它了**, 而且它还会变成**幽灵**。 根因是**算关系的时候读到 frontmatter 里去了**。「晶体接法」那个字段长这样: ```yaml 晶体接法: ["[[花式索引和布尔索引]] b t"] ``` 目标写成 `[[双链]]` 是**故意的**——给卡片改名时 Obsidian 会连它一起改,接法不会失联。 可"算出两张卡之间有没有关系"那一步,是**在整份文件上**找 `[[…]]` 的, 于是 frontmatter 里这一条**也长出一条边**: - 删蓝线删的是**正文**里的链接,动不到 frontmatter → **线还在** - 它在文件里 → **重启照样在** - 目标卡不在这一层时 → 变成**幽灵** 现在算关系**只看正文**。剥的只是**文件最开头**那一段 frontmatter——正文中间的分隔线 (`---` 单独一行)不受影响。 **1.3.95** · 结构窗和故事线里,**「写」模式下右键空白处不再弹那条说明条**。 原来是这么一条:`点顶栏「选框」,然后拖出方框 · Esc 退出`。它出现在"右键空白处、 进了删蓝线模式、但还什么都没选中"这一档——那时候屏幕上一个选中物都没有,弹这句话是噪音。 **其余三档照旧留着**,尤其是「看」模式下右键金线那一条:金色线框选唯一的入口就是顶栏 那颗「选框」按钮,少了这句用户只会盯着线发呆。 **1.3.94** · 修:**关掉 Obsidian 再打开,框的位置变回旧的**(1.3.93 没修干净,这次是根因)。 线索是你自己给的那两句:「**只要我不关 Obsidian,一切正常,关了再打开,又变成这个 bug 了**」。能同时满足这两句的只有一种情况——**存下去的是旧的那一份**。 根因:**两条持久化路线读的不是同一个对象。** 进故事线 / 结构窗就是进**相机档**,那一刻会开一份**草稿**,你的改动全落在草稿里。 问题在于保存视图状态的那个函数**只读 `state.view`,压根不看草稿**——所以开着故事线时, 每一次"保存"写下去的都是**那份还没并进草稿的旧状态**。而边车(`.crystal-boxes.json`) 读的是"草稿优先"。 于是:**屏幕上一切正常**(界面读草稿)、**边车是对的**、**唯独视图状态是旧的**。 两条路线必然分叉,而分叉**只在重开之后**才显形。 现在那个函数改成和"提交草稿"**用同一个式子**(`{ ...view, ...draft }`), 两条路线读同一份东西,不可能再分叉。 > 这条也解释了为什么 1.3.93 的时间戳没能救你:那一版保证"谁新听谁的", > 而**新的那一份本身就没被写进视图状态**——它一直躺在草稿里。 **1.3.93** · 修:**关掉 Obsidian 再打开,蓝色收纳方框会跳回旧位置**——而开着的时候 怎么看都是对的。 你报的原话是「只要我不关 Obsidian,一切正常,关了再打开,又变成这个 bug 了」。 这两句合起来就把它锁死了:**不是跟随坏了,是那一笔没写进文件**。 根因是落盘规则本身:边车(`.crystal-boxes.json`)一直是**「文件无条件优先」**。 那条规矩有一个它自己写明了的前提——**文件永远是较新的那一份**。而它不是: > 写边车是**异步**的,而且关库那条路上催写盘的调用**没有被等待** > (`closeFullscreen` 整个是同步函数,没地方 await)。 > 那一笔没走完,文件就停在旧值上;而**视图状态是同步写掉的,是新的**。 于是开库时旧文件盖掉新状态:**框跳回原处、卡片留在新位置**, 看上去就是「卡片跑到蓝框外面了」——**而且只在重开之后才出现**。 现在每一笔都盖上时间戳,开库时**逐框比时间、谁新听谁的**: - 文件不比我旧 → 听文件的(**平手也让文件赢**,它本来就是为跨机器准备的那一份); - 我严格更新 → 留着我的,并**把文件补写出去**(不然它永远追不上); - 两边都没有时间戳(1.3.93 之前写的存档)→ **听本地的**。本地那份是这台机器上 你最后看到的样子,而文件恰恰可能是写丢的那一份。 > **某一次写丢了,从此不会再毁掉你的摆法**——不管它是为什么丢的。 > 你库里那个框(m:6)现在正好是「视图状态说 2667、边车说 2730」的状态, > **这一版开库就会自己取新的那个**,不用你再拖一遍。 **1.3.92** · **跨层拖金框,蓝框现在跟着走了。**(这一条是前几版都没接住的那个。) **蓝框是按层存的**——你在哪一层画的,它就记在哪一层。而金框从 1.3.88 起 **逐层都有**。这两件事撞在一起就是一个天天会踩的形状: > 你站在**浅层**(比如「机器学习」),拖**某个深文件夹**(比如 > 「…/准确度的陷阱与混沌矩阵」)的金框。这个动作会带动那个文件夹里的卡—— > 可你给那个文件夹画的蓝框,存在**更深那一层**(你是进去画的), > 它压根不在这一屏上,于是**一个都不跟**。 > > 然后你打开结构窗(钉着最深那层)——**卡片已经在蓝框外面好一段距离了**。 现在按**文件夹的包含关系**去找:拖一个金框,凡是**在里面**(它自己那层、 以及它下面任何一层)画的蓝框,只要有成员卡在这一趟里动,就一起跟着走。 > 那些框在浅层**不画**(它们属于别的层,画出来就是幽灵),所以你看不到它们动 > ——但你打开结构窗时它们**已经在正确的位置上了**,这才是要紧的。 > 顺带修了落盘:跨层拖动时,边车写的是**框自己那一层**,不是"你站在哪一层"。 **1.3.91** · 修三条,都跟「拖动金色方框」有关: **① 拖金框时,蓝色收纳方框还是不跟。** 判据一直是错的,而且正好错在你这一类上: 原来问的是「**我这个蓝框的全部成员是不是都在你里面**」。可是一个蓝框**跨两个同级 子文件夹**的时候,它的共同祖先**正是你当时站的那一层**——而按你拍板的「本级不画框」, 那一层根本不长金框。于是哪个框都装不下它,判据回 false,代码就退回一条更老的、 「看蓝框画出来的中心落在哪」的位置判据——**而你手画的框比贴身的金框大,中心落在 外面**,于是**静默不跟**。框里混一张**引进来的外来卡**也是同一个下场。 现在换成一句**充要**的话:**这一趟要动的卡,恰好就是那个金框底下的所有卡。 蓝框里只要有**任何一张**在里面,它就得跟;一张都不在,它就该留在原地。** 跟你怎么画、画多大、收没收起,全都无关。 > 顺带:**收起来的蓝框现在也跟着走了**(它那条标题栏摆在重心上,位移得从它真正的 > 位置加起,以前干脆跳过了它)。 **② 金框的身份被拼重复了。** 同一个文件夹,**站在不同层看会拿到不同的 id**——而框的 名字和收起状态是按 id 存的两张全局表。所以「我在这一层把它收起来了,换一层它又自己 展开了」,改名同理,而且不报错。根因是递归发框时把完整路径又和父路径拼了一遍。 **③ 边框位置的那个文件,会被静默丢掉一笔。** 这个是从**你库里真实的数据**里查出来的: ``` data.json(视图状态,每次拖动同步写) m:6 x=3003 bottom=294 .crystal-boxes.json(边车,11:55:52) m:6 x=2709 bottom=252 ``` 两份对不上,而落盘规则是**文件优先**——所以下次开库那个框会**跳回旧位置**、卡片却 留在新的地方,看就是「卡片跑到蓝框外面去了」,**跟你拖不拖没关系**。根因是关库那条 路上催写盘的调用**没有被等待**(`closeFullscreen` 整个是同步函数),而排队表又在写 成之前就被清空了。现在改成**写成了才出队**,并把合并窗口从一个 700ms 的防抖压到 **一个 tick**(同一次松手里的几笔合并,跨 tick 立刻写)——窗口小到几乎没有。 **1.3.90** · **结构窗顶栏能整条收起来了。** 最左边那颗 **▲** 现在是**一颗按钮两种状态, 颜色就是状态**: - **蓝** = 顶栏所有按钮都摆着(默认,和以前一样); - **白** = **全收起来**,屏幕上只剩这一颗 ▲;再点一下全回来。 收没收起**记着**,下次开窗不用重收。(它顶替了 1.3.84 那对只收晶体名的 ▲/▼—— 那把粒度更细,但现在这一颗一次就把名字和按钮一起收了,所以 ▼ 撤掉了。) > **为什么这回敢连"换晶体"那颗入口一起收**(上一版的注释里明令禁止过):**出口就在 > 原地**——同一颗 ▲ 再点一下全回来。原来那条禁令防的是"入口在需要它的那一刻恰好 > 不可见,而且**没有回头路**",这两件事不一样。底线没松:**收起态下这颗 ▲ 永远 > 留在屏幕上**,它是唯一能把顶栏叫回来的东西。 **1.3.89** · 修两条(都是 1.3.88 带出来的): **① 拖金色方框时,里面的蓝色收纳方框还是不跟着走。** 你报的原话是「打开结构窗时候, 卡片已经移出蓝色方框之外好一段距离了,但是这个蓝色方框里面仍然算着这些卡片, 然后移动蓝色方框,就连同这些物理位置上不在这个方框里面的卡片一起移动」。 根因是 1.3.88 那条跟随的判据写错了:它问的是「蓝框的**中心**落不落在金框矩形里」。 而**蓝框是你手画的,通常比那个贴身的金框大**——往一边探出去之后中心就跑到金框外面, 于是这一趟**一个跟随者都找不到**,一声不响。 现在改成**看成员**:一个蓝框"住在"哪个金框里,看它收的那几张卡**是不是全都在**那个 金框里。这个判据**跟你怎么画无关**。顺带两条: - **拖外层的金框,住在里层的蓝框也跟着**(拖 A 的时候 C 里的卡同样在动); - 蓝框"不许移出金框"那条约束用的是同一个判据——它原来有一模一样的毛病, 蓝框画大一点约束就静默失效了。 **② 金色方框的标题栏下面,加了一条金色实线。** 金框现在一层层套着,抬头和数据之间 没有分界,一眼看不出"这一条是这个框的名字"还是"里面某个子框的东西"。 **1.3.88** · **金色方框一层一层地分开了。** 以前一个文件夹长出来的金框,会把它**整棵 子树**的卡全摊平吞进去——A 里面嵌着 B1、B2,B2 里面又嵌着 C 的时候,屏幕上只有 A 一个 框,四层的卡混在一起,**层级完全看不出来**,而那正是结构窗存在的意义。 现在**每一层文件夹都发一个框**,框就自然套起来了,一个框内部按你拍板的方式横着切: ``` ┌ A ─────────────────────────────────┐ │ [A 自己的卡] ┃ [B1 的卡] ┊ [B2 的卡] │ └────────────────┸────────────┸──────┘ 珊瑚橙竖线 ↑ 金色竖线 ↑ (本级 ↔ 子文件夹) (同级 ↔ 同级) ``` - **珊瑚橙 `#FF6B6B` 实线**分的是**跨层**:这个文件夹自己的卡 ↔ 它的子文件夹; - **金色实线**(和框同一个金)分的是**同级**:两个兄弟子文件夹之间。 再深一层也一样——C 在 B2 里,B2 内部又是「B2 自己的卡 ┃ C」,还是珊瑚橙。 另外三条一起改了(都是同一次报告的): - **蓝色收纳方框现在压在金色方框上面。** 金框变成嵌套的之后,一个蓝框常常嵌在好几层 金框里面,不给它一个更高的层级就会被内层金框盖住。 - **金框是容器了:里面的卡拖不进别的金框**,也不能拖进上一级"自己的卡"那一块 (就是珊瑚橙线的另一边)。拖动时**卡片停在那条边界上**,不是滑进去再弹回来。 **蓝色收纳方框也不能移出它所在的那个金框**——拖和拉大小都出不去。 而蓝框**不是**容器:卡片照样可以拖进拖出。 - **拖金框,里面所有元素跟着走。** 卡片本来就跟着;这次补上的是**蓝色收纳方框** ——它是个自己记着矩形的容器,以前拖金框时它钉在原地不动。 **1.3.87** · **连线接法现在跟着卡片走**——把一颗晶体压缩发给别人,对方打开时线的 走向和你这边一模一样。(用户 10-01 报:「压缩发给朋友,他那边只有左右连线」。) 根因是**接法一直存在本机**:它在视图状态里,而且键是"你当时站在哪一层"。所以 换台电脑是空的、把文件夹复制到库里别处键也对不上——**压缩包本身没带走它**。 现在它写进卡片自己的 frontmatter: ```yaml 晶体接法: ["[[02-中继]] b t"] ``` 一条 = 「往 `02-中继` 那根线,从我的**下**边出去、进它的**上**边」(`t/r/b/l` 是上下右左)。 只在**你亲手拖过连接点**的那些线才写,没动过的一律不写(默认接法是按位置算出来的, 本来就换哪台电脑都一样)。一张卡手动调过 10 根线,也就多约 250 字节。 > - **目标写成 `[[双链]]` 的形状**,所以给那张卡改名时 Obsidian 会连它一起改, > 接法不会失联。 > - **老接法会在开库时自动搬进文件**(一次最多搬一批,剩下的下次开接着搬)。 > - 顺手把「写进文件却读不回来」那条路堵了:解析器原来被调两道,第二道拿到的 > 已经是对象,被当成字符串整表丢掉——文件里写着、屏幕上却没生效,一声不响。 **1.3.86** · 修:**蓝色连线不管卡片怎么摆,永远从左右两边接**。上下叠着的两张卡, 线也是从右边出去、绕一圈回到左边。根因是"没有手工指定时"的兜底判据写死了左右 (只看谁在左谁在右)。现在改成**按位置挑面对面的一边**:上下叠着的走「下→上」, 左右摆的走「右→左」。 这件事跟换电脑还有一层关系:**你亲手拖过的接法是存在本机视图状态里的,不跟着 卡片走**——所以你把资料搬到别人电脑上一跑,一条接法都不在,整屏退化成清一色左右。 改成按位置算之后,**默认接法是从排布推出来的**,哪台电脑都一样;你亲手拖过的那根 仍然以你拖的为准(手动 > 自动)。 **1.3.85** · 修:**点方框的名字改不了名了**——1.3.83 带进来的。1.3.83 让**整个方框** 都能起手拖动,而拖动在**按下的那一刻**就把指针抓走了。于是「按在名字上、原地松手」 那一下:**按下的目标是名字、松开的目标变成了整个方框**,而 `click` 的目标是这两者的 **最近公共祖先**——落到方框上,那就查不到名字了,改名当场哑掉。 (1.3.83 之前它只在**空框**上是好的:那会儿空框在守卫上直接早退,压根没抓指针。 我放开那道早退的时候,把「空框能拖」和「点名字改名」共用的一扇门一起拆了。) 改法和 panzoom / holodrag 里早就写着的那条一致:**过了 4px 才抓指针**。 没真拖的那一下,从头到尾什么都没发生。 **1.3.84** · 修:**结构窗顶栏的晶体名太长,会把后面的按钮挤出窗外**。那条栏是 不换行、也没有宽度上限的,而晶体名是里面**唯一会长的东西**——名字一长,它一路 往右顶,而窗的外层是 `overflow:hidden`,**被顶出去的按钮就再也点不到了**。 现在给名字封了顶(超长用省略号,完整的那颗在悬停提示里),顶栏本身也改成**到边 换行**——两道一起上,按钮再也不会跑出窗外。名字左边多了一对 **▲ / ▼**: **▲ 收起名字,▼ 放出来**(收起后那颗按钮缩成「晶体」两个字,换晶体的入口还在)。 收起来的这件事**记进偏好**,下次开窗不用重收。 (顺带一句:上一版 1.3.83 修好的「空方框拖不动」,要是你还看得到,多半是插件 没重载——在 *设置 → 第三方插件* 里把它关掉再打开一次。) **1.3.83** · 四条,都在故事线 / 结构窗里(用户 09-30)。 **方框整块都能拖了。** 以前只有顶上那条 26px 的标题栏能抓,现在框里**任何一块空白** 都能起手。压在卡片上时拖的仍然是卡片——卡片层级比框高,这一条是层级给的,不用额外判 (所以「拖卡片区域不会拖框」是白拿的)。代价说清楚:在框的空白处按下**不再平移画面**, 推画面请拖框外面,或者用滚轮。顺带修好一条:**一个还没装卡的框以前拖不动**, 现在也能摆了。 **金色方框里的连线,走线方式和进到那个文件夹里一样了。** 「接法」(这根线从卡片的 哪一边出去、怎么拐)是**按层**记的,站在外面看金色方框时查不到那一份,就退回默认的 「看谁在左谁在右」——同一对卡、同一根线,两个地方长得不一样,而屏幕上没有任何东西 解释这件事。现在**各层的接法都算数,当前这一层优先**。 **方框也走格点了。** 手动框吸到格点上(判据和卡片同一把尺子,**左下角**),框里的卡 按**同一段位移整体挪**——相对位置一点不变,不会被挤到一起;金色方框没有自己的坐标, 退一步按整格移动。**大小仍然随你拉**,只有摆在哪才需要对齐。 (格点 1.3.34 撤过一次,这一版按你的要求装回来。) **挑文件夹那块面板的抬头多了一颗「取消」。** 将建在、新建 / 删除 / 重命名晶体、 删除 / 重命名卡片、结构窗挑晶体——它们打开的都是这块面板,关法从这一版起统一成 这一颗(以前只能再点一次「将建在」把它收起来,那颗按钮叫「将建在」,不像一个"关")。 **1.3.82** · 新增:**结构窗里可以一次删掉一批卡片**。框选卡片(顶栏「框:卡」+「选框」, 框住几张)之后,顶栏多出一颗**红框的「删除卡片(n)」**——点一下,这几张 **连笔记文件一起进回收站**(能捡回来),卡片所在的**晶体不动**。 删的是**文件**,所以代价写了三处:按钮上的张数、悬停那句话、底下那条说明条 ——这扇窗**没有确认弹窗**是既定的规矩(09-20 定的),说明条就是仅有的告知。 **按 D 不删卡**:一个裸按键就能把好几个文件扔进回收站,太容易误触 (那会儿手正放在方向键上挪卡片)。要删就走那颗按钮。 (库那一屏的卡档仍然只有移动——那边没有这颗按钮。) **1.3.81** · 修:**点「编辑」会凭空多出一个空白标签页**(全屏 / 浮窗 / 嵌入都中)。 根因是插件向宿主"借一颗现成的标签页"时用的是 `getLeaf(false)`,而那句话的语义 **不是**"拿一颗现成的"——宿主内部走的是 `getUnpinnedLeaf()`:**"拿一颗没被钉住的"**, 当前活动的那颗要是钉住的,它就**当场造一个新的空页**。你在阅读器里时,活动的那颗 正是**晶体库自己**,于是每点一次 ✎ 就多一个。 实证是往宿主 Console 挂了个探针,在标签页被插进 DOM 的那一刻打出了调用栈 (`mountEditor → getLeaf → getUnpinnedLeaf → setActiveLeaf → … → insertBefore`)。 改法:那个位置**根本不需要一颗真标签页,只需要它的"类"**——改成从已经查到的 markdown 标签页上取构造器,只是查表,不激活、不新建。 **1.3.80** · 修:**别的机器改过的卡,这边从 1.3.59 起就没跟上过**。多设备同步把 改好的正文、概念、来源、标签、坐标传过来时,**一样都不会变**,屏幕上什么都不发生—— 看着像通知根本没送到。根因是加「晶体坐标」那一次,调用**参数错位了一格**, 卡片对象被挤掉;而那个名字在作用域里正好还有一份,于是**不报错**。 测试只逮到 2 条,实际断掉的是整条同步链路。 **1.3.79** · 修:新卡还是落在"默认坐标"。1.3.77 里我拿**屏幕坐标**当**世界坐标**用了, 而这两者**只在"相机停在原点、缩放 1"时才重合**——所以新卡永远落在一个 **和你推到哪儿毫无关系**的固定地方。现在先换算再落座:**你说的"当前视口"就是相机 此刻的位置**,新卡因而真的落在你正看着的那一屏**右边 5%、上下居中**。 **1.3.78** · 修:**浮窗 / 嵌入档下点「编辑」会把笔记开到一颗空标签页旁边**。 根因不在"编辑"本身,在它依赖的那个前提:从"当前活动的那颗叶子"裂一半—— 而这两档**多了一颗属于插件自己的标签页**,它完全可以就是活动的那颗。 现在 split 的基准自己挑一颗已有的 markdown 叶子。**全屏档逐字不变** (那两档下"最近用过的那颗"本来就是你的笔记)。 **1.3.77** · 修:**建在子文件夹里的新卡还是落在默认坐标**——建卡有两条路, 我只在其中一条里摆了位,而你走的恰好是另一条(「✎ 在编辑器里写」)。 两条路都是"建一张卡",位置这件事只能一样。落点也按你定的公式改成 **视口右侧 5%**(卡片**右边缘**落在"右边减 5% 宽"上,纵向居中), **不吸附格点**——你给的是具体公式,硬吸反而会差半格。 **1.3.76** · 修:**建在子文件夹里的新卡不会被摆进结构窗的视口**(落回默认位置)。 判据原来写的是"卡片所属晶体的链必须和你看着的那一层完全相等",而**卡片常常建在 更深的子文件夹里**——它明明画在这一屏上(取卡片是递归的),却被这条判据挡掉了。 **1.3.75** · 阅读器顶栏可以**收起来**了:顶栏最右端多一颗图标,点一下把它压成一颗图标, 桌面就多出一条——结构窗和页面窗能摆到原先顶栏占的位置上。**图标两档之间转 180°** (展开态箭头朝上=按下去内容往上走,收起态朝下=按下去挤回来), **图形始终是同一个**,状态的差别由鼠标悬停那句话讲清楚。展开时被压在顶栏下面的窗 会**跟着往下让**。 **1.3.74** · 修 1.3.73 的四处 + 一处你报的:嵌入档那个"跟着标签页大小走"的观察器 **因为一个声明顺序错误从来没建起来过**(而且被一个 catch 吞掉了,没留任何痕迹); 嵌入档按整块屏幕而不是标签页缩放(卡片会撑出半宽的分屏);连点两下切换按钮会跳档; 顶栏那颗按钮的刷新漏了重排。**另外:把卡片存进晶体库之后,结构窗现在会实时显示** ——以前要关一次再打开(刷新的收口从"逐个入口"移到了"库重画就刷窗")。 **1.3.73** · 新增第三档「**嵌入**」:库就长在它那颗 Obsidian 标签页里,不盖任何东西, 可以和别的标签页分屏。顶栏那颗按钮变成**三档循环**(全屏 → 浮窗 → 嵌入), 按钮上写的就是下一档叫什么。嵌入模式**不锁笔记的滚动**(另外两档会,它们盖在笔记上)。 **1.3.72** · 浮窗模式的六处修正:**卫星和悬浮窗跑偏**(它们的坐标是屏幕坐标, 而它们住的那一层是"层内坐标",浮窗时差一个窗口原点);**右下角抓手关库后还留在笔记上** (而且只增不减);拖拽监听没摘(每开一次漏六个);**卡片面板会高于窗口** (`vh` 量的永远是视口)→ 现在 CSS 改问"这一层多大";**顶栏在窄窗口里被裁掉** (连 ✕ 都点不到)→ 改成可换行;窗口比屏幕宽时抓手跑到屏幕外。 **1.3.71** · 修 1.3.70 的致命错误:**晶体库打不开**(报 `Cannot access 'j' before initialization`)。 原因是我把 `viewBox` 的声明放在了用到它的那行**之后**(一个 TDZ 错误),而生产包里变量名被压成 一个字母,从报错上完全看不出是谁。现在声明挪到了使用之前。 **1.3.70** · **浮窗模式**:库可以不占满屏,收成一块可拖可缩的矩形浮在笔记上方, 一边看笔记一边用库。插件设置里选默认档,库里顶栏那颗按钮随时切。 (版本号从 1.3.60 跳到这里:这是一次架构调整——库第一次有了"占多少屏幕"这个概念, 所有"拿视口当边界"的坐标计算都改成了"拿这一层界面当边界"。) **1.3.60** · 收纳方框落盘:每颗晶体一个 `.crystal-boxes.json`(点开头,Obsidian 里看不见), 名字、大小、收起状态、成员都在里面。老版本里只存在本机的位置,升级后第一次开库会自动搬进去。 **1.3.59** · 卡片坐标写进卡片自己的 frontmatter(`晶体坐标: [3, 5]`,以**左下角**为准) ——换台电脑打开,相对摆放位置一样。同时:收纳方框**取消格点**(拖到哪停哪), 新建的卡片和 `+ 框` **落在你正看着的地方**(以前建在世界的固定角落,视口推远了得推回去找)。 **1.3.58** · 收纳方框**按晶体分层**。以前它是全局的——换一个文件夹看,别的层画的框还在原地摆着。 **1.3.57** · 卡片的**格点与方向键**:拖动吸附到格点,选中后方向键一格一格挪。 最小单位 = 卡片宽的 1/10,坐标以**左下角**为准。 **1.3.56** · 修「导入卡片」的四件事。其中两件要紧的:在挑卡片的选择器里点**文件夹名** 会静默改掉你的「将建在」;外来卡拖进子晶体框时一声不响(现在会告诉你去用「+ 框」)。 **1.3.55** · **导入卡片**:从别的晶体引一张卡到这一屏上来,拖进收纳方框, **跨级连蓝线**从此成立。卡片本身一个字都不动。 **1.3.54** · **框选卡片、整批拖走**。顶栏多一颗「框:卡」——框住几张,按住其中任意一张 整批一起动,松手整批判归属。在这之前,把十几张卡收进一个框只能一张一张拖。 **1.3.53** · 修:拖框走的世界距离和卡片不一致(相机会缩放时框走得短);收起态的框一拖就把卡片甩到框外。 **1.3.52** · 收纳方框改成「**你自己画的框**」:可以拖、可以拉大小,不再只是卡片周围一圈算出来的外壳。 收起之后外面有蓝线连进来的卡会点一个**黄点**,鼠标悬停它,相关的框**边框闪一圈**。 **1.3.51** · 收纳方框的颜色加深。第一版太浅,在库自绘的深底上几乎看不见。 **1.3.50** · 修:空框不画 → 「点了 + 框 一点反应都没有」。建完框要能把卡拖进去,而拖进去的前提是**屏幕上看得见那个框**。 **1.3.49** · 拖收纳方框的**标题栏 = 整组卡片一起挪**。 **1.3.48** · 手动收纳方框的入口「**+ 框**」;卡片拖进拖出;删框(框只是分组,删框不动你的卡)。 **1.3.47** · 修:方框没被清场,每重画一次就叠一层。 **1.3.46** · 结构窗的**收纳方框**:每个子晶体自动一个,可改名、可收起;收起后里面的卡和线一起收起来, 但「那边还有东西」这件事不丢。 **1.3.45** · 桌面卡片窗顶上补一行「概念」。 **1.3.44** · 结构窗那一节补全(**看 / 写**模式、右键三种落点);+页的 markdown **按行分页、改完按回车**; 上手补一步「往卡片目录里放一份能读的东西」。README 在这版重写过一次——把**文献模式 · 桌面**提到主功能位置。 **1.3.43** · **重命名卡片 / 重命名晶体**两颗按钮;修「别人在 Obsidian 里改了名,插件不知道」—— 以前会在图上留下一张灰色的、点进去什么都没有的卡。 **1.3.42** · 每一扇窗都有一颗「收纳」,不带条件。 **1.3.41** · 收纳栏最上面那颗 `+`:**外部标签页**,可以内嵌 deepseek、Google 这些站点。 **1.3.40** · 阅读器的**收纳栏**,以及「边看边记」那一栏的开关。 **1.3.39** · 故事线也有「看 / 写」两档了:写模式下拖线真写 `[[双链]]`、选框删蓝线、一层撤销。 **1.3.38** · 去掉写模式里那个「为什么连过去?」输入框——连着拖几根线时每根都被它截一下。 理由照样可以手写在 `]]` 后面,只是不再由插件代劳。 **1.3.37** · 写模式下多一颗「**选框**」删蓝线:框住几根,从卡片正文里精确挖掉那条 `[[链接]]` (两个方向一起删、逐字节可还原、一次撤销)。 **1.3.36** · 蓝线改成和金线一样的**折线**(直线 + 圆弧拐角),不再是贝塞尔弧 ——弧线从卡片中段穿过去,两根一交就看不出谁接谁。 **1.3.35** · 蓝线能从上 / 下 / 左 / 右接(认你拖的那个连接点); 写模式下**右键一张卡就藏掉它所有入链出链**,顶栏「显示全部」一键收回;橡皮筋预览和落地同形同色。 **1.1.31 / 1.1.3 / 1.1.2 / 1.1.1 / 1.1.0** · 草稿纸、边看边记的「返回」、删除卡片; 草稿纸从一堆文字改成**摆到桌面上的一张真卡**。几个补丁版本号是因为发过的号不能复用。 **1.0.2 / 1.0.1 / 1.0.0** · 默认卡片目录改成中性的 `cards`;按自动审查改三处;首次提交。 ## 许可 [GPL-3.0](LICENSE)。