如何在单篇笔记里实现轻量数据库

如何在单篇笔记里实现轻量数据库

介绍

众所周知,在 Obsidian 里实现类似「数据库」的需求一直都是痛点。

虽然借助插件能实现,但往往得给每一项数据都创建单独页面……颇为麻烦,完全不像 Notion 那样舒适且无感。

很久以前我就有一个设想:

如果我有一篇笔记专门收集某个专题的内容,想要把它做成一个「小数据库」——不要求编辑,只求汇总展示里面各个内容的特定属性,能行吗?

这个问题困扰了我半年之久,终于在最近,随着对 OB 和 Dataview 插件的学习,我实现了这个陈年需求。

展示

效果展示:

如何在单篇笔记里实现轻量数据库--展示

可以看到,通过笔记内的多个标题,就能自动生成一个「轻量数据库」进行呈现。

支持特性:

  1. 点击标题可以直接跳转到对应内容
  2. 只需要一篇笔记就可以创建数据库
  3. 支持多个属性的组合
  4. 根据元数据自适应生成表格列
  5. (大体上)实时更新,具体取决于 Dataview 的刷新间隔
  6. 简单易用:只需要三行代码加一个 JS 脚本文件即可实现

工作原理

笔记的书写

需要借助 Dataview 插件,以及固定的格式:

## 标题
[属性::值]  [属性2::值]  
  1. 元数据行紧挨着标题行写(标题层级不限制——几个井号都行)
  2. 可以有多个元数据,使用 [key::value] 的格式,一行内的多个属性之间要 隔开 2 个空格 ⚠️
  3. 同一个属性(例如 标签 )可以在一个内联属性里用逗号写多个值,也可以一行里写多个同名属性(会自动合并)
    • 第一种: [标签:Obsidian,css]
    • 第二种:[标签:Obsidian] [标签:css]

文章在开始的那张图片,源代码模式下就长这样儿:

如何在单篇笔记里实现轻量数据库--笔记的书写

关于内联属性

详见 Dataview 的文档:Adding Metadata - Dataview

数据库的呈现

首先,打开 Dataview 插件的设置,确保这几个选项都是打开的:

如何在单篇笔记里实现轻量数据库--数据库的呈现

然后需要将这个 JS 文件放入你的笔记库,命名为 liteDatabase.js

const useList = false;
const curNote = dv.current();

if (!curNote){
    dv.span("当前文档未加载,请重新打开");
    return;
}

let tarFile = await app.vault.getAbstractFileByPath(curNote.file.path);

// 获取当前文件的 meta 数据
const curFileMeta = app.metadataCache.getFileCache(tarFile);
const headings = curFileMeta.headings;

const headingsList = headings.map( k => k.heading )

// 获取当前文章的文本内容
const curFilePath = curNote.file.path;
const curTFile = await app.vault.getFileByPath(curFilePath)
const content = await app.vault.cachedRead(curTFile);

if (!headings) {
  dv.span("当前文档缺少标题");
  return;
}

const meta = content.matchAll(/#+ (.*)\n(\[.*\:.*\])/gm);

// 标题作为 key,元数据作为 value,做成 DV 表格
let tableHeads = ["标题"]

// 先存成一个 dict
let metaValues = []

if (meta) {
    for (let m of meta) {
        let title = m[1]

        // 检查 title 是否在 headings 内(避免把一些代码块内的内容也当作元数据)
        if (headingsList.indexOf(title) == -1) {
            continue
        }

        let metaList = m[2].split("  ")
        let metaDict = {}
        
        // 添加元数据的 key 到表头
        for (let i = 0; i < metaList.length; i++) {
            let keyValue = metaList[i].replace("[", "").replace("]", "").split("::")
            // console.log(metaList[i], keyValue)

            let key = keyValue[0].trim()
            if (tableHeads.indexOf(key) == -1) {
                tableHeads.push(key)
            }

            if (useList){
                // 1. 列表形式,重复属性会合并
                let value = [keyValue[1]]
                metaDict[key] = metaDict[key] ? metaDict[key].concat(value) : value
            } else {
                // 2. 文本形式,重复属性会使用最新的
                let value = keyValue[1]
                metaDict[key] = metaDict[key] ? metaDict[key] + `,${value}` : value
            }
        }

        let returnValuesList = []

        for (let i = 0; i < tableHeads.length; i++) {
            if (i == 0) {
                returnValuesList.push(`[[#${m[1]}]]`)
            } else {
                returnValuesList.push(metaDict[tableHeads[i]] ? metaDict[tableHeads[i]] : "")
            }
        }

        metaValues.push(returnValuesList)
    }
}

dv.table(
    tableHeads,
    metaValues.map( k => k )
)

你也可以直接在 Github Gist 中查看或下载这个代码文件:DVJS Code - Lite Database for Obsidian
(后续如果有更新会直接传到 Gist 里)

然后,在笔记中插入:


```dataviewjs
dv.view("liteDatabase")
```

在笔记书写符合规则的情况下,现在你应该就能看到轻量数据库的呈现了!

测试文本

如果暂时没写内容,你也可以直接复制这段内容进笔记查看效果:

## 传说之下
[英文名::Undertale]  [标签::感人,剧情向]

「你落入了地底,然后——」
关于友情的故事。

## 星际拓荒
[英文名::Outer Wilds]  [标签::探索,感人]

「嘿,尝试过掉进黑洞吗?」
关于探索的故事。

## 生化奇兵:无限
[标签::好玩]   [标签::感人,剧情向]  [英文名::BioShock Infinite]

「过去多少年,我依旧在你身边」
关于时间的故事。

拓展

因为用的是 JS 语言,所以理论上来讲形式和内容都有很大的拓展可能性

比如你也可以不用 Dataview 内联属性,就用自己定义的某种格式。

或者你想对获取到的元数据再进行二次处理……

修改 JS 文件(或者让 GPT 帮你修改)就可以按自己想要的方式进行定制化!

碎碎念时间

起源

一开始是今年一月份的时候,看 GameOff 的游戏开发比赛发现好多作品非常有趣,然后就一边记录一边想着「打标记」:

如何在单篇笔记里实现轻量数据库--起源

你可以看到,当时我就在尝试类似的「标题 + 下方的标记」这种方式来进行记录,但是苦于 没有办法把它们汇总到一起展示出来

(甚至开始怀念起了 Notion 的数据库 lol)

但是这个问题放到今天,只需要把当时还在探索的「标记方式」替换成 Dataview 的内联属性,就能得到:

如何在单篇笔记里实现轻量数据库--起源

——每个游戏是不是 Godot 开发的,有哪些值得注意的亮点,一目了然!

属性的定义

关于元数据定义形式,主要是参考了 Github Badges 的样式:

如何在单篇笔记里实现轻量数据库--属性的定义

这样一排小徽标排在一起看着还挺好看的。

(应该也可以通过 css 来实现多彩的背景色)

另外就是……放在同一行也可以让正则表达式的筛选更简化一些。

最早其实也尝试过「标签」还有「内联代码」这两种形式。

但标签的话……会污染我的标签池,这种内容我还是希望就局限在「这个页面内」;内联代码倒是没啥问题,想用也可以用的。

至于多个 KeyValue 中间的 两个空格,这个可能是 Dataview 自己的限制?只有一个空格的情况下似乎会有一些识别问题,所以就强行规定两个空格了。

(但我自己都经常忘记打两个,嗨呀)

讨论

若阁下有独到的见解或新颖的想法,诚邀您在文章下方留言,与大家共同探讨。



反馈交流

其他渠道

版权声明