Obsidian双链框架AMOC,更简单的PARA、卡片笔记方法

Obsidian成了我主力工具,练习时长快2年半了,并不是有多喜欢它,每次有新笔记软件都会看一看,希望有能取代它的,可惜都没遇到。

用Obsidian做了1年个人管理,我最喜欢的7个功能发表的时候,其实已经不怎么折腾。这一年,在个人方面,基于Obsidian笔记组织了41场拍摄,20个长视频、6-8万字的摄影课程,公司方面则是一天一更新的短视频策划拍摄后期等工作,同时能自动汇总一周工作,哪怕填写2-3个月前的每日工时都能轻松应付。不过每个月看一本书的目标,去年只完成了4本。不交代一下,容易被当成折腾Obsidian就是不务正业。我只是把原来玩守望先锋的游戏时间放到折腾上。

那这一年我更侧重Obsidian的自动化、可视化统计汇总等功能追加,同时也遇到很严重的问题:维护困难。

遇到的问题

Obsidian的受欢迎程度,很大一部分依赖大量使用Dataview插件。在实际使用中,文件有流动性,需要对树状图进行调整、有修改、归档之类的,例如一个月过去了,就把日记放到对应文件夹,一年过去了也是,这就会相当抓狂,一堆Dataview代码引用了path(文件路径),需要调整。出于方便排序,我采用的"001-xxx"的文件夹索引号命名,一旦修改,真地狱级灾难!

后期有部分笔记采用了Tags,这也导致我的tags标签系统非常臃肿,新建笔记的时候经常想不起到底要打什么标签,不打的话问题大不大?到底是不是需要模板等等。

整体而言,易用性、简洁性都极差,投入的精力消耗过大。

《Building a Second Brain》作者Tiago Forte 介绍 PARA 时就提倡:

Only the simplest, most effortless habits endure long term.
只有最简单、最轻松的习惯才能持久。

这个原则和编程原则KISS (Keep it Simple, Stupid)如出一辙。在Obsidian高强度的使用过程中,很早就引入油管博主Nick Milo的MOC(Maps Of Contents)方法,异常好用。很简单,就是在笔记顶头写上Up:: [[上层目录]]。MOC的其中一种用法,是建立起目录大纲作为的索引:

image.png

去年的分享中,下图的日常、记录、打码的工作、橙猫涉影就是采用MOC的方式,都是手动输入。新增删减都需要调整,比较麻烦,写笔记都像维护系统一样繁琐了。

image.png

我开始思考能否利用什么方法实现这一块的自动化,自动组合起来,不需要那么麻烦手动增删。经过一段时间的探索后,就有了今天给大家介绍的,Automate MOC架构(下面简称AMOC)。

AMOC 介绍

image.png

在Obsidian中,如果树状图是物理结构、Tags标签是魔法结构,那AMOC就是连接物理和魔法的桥梁、中间层,基于双链+Dataview的结构打造,充分发挥了双链功能。

AMOC的目的是为了专注笔记本身,让我忽略笔记本身以外的工作。对于查改增删等操作都在笔记内完成。而笔记则自动形成MOC,即AMOC如下图所示,AMOC只是一个框架,不涉及知识分类。

image.png

编程有个原则:“Don’t Repeat Yourself”,Just Copy。
AMOC的核心功能只需要Copy即可,粘贴到相关笔记,只需3个步骤即可上手建立AMOC。

image.png

AMOC 步骤1:上级索引

AMOC步骤中最关键:key:的一点,就是在笔记中我们要告诉它,它的上级索引是谁,并写上:

up:: [[上层目录]]

双冒号为Dataview能识别的属性,后续步骤的Dataview都基于此项进行双链识别。

AMOC 步骤2:同级索引

  • 当 A 笔记及其他笔记都存在 up:: [[Z笔记]] ,即同时指向Z笔记时,在A笔记中加入下面的code,即可实现同级AMOC,无需理解直接Copy Paste即可。
> [!note] AMOC Standard
> ```dataview
> list
> where up !=undefined and contains(up,this.up) and !contains(file.path,"模板")
> sort file.name
> limit 10
> ```

