第二次漫游:双链、xanadu 与渐进学习

本贴最后更新于 186 天前,其中的信息可能已经时移世异

第二次漫游:双链、xanadu 与渐进学习

这是一次漫游,不同于之前的文章,主要进行一个发散式的闲聊,以及共享我思考的上下文。

渐进学习手册已经修稿完了,增删了一些内容,再加上这两天做的这张图就能完结了:

思源双链流程图

我把图中提到的可选选项都丢到了文章末尾,有需求直接去拿。

Roam 式双链用法其实一直挺简单的,没有分类、文档树这些元素,点进 DailyNotes 里记就完了,必要时打个 [[块引用]] 点进去看反链汇总,没什么复杂的,回归辅助思考的本质。

可能是太过简单,导致使用起来反而有压力,所以需要社区有讨论,让社区来进行背书:放心用,大胆记,真的就这么简单,别整那些复杂的玩具浪费动力。如果你觉得这样记笔记会发展成屎山,那么其它方法也一样会成屎山。

比如我 2020 年的时候就老问我的思源启蒙老师 @zhangzz “真的就这么简单?”,反复确认几次才敢这么用 DailyNotes 。

记录、整理存在着压力

  • 我用双链的时候正巧碰到 @deerain游说反链改版,后来又写了双向链接时代的快速无压记录,这里哈桑老师敏锐地察觉到 Roam 带来的使用体验不在“互相关联与网状”上,而在于记录与整理的压力处理上。
  • 当时我一听,这和我的经验相符啊,关联知识点确实没感觉到对我有啥用,传统的先分类再进去记笔记步骤太多了,写几次就烦了,直接进行一个摆。
  • 也就从这里,我开始了第 N 次笔记入门,当时写下了实践中对双链和写作的一些思考

很多人被传统记录流程拦在了门前,包括我

  • 在这里我想共享的视角是,像之前的我一样不记录的人也是有的。
  • 我没有记录的内在动力,看到炫酷的展示我也会尝试两天,然后放弃。
  • 记录和整理对我而言太累太累,最重要的是,之前的我从未感受到过记录和整理有任何意义或者奖励,它对我的学习而言没有任何帮助。
  • 大多有内在动力的人无法理解,他们从笔记里获得了激励,或者养成了习惯发展为了爱好,但对我来说,笔记的记录和整理过程里只有无聊,以及动力的损耗。
  • 像我这样被挡在门槛前的人,因为从未进门,所以也很少有人在门里听见我们的声音。

笔记的不同使用目的

  • 笔记这个词太过宽泛,并不能反映我们的具体需求,例如有些人只是想要个资料库,进行一些信息管理,有些人是想借助工具帮助自己成长,这是完全不同的需求,我最早在为什么任何笔记系统在 SuperMemo 面前都是垃圾 (如 OneNote 和 Evernote)的评论区里看到这种矛盾,这是不深入笔记社区就无法了解到的。
  • 无论国内还是国外都是。

为什么我不说知识管理

  • 大家对知识管理的定义也很模糊,当我提到知识管理也会被理解为资料管理、创意管理、参考管理、资源管理等等,所以不如直接描述自己的目的,比如:发展自己的想法,让自己变得更有洞察力
  • 而我的目的,只是想让自己有积极起来的欲望。

是表达促进了学习,而不是记录和整理

  • 在笔记社区另一个有趣的点就是讨论整理,分目的来讨论的话,有些信息管理是要去做整理的,比如个人的重要的证件信息、密码之类的。
  • 但如果有人告诉你不去整理就无法学习成长,别信,有坑。
  • 这就像有人和你说记笔记就能获得好成绩一样,太扯了我的朋友。
  • 如果记笔记的过程中有什么能促进学习,那只有表达,表达促进了学习,学习又增强了表达,只有见理不明才会说话不清。

表达也存在着压力

  • 将语言组织成有逻辑的长句对我来说有点吃力,之前记笔记被劝退也有这方面的原因,后来学到了使用大纲快速固定想法:

    • 只敲下想法的关键词,回车,缩进,写下新的想法关键词,不考虑修辞,不考虑逻辑。
  • 这时候我才明白,为什么 FLY 老哥说他的块引用会在很深的大纲层级中使用,而不仅仅是在大纲首项中用。

  • 这种速记有让我爽到,它让我的记录第一次跟上了思考的速度,以往我都是写写停停,琢磨下修辞,然后开始走神犯困。

哈桑的 DailyNotes 流程

  • 有点混淆的是,哈桑老师分享的是自己解析出来的个人流程,有 Page 级的引用和上下文反链,但是没怎么说块级引用。
  • 【笔记方法分享】conor 的写作工作流 | 块引用的作用里看出块级引用的作用,就在 FLY 老哥分享的 Conor 工作流里,我学到了第二个关于双链的知识点:DailyNotes 中的记录是一种记忆的锚点,可以通过记录的上下文事件唤醒记忆,它也可以用来索引。
  • 所以准确的说,DailyNotes 流程的全称应该是“哈桑的 DailyNotes 流程”。

ADHD、Xanadu 与非线性写作

  • 提出 Project Xanadu 的 Ted Nelson 有严重的注意力缺陷障碍(Attention Deficit Disorder, ADD),他很容易迷失在大脑产生的疯狂的大量联想中,同样他也无法忍受传统写作和结构:

  • 在了解 Ted Nelson 之前我对 Project Xanadu 并没有什么共鸣,那些愿景离我太远,什么对信息追溯到其来源、看到谁引用了原版重新混合和扩展了原版,什么出版系统付费啊,这都是多人模式下的功能,我是单机玩家没什么实感。

  • 但是当知道他的注意力缺陷障碍后,这我懂啊,切换了视角不去看那些愿景,而是基于 Ted Nelson 对注意力解放的追求来看,Project Xanadu 也可以拿来当非线性写作用:

  • 可以先尽情写一堆可能用到或用不到的想法片段,而选择展示哪些內容,如何组织起來则是分成了另一件任务。

  • image

  • 双向链接就可以用来跟踪所有不同的想法,因为这些想法会发展成不同的写作路径,同一个想法可能存在于不同的上下文语境中。

  • 沿着链接能激活不同的上下文语境,因为都是自己思考的内容,所以很容易激活大脑中的相关记忆,就像在自己的大脑中漫游,SuperMemo18 加入的神经学习就是类似这种模式。

  • 当然,前提是你链接的是自己大脑里的想法,不是完全陌生的信息,我试过链接到资料,漫游到它们时得停好久重新阅读,很不爽。

神经学习、想法与双链

  • SuperMemo18 加入的神经学习有一个链接功能,不过是单链,可以手动建立双链。
  • 刚开始用神经学习时我就热衷于打链接,使用后感觉再次上当了:这玩意跟双链的互相关联一样没啥效果啊。
  • 初期我是拿来当分类用,后来发现不行,和双链软件一样完全没效果,和 SM 社区交流后发现得链接重要的 idea,不能只是分类、预整理:
  • 【概念】和【链接】是想要将我们脑内的知识网络映射出来,【概念】就应该是脑子里一瞬间能反应过来的事物,而不是庞大臃肿的分类学集合,那些包含太多卡片的【概念】,其内部的卡片间逻辑无法真正映射到脑中,事物间的联系被模糊了,而这应该是神经学习使用者不想见到的。

  • 嘿巧了,在思源里也能这么用双链啊!从那时起我开始用双链发展我的想法,不只是单纯的信息记录和整理。
  • 现在以我的三年来的经验来看:双链对知识和想法的组织极其有用
  • 我无法告诉你它对信息管理用处大不大,只从记录的视角来看它改变了规则,将整理的压力转移到未来,但就像【共同探讨】移动块 / 反链 / 快速无压记录 / 标签 评论区种所讨论的那样:“整理的压力一直都有,无论是现在还是未来,它都在那里,区别只在于你什么时候处理这个压力。”
  • 这句话对信息管理来说是对的,但如果你像我一样是为了发展想法、获得成长来使用思源,那规则完全不一样了,我们完全不需要在思源里整理数据。
  • 我们只需要在思源里发展想法,进行表达。当进行表达的那一刻,就已经做出了在大脑里进行了整理。
  • 这种需求不需要必须建立索引,记录的上下文事件、想法之间的联系都能帮助定位到笔记,它们的使用成本又很低。
  • 也就是说我们在大脑里建立索引,而不是在笔记里。
  • 除此之外整理信息是不会获得内在奖励的,它会损耗你的内在动力,但发展想法会产生内在奖励,提升你的内在动力,这是极大的不同,也是为什么使用双链和大纲能越写越爽。
  • 现在就剩下一个问题,哈桑老师所说的双链无压到底是不是 Roam 故意设计出来的?

