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

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

第二次漫游:双链、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 "";
                }
            }
        }
        
  • 思源笔记

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

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

    22337 引用 • 89381 回帖
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 更新了该帖

相关帖子

欢迎来到这里!

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

注册 关于
请输入回帖内容 ...
    • dailynote 由于自下而上,会更让我随心所欲的去探索,但是作为研究生,当务之急是做好自己的工作,发论文,dailynote 会让我更分心。

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

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

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

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

    出个门,回来再和 Achuan 聊。

    1 回复
  • 其他回帖
  • 有几个有趣的点可以和 VV 你分享下,这也是文章里想要表达的“笔记的不同使用目的”:

    1. DailyNotes 流程是临时哈桑老师自己的用法,是双链用法的子集。
      • 他提到过在后期整理时,可以直接拖动反链到正文,在当时的双链社区很少有人会这么做,因为会直接丢失记录的上下文,这点还是很重要的,因为当时记录的上下文组成的事件会成为日后的索引。
      • 另一个点在于“整理”,这涉及到笔记的不同使用目的。
    2. 笔记有不同的使用目的,而在发展思考上,整理没什么太大用处,对大多数人而言,整理是一种不敢忤逆的政治正确。
      • 先看一下 23 年临时哈桑老师的回复:
      • image.png
      • 哈桑在这里没有进行更细致的讨论,我们对其进行一些补充:
        • 每个人使用笔记软件的目的不一样,粗略可分为:
          • 信息管理
          • 任务管理
          • 发展思考
        • 对于信息、任务管理来说,会存在对数据进行整理的场景,以进行更好的回顾和管理。
        • 但是对于发展思考来说:
          • 没有必要整理
          • 不一定要将想法结构化,或者组织成文展现给其它人
        • 发展思考和信息管理是完全不同的使用目的,我的经验是,发展思考更多的是需要表达,以及辅助表达的工具(大纲、模板等),我能很明显感受到表达促进了思考,而思考又反过来增强了表达能力。
    3. 在探索“发展思考”这个方向时,我发现缺少的不是管理和回顾的手段,而是持续思考的动力。
      • 在我的笔记里有价值的是那些想法,我的关注点也是它们,我是这么管理和回顾的:

        • 管理:有价值的想法会被链接到重要的观点上,这些观点会在脑子里组织重要的想法,然后发展为写作大纲
        • 回顾:那些有价值的观点会被牢记,遇到相关的信息会主动激活进行完善,如果想要在软件里定位的话,能通过直接搜索、写作大纲、记录时的上下文、反链筛选来定位。
      • 产生了想法后才会去记录,这时在大脑里已经做出了第一步管理,记录则是第二步管理,深度的思考带来了能轻松管理这些想法的能力。

      • 这些对于发展思考就足够了,再多就没必要,因为使用工具本身会损耗动力,例如使用大纲、模板,甚至打字记录也会消耗我的动力。

      • 那么对于发展思考来说:

      • 归集后的笔记,由于结构上的零散,又需要形成结构化的笔记内容,这又到了传统笔记的领域。

        哪怕是结构化的笔记内容,依旧只适合创作它的人,不适合一个新接触它的人,于是还可能需要按照一般的写作方式,将“笔记”转化为别人能接受的文章。

        • “将笔记转化为别人能接受的文章”是表达的方式之一,我现在很少这么做,因为我发现每次写完文章都会陷入长时间不记笔记的状态,太累了,调整样式、将想法组织成有逻辑的文本太累了,动力损耗太多。
      • 而这些数据,都需要“多维表格”这样的一个东西,来帮你方便的管理和筛选。

        • 信息管理需求多维表格,对于发展思考来说它是多余动作,管理想法本身就是一件很简单的事,只需要思考就行了。

    总而言之,是笔记使用目的的不同。

    • 不同的使用目的带来了不同的用法,信息管理和发展思考有一个关键不同在于:
      • 发展思考的过程中是思考驱动了记录,如果缺少动力,我就不会去思考,更何谈去记录,所以在这个过程会舍弃没有必要的动作,尽量以最小的摩擦来实现最大的探索。
    • 本篇文章里展示的流程和 DailyNotes 工作流是不同的,DN 被很多人用在信息的记录和管理上,但本篇文章里展示的双链是为了发展思考,诞生出有力的观点。
  • 文中提到的表格汇总是为了模拟 RoamResearch 里的 AllPage 功能,能汇总展示所有的页面(在思源里是文档块主题),能一览页面的被引用的次数,当个最后兜底的功能。

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

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

    image.png

  • 我双链用的不多,个人感觉双链更适合输入和段落内容整理,如果涉及到文档归类整理,那还得是数据库,双链在文档整理这块其实无能为力,但思源包括 ob 其实是脚踩两条船,所以依靠 MOC+ 标签 + 文档树才能兼容旧习惯,现在有了数据库算是给了文档整理兜底的方案,文档整理感觉比 MOC 舒服很多。我的流程是 DN 双链到文档,文档进入数据库整理,内容通过反链整理,两套流程也并不会打架,维护数据库和文档在我看来也不是什么压力巨大的事情,这么多 Notion 和多维表用户也没听说过有很大压力,如果一套系统和流程要井然有序肯定是要做功才能保持负熵的,就算是双链也是如此,需要思考怎么样传递、是块引用还是嵌入块才能不破坏原文或者方便后期查看跳转,这一套下来我觉得也是有一定门槛的。

  • 查看全部回帖