社区

曲淡歌
曲淡歌@曲淡歌

17 小时前

发散性思考与线性输出

我以前一直都是采用传统的笔记方法,从上往下写。

这种线性似乎是理所应当、浑然天成的,但是人在想问题的时候又喜欢在草稿上写写画画,这个时候思维的组织方式却是非线性的。

接近的概念就是头脑风暴之类的思考组织方式,无意掉书袋,因此本文不再就此概念做过多讨论,我们姑且定义两种方式,不再深究其描述是否准确:

  1. 从上往下的一维:线性
  2. 上下左右都有:发散性

这里就会引入一个问题,当你的大脑在发散性思考的时候,用线性的笔记辅助思考,就会出现脑子和手打架的窘境。

举例来说:我在写某个主题的时候,会突然联想到一个分支想法,它不适合放进当前的正文,但是与之又有关联。如果是传统的笔记方法,这里可以使用便签(callout),但这样终究不太适合内容组织。因此类似mindmap的工具都会提供二维的内容输出方式。

但是这会引入一个新问题——发散思考之后,如何输出高可读性的内容。

不知道大家有没有这样的体验:自己做的思维导图再烂也能看懂,而别人做的再好也看得很晕。

这就是非线性(发散性)内容的弊端,除非是自己生产(即已完成内化)的内容,否则非线性内容先天就更难理解。

因此我们需要找到一个允许我们发散性思考,但是又能快捷的输出线性内容的的方法。

线性输出脚本的前身

当我产生了这个需求的时候,我先是尝试用obsidian的引用功能来实现它,因为excalidraw本身是支持对外提供内容的嵌入的。但是很难做到方便快捷。

于是我在网上查询资料,了解到两位先驱者的探索:

学习了两位网友关于excalidraw笔记如何实现线性输出的思考,其中

  • Note必利阀制作了一个脚本,可以把excalidraw中选中的文本和图像按编辑的时间顺序输出为文字与图像的引用,最终复制到剪贴板,我们只需要把这个粘贴到想要用的地方就行
  • 熊猫别熬夜制作了一套脚本,要求使用者在excalidraw编写好符合一定格式的标题,然后通过脚本把标题与对应的内容引用出来

前者的优势是输出的内容为文字与图片链接,是可以被标准md识别的,但缺少了excalidraw的强大图形能力(因为它要求把freedraw转成图片,后续再编辑也是很麻烦的)。另外因为excalidraw的Frame和Group还不支持嵌套,所以如果有画中画这样的展现形式,则无法用后者的脚本实现。

后者直接把excalidraw中的图形引用过来,能更好的保留excalidraw的功能,不过这样也导致如果有发布文章的需求,后续可能需要再手动去把excalidraw引用转换为图片。

在学习两位的过程中,我厘清了 发散性思考线性输出 的概念,同时基于我自己的日记工作流,对熊猫别熬夜的脚本进行了修改,最终我的线性输出脚本诞生了。

线性输出脚本

脚本介绍

本脚本的全名应该叫 excalidraw线性输出到同名笔记,它的功能也很简单,一言以蔽之:通过识别规定格式的文本,把与文本组合的内容以excalidraw的嵌入链接形式输出到对应笔记的指定标题下。

如下图所示:
assets/Pasted image 20240907152957.png

本脚本通过识别形如 #1 标题 的文本,解析为 标题,并将该文本所属的组合(优先级分别为:Frame>Group>Element)引用链接插入到标题之后。