在我的Ob中,绘图、橙猫涉影……模板、白板,这几个同层级的AMOC笔记,都具备up:: [[Homepage]],在日记中加入上面的Code,其展示如下图:

同级MOC

我习惯上会加上Callout样式,毕竟颜值是最大生产力。

AMOC 步骤3:下级索引

当B、C、D笔记都输入了 up:: [[A笔记]] ,即同上级索引指向A笔记时,在A笔记中加入下面的Code,即可实现BCD的集合。

> [!note] AMOC Down
> ```dataview
> list
> where up !=undefined and contains(up,this.file.link) and !contains(file.path,"模板")
> sort file.name
> limit 10
> ```

这段代码的差别仅仅只是把步骤2的 this.up 换成了 this.file.link,即可实现。

在我的笔记里,还结合Minimal主题的list-cards,在笔记 ‘CMSY.md’ 中效果如图:

image.png

如果有一天,我不希望 ‘CMSYInbox’ 笔记出现,只需要把 ‘CMSYInbox’ 的up修改为其他笔记即可,全自动调整。

AMOC的补充说明

AMOC 概括起来就是

  1. 笔记自身建立了 up::
  2. 直接粘贴两段Dataview

不管我们是从笔记外部调整了它的树状图还是标签,都不会产生任何影响。如果你修改笔记名字,Obsidian的双链功能会自动同步全局更改。专注笔记本身,不产生过多的焦虑和担忧。

在这个功能之后,我延伸了一下,同时读取2层:

image.png

对应的Dataviewjs如下,搭配MCL css使用,模板中有:

```dataviewjs
	const listLimit =10
	const calloutSytle = ">> [!abstract]- 📖"

	//过滤掉日记、excalidraw、canvas的MOC 和模板、box
	let filterPages = dv.pages('-#日记 and -#excalidraw')
		.filter(p => p.file.path.contains("白板")==0 && 
			p.file.path.contains("box")==0 &&
			p.file.path.contains("模板")==0 && 
			p.up && 
			p.file.name.contains("日记")==0)
		.sort(p => p.file.name,"desc")

	let pages = filterPages.filter(p => String(p.up).contains(dv.current().file.link))

	let formatMulti = "> [!multi-column]\n> "
	let nextlevel = pages.map(async(page) => {
	  return  calloutName + page.file.link
	})
	//console.log(arr)
	for (let page of pages) {
		var uplink = page.file.link 
		formatMulti += "\n" + calloutSytle  + "[[" + page.file.path + "|"  + page.file.frontmatter.aliases + "]]"
		
		if(filterPages.where(p => String(p.up).contains(uplink)).file.length!=0)
			formatMulti += "\n>> - "+filterPages.where(p => String(p.up).contains(uplink)).limit(listLimit).file.link.join("\n>> - ")
		else
			formatMulti += "\n>> 无"
		formatMulti += "\n>"

	}

	dv.paragraph(formatMulti)
```

考虑到它的易用性和可维护性,2层AMOC足够,适可而止。

AMOC在不同操作下的简单之处:

  • 新建:任何位置新建,笔记中写上up:: [[上级目录]],自动添加到目录当中。
  • 修改:分类、归档等调整,笔记中修改up:: [[新的上级目录]],自动纳入到新的目录,如Inbox、Done、Achieve当中。
    • 一开始的时候,《摄影》的 上层AMOC为《橙猫涉影》,但因为用的太多,需要多层AMOC跳转,我直接对《摄影》修改 up:: [[Homepage]] 即可建立新的AMOC。
  • 延伸:采用了contains 写法而不是 up=this.up,提供了更多的可拓展性,让笔记的 up 可以指向多个AMOC笔记。
    • 这篇文章一开始放在公众号下,但后来忘记了,去 ‘Obsidian MOC’ 下找不到,这种有交叉属性的,可以同时放到up中 up:: [[公众号]], [[obsidian MOC]] ,当然也可以用标签来实现,只是个人更喜欢AMOC的方式,我的标签有另外用途。

搭配主题和Callout样式,AMOC最终在一个文档中的效果图:
image.png

基于AMOC搭建PARA

PARA在笔记中很受欢迎,我用AMOC组织了相关文件,模板文末有提供下载。
搭建PARA,只需要5个笔记:

  • 结构中心.md 作为总控
  • 代表PARA的4个笔记,粘贴下面内容即可:
up:: [[PARA结构中心]]

> [!note]- 最近编辑:当前文件夹下
> ```dataviewjs
> dv.table(["文件","修改时间"],dv.pages()
> .filter(p => p.file.folder.contains(dv.current().file.folder) && p.file.name!=dv.current().file.name)
> .sort(p => p.file.mtime,"desc")
> .limit(10)
> .map(item => {return [item.file.link,item.file.mtime]}))
> ```

# `=this.file.name`

> 💡YAML中的cssclasses采用list-card,横向显示list,需搭配主题Minimal使用

## AMOC

> [!note]- AMOC Standard
> ```dataview
> LIST
> where up !=undefined and contains(up,this.up) and !contains(file.path,"模板")
> sort file.name
> limit 10
> ``` 

> [!note]- AMOC Down
> ```dataview
> LIST
> where up !=undefined and contains(up,this.file.link) and !contains(file.path,"模板")
> sort file.folder asc
> limit 10
> ``` 


在PARA这4个笔记中的内容是一模一样的,仅仅只需要依据不同的笔记名,即可基于自身 this.file 属性,实现一统江湖,大体架构如下:

AMOC在PARA中效果

在这个模板基础上,我只要两步操作:

  1. 新建笔记
  2. 在顶部写上 up:: [[01Projects] 即可划分为对应的领域,前面的数字用于排序。

在结构中心.md中我也放入了读取多层索引的Code,具体可看模板,效果如下:
image.png

基于AMOC搭建卡片笔记

和PARA的方法,几乎是一样的:1个中心.md,在闪念、文献、永久3个笔记里面放上是一模一样的内容。

image.png

至于要不要为它们建立独立文件夹,就看你们自己的习惯,用文件夹会更规范也方便后期管理,这里只是出于展示的用途,怎么操作都不会影响到AMOC,基于双链以及文件属性内联了,用起来就非常轻松。

模板下载与说明

树状图毕竟包含了全部文件,要完全放弃是不可能的,在这一块,我也经常用上 this.file.folder 来进行处理,这就要求AMOC的笔记必须放在合适的文件夹下,符合我们常规树状图的做法,差别在于相对路径和绝对路径。

在标题上,我经常采用下面写法,非常方便。

# `=this.file.name` 

:point_right:模板下载

结语

万事开头难,至少现在不用再担心开头难了。
在新建md笔记的时候,我不用再考虑到底放哪个文件夹,到底要加哪个tag,畏手畏脚的,现在只需要专注笔记本身,加上up:: [[上层索引]]即可,还可随时变更,不受任何约束freestyle,将双链的功能发挥到极致。

本来想分享过去一年所增加的功能,结果涉及到有理论的,即使AMOC这么简单的功能,都需要很大篇幅讲述,有机会再分享一些自动化功能!

13 个赞

橙猫 大佬,多謝分享

您的方法實用有效,僅照本操作 AMOC 的兩段dataview,立馬有感!!謝謝:heart:
:+1::+1::+1::+1::+1:
期待更多的分享,謝謝

按照步骤2添加了代码,但是呈现效果就是普通的list,不是如图中呈现的“section1 |section 2|section 3”…
请问步骤2中的呈现效果也是通过代码实现的吗?还是说是手动编辑?或者是用了其他的插件实现了这样的呈现效果呢?

1 个赞

那个效果是我更换了代码,根据不太设备尺寸调整了样式,截图没对应上,你可以参考一下

const callout_name = "> [!example]+ ";
let files = dv.pages('-#日记').where(p =>p.up!=undefined&&String(p.up).contains(dv.current().up)).sort(p => p.file.name,"desc").limit(10).file
let arr = await Promise.all(files.map(async(file)=>{
	let rtn =file.frontmatter.banner_icon+file.link;
	if(file.link.path==dv.current().file.link.path)
		rtn =  file.frontmatter.banner_icon+file.name;
    return rtn}))

if(window.screen.width < 600)
	dv.paragraph( "> [!example]- "+ "索引\n> - "+arr.join("\n> - "))
else
	dv.paragraph( callout_name+arr.join(" | "))

1 个赞