(新坑)打算在 emacs 上复刻 Tana 的 SuperTag

这么多年了,Org-mode 的 Tag 一直不进化,让人用不下去。而且,用 Agenda 来过滤标签,很快就陷进性能瓶颈。

这两天看了 Tana 的介绍视频,以及详细地了解了 Tana 的 SuperTag 机制。

觉得可以基于 Org-mode 基础特性,实现类似 Tana 的 SuperTag 机制。

该包暂时命名为 org-supertag。我会在这里记录关于该包的思考。

11 个赞

SuperTag 的机制:

  1. Node。是 Tana 的基本信息单位,基本上一个圆点+一段字符,就是一个 Node。每个 Node 都可以单独展开为独立的页面。

  2. SuperTag。Node 因 SuperTag 而变得不同。没有了 SuperTag 的 Node 就是一个圆点+一个字符。

Field。是 SuperTag 机制的核心,扮演 3 个角色:记录属性,命令触发器,AI 启动器。

  • 记录属性。在标记 Tag 的同时,为 Node 记录属性。Node + Tag,让 Node 灵活地拥有很多不同的类型,甚至同一个 Node 可以拥有多个类型,适用于多种场景。
  • 命令触发器。在 Field 里设置了某种命令后,SuperTag 会变成可点击按钮,点击后,可以让 Node 可以执行某种动作。
  • AI 启动器。是命令触发器的变体。搭配 Prompt,点击 SuperTag 后就可以执行 AI 命令,这会非常方便。
  1. 多视图。Tana 本身具备 List, Table, Cards, Tabs and Calendar 等视图。

纵览以上,可以发现,Org-mode 已经实现大部分 Tana 的基础特性:

  • Agenda View:可以过滤/聚合同一个标签的 org-headline
  • Property:属性抽屉,则让每一个 org-headline 具备不同的属性,实现不同的记录。甚至也可以做到以不同的属性值作为触发器。
  • Column View:org-mode 内置了基于 headline 的 Property 转换成列表展示的方法。
  • Tag:尽管 Tag 机制比较残,然而,它居然也已经做好了标签继承的工作。

目前,我已经简单的实现了 Field 的 “记录属性” 的特性:

  • 记录属性,让 Property 与 Tag 关联。当前在 Headline 中应用不同的 Field 时,自动添加不同的标签。实现了这个,就可以反过来,设计基础的 Tag 机制——在添加对应 Tag 的时候,同时为当前的 Headline 设置了对应的 Property。
1 个赞

AI 命令 是啥?

一个触发机制,一个 Node 和一个记录了 AI Prompt 的 SuperTag 组合。

此时,可以将 SuperTag 当作按钮,点击后,AI 会自动将 Node 当作输入,然后自动输出内容。

跟你在聊天窗口里的效果差不多。

换言之,此时的 Node 变成了一个可执行的对象。

org-supertag 的第一个应用场景,就是阅读 org-mode 的更新说明。

每一次读起来都非常头痛,完全不结构化。

同一个 org-store-link 的更新说明,散落在不同的地方…

非常需要用 org-supertag 标记好,然后再汇总输出

org-supertag 的数据结构

  • 基础类别: file node tag field
  • 只有标记了 supertag 的 headline 才视为 node
  • node 记录两个属性:node-id、file-path。前者用于标记,以方便追踪。后者则是用于记录 Node 当前所在位置
  • supertag 本身也记录 tag-id,该 id 与 node-id 关联
  • 一个 node 可以标记多个 supertag
  • 一个 supertag 可以标记多个 node
  • 一个 node 可以有多个 field
  • 一个 tag 可以无需添加 field
  • 当 tag 添加了 field,只能与这一套 field 关联
  • field 本身不具备 id,它的实例将储存在 node 的下方(以 property 的形式)
  • field 通过创建 tag 模板的形式,与对应的 tag 进行关联
  • field 的数值,在序列化之后,与 tag-id 保存到哈希表里(由于不是解耦的,所以无需 id)
1 个赞

有点头痛,到底是 哈希表 只用于储存映射关系,还是用 哈希表 储存所有相关的数据?

最终决定,使用 中间表 + 分表 的方式。以适应 org-supertag 多数据实体(file、tag、field、node),多种关系(一对多,多对一),以及未来可能针对新的功能,所预留的拓展性。

field 与 tag 的多重关系验证

探索了 emacs 除了键盘之外的交互

第一次在 emacs 上创建了按钮,以及 popup-menu 菜单

简单验证 org-element-cache 机制,可以完整读取 org-headline 以及它下方的内容,以及分辨出不同 org-headline 下方内容的不同