脚本的优点:
1. 保留了excalidraw的图形能力
2. 将内容输出到指定文件并生成逐级标题,让思考输出的内容可以与文件本身融合,大纲可识别
3. 支持自定义在哪个标题后插入,并且会根据设置标题动态调整生成标题的层级(比如设置在 #灵感 后插入,则从二级标题开始生成,如果设置为 ##灵感,则会从三级标题开始生成,确保生成内容为子内容)

脚本使用说明

下载

你可以在我的Github下载:dangehub/aqu_ob_share: Share my Obsidian techniques

或者在文末直接复制源代码,自己新建一个md文件粘贴进去就好。

安装使用

  1. 把脚本放到excalidraw的script目录下
  2. 前往excalidraw插件设置,在最后一项 已安装脚本设置 中修改 Custom Misc Header,设置为自己想要插入在哪个标题后,参考值 ## 1.3 杂记
  3. 点击脚本按钮or使用命令工具
  4. 1.excalidraw 的线性内容会被输出到 1 中的 ## 1.3 杂记 标题下

视频教程见:obsidian+excalidraw+线性输出脚本=快乐日记

附脚本源代码

“`

// 获取脚本设置

let settings = ea.getScriptSettings();

// 设置默认值(如果是首次运行)

if (!settings[“Custom Misc Header”]) {

settings = {

…settings,  // 保留现有设置

“Custom Misc Header”: {

value: “## 1.3 杂记”,

description: “自定义杂记标题,用于插入 Excalidraw 内容”

}

};

ea.setScriptSettings(settings);

}

// 使用设置中的自定义杂记标题

const customMiscHeader = settings[“Custom Misc Header”].value;

// 计算customMiscHeader中的 #数量

const customHeaderLevel = (customMiscHeader.match(/^#+/) || [”])[0].length;

// 获取笔记的基本路径和笔记名

const currentFile = app.workspace.getActiveFile();

if (!currentFile) {

new Notice(“❌ 无法获取当前文件”, 3000);

return;

}

// 获取excalidraw文件路径、文件名,准备生成对应笔记
const filePath = currentFile.path;

const fileName = currentFile.name;

const fileBaseName = fileName.replace(‘.excalidraw’, ”);

// 初始化变量

let frameIds = [];

let extrTexts = ”;

// 获取所有以’#’开头的文本元素(即标题)

let allEls = ea.getViewElements().filter(el => el.type === “text” && el.text.startsWith(‘#’));

// 对标题进行排序

allEls.sort((a, b) => {

let aMatch = a.text.match(/^#([\d.]+)/);

let bMatch = b.text.match(/^#([\d.]+)/);

if (!aMatch || !bMatch) return 0;

let aParts = aMatch[1].split(‘.’).map(Number);

let bParts = bMatch[1].split(‘.’).map(Number);

for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {

if (aParts[i] === undefined) return -1;

if (bParts[i] === undefined) return 1;

if (aParts[i] !== bParts[i]) return aParts[i] – bParts[i];

}

return 0;

});

for (let i of allEls) {

let elText = i.rawText.trim(); // 使用 rawText 而不是 text,以规避换行符问题

let elID = i.id;

let match = elText.match(/^#([\d.]+)\s+(.*)/);

if (!match) continue;

let numberPart = match[1];

let titlePart = match[2];

// 计算标题级别

let levels = numberPart.split(‘.’).length;
    let headLevel = Math.min(levels + customHeaderLevel, 6); // 根据customMiscHeader的级别调整

let heads = ‘#’.repeat(headLevel);

let titleText = “”;

let titleLink = “”;

let embedlinks = [];

let nums = 99;

// 处理excalidraw中的Frame、Group

if (i.frameId && !frameIds.includes(i.frameId)) {

elID = i.frameId;

frameIds.push(elID);

titleLink = ${fileName}#^frame=${elID};

for (let j of ea.getViewElements().filter(el => el.type === “embeddable”)) {

if (j.frameId == elID) {

embedlinks.push(\n!${j.link})

let objectFrame = ea.getViewElements().filter(el => el.frameId === elID);

nums = objectFrame.length;

}

}

} else if (i.groupIds) {

titleLink = ${fileName}#^group=${elID};

for (let j of ea.getViewElements().filter(el => el.type === “embeddable”)) {

if (j.groupIds.some(groupId => i.groupIds.includes(groupId))) {

embedlinks.push(\n!${j.link})

let objectFrame = ea.getViewElements().filter(el => el.groupIds.some(groupId => i.groupIds.includes(groupId)));

nums = objectFrame.length;

}

}

} else {

titleLink = ${fileName}#^${elID};

}

// 生成标题文本

if (embedlinks.length > 0) {

let extrEmbedlinks = embedlinks.join(‘\r\n’);

titleText = ${heads} ${titlePart}\n${extrEmbedlinks}\n;

if (nums > 3) {

titleText += ![[${titleLink}]]\n;

}

} else {

titleText = ${heads} ${titlePart}\n;

if (nums > 2) {

titleText += ![[${titleLink}]]\n;

}

}

extrTexts += titleText;

}

// 构建输出文件路径

let outputFileName = ${fileBaseName}.md;

let outputPath = filePath.replace(‘.excalidraw’, ”);

// 检查输出文件是否存在

let outputFile = app.vault.getAbstractFileByPath(outputPath);

if (!outputFile) {

new Notice(❌ 输出文件不存在:${outputPath}, 3000);

// 尝试创建文件

try {

await app.vault.create(outputPath, ”);

outputFile = app.vault.getAbstractFileByPath(outputPath);

new Notice(✅ 已创建新文件:${outputPath}, 2000);

} catch (error) {

new Notice(❌ 无法创建文件:${outputPath}, 3000);

return;

}

}

// 读取输出文件内容

let outputContent = await app.vault.read(outputFile);

// 创建唯一标识符

let excalidrawIdentifier = EXCALIDRAW_CONTENT_${fileName.replace(/[^a-zA-Z0-9]/g, "_")};

// 构建新的 Excalidraw 内容

let newExcalidrawContent = <!-- BEGIN ${excalidrawIdentifier} -->\n${extrTexts}\n<!-- END ${excalidrawIdentifier} -->;

// 检查是否已存在 Excalidraw 内容

let startMarker = <!-- BEGIN ${excalidrawIdentifier} -->;

let endMarker = <!-- END ${excalidrawIdentifier} -->;

let startIndex = outputContent.indexOf(startMarker);

let endIndex = outputContent.indexOf(endMarker);

// 辅助函数:获取标题级别

function getHeaderLevel(header) {

return header.match(/^#+/)[0].length;

}

// 辅助函数:查找下一个相同或更高级别的标题

function findNextHeader(content, startIndex, currentLevel) {

const headerRegex = /^#{1,6}\s/gm;

headerRegex.lastIndex = startIndex;

let match;

while ((match = headerRegex.exec(content)) !== null) {

if (getHeaderLevel(match[0]) <= currentLevel) {

return match.index;

}

}

return content.length;

}

if (startIndex !== -1 && endIndex !== -1) {

// 如果存在,更新现有内容

outputContent = outputContent.substring(0, startIndex) +

newExcalidrawContent +

outputContent.substring(endIndex + endMarker.length);

} else {

// 如果不存在,在自定义杂记标题后插入新内容

let miscIndex = outputContent.indexOf(customMiscHeader);

if (miscIndex !== -1) {

let currentHeaderLevel = getHeaderLevel(customMiscHeader);

// 找到下一个相同或更高级别的标题或文件末尾

let nextHeaderIndex = findNextHeader(outputContent, miscIndex + customMiscHeader.length, currentHeaderLevel);

// 在自定义杂记标题和下一个标题之间插入新内容

outputContent = outputContent.substring(0, nextHeaderIndex) +

“\n\n” + newExcalidrawContent + “\n\n” +

outputContent.substring(nextHeaderIndex);

} else {

// 如果没有找到自定义杂记标题,则在文件末尾添加

outputContent += \n\n${customMiscHeader}\n\n + newExcalidrawContent;

}

}

// 更新输出文件

await app.vault.modify(outputFile, outputContent);

new Notice(✅ Excalidraw 内容已更新到文件:${outputPath}, 2000);
“`

本文永久更新地址:

https://blogs.qudange.top/p/%e4%b8%80%e9%94%ae%e6%8a%8aexcalidraw%e7%9a%84%e5%a4%b4%e8%84%91%e9%a3%8e%e6%9a%b4%e8%be%93%e5%87%ba%e4%b8%ba%e7%ba%bf%e6%80%a7%e6%96%87%e7%ab%a0/

17 小时前 1
阿房公
阿房公@阿房公

2 天前

读《明朝那些事》,最后一章写的是崇祯皇帝自尽后的故事。我以为作者会接着描写大明的衰败,或者是清军入关后的物是人非,可是并没有。他写了徐霞客,写了徐霞客坐在了黄山顶,听了一整天的大雪融化声。
那段结束语:我之所以写徐霞客,是想告诉你,所谓百年功名、千秋霸业与一件事情相比,其实算不了什么。这件事情就是用你喜欢的方式度过一生!
人生海海,不过尔尔,其实没什么意义。

#他山之石 #感悟
iPhone iOS Safari
2 天前 1
Duke Yin
Duke Yin@duke

5 天前

今天把所有设备的默认搜索引擎都从百度换成Microsoft Bing了,现在百度搜索越来越难找到自己想要的内容了,错误信息,误导信息绝对会被排在首页、前排,每次搜索都需要浪费大量时间精力筛选,也许是百度的摆烂,现在看Bing越来越顺眼了。

#Bing #百度
5 天前 2
阿房公
阿房公@阿房公

2 周前

办公室越狱的小乌龟,历经九九八十一难,最终在前往打印室的取经路上,被如来神掌按住了。

#hoho
iPhone iOS Safari
2 周前 6
Duke Yin
Duke Yin@duke

2 周前

第一章节结束后的过场动画是工笔水墨风格,第二章是木偶定格动画,第三章是吉卜力风格,而且讲了一个很好的故事。

#黑神话悟空
iPhone iOS Safari
2 周前 2
阿房公
阿房公@阿房公

2 周前

所有人都说《黑神话·悟空》只有两个结局剧情,然而我却实践并顿悟了第三个结局——打不过就打不过,我其实并不是天命人,老子放弃了。

#呵呵哒 #感悟
iPhone iOS Safari
2 周前 9
Duke Yin
Duke Yin@duke

2 周前

  • 玩的PS5版,目前到第三章打完妙音
  • 个人认为美术尤其是环境美术已经是世界第一梯队了
  • 中国古建筑、壁画、雕塑登峰造极,每个章节过场都是一个完整的动画短片,惊喜
  • 音乐和配音都非常棒,爱不释耳
  • 游戏难度很高,大部分Boss需要找到克制的打法/法术/道具才能勉强通过,类似但不是典型魂游戏
  • 除了多跑一次,几乎没有死亡惩罚
  • 缺点
    • PS5版优化有些问题,地图衔接处有明显卡顿,性能模式部分场景掉帧严重,画面糊
    • PS5版几乎没有手柄震动,没有自适应板机,画面没有HDR,没有优化加载速度
    • 遇到过一次卡退跳出
    • 玩法设计不够成熟,比如无法有效克制远程敌人,某些boss缺乏趣味,未考虑玩家视角,攻击判定很迷,武器单一,部分道具几乎无用
    • 地图设计较为潦草,看不到魂游戏那种精妙的令人眼前一亮的箱庭设计,有一些很莫名其妙的走独木桥桥段以及看似区域实则空气墙堵路的设计
    • PS5版不能自定义手柄按键
#黑神话悟空
2 周前 3
阿房公
阿房公@阿房公

2 周前

我们,都曾以为自己便是大圣。
但终究,大多活成了那个八戒。

#感悟 #这就是生活
iPhone iOS Safari
2 周前 6
Duke Yin
Duke Yin@duke

3 周前

在加拿大东部新斯科舍省,有三条连着的街,它们的名字分别是:“这条街”,“那条街”,和“另一条街”。

iPhone iOS Safari
3 周前 3
Duke Yin
Duke Yin@duke

3 周前

Steam提供了 #黑神话悟空 的测试工具,实测RTX2070不开光线追踪,开FSR和帧生成在1080p尺寸高画质可以基本稳60帧,测试场景不算复杂,几乎没有角色,没有大幅度运镜,也没有战斗和特效。黑猴还是挺吃性能的。但是,我买的PS5版,希望不要遇到 #Cyberpunk2077 一样的预售欺诈吧。

3 周前 4
司大官人
司大官人@司大官人

3 月前

故事不长,也不难讲,脸红相识,眼红散场。

AndroidOS Chrome
3 月前 46
司大官人
司大官人@司大官人

3 月前

不曾再你巅峰时慕名而来 也未曾在你低谷时离你而去

3 月前 33
司大官人
司大官人@司大官人

3 月前

如果忘记你那么容易,那我爱你干嘛!

3 月前 38
司大官人
司大官人@司大官人

3 月前

你怕不怕,这辈子就是上辈子所说的下辈子?

3 月前 33
司大官人
司大官人@司大官人

4 月前

见或不见 亦很挂念
你在心里 亦于面前

AndroidOS Chrome
4 月前 39
座右铭
座右铭@座右铭

5 月前

证明自己是对的是一种执念。

ankuaifu

#信念
5 月前 75
座右铭
座右铭@座右铭

10 月前

一颗平静的心,一个健康的身体,一个充满爱的家……这些东西都是买不到的。这些必须通过努力才能获取。

纳瓦尔

#健康 #努力 #平静 #爱
10 月前 96
座右铭
座右铭@座右铭

10 月前

脑力劳动者和体力劳动者工作时,有个明显的区别——脑力劳动者的工作并不完全是有效的。
前者的产出只是信息,并不能直接变成产品;后者产出是具体的有效的,可以直接变成产品。
对于脑力工作者来说,忙碌本身没意义。你的工作中有效性产出的占比高低,才是真正决定你工作价值的关键所在。

网友

#产品 #价值 #工作 #忙碌
10 月前 93
座右铭
座右铭@座右铭

10 月前

闲暇是人生的精华,除此之外,人的整个一生就只是辛苦和劳作而已。
头脑思想狭隘的人容易受到无聊的侵袭,其原因就是他们的智力纯粹服务于他们的意欲,是意欲的工具。

叔本华

#人生 #思想 #意欲
10 月前 105
座右铭
座右铭@座右铭

10 月前

一个有原则的人就是一个有力量的人。

原则是深思熟虑的结果,不受他人左右,不受情绪左右。比如不需要和所有人都成为朋友,和人相处追求互利互惠,就是两条可以参考的原则。

原则越清晰,你越有力量。软弱的人,本质上就是没有原则的人。

网友

#互惠 #力量 #原则
10 月前 85

Loading...
载入中

已到底部

没有可加载的页面

C
写微博
S
搜索
J
下一篇微博/评论
K
上一篇微博/评论
R
回复
E
编辑
O
显示隐藏评论
T
回顶部
L
登录
H
显示隐藏帮助
Ctrl+Enter
提交发布
ESC
取消并清除内容