ADHD、Conor White-Sullivan 和上下文反链

  • RoamResearch 刚开始可以看成 Project Xanadu 的单机版,现在有没有加入类似 Xanadu 的愿景我不好说,但是仅从设计来看,RoamResearch 解放注意力更直接:

    • 有一个笔记的最底层 DailyNotes,可以当成写任何东西的草稿纸
    • 有一个 Page 级的引用 [[,配合大纲能进行快速输入
    • 有一个块级引用 ((,可以将快速输入的内容进行再组织
    • 最重要的,它和 Xanadu 不同的是加入了上下文反链,这改变了游戏规则,诞生了一系列突破想象力的功能和用法,例如反链转移、空白节点、反链的筛选等等。
  • 巧合的是,Conor 也患有 ADHD,他曾多次提到这点并推荐 ADHD 患者使用 RoamResearch 来改善记录:

    • image
  • 哈桑老师对 Roam 的判断是对的,或许是能感同身受传统记录和结构带来的压力,那些热爱写作的 ADHD 患者会去尝试做出改变,果然替身使者是会相互吸引的。

  • 后来我在 Roam 社区看到了渐进形式化这个概念,酷!它把我想要解释的东西全部串联起来:Roam 的设计、传统记录的压力、编程注意力、流畅的非线性写作、结构化的思想工具等等,于是一次漫游:学习中的快乐与束缚就诞生了。

渐进形式化

  • 支持渐进形式化的软件很适合我,比如 Roam 式双链和 SuperMemo。

  • 它们以一定的程度贴合我跳跃的思维,我能直观地理解如何定制和使用它们,用来适应我的特定方式,并且它们支持了我这样做。

  • 比如:

    • 我不想先分类再记录,那么双链就给了我选择可以不去分类。
    • 我不想进行正式的、有逻辑的表达,那就能使用大纲快速输入想法。
    • 我很少能阅读完一本书,看了两三页就开始犯困,只想看有趣的内容,渐进阅读鼓励我这么做。
  • 这些软件对使用中的压力进行了优化,这真的很棒。

SuperMemo 中的压力优化

  • 说起压力,Anki 类软件用起来的压力对我来说是最大的,每天都要日清太反人类了,用之前雄心壮志,考完试之后再也不想打开。
  • SuperMemo 原来也是,后来出现了优先级队列、复习自动排序和自动推迟这些优化压力的功能,官方 wiki 也一直在引导使用这些功能,以尽可能低的摩擦力找到学习中产生的内在动力。
  • 我非常喜欢 SuperMemo 中的渐进主义:当感觉到无聊或者注意力不集中就可以跳过。
  • 以及作者对知识的态度:用你的新知识来娱乐。
  • 从学习中得到动力之后我就像赚到了第一桶金,想去玩点花活,去尝试了很多学习策略,后来发现还是表达最经济实惠,消耗的动力不多,效果也挺好。
  • 另外我还在 SM 中使用提取练习这个消耗大量动力的策略,表达的低消耗能保持我动力的收支平衡。

各个语境下的自上而下与自下而上

  • 说起这个是突然想起好玩的事,这两个词是个筐,你往里装什么都行,只要能解释的通。

  • 在笔记社区常用自上而下和自下而上这两个词,分别指代传统记录和双链的不分类记录。

  • 我曾经用自上而下来描述渐进阅读,用自下而上来形容双链,产生了融合两者的想法:

    • 既有自上而下,又有自下而上。
  • 所以当我看到 Andy 老师的自上而下,再自下而上时,我兴奋地认为他和我的想法一样,结果他不是在讲笔记方法,而是在说学习上的大方向:先整体,再细节,通过整体激发直觉与兴趣,再去学习基础。

  • 在文章里他提到的还原论是通过将复杂的系统分解成基本组分,这个过程类似渐进阅读,是自上而下地拆解。

  • 但是他反而用自下而上来指代还原论,我想 Andy 老师可能是在说拆解后从基本部分开始学习的过程是自下而上的,我拗了好久才理解。

  • 后来又在注意力的认知神经机制是什么里看到这两个词,在这里:

    • 自下而上指的是通过视觉等信息的外界刺激,由外部环境信息驱动注意力。
    • 自上而下指的是根据当前任务的目标和以往的知识对视觉通路中信息的进行的调控,这是大脑内部信息驱动的注意力。
  • 挺有趣的,我想起之前哈桑老师在 MOC - 管理链接而非本体里提到过面向主题和面向结构,后来又在哈桑式 DailyNote 工作流整理之难里提到过“如果预先维护一个结构,那就失去 daily notes 的意义了”。

  • 我从注意力的角度来思考,发现面向主题和面向结构本质都是在做注意力编程:

    • 面向主题要求持久思考某个主题,然后在思考中建立自己的结构。
    • 面向结构里,如果热衷维护结构则会把注意力都放在维护上,容易忽视思考。
  • 而一些工具就是依靠结构对注意力的编程实现的,比如模版啊、写作结构都是的。

  • 我们可以进行扩展,比如间隔重复系统是在时间线上编程注意力,卡片的提示也是在编程注意力,这些都在我之前的文章中。

到这里,我的任务就已经结束,如果您想与我进行更深入的交流,可以加入:

思源笔记民间交流群:538155062

思源笔记频道:频道号 SiYuanTFT42

一些资源:

图中提到的可选选项:

  1. DailyNotes 汇总展示

    • 方法一:可使用 F 的文档流插件对所有 DailyNotes 进行汇总展示

      1. 在集市里找到文档流插件下载并启用

      2. image

      3. 安装好后在左上角点击文档流插件图标

      4. 选择 SQL 查询

        • image
      5. 输入搜索所有 DailyNotes 的查询语句,例如:

        • select B.* from blocks as B join attributes as A
          on B.id = A.block_id
          where A.name like 'custom-dailynote-202%%%%%'
          order by A.value desc
          
    • 方法二:在汇总所有 DailyNotes 的时候,会遇到文档数量太多导致卡顿的情况,我是用烟火酱的块嵌入分页辅助挂件解决的,挂件已经下架,可以在这里找到,在浏览嵌入块的时候可以利用下面的预览折叠列表功能查看子级。

  2. 使用查询挂件建立主题的表格索引

    1. 打开集市下载查询挂件

      • image
    2. 在思源里使用“/挂件”唤出查询挂件,点击Query 图标唤出代码块

      • recording
    3. 复制烟火酱分享的 SQL 填入代码块

  3. 思源增强插件,在思源里提升双链使用的体验,有页内反链和反链筛选等功能,在集市里下载启用即可

    • image.png
    • 使用预览:
    • recording.gif
  4. HBLX 主题的功能:聚焦折叠的列表大纲后自动展开、预览折叠的列表大纲

    • 效果图

      • recording

      • 新版本记得打开点击聚焦选项(选项怎么又多了

        • image
    • 使用方法

      • 打开思源笔记设置

      • 点击进入外观——代码片段

        • image
      • 在 CSS 一栏添加下方给出的代码片段并启用:

        • image
      • 在 JS 一栏添加下方给出的代码片段并启用

        • image
      • 配合使用的 CSS

        • /*列表折叠*/
          .protyle-wysiwyg [data-node-id].li[fold="1"]>.h1>[spellcheck]:not(.fn__flex-1.history__text.protyle [data-node-id].li[fold="1"]>.h1>[spellcheck])::after,
          .protyle-wysiwyg [data-node-id].li[fold="1"]>.h2>[spellcheck]:not(.fn__flex-1.history__text.protyle [data-node-id].li[fold="1"]>.h2>[spellcheck])::after,
          .protyle-wysiwyg [data-node-id].li[fold="1"]>.h3>[spellcheck]:not(.fn__flex-1.history__text.protyle [data-node-id].li[fold="1"]>.h3>[spellcheck])::after,
          .protyle-wysiwyg [data-node-id].li[fold="1"]>.h4>[spellcheck]:not(.fn__flex-1.history__text.protyle [data-node-id].li[fold="1"]>.h4>[spellcheck])::after,
          .protyle-wysiwyg [data-node-id].li[fold="1"]>.h5>[spellcheck]:not(.fn__flex-1.history__text.protyle [data-node-id].li[fold="1"]>.h5>[spellcheck])::after,
          .protyle-wysiwyg [data-node-id].li[fold="1"]>.h6>[spellcheck]:not(.fn__flex-1.history__text.protyle [data-node-id].li[fold="1"]>.h6>[spellcheck])::after,
          .protyle-wysiwyg [data-node-id].li[fold="1"]>.p>[spellcheck]:not(.fn__flex-1.history__text.protyle [data-node-id].li[fold="1"]>.p>[spellcheck])::after {
              content: "  · · ·  ";
              font-family: "Trebuchet MS";
              display: inline;
              font-weight: bold;
              vertical-align: 5%;
              font-size: 75%;
              color: rgb(95, 99, 104);
              word-break: break-all;
              border: 1px solid rgb(95, 99, 104);
              margin-left: 9px;
              border-radius: 5px;
          }
          
      • JS

        
        /**
         * HBXL主题特性js
         */
        
        window.HBuilderXLight = {};
        window.HBuilderXLight.ButtonControl = {};
        
        //渲染进程和主进程通信
        const { ipcRenderer } = require('electron');
        ipcRenderer.on("siyuan-send_windows", (event, data) => {
            //console.log(data);
            switch (data.Value) {
                case "ButtonControl"://按钮状态控制
                    var ControlButtonIDs = data.ControlButtonIDs;
                    for (let index = 0; index < ControlButtonIDs.length; index++) {
                        const ControlButtonID = ControlButtonIDs[index];
                        window.HBuilderXLight.ButtonControl[ControlButtonID.ButtonID][ControlButtonID.ControlFun]();
                    }
                    break;
        
                default:
                    break;
            }
        });
        function broadcast(data) {
            ipcRenderer.send("siyuan-send_windows", data);
        }
        
        /**----------------------------------自动展开悬浮窗折叠列表,展开搜索条目折叠列表,聚焦单独列表-----体验优化----------------------------------*/
        
        function autoOpenList() {
        
            setInterval(() => {
                //找到所有的悬浮窗
                var Preview = document.querySelectorAll("[data-oid]");
        
                //如果发现悬浮窗内首行是折叠列表就展开并打上标记
                if (Preview.length != 0) {
                    for (let index = 0; index < Preview.length; index++) {
                        diguiTooONE_2(Preview[index], (v) => {
                            if (v.classList.contains("block__content")) {
                                var vs = v.children;
                                for (let index = 0; index < vs.length; index++) {
                                    var obj = vs[index].children[1]
                                    if (obj == null) continue;
                                    const element = obj.firstElementChild.firstElementChild;
                                    if (element == null) continue;
                                    if (!element.classList.contains("li")) continue;//判断是否是列表
                                    if (element.getAttribute("foldTag") != null) continue;//判断是否存在标记
                                    if (element.getAttribute("foid") == 0) continue;//判断是折叠
        
                                    element.setAttribute("fold", 0);
                                    element.setAttribute("foldTag", true);
                                }
                                return true;
                            }
                            return false;
                        }, 7)
                    }
                }
        
        
                var searchPreview = document.querySelector("#searchPreview [data-doc-type='NodeListItem'].protyle-wysiwyg.protyle-wysiwyg--attr>div:nth-child(1)");
                if (searchPreview == null) {
                    searchPreview = document.querySelector("#searchPreview [data-doc-type='NodeList'].protyle-wysiwyg.protyle-wysiwyg--attr>div:nth-child(1)>div:nth-child(1)");
                }
                if (searchPreview != null && searchPreview.getAttribute("data-type") == "NodeListItem" && searchPreview.getAttribute("fold") == 1) {
                    if (searchPreview.getAttribute("foldTag") == null) {//判断是否存在标记
                        searchPreview.setAttribute("fold", 0);
                        searchPreview.setAttribute("foldTag", true);
                    }
                }
                var contentLIst = [];
                var item1 = document.querySelectorAll(".layout-tab-container>.fn__flex-1.protyle:not(.fn__none) [data-doc-type='NodeListItem'].protyle-wysiwyg.protyle-wysiwyg--attr>div:nth-child(1)");
                var item2 = document.querySelectorAll(".layout-tab-container>.fn__flex-1.protyle:not(.fn__none) [data-doc-type='NodeList'].protyle-wysiwyg.protyle-wysiwyg--attr>div:nth-child(1)>div:nth-child(1)");
                contentLIst = [...item1, ...item2];
                for (let index = 0; index < contentLIst.length; index++) {
                    const element = contentLIst[index];
                    if (element != null && element.getAttribute("data-type") == "NodeListItem" && element.getAttribute("fold") == 1) {
                        if (element.getAttribute("foldTag") != null) continue;//判断是否存在标记
                        element.setAttribute("fold", 0);
                        element.setAttribute("foldTag", true);
                    }
                }
        
            }, 500)
        }
        
        /**----------------------------------列表折叠内容预览查看---------------------------------- */
        function collapsedListPreview() {
            BodyEventRunFun("mouseover", collapsedListPreviewEvent, 3000)
        }
        
        
        
        function collapsedListPreviewEvent() {
            var _turn = [...document.querySelectorAll(".layout-tab-container>.fn__flex-1.protyle:not(.fn__none) [data-node-id].li[fold='1']"),
            ...document.querySelectorAll("[data-oid] [data-node-id].li[fold='1']"),
            ...document.querySelectorAll("#searchPreview [data-node-id].li[fold='1']")];//查询页面所有的折叠列表
            var turn = [];
            for (let index = 0; index < _turn.length; index++) {//找到列表第一列表项(父项)
                const element = _turn[index].children[1];
                var item = element.className;
                if (item == "p" || item == "h1" || item == "h2" || item == "h3" || item == "h4" || item == "h5" || item == "h6") {
                    turn.push(element.firstElementChild)
                }
            }
        
            //检查注册事件的折叠列表是否恢复未折叠状态,是清除事件和去除标志属性
            var ListPreview = [...document.querySelectorAll(".layout-tab-container>.fn__flex-1.protyle:not(.fn__none) [ListPreview]"),
            ...document.querySelectorAll("[data-oid] [ListPreview]"),
            ...document.querySelectorAll("#searchPreview [ListPreview]")];
            for (let index = 0; index < ListPreview.length; index++) {
                const element = ListPreview[index];
                var fold = element.parentElement.getAttribute("fold")
        
                if (fold == null || fold == 0) {
                    element.removeAttribute("ListPreview");
                    var item = element.firstElementChild;
                    myRemoveEvent(item, "mouseenter", LIstIn);//解绑鼠标进入
                    myRemoveEvent(item.parentElement.parentElement, "mouseleave", LIstout);//解绑鼠标离开
        
                    var items = Array.from(item.parentElement.parentElement.children);
                    for (let index = 0; index < items.length; index++) {
                        const element = items[index];
                        if (element.getAttribute("triggerBlock") != null) {
                            element.remove();
                        }
                    }
                }
            }
        
            for (let index = 0; index < turn.length; index++) {//重新注册、筛选未注册鼠标事件折叠列表
                const element = turn[index];
                var elementPP = element.parentElement.parentElement;
        
                if (element.parentElement.getAttribute("ListPreview") != null) {
                    myRemoveEvent(element, "mouseenter", LIstIn);//解绑鼠标进入
                    myRemoveEvent(elementPP, "mouseleave", LIstout);//解绑鼠标离开
        
                    AddEvent(element, "mouseenter", LIstIn);//注册鼠标进入
                    AddEvent(elementPP, "mouseleave", LIstout);//注册鼠标离开
                } else {
                    element.parentElement.setAttribute("ListPreview", true);
                    AddEvent(element, "mouseenter", LIstIn);//注册鼠标进入
                    AddEvent(elementPP, "mouseleave", LIstout);//注册鼠标离开
                }
            }
        }
        
        var flag22 = false;
        
        function LIstout(e) {
            items = Array.from(e.target.children);
            flag22 = false;
            for (let index = 0; index < items.length; index++) {
                const element = items[index];
                if (element.getAttribute("triggerBlock") != null) {
                    element.remove();
                }
            }
        }
        
        function LIstIns(e) {
        
            var id = setInterval(() => {
        
                if (!flag22) {
                    clearInterval(id);
                    return;
                }
        
                var obj = e.target;
        
                var timeDiv = addinsertCreateElement(obj, "div");
                timeDiv.style.display = "inline-block";
                timeDiv.style.width = "0px";
                timeDiv.style.height = "16px";
        
                var X = timeDiv.offsetLeft;
                var Y = timeDiv.offsetTop;
                timeDiv.remove();
        
                var item = obj.parentElement.parentElement;
                if (item == null) return;
                var items = item.children
                var itemobj = items[items.length - 1];
                if (itemobj != null && itemobj.getAttribute("triggerBlock") != null) {
        
                    var items1 = items[items.length - 1];
                    items1.style.top = (Y + 35) + "px";
                    items1.style.left = (obj.offsetLeft + 35) + "px";
                    var items2 = items[items.length - 2];
                    items2.style.top = (Y + 2) + "px";
                    items2.style.left = (X + 45) + "px";
                    return;
                }
        
            }, 500);
        }
        
        function LIstIn(e) {
            flag22 = true;
        
            var obj = e.target;
            var timeDiv = addinsertCreateElement(obj, "div");
            timeDiv.style.display = "inline-block";
            timeDiv.style.width = "0px";
            timeDiv.style.height = "16px";
        
            var X = timeDiv.offsetLeft;
            var Y = timeDiv.offsetTop;
            timeDiv.remove();
        
            var f = obj.parentElement.parentElement;
            if (!f) return;
            var items = f.children;
        
            var itemobj = items[items.length - 1];
            if (itemobj != null && itemobj.getAttribute("triggerBlock") != null) return;
        
            var triggerBlock1 = CreatetriggerBlock(e)//创建触发块1
            //设置触发块样式,将触发块显示在〔 ··· 〕第二行位置
            triggerBlock1.style.top = (Y + 35) + "px";
            triggerBlock1.style.left = (obj.offsetLeft + 35) + "px";
            AddEvent(triggerBlock1, "mouseenter", () => {
                //一秒延时后搜索打开的悬浮窗,将悬浮窗中的列表展开,重复检查三次
                setTimeout(Suspended, 1000)
            });//注册鼠标进入
        
            var triggerBlock2 = CreatetriggerBlock(e)//创建触发块2
            //设置触发块样式,将触发块显示在〔 ··· 〕位置
            triggerBlock2.style.top = (Y + 2) + "px";
            triggerBlock2.style.left = (X + 45) + "px";
        
            AddEvent(triggerBlock2, "mouseenter", () => {
                //一秒延时后搜索打开的悬浮窗,将悬浮窗中的列表展开,重复检查三次
                setTimeout(Suspended, 1000)
            });//注册鼠标进入
        
            //一秒延时后搜索打开的悬浮窗,将悬浮窗中的列表展开,重复检查三次
            var previewID = obj.parentElement.parentElement.getAttribute("data-node-id");
            var jisu = 0;
            function Suspended() {
                jisu++;
                var y = false;
                if (jisu == 3) return
                var Sd = document.querySelectorAll("[data-oid]");
                if (Sd.length >= 1) { //如果找到那么就将悬浮窗中列表展开
                    for (let index = 0; index < Sd.length; index++) {
                        const element = Sd[index];
                        var item = element.children[1].firstElementChild.children[1].firstElementChild.firstElementChild;
                        if (item == null) continue;
                        if (item.getAttribute("data-node-id") == previewID) {
                            item.setAttribute("fold", 0);
                            y = true;
                        }
                    }
                }
                if (!y) { setTimeout(Suspended, 800) }
            }
            LIstIns(e);
        }
        
        function CreatetriggerBlock(e) {
            var objParent = e.target.parentElement;
            var triggerBlock = addinsertCreateElement(objParent.parentElement, "div");//创建触发块
            //设置触发块样式,将触发块显示在〔 ··· 〕位置
            triggerBlock.setAttribute("triggerBlock", true);
            triggerBlock.style.position = "absolute";
            triggerBlock.style.width = "40px";
            triggerBlock.style.height = "15px";
            //triggerBlock.style.background="red";
            triggerBlock.style.display = "flex";
            triggerBlock.style.zIndex = "9";
            triggerBlock.style.cursor = "pointer";
            triggerBlock.style.WebkitUserModify = "read-only";
            triggerBlock.setAttribute("contenteditable", "false");
            triggerBlock.innerHTML = "";
        
            //获取折叠列表ID,设置悬浮窗
            //protyle-wysiwyg__embed data-id
            var previewID = objParent.parentElement.getAttribute("data-node-id");
            triggerBlock.setAttribute("class", "protyle-attr");
        
            triggerBlock.style.opacity = "0";
            //在触发块内创建思源超链接 
            triggerBlock.innerHTML = "<span data-type='a' class='list-A' data-href=siyuan://blocks/" + previewID + ">####</span>";
            //将这个思源连接样式隐藏
            var a = triggerBlock.firstElementChild;
            a.style.fontSize = "15px";
            a.style.lineHeight = "15px";
            a.style.border = "none";
            return triggerBlock;
        }
        
        /**----------------鼠标中键标题、列表文本折叠/展开----------------*/
        function collapseExpand_Head_List() {
            var flag45 = false;
            AddEvent(document.body, "mouseup", () => {
                flag45 = false;
            });
        
            AddEvent(document.body, "mousedown", (e) => {
                if (e.button == 2) { flag45 = true; return }
                if (flag45 || e.shiftKey || e.altKey || e.button != 1) return;
                var target = e.target;
        
                if (target.getAttribute("contenteditable") == null) {
                    isFatherFather(target, (v) => {
                        if (v.getAttribute("contenteditable") != null) {
                            target = v;
                            return true;
                        }
                        return false;
                    }, 10);
                }
        
                var targetParentElement = target.parentElement;
                if (targetParentElement == null) return;
        
                //是标题吗?
                if (targetParentElement != null && targetParentElement.getAttribute("data-type") == "NodeHeading") {
        
                    var targetParentElementParentElement = targetParentElement.parentElement;
                    //标题父元素是列表吗?
                    if (targetParentElementParentElement != null && targetParentElementParentElement.getAttribute("data-type") == "NodeListItem") {
                        e.preventDefault();
                        //列表项实现折叠
                        _collapseExpand_NodeListItem(target);
                    } else {
                        e.preventDefault();
                        //标题块标项实现折叠
                        _collapseExpand_NodeHeading(target);
                    }
                } else {//是列表
                    var targetParentElementParentElement = targetParentElement.parentElement;
                    if (targetParentElementParentElement != null && targetParentElementParentElement.getAttribute("data-type") == "NodeListItem") {
                        e.preventDefault();
                        //列表项实现折叠
                        _collapseExpand_NodeListItem(target);
                    }
                }
            });
        
            //标题,块标实现折叠
            function _collapseExpand_NodeHeading(element) {
        
                var i = 0;
                while (element.className != "protyle" && element.className != "fn__flex-1 protyle" && element.className != "block__edit fn__flex-1 protyle" && element.className != "fn__flex-1 spread-search__preview protyle") {
                    if (i == 999) return;
                    i++;
                    element = element.parentElement;
                }
                var ddddd = element.children;
                for (let index = ddddd.length - 1; index >= 0; index--) {
                    const element = ddddd[index];
                    if (element.className == "protyle-gutters") {
                        var fold = diguiTooONE_1(element, (v) => { return v.getAttribute("data-type") === "fold"; })
                        if (fold != null) fold.click();
                        return;
                    }
                }
            }
        
            //列表,列表项实现折叠
            function _collapseExpand_NodeListItem(element) {
        
                //在悬浮窗中第一个折叠元素吗?
                var SiyuanFloatingWindow = isSiyuanFloatingWindow(element);
                if (SiyuanFloatingWindow) {
                    var vs = isFatherFather(element, (v) => v.classList.contains("li"), 7);
                    if (vs != null && (vs.previousElementSibling == null)) {
                        var foid = vs.getAttribute("fold");
                        if (foid == null || foid == "0") {//判断是折叠
                            vs.setAttribute("fold", "1");
                        } else {
                            vs.setAttribute("fold", "0");
                        }
                        return;
                    }
                }
        
        
                var i = 0;
                while (element.getAttribute("contenteditable") == null) {
                    if (i == 999) return;
                    i++;
                    element = element.parentElement;
                }
                var elementParentElement = element.parentElement.parentElement;
        
                var fold = elementParentElement.getAttribute("fold");
        
                if (elementParentElement.children.length == 3) return;
        
                if (fold == null || fold == "0") {
                    setBlockfold_1(elementParentElement.getAttribute("data-node-id"));
                } else {
                    setBlockfold_0(elementParentElement.getAttribute("data-node-id"));
                }
            }
        }
        
        /**
         * 
         * @param {*} element 元素是否在思源悬浮窗中
         * @returns 是返回悬浮窗元素,否返回null
         */
        function isSiyuanFloatingWindow(element) {
            return isFatherFather(element, (v) => {
                if (v.getAttribute("data-oid") != null) {
                    return true;
                }
                return false;
            });
        }
        
        
        
        
        /**----------------------------------查找匹配列表条目前的图标可以鼠标悬停打开悬浮窗--------------------------------------*/
        
        function findMatchingListEntries() {
            BodyEventRunFun("mouseover", _findMatchingListEntries, 3000);
        }
        
        function _findMatchingListEntries() {
        
            var searchList = document.getElementById("searchList");
            var globalSearchList = document.getElementById("globalSearchList");
        
            if (searchList != null) {
                var searchLists = searchList.children;
                for (let index = 0; index < searchLists.length; index++) {
                    const element = searchLists[index];
                    //var b3_list_item__text=element.children[1];
                    //if(element.getAttribute("title")!=null)break;
                    //element.setAttribute("title",b3_list_item__text.innerText);
        
                    var itemlianjie = element.firstElementChild;
                    if (itemlianjie == null || itemlianjie.getAttribute("data-defids") != null) break;
                    itemlianjie.setAttribute("data-defids", '[""]');
                    itemlianjie.setAttribute("class", "b3-list-item__graphic popover__block");
                    itemlianjie.setAttribute("data-id", element.getAttribute("data-node-id"));
                }
            };
        
            if (globalSearchList != null) {
                var globalSearchLists = globalSearchList.children;
                for (let index = 0; index < globalSearchLists.length; index++) {
                    const element = globalSearchLists[index];
                    //var b3_list_item__text=element.children[1];
                    //if(element.getAttribute("title")!=null)break;
                    //element.setAttribute("title",b3_list_item__text.innerText);
                    var itemlianjie = element.firstElementChild;
                    if (itemlianjie == null || itemlianjie.getAttribute("data-defids") != null) break;
                    itemlianjie.setAttribute("data-defids", '[""]');
                    itemlianjie.setAttribute("class", "b3-list-item__graphic popover__block");
                    itemlianjie.setAttribute("data-id", element.getAttribute("data-node-id"));
                }
            };
        }
        
        
        
        /**------------------------------------思源悬浮窗头栏中键关闭------------------------------------- */
        function theFloatingWindowIsClosed() {
            AddEvent(document.body, "mousedown", (e) => {
                if (e.button != 1) return;
                var element = e.target;
                var className = element.className;
                if (className == "block__icons block__icons--border" || className == "fn__space fn__flex-1" || className == "maxMinButton") {
                    element = element.parentElement;
                } else {
                    return;
                }
        
                diguiTooONE_1(element, (v) => {
                    if (v.getAttribute("data-type") == "close") {
                        v.click();
                        return true;
                    }
                    return false;
                })
            });
        }
        
        /**----------------------思源悬浮窗检测到单个段落块,单个列表项,面包屑前一级展示/反链计数悬浮窗反链引用高亮标记----------------------- */
        /*请无视糟糕中文代码 */
        function SuspendedWindowNoSection() {
        
            setInterval(() => {
                //获取所有悬浮窗内容类型,排除一个以元素和非段落块,查找面包屑模拟点击倒数第二级展开内容
                document.querySelectorAll("[data-oid]:not([zhankai])").forEach((da) => {
        
                    if (da.querySelector(".fn__loading")) return; //有内容没加载
        
        
                    var protyles;
                    try {
                        protyles = da.children[1].children;
                        if (protyles == null) return;
                    } catch (error) {
                        return;
                    }
        
                    for (let index = 0; index < protyles.length; index++) {
                        const element = protyles[index];
                        /***********************************************排除失效引用但还站一个悬浮窗格的情况 */
                        if (element.querySelector("[data-doc-type]" && element.firstElementChild != null) == null) return;
                    }
        
                    if (da.getAttribute("zhankai") != null) return;//已经处理过的悬浮窗
                    da.setAttribute("zhankai", true);//标记已经处理,防止下次循环多次处理
        
                    Array.from(da.children[1].children).forEach((g) => {
        
                        var v = g.querySelector("[data-doc-type]");//查询基力悬浮窗内容类型元素
                        if (v.children.length != 1 && v.getAttribute("data-doc-type") != "NodeHeading") return;//剔除data-doc-type存在多个子元素的悬浮窗
        
        
                        var 悬浮窗类型 = "引用悬浮窗";
                        var 反链引用ID;
                        var defmark = v.querySelector(".def--mark");
                        if (defmark) {
                            悬浮窗类型 = "反链悬浮窗";//判断悬浮窗类型
                            反链引用ID = defmark.getAttribute("data-id");
                        }
        
        
                        //console.log(悬浮窗类型, 反链引用ID);
                        var 悬浮窗出现的单独块是什么块;
                        //悬浮窗出现的单独块是什么块?
                        switch (v.firstElementChild.className) {
                            case "render-node"://嵌入块
                                悬浮窗出现的单独块是什么块 = "嵌入块"; break;
                            case "p"://段落块
                                悬浮窗出现的单独块是什么块 = "段落块"; break;
                            case "li"://单独列表项块//列表只要有三个子元素就是单独列表项块
                                if (v.firstElementChild.children.length == 3) 悬浮窗出现的单独块是什么块 = "列表块";
                                else 悬浮窗出现的单独块是什么块 = ""; break;
                            case "sb"://单独超级快
                                悬浮窗出现的单独块是什么块 = "超级块"; break;
                            default:
                                悬浮窗出现的单独块是什么块 = ""; break;
                        }
        
        
                        var 引用ID;
                        var 拟点击面包屑模元素;
        
                        var 时间累加器 = 0;
                        var iD = setInterval(() => {
                            时间累加器 += 100;
                            if (时间累加器 > 3000) clearInterval(iD);//超时
        
        
                            try {
        
                                if (悬浮窗出现的单独块是什么块 != "超级块") {
                                    引用ID = v.parentElement.previousElementSibling.firstElementChild.lastElementChild.getAttribute("data-node-id");//记录原块id
                                } else {//思源超级块不显示面包屑上因此需要单独处理
                                    引用ID = v.firstElementChild.getAttribute("data-node-id");
                                }
                                拟点击面包屑模元素 = v.parentElement.previousElementSibling.firstElementChild.lastElementChild.previousElementSibling.previousElementSibling.children[1];
                                clearInterval(iD);//上段代码没报错就证明元素获取成功了,关闭定时器
        
                                if (悬浮窗出现的单独块是什么块 != "") {
                                    悬浮窗面包屑跳转();
                                } else {
                                    不需要模拟点击的反链展示();
                                }
        
                            } catch (error) {
                                console.log("面包屑模拟点击元素获取错误", v.parentElement.previousElementSibling.firstElementChild.lastElementChild);
                            }
                        }, 100);
        
        
                        function 悬浮窗面包屑跳转() {
                            拟点击面包屑模元素.click();
        
                            const observer = new MutationObserver(() => {//创建监视
                                observer.disconnect();//关闭监视
                                setTimeout(() => {
                                    switch (悬浮窗类型) {
                                        case "反链悬浮窗":
        
                                            switch (悬浮窗出现的单独块是什么块) {
                                                case "嵌入块":
                                                    var ts = v.parentElement.querySelectorAll(`[data-content="select * from blocks where id='${反链引用ID}'"]`);
                                                    for (let index = 0; index < ts.length; index++) {
                                                        const element = ts[index];
                                                        element.classList.add("blockhighlighting")///块高亮
                                                        if (index == 0) 高亮内容跳转到悬浮窗中间(element, v);
                                                    }
                                                    break;
                                                default:
                                                    var ts = v.parentElement.querySelectorAll(`[data-id='${反链引用ID}'][data-subtype]`);
                                                    for (let index = 0; index < ts.length; index++) {
                                                        const element = ts[index];
                                                        element.classList.add("blockRefhighlighting")//引用高亮
                                                        isFatherFather(element, (f) => {
                                                            if (f.getAttribute("contenteditable") != null) {
                                                                f.classList.add("blockhighlighting")//块高亮
                                                                if (index == 0) 高亮内容跳转到悬浮窗中间(element, v);
                                                                return true;
                                                            }
                                                            if (f.tagName == "TD" || f.tagName == "TR") {
                                                                f.classList.add("blockhighlighting")//块高亮
                                                                if (index == 0) 高亮内容跳转到悬浮窗中间(element, v);
                                                                return true;
                                                            }
                                                            return false;
                                                        }, 4)
                                                    }
                                                    break;
                                            }
                                            break;
                                        case "引用悬浮窗":
                                            switch (悬浮窗出现的单独块是什么块) {
                                                case "嵌入块":
                                                    var ts = v.parentElement.querySelector(`[data-node-id='${引用ID}'][data-type="NodeBlockQueryEmbed"]`);
                                                    ts.classList.add("blockhighlighting")//引用高亮
                                                    高亮内容跳转到悬浮窗中间(ts, v);
                                                    break;
                                                default:
                                                    var ts = v.parentElement.querySelector(`[data-node-id='${引用ID}']`);
                                                    ts.classList.add("blockhighlighting")//引用高亮
                                                    高亮内容跳转到悬浮窗中间(ts, v);
                                                    break;
                                            }
        
                                            break;
                                        default:
                                            break;
                                    }
                                }, 500)
        
                            });
                            observer.observe(v, { attributes: false, childList: true, subtree: false });
                        }
        
                        function 不需要模拟点击的反链展示() {
                            switch (悬浮窗类型) {
                                case "反链悬浮窗":
                                    var protyle_content = v.parentElement;
                                    var ts = protyle_content.querySelectorAll(`[data-id='${反链引用ID}']`);
                                    for (let index = 0; index < ts.length; index++) {
                                        const element = ts[index];
                                        element.classList.add("blockRefhighlighting")//引用高亮
                                        isFatherFather(element, (f) => {
                                            if (f.getAttribute("contenteditable") != null) {
                                                f.classList.add("blockhighlighting")//块高亮
                                                if (index == 0) 高亮内容跳转到悬浮窗中间(element, v)
                                                return true;
                                            }
                                            if (f.tagName == "TD" || f.tagName == "TR") {
                                                f.classList.add("blockhighlighting")//块高亮
                                                if (index == 0) 高亮内容跳转到悬浮窗中间(element, v)
                                                return true;
                                            }
                                            return false;
                                        }, 4)
                                    }
                                    break;
                                case "引用悬浮窗":
                                    var protyle_content = v.parentElement;
                                    var ts = protyle_content.querySelector(`[data-node-id='${引用ID}']`);
                                    //ts.scrollIntoView(true);
                                    // ts.classList.add("blockRefhighlighting")//引用高亮
        
                                    break;
                                default:
                                    break;
                            }
                        }
        
                        function 高亮内容跳转到悬浮窗中间(element, v) {
                            setTimeout(() => {
                                element.scrollIntoView(false);
                                setTimeout(() => {
                                    var tim = v.parentElement;
                                    //console.log(tim, tim.scrollTop);
                                    if (tim.scrollTop != 0) {
                                        tim.scrollBy(0, tim.offsetHeight / 2);
                                    }
                                }, 100);
                                isFatherFather(v, (g) => {
                                    if (g.getAttribute("class") == "block__content") {
                                        g.scrollTo(0, 0);
                                        return true;
                                    }
                                    return false;
                                }, 5)
                            }, 100);
                        }
        
                    });
                });
            }, 1000);
        
        }
        
        /* 操作 */
        
        /**
         * 获得所选择的块对应的块 ID
         * @returns {string} 块 ID
         * @returns {
         *     id: string, // 块 ID
         *     type: string, // 块类型
         *     subtype: string, // 块子类型(若没有则为 null)
         * }
         * @returns {null} 没有找到块 ID */
        function getBlockSelected() {
            let node_list = document.querySelectorAll('.protyle-wysiwyg--select');
            if (node_list.length === 1 && node_list[0].dataset.nodeId != null) return {
                id: node_list[0].dataset.nodeId,
                type: node_list[0].dataset.type,
                subtype: node_list[0].dataset.subtype,
            };
            return null;
        }
        
        function ClickMonitor() {
            window.addEventListener('mouseup', MenuShow)
        }
        
        function MenuShow() {
            setTimeout(() => {
                let selectinfo = getBlockSelected()
                if (selectinfo) {
                    let selecttype = selectinfo.type
                    let selectid = selectinfo.id
                    if (selecttype == "NodeList" || selecttype == "NodeTable") {
                        setTimeout(() => InsertMenuItem(selectid, selecttype), 0)
                    }
                }
            }, 0);
        }
        
        function InsertMenuItem(selectid, selecttype) {
            let commonMenu = document.querySelector("commonMenu")
            let readonly = commonMenu.querySelector(".b3-menu__item--readonly")
            let selectview = commonMenu.querySelector('[id="viewselect"]')
            if (readonly) {
                if (!selectview) {
                    commonMenu.insertBefore(ViewSelect(selectid, selecttype), readonly)
                    commonMenu.insertBefore(MenuSeparator(), readonly)
                }
            }
        }
        
        function ViewMonitor(event) {
            let id = event.currentTarget.getAttribute("data-node-id")
            let attrName = 'custom-' + event.currentTarget.getAttribute("custom-attr-name")
            let attrValue = event.currentTarget.getAttribute("custom-attr-value")
            let blocks = document.querySelectorAll(`.protyle-wysiwyg [data-node-id="${id}"]`)
            if (blocks) {
                blocks.forEach(block => block.setAttribute(attrName, attrValue))
            }
            let attrs = {}
            attrs[attrName] = attrValue
            设置思源块属性(id, attrs)
        }
        
        
        
        
        //+++++++++++++++++++++++++++++++++思源API++++++++++++++++++++++++++++++++++++
        //思源官方API文档  https://github.com/siyuan-note/siyuan/blob/master/API_zh_CN.md
        
        
        
        async function queryAPI(sqlstmt, then, obj = null) {
            await 向思源请求数据("/api/query/sql", {
                stmt: sqlstmt
            }).then((v) => then(v.data, obj))
        }
        
        /**
         * 
         * @param {*} 内容块id 
         * @param {*} 回调函数 
         * @param {*} 传递对象 
         */
        async function 根据ID获取人类可读路径(内容块id, then, obj = null) {
            await 向思源请求数据('/api/filetree/getHPathByID', {
                id: 内容块id
            }).then((v) => then(v.data, obj))
        }
        
        
        
        async function 以id获取文档聚焦内容(id, then, obj = null) {
            await 向思源请求数据('/api/filetree/getDoc', {
                id: id,
                k: "",
                mode: 0,
                size: 36,
            }).then((v) => then(v.data, obj))
        }
        
        async function 更新块(id, dataType, data, then = null, obj = null) {
            await 向思源请求数据('/api/block/updateBlock', {
                id: id,
                dataType: dataType,
                data: data,
            }).then((v) => {
                if (then) then(v.data, obj);
            })
        }
        
        async function 设置思源块属性(内容块id, 属性对象) {
            let url = '/api/attr/setBlockAttrs'
            return 解析响应体(向思源请求数据(url, {
                id: 内容块id,
                attrs: 属性对象,
            }))
        }
        
        async function 获取块属性(内容块id, then = null, obj = null) {
            let url = '/api/attr/getBlockAttrs'
            return 向思源请求数据(url, {
                id: 内容块id
            }).then((v) => {
                if (then) then(v.data, obj);
            })
        }
        
        async function 向思源请求数据(url, data) {
            const response = await fetch(url, {
                body: JSON.stringify(data),
                method: 'POST',
                headers: {
                    Authorization: `Token ''`,
                }
            });
            if (response.status === 200)
                return await response.json();
            else return null;
        }
        
        async function 解析响应体(response) {
            let r = await response
            return r.code === 0 ? r.data : null
        }
        
        
        async function 获取文件(path, then = null, obj = null) {
            let url = '/api/file/getFile';
            await 向思源请求数据(url, {
                path: path
            }).then((v) => {
                if (then) then(v, obj);
            });
        }
        
        
        /**
         * 
         * @param {*} notebookID 笔记本id
         * @param {*} dataPath 父文档路径
         * @param {*} sortType 文档排序类型
         * @param {*} then 回调函数
         * @param {*} obj 传递对象
         */
        async function 获取子文档数据(notebookID, dataPath, sortType, then = null, obj = null) {
            let url = "/api/filetree/listDocsByPath";
            await 向思源请求数据(url, {
                notebook: notebookID,
                path: dataPath,
                sort: sortType,
            }).then((v) => {
                if (then) then(v.data.files, obj);
            });
        }
        
        
        
        
        async function 写入文件(path, filedata, then = null, obj = null, isDir = false, modTime = Date.now()) {
        
            let blob = new Blob([filedata]);
            let file = new File([blob], path.split('/').pop());
            let formdata = new FormData();
            formdata.append("path", path);
            formdata.append("file", file);
            formdata.append("isDir", isDir);
            formdata.append("modTime", modTime);
            await fetch(
                "/api/file/putFile", {
                body: formdata,
                method: "POST",
                headers: {
                    Authorization: `Token ""`,
                },
            }).then((v) => {
                console.log(v);
                setTimeout(() => {
                    if (then) then(obj);
                }, 200)
            });
        }
        async function 写入文件2(path, filedata, then = null, obj = null, isDir = false, modTime = Date.now()) {
        
            let blob = new Blob([filedata]);
            let file = new File([blob], path.split('/').pop());
            let formdata = new FormData();
            formdata.append("path", path);
            formdata.append("file", file);
            formdata.append("isDir", isDir);
            formdata.append("modTime", modTime);
            const response = await fetch(
                "/api/file/putFile", {
                body: formdata,
                method: "POST",
                headers: {
                    Authorization: `Token ""`,
                },
            });
        
            if (response.status === 200) {
                if (then) then(obj);
            }
        }
        
        
        //+++++++++++++++++++++++++++++++++辅助API++++++++++++++++++++++++++++++++++++
        
        
        /**
         * 方便为主题功能添加开关按钮,并选择是否拥有记忆状态
         * @param {*} ButtonID 按钮ID。
         * @param {*} ButtonTitle 按钮作用提示文字。
         * @param {*} ButtonFunctionType 按钮功能类型。:EnumButtonFunctionType 枚举
         * @param {*} ButtonCharacteristicType 按钮特性类型。:EnumButtonCharacteristicType 枚举
         * @param {*} NoButtonSvg 按钮激活Svg图标路径
         * @param {*} OffButtonSvg 按钮未激活Svg图标路径
         * @param {*} OnClickRunFun 按钮开启执行函数
         * @param {*} OffClickRunFun 按钮关闭执行函数
         * @param {*} Memory 是否设置记忆状态 true为是留空或false为不设置记忆状态。
         */
        function HBuiderXThemeToolbarAddButton(ButtonID, ButtonFunctionType, ButtonCharacteristicType, ButtonTitle, ONButtonSvgURL, OffButtonSvgURL, OnClickRunFun, OffClickRunFun, Memory = false) {
        
            //确认主题功能区toolbar是否存在,不存在就创建
            var HBuiderXToolbar = document.getElementById("HBuiderXToolbar");
            if (HBuiderXToolbar == null) {
                if (isPc()) {
                    HBuiderXToolbar = document.createElement("div");
                    HBuiderXToolbar.id = "HBuiderXToolbar";
                    HBuiderXToolbar.style.marginRight = "3px";
                    HBuiderXToolbar.style.marginTop = "6px";
                    document.getElementById("windowControls").parentElement.insertBefore(HBuiderXToolbar, document.getElementById("windowControls"));
        
                    HBuiderXToolbar.style.width = "35px";
                    HBuiderXToolbar.style.overflow = "hidden";
                    AddEvent(HBuiderXToolbar, "mouseover", () => { HBuiderXToolbar.style.width = "auto" })
                    AddEvent(HBuiderXToolbar, "mouseout", () => { HBuiderXToolbar.style.width = "35px" })
        
                } else if (isBrowser()) {
                    HBuiderXToolbar = insertCreateAfter(document.getElementById("barMode"), "div", "HBuiderXToolbar");
                    HBuiderXToolbar.style.marginRight = "3px";
                    HBuiderXToolbar.style.marginTop = "4px";
        
                    HBuiderXToolbar.style.width = "35px";
                    HBuiderXToolbar.style.overflow = "hidden";
                    AddEvent(HBuiderXToolbar, "mouseover", () => { HBuiderXToolbar.style.width = "auto" })
                    AddEvent(HBuiderXToolbar, "mouseout", () => { HBuiderXToolbar.style.width = "35px" })
                }
                else if (isPcWindow()) {
                    HBuiderXToolbar = insertCreateBefore(document.getElementById("minWindow"), "div", "HBuiderXToolbar");
                    HBuiderXToolbar.style.position = "absolute";
                    HBuiderXToolbar.style.height = "25px";
                    HBuiderXToolbar.style.paddingTop = "2px";
                    HBuiderXToolbar.style.overflowY = "scroll";
                    HBuiderXToolbar.style.right = "77px";
        
                    HBuiderXToolbar.style.width = "35px";
                    HBuiderXToolbar.style.overflow = "hidden";
                    AddEvent(HBuiderXToolbar, "mouseover", () => { HBuiderXToolbar.style.width = "auto" })
                    AddEvent(HBuiderXToolbar, "mouseout", () => { HBuiderXToolbar.style.width = "35px" })
                } else if (isPhone()) {
                    HBuiderXToolbar = insertCreateBefore(document.getElementById("toolbarEdit"), "div", "HBuiderXToolbar");
                    HBuiderXToolbar.style.position = "relative";
                    HBuiderXToolbar.style.height = "25px";
                    HBuiderXToolbar.style.overflowY = "scroll";
                    HBuiderXToolbar.style.paddingTop = "7px";
                    HBuiderXToolbar.style.marginRight = "9px";
                }
            }
        
        
            var addButton = addinsertCreateElement(HBuiderXToolbar, "div");
            addButton.style.width = "17px";
            addButton.style.height = "100%";
            addButton.style.float = "left";
            addButton.style.marginLeft = "10px";
            addButton.style.backgroundImage = "url(" + OffButtonSvgURL + ")";
            addButton.style.backgroundRepeat = "no-repeat";
            addButton.style.backgroundPosition = "left top";
            addButton.style.backgroundSize = "100%";
            addButton.style.cursor = "pointer";
        
            addButton.setAttribute("title", ButtonTitle);
            addButton.id = ButtonID;
        
            var offon = "0";
            var isclick = 0;
        
            var broadcastData_off = { "Value": "ButtonControl", "ControlButtonIDs": [{ "ButtonID": ButtonID, "ControlFun": "ControlFun_off" }] };
            var broadcastData_on = { "Value": "ButtonControl", "ControlButtonIDs": [{ "ButtonID": ButtonID, "ControlFun": "ControlFun_on" }] };
        
            if (Memory) {
                GetItem(ButtonID, (v) => {
                    offon = v;
                    if (offon == "1") {
                        _on();
                    } else if (offon == null || offon == undefined) {
                        offon = "0";
                    }
                });
        
            }
        
            AddEvent(addButton, "click", () => {
                window.HBuilderXLight.ButtonControl[ButtonID].Isclick = isclick = 1;
        
                if (offon == "0" || offon == null || offon == undefined) {
                    broadcast(broadcastData_on);
                    return;
                }
        
                if (offon == "1") {
                    broadcast(broadcastData_off);
                    return;
                }
            });
        
        
            AddEvent(addButton, "mouseover", () => {
                if (offon == "0" || offon == null || offon == undefined) {
                    addButton.style.filter = "drop-shadow(rgb(0, 0, 0) 0px 0)";
                }
            });
            AddEvent(addButton, "mouseout", () => {
                if (offon == "0" || offon == null || offon == undefined) {
                    addButton.style.filter = "none";
                }
            });
        
        
            function buttonCharacteristicDispose() {
                switch (ButtonCharacteristicType) {
                    case 2:
                        if (ButtonFunctionType == 2) {
        
                            var topicfilterButtons = [];
                            var ButtonControl = window.HBuilderXLight.ButtonControl;
        
                            for (var t in ButtonControl) {
                                if (t != ButtonID && ButtonControl[t].ButtonFunctionType == 2 && ButtonControl[t].OffOn == "1") {
                                    //console.log(t, ButtonControl[t].OffOn);
                                    ButtonControl[t].Isclick = 1;
                                    topicfilterButtons.push(ButtonControl[t]);
                                }
                            }
        
                            if (topicfilterButtons.length == 0) {
                                window.HBuilderXLight.ButtonControl[ButtonID].Isclick = isclick = 0;
                                return;
                            }
                            //console.log(ButtonControl[ButtonID].Isclick);
                            if (window.HBuilderXLight.ButtonControl[ButtonID].Isclick == 1) {
                                var index = (topicfilterButtons.length) - 1;
                                var id = setInterval(() => {
                                    if (index >= 0) {
                                        //console.log(window.HBuilderXLight.ButtonControl[ButtonID].Isclick);
        
                                        const element = topicfilterButtons[index];
                                        element["ControlFun_off"]();
                                    } else {
                                        // console.log(window.HBuilderXLight.ButtonControl[ButtonID].Isclick);
        
                                        clearInterval(id);
                                        window.HBuilderXLight.ButtonControl[ButtonID].Isclick = isclick = 0;
        
                                    }
                                    index--;
                                }, 300)
                            } else {
                                for (let index = 0; index < topicfilterButtons.length; index++) {
                                    const element = topicfilterButtons[index];
                                    element["ControlFun_off"]();
                                }
                            }
                        }
        
                        break;
        
                    default:
                        break;
                }
            }
        
            function _off() {
                addButton.style.backgroundImage = "url(" + OffButtonSvgURL + ")";
                addButton.style.filter = "none";
                window.HBuilderXLight.ButtonControl[ButtonID].OffOn = offon = "0";
                //console.log(window.HBuilderXLight.ButtonControl[ButtonID].Isclick);
        
                if (Memory && window.HBuilderXLight.ButtonControl[ButtonID].Isclick == 1) {
                    //console.log("bbbbbbbbbbbb")
        
                    SetItem(ButtonID, "0", () => {
                        OffClickRunFun(addButton);
                        window.HBuilderXLight.ButtonControl[ButtonID].Isclick = isclick = 0;
                    });
                } else {
                    //console.log("aaaaaaaaaaa")
                    OffClickRunFun(addButton);
                    window.HBuilderXLight.ButtonControl[ButtonID].Isclick = isclick = 0;
                };
            }
            function _on() {
                addButton.style.backgroundImage = "url(" + ONButtonSvgURL + ")";
                addButton.style.filter = "drop-shadow(rgb(0, 0, 0) 0px 0)";
                window.HBuilderXLight.ButtonControl[ButtonID].OffOn = offon = "1";
                //console.log(window.HBuilderXLight.ButtonControl[ButtonID].Isclick);
                if (Memory && window.HBuilderXLight.ButtonControl[ButtonID].Isclick == 1) {
                    SetItem(ButtonID, "1", () => {
                        OnClickRunFun(addButton);
                        buttonCharacteristicDispose();
                    });
                } else {
                    OnClickRunFun(addButton);
                    buttonCharacteristicDispose();
                }
            }
        
            window.HBuilderXLight.ButtonControl[ButtonID] = {
                "ControlFun_off": _off,
                "ControlFun_on": _on,
                "OffOn": offon,
                "Isclick": isclick,
                "ButtonFunctionType": ButtonFunctionType,
                "ButtonCharacteristicType": ButtonCharacteristicType
            }
        }
        
        function setItem(key, value) {
            window.HBuilderXLight.config[key] = value;
        }
        
        function getItem(key) {
            return window.HBuilderXLight.config[key] === undefined ? null : window.HBuilderXLight.config[key];
        }
        
        function removeItem(key) {
            delete window.HBuilderXLight.config[key];
        }
        
        
        function SetItem(key, value, fun = null) {
            获取文件("/data/widgets/HBuilderX-Light.config.json", (config) => {
                if (config) {//不存在配置文件就要创建
                    config[key] = value;
                    //console.log(config);
                    写入文件2("/data/widgets/HBuilderX-Light.config.json", JSON.stringify(config, undefined, 4), fun);
                } else {
                    写入文件2("/data/widgets/HBuilderX-Light.config.json", JSON.stringify({ "HBuilderXLight": 1, key: value }, undefined, 4), fun);
                }
            });
        }
        
        function GetItem(key, then = null) {
            获取文件("/data/widgets/HBuilderX-Light.config.json", (config) => {
                if (config) {//不存在配置文件就要创建
                    // console.log(config[key]);
                    try {
        
                        if (then) then(config[key]);
                    } catch (error) {
                        if (then) then(null);
                    }
                } else {
                    写入文件2("/data/widgets/HBuilderX-Light.config.json", JSON.stringify({ "HBuilderXLight": 1 }, undefined, 4), then(null));
                }
            });
        }
        
        function RemoveItem(key, fun = null) {
            获取文件("/data/widgets/HBuilderX-Light.config.json", (config) => {
                if (config) {//不存在配置文件就要创建
                    delete config[key];
                    //console.log(config);
                    写入文件2("/data/widgets/HBuilderX-Light.config.json", JSON.stringify(config, undefined, 4), fun);
                } else {
                    写入文件2("/data/widgets/HBuilderX-Light.config.json", JSON.stringify({ "HBuilderXLight": 1 }, undefined, 4), fun);
                }
            });
        }
        
        
        
        
        
        /**
         * 在DIV光标位置插入内容
         * @param {*} content 
         */
        function insertContent(content) {
            if (content) {
                var sel = window.getSelection();
                if (sel.rangeCount > 0) {
                    var range = sel.getRangeAt(0); //获取选择范围
                    range.deleteContents(); //删除选中的内容
                    var el = document.createElement("div"); //创建一个空的div外壳
                    el.innerHTML = content; //设置div内容为我们想要插入的内容。
                    var frag = document.createDocumentFragment(); //创建一个空白的文档片段,便于之后插入dom树
                    var node = el.firstChild;
                    var lastNode = frag.appendChild(node);
                    range.insertNode(frag); //设置选择范围的内容为插入的内容
                    var contentRange = range.cloneRange(); //克隆选区
        
                    contentRange.setStartAfter(lastNode); //设置光标位置为插入内容的末尾
                    contentRange.collapse(true); //移动光标位置到末尾
                    sel.removeAllRanges(); //移出所有选区
                    sel.addRange(contentRange); //添加修改后的选区
        
                }
            }
        }
        
        
        /**
         * 获取DIV文本光标位置
         * @param {*} element 
         * @returns 
         */
        function getPosition(element) {
            var caretOffset = 0;
            var doc = element.ownerDocument || element.document;
            var win = doc.defaultView || doc.parentWindow;
            var sel;
            if (typeof win.getSelection != "undefined") {
                //谷歌、火狐
                sel = win.getSelection();
                if (sel.rangeCount > 0) {
                    var range = sel.getRangeAt(0);
                    var preCaretRange = range.cloneRange(); //克隆一个选区
                    preCaretRange.selectNodeContents(element); //设置选区的节点内容为当前节点
                    preCaretRange.setEnd(range.endContainer, range.endOffset); //重置选中区域的结束位置
                    caretOffset = preCaretRange.toString().length;
                }
            } else if ((sel = doc.selection) && sel.type != "Control") {
                //IE
                var textRange = sel.createRange();
                var preCaretTextRange = doc.body.createTextRange();
                preCaretTextRange.moveToElementText(element);
                preCaretTextRange.setEndPoint("EndToEnd", textRange);
                caretOffset = preCaretTextRange.text.length;
            }
            return caretOffset;
        };
        /**
         * 在指定DIV索引位置设置光标
         * @param {*} element 
         * @param {*} index 
         */
        function setCursor(element, index) {
            var codeEl = element.firstChild;
            var selection = window.getSelection();
            // 创建新的光标对象
            let range = selection.getRangeAt(0);
            // 光标对象的范围界定为新建的代码节点
            range.selectNodeContents(codeEl)
            // 光标位置定位在代码节点的最大长度
            // console.log(codeEl.length);
            range.setStart(codeEl, index);
            // 使光标开始和光标结束重叠
            range.collapse(true)
            selection.removeAllRanges()
            selection.addRange(range)
        }
        
        
        /**
         * 获得文本的占用的宽度
         * @param {*} text 字符串文班
         * @param {*} font 文本字体的样式
         * @returns 
         */
        function getTextWidth(text, font) {
            var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
            var context = canvas.getContext("2d");
            context.font = font;
            var metrics = context.measureText(text);
            return metrics.width;
        }
        
        /**
         * 触发元素的事件
         * @param {触发元素事件} type 
         * @param {*} element 
         * @param {*} detail 
         */
        function trigger(type, element) {
            var customEvent = new Event(type, { bubbles: false, cancelable: true });
            element.dispatchEvent(customEvent);
        }
        
        /**
         * 向body注入新style覆盖原本的css
         * @param {css文本字符串} csstxt 
         */
        function injectionCss(csstxt) {
            var styleElement = document.createElement('style');
        
            styleElement.innerText = t;
        
            document.body.appendChild(styleElement);
        };
        
        /**
         * 向指定父级创建追加一个子元素,并可选添加ID,
         * @param {Element} fatherElement 
         * @param {string} addElementTxt 要创建添加的元素标签
         * @param {string} setId 
         * @returns addElementObject
         */
        function addinsertCreateElement(fatherElement, addElementTxt, setId = null) {
            if (!fatherElement) console.error("指定元素对象不存在!");
            if (!addElementTxt) console.error("未指定字符串!");
        
            var element = document.createElement(addElementTxt);
        
            if (setId) element.id = setId;
        
            fatherElement.appendChild(element);
        
            return element;
        }
        
        
        /**
         * 向指定元素后创建插入一个元素,可选添加ID
         * @param {*} targetElement 目标元素
         * @param {*} addElementTxt 要创建添加的元素标签
         * @param {*} setId 为创建元素设置ID
         */
        function insertCreateAfter(targetElement, addElementTxt, setId = null) {
        
            if (!targetElement) console.error("指定元素对象不存在!");
            if (!addElementTxt) console.error("未指定字符串!");
        
            var element = document.createElement(addElementTxt);
        
            if (setId) element.id = setId;
        
            var parent = targetElement.parentNode;//得到父节点
            if (parent.lastChild === targetElement) {
                //如果最后一个子节点是当前元素那么直接添加即可
                parent.appendChild(element);
                return element;
            } else {
                parent.insertBefore(element, targetElement.nextSibling);//否则,当前节点的下一个节点之前添加
                return element;
            }
        }
        
        
        /**
         * 向指定元素前创建插入一个元素,可选添加ID
         * @param {*} targetElement 目标元素
         * @param {*} addElementTxt 要创建添加的元素标签
         * @param {*} setId 为创建元素设置ID
         */
        function insertCreateBefore(targetElement, addElementTxt, setId = null) {
        
            if (!targetElement) console.error("指定元素对象不存在!");
            if (!addElementTxt) console.error("未指定字符串!");
        
            var element = document.createElement(addElementTxt);
        
            if (setId) element.id = setId;
        
            targetElement.parentElement.insertBefore(element, targetElement);
        
            return element;
        }
        
        
        
        /**
         * 为元素注册监听事件
         * @param {Element} element 
         * @param {string} strType 
         * @param {Fun} fun 
         */
        function AddEvent(element, strType, fun) {
            //判断浏览器有没有addEventListener方法
            if (element.addEventListener) {
                element.addEventListener(strType, fun, false);
                //判断浏览器有没 有attachEvent IE8的方法
            } else if (element.attachEvent) {
                element.attachEvent("on" + strType, fun);
                //如果都没有则使用 元素.事件属性这个基本方法
            } else {
                element["on" + strType] = fun;
            }
        }
        
        
        /**
         * 为元素解绑监听事件
         * @param {Element}  element ---注册事件元素对象
         * @param {String}   strType ---注册事件名(不加on 如"click")
         * @param {Function} fun	 ---回调函数
         * 
         */
        function myRemoveEvent(element, strType, fun) {
            //判断浏览器有没有addEventListener方法
            if (element.addEventListener) {
                // addEventListener方法专用删除方法
                element.removeEventListener(strType, fun, false);
                //判断浏览器有没有attachEvent IE8的方法
            } else if (element.attachEvent) {
                // attachEvent方法专用删除事件方法
                element.detachEvent("on" + strType, fun);
                //如果都没有则使用 元素.事件属性这个基本方法
            } else {
                //删除事件用null
                element["on" + strType] = null;
            }
        }
        
        
        /**
        * 加载脚本文件
        * @param {string} url 脚本地址
        * @param {string} type 脚本类型
        */
        function loadScript(url, type = 'module') {
            let script = document.createElement('script');
            if (type) script.setAttribute('type', type);
            script.setAttribute('src', url);
            document.head.appendChild(script);
        }
        
        
        
        /**
         * 得到思源toolbar
         * @returns 
         */
        function getSiYuanToolbar() { return document.getElementById("toolbar"); }
        
        /**
         * 得到HBuiderXToolbar
         * @returns 
         */
        function getHBuiderXToolbar() { return document.getElementById("HBuiderXToolbar"); }
        
        
        
        /**简单判断目前思源是否是手机模式 */
        function isPhone() {
            return document.getElementById("toolbarEdit") != null;
        }
        /**简单判断目前思源是否是pc窗口模式 */
        function isPcWindow() {
            return document.body.classList.contains("body--window");
        }
        /**简单判断目前思源是pc模式 */
        function isPc() {
            return document.getElementById("windowControls") != null;
        }
        
        
        /**
         * 加载样式文件
         * @param {string} url 样式地址
         * @param {string} id 样式 ID
         */
        function loadStyle(url, id, cssName) {
        
            var headElement = document.head;
        
            let style = document.getElementById(id);
            if (id != null) {
                if (style) headElement.removeChild(style);
            }
        
            style = document.createElement('link');
            if (id != null) style.id = id;
        
        
            style.setAttribute('type', 'text/css');
            style.setAttribute('rel', 'stylesheet');
            style.setAttribute('href', url);
            if (cssName != null) style.setAttribute("class", cssName);
            headElement.appendChild(style);
            return style;
        }
        
        /**
         * 取出两个数组的不同元素
         * @param {*} arr1 
         * @param {*} arr2 
         * @returns 
         */
        function getArrDifference(arr1, arr2) {
            return arr1.concat(arr2).filter(function (v, i, arr) {
                return arr.indexOf(v) === arr.lastIndexOf(v);
            });
        }
        
        /**
         * 取出两个数组的相同元素
         * @param {*} arr1 
         * @param {*} arr2 
         * @returns 
         */
        function getArrEqual(arr1, arr2) {
            let newArr = [];
            for (let i = 0; i < arr2.length; i++) {
                for (let j = 0; j < arr1.length; j++) {
                    if (arr1[j] === arr2[i]) {
                        newArr.push(arr1[j]);
                    }
                }
            }
            return newArr;
        }
        
        /**
         * 思源吭叽元素属性解析看是否包含那种行级元素类型
         * @param {} attributes 
         * @param {*} attribute 
         * @returns 
         */
        function attributesContains(attributes, attribute) {
            if (attribute == true) return;
            if (attributes == null) return false;
            var arr = attributes.split(" ");
            if (arr.length != 0) {
                arr.forEach((v) => {
                    if (v == attribute) attribute = true;
                });
                return attribute == true ? true : false;
            } else {
                return attributes == attribute;
            }
        }
        /**
         * 间隔执行指定次数的函数(不立即执行)
         * @param {*} time 间隔时间s
         * @param {*} frequency 执行次数
         * @param {*} Fun 执行函数
         */
        function IntervalFunTimes(time, frequency, Fun) {
        
            for (let i = 0; i < frequency; i++) {
                sleep(time * i).then(v => {
                    Fun();
                })
            }
        
            function sleep(time2) {
                return new Promise((resolve, reject) => {
                    setTimeout(() => {
                        resolve()
                    }, time2)
                })
            }
        }
        
        /**
         * 获得当前浏览器缩放系数 默认值为1
         * @returns 
         */
        function detectZoom() {
            var ratio = 0, screen = window.screen, ua = navigator.userAgent.toLowerCase();
            if (window.devicePixelRatio !== undefined) {
                ratio = window.devicePixelRatio;
            } else if (~ua.indexOf('msie')) {
                if (screen.deviceXDPI && screen.logicalXDPI) {
                    ratio = screen.deviceXDPI / screen.logicalXDPI;
                }
            } else if (window.outerWidth !== undefined && window.innerWidth !== undefined) {
                ratio = window.outerWidth / window.innerWidth;
            }
            if (ratio) {
                ratio = Math.round(ratio * 100);
            }
            return ratio * 0.01;
        };
        /**
         * 递归DOM元素查找深度子级的一批符合条件的元素返回数组
         * @param {*} element 要查找DOM元素
         * @param {*} judgeFun 查找函数 : fun(v) return true or false
         * @returns array
         */
        function diguiTooALL(element, judgeFun) {
        
            var target = [];
        
            if (element == null) return null;
            if (judgeFun == null) return null;
        
        
            digui(element);
            return target;
        
            function digui(elem) {
                var child = elem.children;
                if (child.length == 0) return;
        
                for (let index = 0; index < child.length; index++) {
                    const element2 = child[index];
                    if (judgeFun(element2)) {
                        target.push(element2);
                        digui(element2);
                    } else {
                        digui(element2);
                    }
                }
            }
        };
        
        /**
        * 递归DOM元素查找深度子级的第一个符合条件的元素 - 子级的子级深度搜索赶紧后在搜索下一个子级
        * @param {*} element 要查找DOM元素
        * @param {*} judgeFun 查找函数: fun(v) return true or false
        * @returns element
        */
        function diguiTooONE_1(element, judgeFun, xianz = 999) {
        
            if (element == null) return null;
            if (judgeFun == null) return null;
            var i = xianz <= 0 ? 10 : xianz;
        
            return digui(element);
        
            function digui(elem) {
        
                if (i <= 0) return null;
                i--;
        
                var child = elem.children;
                if (child.length == 0) return null;
        
                for (let index = 0; index < child.length; index++) {
                    const element2 = child[index];
                    if (judgeFun(element2)) {
                        return element2;
                    } else {
                        var item = digui(element2);
                        if (item == null) continue;
                        return item;
                    }
                }
                return null;
            }
        }
        
        /**
        * 递归DOM元素查找深度子级的第一个符合条件的元素-同层全部筛选一遍在依次深度搜索。
        * @param {*} element 要查找DOM元素
        * @param {*} judgeFun 查找函数 : fun(v) return true or false
        * @param {*} xianz 限制递归最大次数
        * @returns element
        */
        function diguiTooONE_2(element, judgeFun, xianz = 999) {
        
            if (element == null || element.firstElementChild == null) return null;
            if (judgeFun == null) return null;
            var i = xianz <= 0 ? 10 : xianz;
            return digui(element);
        
            function digui(elem) {
        
                if (i <= 0) return null;
                i--;
        
                var child = elem.children;
                var newchild = [];
                for (let index = 0; index < child.length; index++) {
                    const element2 = child[index];
                    if (judgeFun(element2)) {
                        return element2;
                    } else {
                        if (newchild.firstElementChild != null) newchild.push(element2);
                    }
                }
        
                for (let index = 0; index < newchild.length; index++) {
                    const element2 = newchild[index];
                    var item = digui(element2);
                    if (item == null) continue;
                    return item;
                }
                return null;
            }
        }
        /**
         * 不断查找元素父级的父级知道这个父级符合条件函数
         * @param {*} element 起始元素
         * @param {*} judgeFun 条件函数
         * @param {*} upTimes 限制向上查找父级次数
         * @returns 返回符合条件的父级,或null
         */
        function isFatherFather(element, judgeFun, upTimes) {
            var i = 0;
            for (; ;) {
                if (!element) return null;
                if (upTimes < 1 || i >= upTimes) return null;
                if (judgeFun(element)) return element;
                element = element.parentElement;
                i++;
            }
        }
        
        
        /**
         * 获得焦点所在的块
         * @return {HTMLElement} 光标所在块
         * @return {null} 光标不在块内
         */
        function getFocusedBlock() {
            let block = window.getSelection()
                && window.getSelection().focusNode
                && window.getSelection().focusNode.parentElement; // 当前光标
            while (block != null && block.dataset.nodeId == null) block = block.parentElement;
            return block;
        }
        
        
        /**
         * 获得指定块位于的编辑区
         * @params {HTMLElement} 
         * @return {HTMLElement} 光标所在块位于的编辑区
         * @return {null} 光标不在块内
         */
        function getTargetEditor(block) {
            while (block != null && !block.classList.contains('protyle-content')) block = block.parentElement;
            return block;
        }
        
        
        /**
         * 清除选中文本
         */
        function clearSelections() {
            if (window.getSelection) {
                var selection = window.getSelection();
                selection.removeAllRanges();
            } else if (document.selection && document.selection.empty) {
                document.selection.empty();
            }
        }
        
        /**
         * body全局事件频率优化执行
         * @param {*} eventStr 那种事件如 "mouseover"
         * @param {*} fun(e) 执行函数,e:事件对象
         * @param {*} accurate 精确度:每隔多少毫秒检测一次触发事件执行
         * @param {*} delay 检测到事件触发后延时执行的ms
         * @param {*} frequency 执行后再延时重复执行几次
         * @param {*} frequencydelay 执行后再延时重复执行之间的延时时间ms
         */
        function BodyEventRunFun(eventStr, fun, accurate = 100, delay = 0, frequency = 1, frequencydelay = 16) {
            var isMove = true;
            var _e = null;
            AddEvent(document.body, eventStr, (e) => { isMove = true; _e = e })
            setInterval(() => {
                if (!isMove) return;
                isMove = false;
                setTimeout(() => {
                    fun(_e);
                    if (frequency == 1) return;
                    if (frequencydelay < 16) frequencydelay = 16;
        
                    var _frequencydelay = frequencydelay;
                    for (let index = 0; index < frequency; index++) {
                        setTimeout(() => { fun(_e); }, frequencydelay);
                        frequencydelay += _frequencydelay;
                    }
        
                }, delay);
            }, accurate);
        }
        
        /**
         * 为元素添加思源悬浮打开指定ID块内容悬浮窗事件
         * @param {*} element 绑定的元素
         * @param {*} id 悬浮窗内打开的块的ID
         */
        function suspensionToOpenSiyuanSuspensionWindow(element, id) {
            element.setAttribute("data-defids", '[""]');
            element.classList.add("popover__block");
            element.setAttribute("data-id", id);
        }
        
        /**
         * 为元素添加思源点击打开指定ID块内容悬浮窗事件
         * @param {*} element 绑定的元素
         * @param {*} id 悬浮窗内打开的块的ID
         */
        function clickToOpenSiyuanFloatingWindow(element, id) {
            element.classList.add("protyle-wysiwyg__embed");
            element.setAttribute("data-id", id);
        }
        
        /**
         * 控制台打印输出
         * @param {*} obj 
         */
        function c(...data) {
            console.log(data);
        }
        
        /**
         * 安全While循环
         * frequency:限制循环次数
         * 返回值不等于null终止循环
         */
        function WhileSafety(fun, frequency = 99999) {
            var i = 0;
            if (frequency <= 0) {
                console.log("安全循环次数小于等于0")
                return;
            }
            while (i < frequency) {
                var _return = fun();
                if (_return != null || _return != undefined) return _return;
                i++;
            }
        }
        /**设置思源块展开 */
        function setBlockfold_0(BlockId) {
            设置思源块属性(BlockId, { "fold": "0" });
        }
        /**设置思源块折叠 */
        function setBlockfold_1(BlockId) {
            设置思源块属性(BlockId, { "fold": "1" });
        }
        
        /**
            * 得到光标编辑状态下的显示commonMenu菜单;
            * @returns 
            */
        function getcommonMenu_Cursor() {
            if ((window.getSelection ? window.getSelection() : document.selection.createRange().text).toString().length != 0) return null;
            var commonMenu = document.querySelector("#commonMenu:not(.fn__none)");
            if (commonMenu == null) return null;
            if (commonMenu.firstChild == null) return null;
            if (commonMenu.children.length < 8) {
                return commonMenu;
            }
            return null;
        }
        
        /**
            * 得到光标选中编辑状态下的显示commonMenu菜单;
            * @returns 
            */
        function getcommonMenu_Cursor2() {
            if ((window.getSelection ? window.getSelection() : document.selection.createRange().text).toString().length != 0) {
                return document.querySelector("#commonMenu:not(.fn__none)");
            };
            return null;
        }
        
        /**
         * 得到快选中状态下的显示commonMenu菜单;
         * @returns 
         */
        function getcommonMenu_Bolck() {
            var commonMenu = document.querySelector("#commonMenu:not(.fn__none)");
            if (commonMenu.children.length < 8) {
                return commonMenu;
            }
            return null;
        }
        
        function getNotebookID(docId, then) {
            queryAPI(`SELECT box, path FROM blocks WHERE id = '${docId}'`, (g) => {
                let notebook = g[0].box;
                then(notebook);
            });
        }
        
        function getDocPath(docId, then) {
            queryAPI(`SELECT box, path FROM blocks WHERE id = '${docId}'`, (g) => {
                let thisDocPath = g[0].path;
                then(thisDocPath);
            });
        }
        
        
        
        
        
        /**++++++++++++++++++++++++++++++++按需调用++++++++++++++++++++++++++++++ */
        
        
        
        function 调整新窗口头栏位置() {
            if (!document.body.classList.contains("body--window")) return;
            const toolbar__window = document.querySelector("body > .toolbar__window");
            const layouts = document.getElementById("layouts")?.parentElement;
            var toolbarTxt = insertCreateBefore(toolbar__window.firstElementChild, "div", "toolbarTxt");
            if (toolbar__window && layouts) {
                document.body.insertBefore(toolbar__window, layouts);
            }
        
        
            /************************************************************窗口一些元素调整 */
            var layoutTabBarReadonly = document.querySelector(".layout-tab-bar.layout-tab-bar--readonly.fn__flex-1");
            layoutTabBarReadonly.style.paddingRight = "0px";
            layoutTabBarReadonly.style.marginTop = "-26px";
        
        
        
            /**********************************************************更改子窗口标题 */
            let _id = /^\d{14}\-[0-9a-z]{7}$/;
            let _url = /^siyuan:\/\/blocks\/(\d{14}\-[0-9a-z]{7})\/*(?:(?:\?)(\w+=\w+)(?:(?:\&)(\w+=\w+))*)?$/;
            /**
             * 获得目标的块 ID
             * @params {HTMLElement} target: 目标
             * @return {string} 块 ID
             * @return {null} 没有找到块 ID
             */
            function getTargetBlockID(target) {
                let element = target;
                while (element != null
                    && !(element.localName === 'a' && element.href
                        || element.dataset.href
                        || _id.test(element.dataset.nodeId)
                        || _id.test(element.dataset.oid)
                        || _id.test(element.dataset.id)
                        || _id.test(element.dataset.rootId)
                    )) element = element.parentElement;
        
                if (element != null) {
                    if (_id.test(element.dataset.nodeId)) return element.dataset.nodeId;
                    if (_id.test(element.dataset.oid)) return element.dataset.oid;
                    if (_id.test(element.dataset.id)) return element.dataset.id;
                    if (_id.test(element.dataset.oid)) return element.dataset.rootId;
                    if (_url.test(element.dataset.href)) return url2id(element.dataset.href);
                    if (_url.test(element.href)) return url2id(element.href);
                    return element.href || element.dataset.href || null;
                }
                else return null;
            }
            function url2id(url) {
                let results = _url.exec(url);
                if (results && results.length >= 2) {
                    return results[1];
                }
                return null;
            }
            var reg = new RegExp('<[^>]+>', 'gi');  //过滤所有的html标签,不包括内容
        
            var title = document.querySelector("title");
            title.innerText = "[#] 思源子窗口 - HBuilderX [#]";
            toolbarTxt.innerText = "[#] 思源子窗口 - HBuilderX [#]";
        
            AddEvent(document.body, "click", (e) => {
                //console.log(e);
        
                var title = document.querySelector("title");
                var TargetBlockID = getTargetBlockID(e.target);
                if (TargetBlockID == null) {
                    title.innerText = "[#] 思源子窗口 - HBuilderX [#]";
                    toolbarTxt.innerText = "[#] 思源子窗口 - HBuilderX [#]";
                    return;
                };
                titleTxt(TargetBlockID);
            })
        
            function titleTxt(TargetBlockID) {
        
                以id获取文档聚焦内容(TargetBlockID, (v) => {
                    var htmltxt = v.content;
        
                    var element = document.createElement("div");
                    element.innerHTML = htmltxt;
        
                    htmltxt = diguiTooONE_1(element, (v) => {
                        return v.getAttribute("contenteditable") == "true";
                    })
        
                    var txt = (htmltxt.innerText).replace(reg, '');
                    if (txt == "" || txt == "") {
                        txt = "[#] 思源子窗口 - HBuilderX [#]";
                        根据ID获取人类可读路径(TargetBlockID, (v) => {
                            title.innerText = "[#] " + v.substring(1, v.length) + " [#]";
                            toolbarTxt.innerText = "[#] " + v.substring(1, v.length) + " [#]";
        
                        })
                        return;
                    }
                    if (txt.length > 25) {
                        title.innerText = "[#] " + txt.substring(0, 25) + "...";
                        toolbarTxt.innerText = "[#] " + txt.substring(0, 25) + "...";
        
                    } else {
                        title.innerText = "[#] " + txt + " [#]";
                        toolbarTxt.innerText = "[#] " + txt + " [#]";
                    }
                    element.remove();
                });
            }
        
        
        
        }
        
        
        
        
        
        调整新窗口头栏位置();
        
        
        var Funs;
        
        if (isPhone()) {
        
            Funs = [
                collapseExpand_Head_List,//鼠标中键标题、列表文本折叠/展开
                () => console.log("==============>来自HBXL的附加CSS和特性JS_已经执行<==============")
            ];
            /*  
        
        
        
        
            */
        
        } else {
        
            Funs = [
                autoOpenList,//自动展开悬浮窗内折叠列表(第一次折叠)
                collapsedListPreview,//折叠列表内容预览查看
                collapseExpand_Head_List,//鼠标中键标题、列表文本折叠/展开
                theFloatingWindowIsClosed,//思源悬浮窗头栏中键关闭
                SuspendedWindowNoSection,//思源悬浮窗检测到单个段落块,单个列表项,面包屑前一级展示
                () => console.log("==============>来自HBXL的附加CSS和特性JS_已经执行<==============")
            ];
        
            /*
                // createHBuiderXToolbar();//创建BuiderXToolbar
        
                //setTimeout(() => ClickMonitor(), 3000);//各种列表转xx
        
        
                showDocumentCreationDate();//为打开文档标题下面显示文档创建日期
        
                displayParentChildDocuments2();//为文档展示父子文档
        
                autoOpenList();//自动展开悬浮窗内折叠列表(第一次折叠)
        
                collapsedListPreview();//折叠列表内容预览查看
        
                collapseExpand_Head_List();//鼠标中键标题、列表文本折叠/展开
        
                // findMatchingListEntries();//查找匹配列表条目前的图标可以鼠标悬停打开悬浮窗
        
        
        
                theFloatingWindowIsClosed();//思源悬浮窗头栏中键关闭
        
                zoomOutToRestoreTheFloatingWindow();//钉住悬浮窗增强
        
        
                SuspendedWindowNoSection();//思源悬浮窗检测到单个段落块,单个列表项,面包屑前一级展示
        
            */
        
        }
        
        
        setTimeout(() => {
            Funs.reverse();
            var index = (Funs.length) - 1;
            var ID = setInterval(() => {
        
                if (index >= 0) {
                    Funs[index]();
                } else {
                    clearInterval(ID);
        
        
                }
                index--;
            }, 100)
        }, 1000)
        
        
        
        
        
        
        
          /****************************思源API操作**************************/ 
          async function 设置思源块属性(内容块id, 属性对象) {
            let url = '/api/attr/setBlockAttrs'
            return 解析响应体(向思源请求数据(url, {
                id: 内容块id,
                attrs: 属性对象,
            }))
          }
          async function 向思源请求数据(url, data) {
            let resData = null
            await fetch(url, {
                body: JSON.stringify(data),
                method: 'POST',
                headers: {
                    Authorization: `Token ''`,
                }
            }).then(function (response) { resData = response.json() })
            return resData
          }
          async function 解析响应体(response) {
            let r = await response
            return r.code === 0 ? r.data : null
          }
        /* 操作 */ 
        
        /**
         * 获得所选择的块对应的块 ID
         * @returns {string} 块 ID
         * @returns {
         *     id: string, // 块 ID
         *     type: string, // 块类型
         *     subtype: string, // 块子类型(若没有则为 null)
         * }
         * @returns {null} 没有找到块 ID */
        function getBlockSelected() {
            let node_list = document.querySelectorAll('.protyle-wysiwyg--select');
            if (node_list.length === 1 && node_list[0].dataset.nodeId != null) return {
                id: node_list[0].dataset.nodeId,
                type: node_list[0].dataset.type,
                subtype: node_list[0].dataset.subtype,
            };
            return null;
        }
        
        function ClickMonitor () {
          window.addEventListener('mouseup', MenuShow)
        }
        
        function MenuShow() {
          setTimeout(() => {
            let selectinfo = getBlockSelected()
              if(selectinfo){
              let selecttype = selectinfo.type
              let selectid = selectinfo.id
              if(selecttype=="NodeList"||selecttype=="NodeTable"||selecttype=="NodeParagraph"){
                setTimeout(()=>InsertMenuItem(selectid,selecttype), 0)
              }
            }
          }, 0);
        }
        
        
        function InsertMenuItem(selectid,selecttype){
          let commonMenu = document.querySelector(".b3-menu__items")
          let  readonly = commonMenu.querySelector(".b3-menu__item--readonly")
          let  selectview = commonMenu.querySelector('[id="viewselect"]')
          if(readonly){
            if(!selectview){
            commonMenu.insertBefore(ViewSelect(selectid,selecttype),readonly)
            commonMenu.insertBefore(MenuSeparator(),readonly)
            }
          }
        }
        
        function ViewMonitor(event){
          let id = event.currentTarget.getAttribute("data-node-id")
          let attrName = 'custom-'+event.currentTarget.getAttribute("custom-attr-name")
          let attrValue = event.currentTarget.getAttribute("custom-attr-value")
          let blocks = document.querySelectorAll(`.protyle-wysiwyg [data-node-id="${id}"]`)
          if(blocks){
            blocks.forEach(block=>block.setAttribute(attrName,attrValue))
          }
          let attrs={}
            attrs[attrName] =attrValue
          设置思源块属性(id,attrs)
        }
        
        setTimeout(()=>ClickMonitor(),1000)
        
        
        // REF https://github.com/Zuoqiu-Yingyi/siyuan-theme-dark-plus/blob/main/theme.js
        window.theme = {};
        
        /* 颜色配置文件列表 */
        window.theme.colors = [
            'palette/autumn.css',
            'palette/blue.css',
            'palette/cyan.css',
            'palette/dark.css',
            'palette/green.css',
            'palette/light.css',
            'palette/spring.css',
            'palette/tonight.css',
            'palette/fountain.css',
            'palette/bearlight.css',
            'palette/fruitspink.css',
            'palette/mediawiki.css',
            'palette/qianlan.css',
            'palette/yinxiang.css',
            'palette/pbook.css',
        ];
        
        /* DOM 节点 ID */
        window.theme.IDs = {
            STYLE_COLOR: 'custom-id-style-theme-color',
            BUTTON_TOOLBAR_CHANGE_COLOR: 'custom-id-button-toolbar-change-color',
            LOCAL_STORAGE_COLOR_HREF: 'langzhou-color-href',
        };
        
        /* 循环迭代器 */
        window.theme.Iterator = function* (items) {
            // REF [ES6中的迭代器(Iterator)和生成器(Generator) - 小火柴的蓝色理想 - 博客园](https://www.cnblogs.com/xiaohuochai/p/7253466.html)
            for (let i = 0; true; i = (i + 1) % items.length) {
                yield items[i];
            }
        }
        
        /**
         * 加载样式文件
         * @params {string} href 样式地址
         * @params {string} id 样式 ID
         */
        window.theme.loadStyle = function (href, id = null) {
            let style = document.createElement('link');
            if (id) style.id = id;
            style.type = 'text/css';
            style.rel = 'stylesheet';
            style.href = href;
            document.head.appendChild(style);
        }
        
        /**
         * 更新样式文件
         * @params {string} id 样式文件 ID
         * @params {string} href 样式文件地址
         */
        window.theme.updateStyle = function (id, href) {
            let style = document.getElementById(id);
            if (style) {
                style.setAttribute('href', href);
            }
            else {
                window.theme.loadStyle(href, id);
            }
        }
        
        setTimeout(() => {
            const drag = document.getElementById('drag'); // 标题栏
            const themeStyle = document.getElementById('themeStyle'); // 当前主题引用路径
            if (drag && themeStyle) {
                const THEME_ROOT = new URL(themeStyle.href).pathname.replace('theme.css', ''); // 当前主题根目录
                /* 通过颜色配置文件列表生成完整 URL 路径 */
                const colors_href = [];
                window.theme.colors.forEach(color => colors_href.push(`${THEME_ROOT}${color}`));
                window.theme.iter = window.theme.Iterator(colors_href);
                var color_href = window.siyuan?.storage[window.theme.IDs.LOCAL_STORAGE_COLOR_HREF];
                if (!color_href) {
                    localStorage.getItem(window.theme.IDs.LOCAL_STORAGE_COLOR_HREF);
                }
                if (color_href) { // 将迭代器调整为当前配色
                    for (let i = 0; i < window.theme.colors.length; ++i) {
                        if (window.theme.iter.next().value === color_href) break;
                    }
                }
                else { // 迭代器第一个为当前配色
                    color_href = window.theme.iter.next().value;
                    localStorage.setItem(window.theme.IDs.LOCAL_STORAGE_COLOR_HREF, color_href);
                    setLocalStorageVal(window.theme.IDs.LOCAL_STORAGE_COLOR_HREF, color_href);
                }
        
                /* 加载配色文件 */
                window.theme.updateStyle(window.theme.IDs.STYLE_COLOR, color_href);
        
                const button_change_color = document.createElement('button'); // 切换主题颜色按钮
                button_change_color.id = window.theme.IDs.BUTTON_TOOLBAR_CHANGE_COLOR;
                button_change_color.className = 'toolbar__item b3-tooltips b3-tooltips__sw';
                button_change_color.ariaLabel = '切换主题颜色';
                button_change_color.innerHTML = `<svg><use xlink:href="#iconTheme"></use></svg>`;
                button_change_color.addEventListener('click', e => {
                    color_href = window.theme.iter.next().value;
                    localStorage.setItem(window.theme.IDs.LOCAL_STORAGE_COLOR_HREF, color_href);
                    setLocalStorageVal(window.theme.IDs.LOCAL_STORAGE_COLOR_HREF, color_href);
                    window.theme.updateStyle(window.theme.IDs.STYLE_COLOR, color_href);
                });
                // REF [JS DOM 编程复习笔记 -- insertAdjacentHTML(九) - 知乎](https://zhuanlan.zhihu.com/p/425616377)
                drag.insertAdjacentElement('afterend', button_change_color);
                drag.insertAdjacentHTML('afterend', `<div class="protyle-toolbar__divider"></div>`);
        
            }
        }, 0);
        
        /**
         * 发送API请求
         * @param {*} data 
         * @param {*} url 
         * @returns 
         */
        async function postRequest(data, url){
            let result;
            await fetch(url, {
                body: JSON.stringify(data),
                method: 'POST',
                headers: {
                    "Authorization": "Token ",
                    "Content-Type": "application/json"
                }
            }).then((response) => {
                result = response.json();
            });
            return result;
        }
        /**
         * 设置LocalStorage
         * @param {*} ikey 
         * @param {*} ival 
         */
        async function setLocalStorageVal(ikey, ival) {
            let url = "/api/storage/setLocalStorageVal";
            let response = await postRequest({app: getAppId(), key: ikey, val: ival}, url);
            if (window.top.siyuan.storage != undefined) {
                window.top.siyuan.storage[ikey] = ival;
            }
            function getAppId() {
                let wsurl = window.top.siyuan.ws.ws.url;
                let appIdMatchResult = wsurl.match(new RegExp(`(\\?app=|&app=)[^&]+`, "g"));
                if (appIdMatchResult.length == 1){
                    return appIdMatchResult[0].substring(5);
                }else if (appIdMatchResult.length > 1) {
                    console.warn("正则获取appId错误", appIdMatchResult);
                    return appIdMatchResult[0].substring(5);
                }else {
                    console.error("正则获取appId错误", appIdMatchResult);
                    return "";
                }
            }
        }
        
  • 思源笔记

    思源笔记是一款隐私优先的个人知识管理系统,支持完全离线使用,同时也支持端到端加密同步。

    融合块、大纲和双向链接,重构你的思维。

    22026 引用 • 87850 回帖 • 4 关注
4 操作
dammy 在 2024-05-03 23:35:25 更新了该帖
dammy 在 2024-05-03 23:21:56 更新了该帖
dammy 在 2024-03-31 21:20:59 更新了该帖
dammy 在 2024-03-31 16:55:16 更新了该帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • dammy 1 1 赞同
    • 为什么后来互联网用超链接,而不是所谓的双链?这还不明显吗?——反链其实不重要!!!

      • 互联网使用超链接和反链没有用没有逻辑上的关系:

        1. 使用场景不同

          1. 互联网连接的是公共网页和资源,它的目的是为了提供信息的传播
          2. 思源是一个本地笔记软件,更细分下是本地知识管理系统,它并不需要传播信息,侧重于辅助组织知识。
        2. 反链不同

          1. xanadu 设想的反链仅能知道哪些文档链接到了它,这在一些 wiki 里早就能实现
          2. Roam 的上下文反链——也就是思源现在的反链,能通过面包屑提供链接时的上下文信息,也能通过切换面包屑来查看更广阔的上下文,上下文信息对于反链特别重要,因为不用打开原文档也能通过查看反链理解它在说什么。
      • 在这里,你犯了两个错误:

        1. 互联网和本地知识管理系统服务的对象、设计目的都是不一样的,你无法用“互联网不使用反链”来证明“本地知识管理系统里反链无用”。
        2. 你混淆了反链。你在用 xanadu 的反链来证明大纲双链笔记里的反链无用,这两者是不一样的东西。
    • 很多人以为 Roam Research 带来了双链,其实 RR 最大贡献是【更方便的创建链接】,因为在这之前,创建链接,需要先去目标文档复制链接,然后到当前文档粘贴,而 RR 只需要在当前页面[[]]即可完成创建链接,这就是 RR 的创新点。

    • 互联网和维基百科这两个世界上最大、最先进的知识库(或者说笔记软件)用实践告诉我们,双链真的不重要。

      • 在这里,你犯了一个经典错误,混淆了信息和知识,所以你衍生的结论也是错误的:

        • 信息:外部的,通常指的是经过组织或处理的数据,例如你说的互联网和维基百科
        • 知识:内部的,通常指的是信息加上理解和经验的结合体。它是主观的,依赖于个人的认知和经验
      • 在我之前的文章里引入了先验知识这个概念来澄清这种混淆,先验知识就是大脑中已经存在的知识,思源自称知识管理系统而不是信息管理系统的原因就是,它确实能辅助思考,发展大脑中的知识,在我所有的文章、回复里,知识都是指的是大脑中的先验知识。

      • 综上,互联网和维基百科是信息库,服务的对象不同,所以设计出来的目的也不一样,它是为了传播信息,而不是像思源一样发展个人知识,所以你无法用互联网和维基百科证明双链真的不重要。

    • 反链面版我觉得对于知识管理的重要程度相当于字数统计。有这精力还不如开发画板、多维表格。

      1. 你的结论有点混乱

        • 前提:

          • 你混淆了反链
          • 你混淆了信息和知识
          • 你没有声明关于知识管理的准确定义
        • 所以你的结论在我看来也是混乱的,我无法判断你是在讲信息管理还是大脑中的先验知识管理。

      2. 我并没有反对开发画板、多维表格

        • 事实上在我之前的文章里已经说过,大多数人对笔记的定义太过模糊,并不需求真正的先验知识管理,他们只是需求玩具和信息管理,那么一个软件为了活下去就必须开发大多数人需求的功能,这无可厚非,所以我并不反对画板和多维表格的开发。
        • 我只是实话实说,多维表格使用起来太累了,对发展大脑知识也没什么用,如果有人需求对发展大脑中的知识,那么可以回头看看思源中的双链以及背后的渐进学习,轻松又简单。
        • 实际上在思源里双链功能已经趋近完善,也没什么好开发的,唯一可惜的是一些功能落在了插件和代码片段上,造成了不必要的上手成本。
    1 回复
    2 操作
    dammy 在 2024-04-01 10:42:13 更新了该回帖
    dammy 在 2024-04-01 09:37:09 更新了该回帖
  • 其他回帖
    • dailynote 由于自下而上,会更让我随心所欲的去探索,但是作为研究生,当务之急是做好自己的工作,发论文,dailynote 会让我更分心。

      • 是这样的,因为对注意力的限制少,所以会鼓励你随心所欲地去探索,但我也见过像你一样有考核指标的老铁一直在使用双链,他们有些是用“项目管理”、“任务管理”之类的对注意力进行管理,类似你说的自上而下的方式,可惜我并没有这方面经验,无法帮到你。
    • 很多人说数据库没用,但是用作任务管理,我觉得是不可或缺的,对我来说很重要

      • 任务管理上我并没有涉及,不敢下结论有没有用,但我诚心推荐你先使用稳定成熟的数据库软件,先毕了业再说
    • 另一方面,明确自己某个领域的那些方面还不了解,需要去加强,而不是仅靠自下而上没有目的地积累,对我而言,自上而下的专精效率更高,因为目的明确,或许还有截止日期,自下而上适合作为补充,而不是主导。当然这一方法更适合自己的专业领域,若是非专业领域,可以先靠自下而上渐进式了解大体知识结构,然后再搭建起自己的框架。但是我目前喜欢给领域笔记,一口气写一系列自己感兴趣的问题,哪天看到了,就去了解具体的知识。我哪天想到其他的问题,我直接在领域笔记写上就好了,没必要用 dailynote 传递等着未来整理,完全可以当下整理。

      • 这一段正好能说明文中的一些观点:
        • 自上而下和自下而上
          1. 从目前我的理解上来看,Achuan 宝所说的自上而下是指从整体逐渐到部分的学习方法,那以这个角度来看,自下而上就是从部分到整体,从学习某门专业的角度来看,就如我文章里所说的可以结合两者。
          2. 以我惨痛的经验,我建议在适当时候自下而上追求甚解,如果不从基础开始理解背后原理,就会被怎么做蒙蔽了双眼:“毕业后也只能照葫芦画瓢,用在学校里学到的怎么做来处理老师早就教过的问题,但没有了背后的为什么支撑,面对新情况他就束手无策了。”
            • 我在毕业后感受到过这种恐惧,以及发现别人能将知识融入生活,有很强的自主思考能力,而我面对新情况时只有一团浆糊。
          3. 当然在毕业的压力下上面的话可以无视,毕竟能先毕业是最重要的。
        • 笔记的不同使用目的
          1. 信息管理
            1. 关于双链,我给出的流程是发展想法,也就是追求甚解,这个过程是没有“交给未来”整理这一步的,因为不涉及在思源内进行信息管理。
            2. 关于 DailyNotes 工作流,哈桑老师在哈桑式 DailyNote 工作流整理之难 里有强调过:“后期的整理并不是在为前期的懒惰买单,而是在有了充足的素材之后进行更全面更深入的思考”,整体来看也是个追求甚解的过程,对信息管理也未曾提及。
    • 不要太执着用 dailynote 块引积累,我觉得你块引一个主题再写东西,和直接块引,点击文章或者新建文档,建立一个 Inbox 列表,记录 idea,效果在大部分笔记应该是没什么差别的,而后者对我来说更实用心智压力更小,因为它就在文章里我也不需要再打开查看,可以随意再编辑、能概览全局。当然 dailynote 可以记录当时你的想法,但是这真的重要吗?我真的会觉得大部分的 dailynote 笔记都非常琐碎(或许是我记录方式的问题),现在的我会更倾向于把自己某一阶段的想法总结放在笔记下面。

      • 这我在实践里也注意到了这点:我把主题当成了标签使。
        • 块引的对象可以不是简短的标签式主题
          • 我之前的主题多是简短的标题、书籍名、人物名、事件,后来发现使用这些主题和使用标签没什么区别。
          • 后来我尝试使用具体的陈述性标题来当主题,用来明确主张,这样我之后会围绕着这个主张进行分析思考,用这种方式来管理注意力。
        • 可以在较深层级中使用块引用
          • DailyNotes 流程给出的记录方法是先打块引用再写内容,一开始我也这么做,后来我改变了块引用的使用侧重点,主要进行发展想法,对具有明确主张的标题进行块引用。
          • 这改变了记录流程,只有我在想到它时,觉得能完善这个论点,我才会进行块引用链接,这是为了传递当时思考的上下文,而不是为了收集资料而链接。
      • 所以我的文章里一直在强调要发展想法,为想法建立一个具有陈述性标题的页面,例如“标签激活的知识太少,具有精准的陈述性标题笔记激活的知识更多”这样一个页面,然后思考的记录中想到它就进行链接。
      • 而那些简短的标签式主题被我当作空白节点,不再是侧重点,我文中的图有说到。

    出个门,回来再和 Achuan 聊。

    1 回复
  • 文中提到的表格汇总是为了模拟 RoamResearch 里的 AllPage 功能,能汇总展示所有的页面(在思源里是文档块主题),能一览页面的被引用的次数,当个最后兜底的功能。

    在思源里这么用是因为文档树上同一级文档块多了就会太卡,对子文档的最大列出数量也有限制,所以不如搞个页面来汇总展示所有文档块主题。

    在思源里是用查询挂件实现的,跟着我写的步骤走就行了,这是完成后的效果图:

    image.png

  • 我也说一下我的想法和困惑。

    对于你说到的这两种应用,

    先说第一种。在我看来反向链接就是正文这个思路在很多情况下都没法成立。因为即使是同一个内容,用在不同的主题 话题 想法里,也需要用不同的方式去表达。仅仅看反向链接面板上的内容是没法把这些内容当成正文的,每次都需要重新修改。当然,这也和我的性格有关,我就是要认认真真地表述每一句话,我是不可能说留下只言片语,堆在那里,很久以后回看这些碎片时还能够完整的记起来自己当时的想法。即使我当时写的是只言片语,我也要赶紧把这些碎片补充成完整的内容才行。

    所以无论是把反向链接面板当作正文还是使用嵌入的方式,对我来说基本都是不可行的。

    对于第二点,

    首先在我看来,在当天日记页面建立一个传递形式,双向链接然后写内容,并不比打开那个页面去写内容,要快多少,轻松多少。如果嫌这样事前压力还是有点大,那就先随便找个地方把想写的话写完,然后剪切到他该在的地方去。

    其次,SiYuan 笔记的块拖动实在是太不好用了。我想拖动反向链接面板里的那个嵌套列表,但鼠标老是没法放到自己能够选中那个块的位置上。可能是因为我没有掌握技巧,但我也确实不想再尝试了。

    然后,看着自己的反向链接面板上有那么多的锚文本,我是真的觉得头大。我只想看正文,只想看主要内容,不想看到有那么多的锚文本干扰我的视线。

    最后,单向链接确实不错,但是没有像建立双向链接那么方便的,建立单向链接的方式。把双向链接改为单向链接的确不错,但是在 SiYuan 笔记里这个操作太麻烦,竟然还需要我用鼠标去点击好几次,我完全无法接受,我喜欢尽可能用键盘操作,但是 SiYuan 笔记不给我这样的机会。

    1 回复
  • 查看全部回帖