Org Element Cache Test Results
==========================


=== Version Info ===
(:org-version "9.7.11" :cache-implementation avl-tree- :features
	      (:has-cache-reset t :has-cache-reset-all nil :has-cache-statistics
				nil))

=== Node Content ===
(:element
 (headline
  (:standard-properties
   [1 1 24 140 140 0 (:title) first-section element t nil 60 138 1 #<buffer *Org
      Element Cache Test*> nil nil
      (org-data
       (:standard-properties
	[1 1 1 262 262 0 nil org-data nil t nil 3 262 nil #<buffer *Org Element
	   Cache Test*> nil nil nil]
	:path nil :CATEGORY nil))]
   :pre-blank 0 :raw-value "Test Headline" :title
   [org-element-deferred org-element-property-2 (:raw-value) nil] :level 1
   :priority nil :tags ("test") :todo-keyword nil :todo-type nil
   :footnote-section-p
   [org-element-deferred org-element--headline-footnote-section-p nil nil]
   :archivedp [org-element-deferred org-element--headline-archivedp nil nil]
   :commentedp nil :CUSTOM_ID "test1"))
 :headline "Test Headline" :tags ("test") :properties
 (("CATEGORY" . "???") ("CUSTOM_ID" . "test1") ("BLOCKED" . "")
  ("ALLTAGS" . ":test:") ("TAGS" . ":test:") ("FILE") ("PRIORITY" . "B")
  ("ITEM" . "Test Headline"))
 :content
 ":PROPERTIES:\n:custom_id: test1\n:END:\nSome content here.\n\n** Subheading\n- List item 1\n- List item 2\n  - Subitem 2.1\n\n"
 :full-text
 "* Test Headline :test:\n:PROPERTIES:\n:custom_id: test1\n:END:\nSome content here.\n\n** Subheading\n- List item 1\n- List item 2\n  - Subitem 2.1\n\n")

=== Subtree Parse ===
((:type headline :raw-value "Test Headline" :tags ("test") :level 1)
 (:type property-drawer :properties nil)
 (:type headline :raw-value "Subheading" :tags nil :level 2)
 (:type plain-list :items nil) (:type item :bullet "- " :paragraph nil)
 (:type item :bullet "- " :paragraph nil) (:type plain-list :items nil)
 (:type item :bullet "- " :paragraph nil))

=== Field Value ===
(:field "custom_id" :value "test1" :begin 37 :end 54)

=== Cache Status ===
(:enabled t :cache-info (:type avl-tree :size 11) :statistics
	  "Statistics not available")

=== Cache Invalidation ===
(:invalidated-region (1 . 140) :cache-status
		     (:enabled t :cache-info (:type avl-tree :size 11)
			       :statistics "Statistics not available"))

期待,大佬预计什么时候发布一个beta版本

重构中…zsbd

支持多级标签么

多级标签是指?父子标签?

也就是层级标签,例如 图片

一篇笔记可以同时属于 #emacs/elisp#emacs/elisp ,也可以只属于 #emacs

哦,这种,父子标签是可以同时添加到一个笔记(或者一个 org-headline)

我看了一下现在的实现,可能和你的想法有出入:

  1. 一篇笔记可以属于多个标签,完全没问题。
  2. 目前标签无层级概念,换言之没有 a 是 b 的父标签, b 是 a 的子标签这种关系。原因是,经过推敲,我认为 Tana 上标签的引用关系,更适合体现标签之间平面化联系的。我实现了引用字段,可以通过检索标签,找到对应的 node,然后添加。然后关于引用关系,在视觉上的表达,可以通过设计一个类似 org-columnview 的方法,提供表格视图,其中提供引用关系的展示。暂时还不确定,太多基础功能需要开发了。
  3. 但一开始设计的时候,已经支持了嵌套标签的表示(仅仅是显示),可以用 #lisp/elisp #pkm-tools/org-mode 这种方式。
  4. 视觉的层级标签表示,暂时还没想到实现的方法。
  5. 我还在犹豫父子标签关系(或者层级标签的实现),主要是考虑到操作成本可能太高。Emacs 的交互手段有限,比较可靠的还是通过 minibuffer 中的对话,但这个方式的成本很高,如果再加入父子标签的确认,就更加推高了创建一个标签的操作成本了。

终于实现一个稳定的基础框架…踩了好多坑,一把血一把泪。还好是终于实现了,现在修改功能什么的,比较有的放矢,不会特别混乱。

这个结构让人安心😀

1 个赞

可以多标签筛选就可以了,标签多层级个人认为是一种负担。

你提到的标签命名也是一种变相的实现方法。

这个功能在计划内