-
Notifications
You must be signed in to change notification settings - Fork 183
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #841 from Moyf/moy-new-articles
关于 Note toolbar 插件的介绍文章
- Loading branch information
Showing
6 changed files
with
576 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
--- | ||
uid: 20240808235819 | ||
title: 如何创建动态的笔记目录 | ||
tags: [Obsidian/插件, Obsidian, OB分享] | ||
description: | ||
author: Moy | ||
type: other | ||
draft: false | ||
editable: false | ||
modified: 20240911095148 | ||
--- | ||
|
||
# 如何创建动态的笔记目录 | ||
|
||
> 本帖的想法来自 [DataviewJS 的提取与汇总 - 经验分享 - Obsidian 中文论坛](https://forum-zh.obsidian.md/t/topic/28337/6) 以及 [使用 Dataview 读取文件中所有标题 - 经验分享 - Obsidian 中文论坛](https://forum-zh.obsidian.md/t/topic/36955/2) | ||
> 感谢前人的经验分享,希望也能对你有所帮助。 | ||
## 效果 | ||
|
||
![](https://cdn.pkmer.cn/images/202409110952715.png!pkmer) | ||
|
||
其中,**目录** 里的链接是使用 Dataview JS 脚本自动生成的,会随着笔记内容自动更新。 | ||
|
||
和其他方案的对比: | ||
|
||
1. 和 **Automatic Table of Content** 等插件相比,使用了大部分人都在用的 Dataview 插件,减少额外的插件安装 | ||
2. 和 **Table of Contents** 插件相比,优势是可以自动实时更新,劣势是并非实际的 Markdown 文本,无法脱离插件使用 | ||
|
||
## 使用方法 | ||
|
||
将 dataviewjs 代码粘贴进笔记,开启 Dataview 插件的脚本渲染功能的情况下,直接会生效。 | ||
|
||
生成的目录本质是 `[[#标题]]` 形式的链接,可以直接点击跳转。 | ||
|
||
## 代码 | ||
|
||
![](https://cdn.pkmer.cn/images/202409110952672.png!pkmer) | ||
|
||
第一版:纯列表形式 | ||
|
||
````javascript | ||
```dataviewjs | ||
const startHeadinglevel = 2; | ||
const file = app.workspace.getActiveFile(); | ||
const { headings } = app.metadataCache.getFileCache(file); | ||
// 全列表的形式 | ||
const raws = headings.map( p => { | ||
let repeatCount = Math.max((p.level - startHeadinglevel) * 4, 0); | ||
let spacesPrefix = ' '.repeat( repeatCount + 4 ); | ||
let listSign = repeatCount > 0 ? '- ' : ''; | ||
let linkText = `[[#${p.heading}]]`; | ||
let headingList = (p.level < startHeadinglevel) ? `- ${linkText}` : `${spacesPrefix}- ${linkText}`; | ||
return headingList; | ||
} | ||
) | ||
let result = raws.join('\n'); | ||
// 添加行距 | ||
dv.container.style.lineHeight = "1.5em"; | ||
dv.paragraph(result) | ||
``` | ||
```` | ||
|
||
第二种:分层形式 | ||
|
||
````javascript | ||
```dataviewjs | ||
// 尝试另一种形式,个人更喜欢让一级列表特殊一点 | ||
const startHeadinglevel = 2; | ||
const file = app.workspace.getActiveFile(); | ||
const { headings } = app.metadataCache.getFileCache(file); | ||
let output = ''; | ||
const additionalAttr = {attr: {style: 'margin-block-start: 0 !important; margin-block-end: 0 !important;'}}; | ||
// 整个 dv 容器的样式调整 | ||
dv.container.style.marginBlockStart = "0em"; | ||
headings.forEach ( h => { | ||
if (h.level < startHeadinglevel) { | ||
// 先输出之前的子列表内容 | ||
if (output) dv.paragraph(output, additionalAttr); | ||
// 将一级标题输出成单独的段落 | ||
output = `[[#${h.heading}]]\n`; | ||
dv.paragraph(output); | ||
output = ''; | ||
} else { | ||
output += `${' '.repeat((h.level - startHeadinglevel) * 4)}- [[#${h.heading}]]\n`; | ||
} | ||
}) | ||
// 把最后的残余内容输出出来 | ||
if (output) dv.paragraph(output, additionalAttr); | ||
``` | ||
```` | ||
|
||
## 脚本要点 | ||
|
||
- 最初的难点就是 ==怎么用 Dataview 输出有层级的列表==,因为直接加空格前缀会导致文本内容被识别成「代码」而非「嵌套列表」 | ||
- 查看 @PlayerMiller 的代码发现可以通过 **将所有文本一起输出成 paragraph** 的方式渲染成正确的列表 | ||
- 但是对第二版来说,没法让作为「非列表」的一级标题很好地共同渲染,所以分段进行单独渲染 | ||
- DV 样式调整 | ||
- 为了调整行间距等样式,使用 `dv.container.style` 直接给 dv 容器指定样式 | ||
- 针对列表的段落,用 `dv.paragraph(output, {attr: {style: 'margin-block-start: 0 !important; margin-block-end: 0 !important;'}} )` 单独指定样式 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,251 @@ | ||
--- | ||
uid: 20240801133135 | ||
title: 如何在单篇笔记里实现轻量数据库 | ||
tags: [Obsidian, OB分享] | ||
description: 在单篇笔记里实现轻量数据库 | ||
author: Moy | ||
type: other | ||
draft: false | ||
editable: false | ||
modified: 20240911095343 | ||
--- | ||
|
||
# 如何在单篇笔记里实现轻量数据库 | ||
|
||
## 介绍 | ||
|
||
众所周知,在 Obsidian 里实现类似「数据库」的需求一直都是痛点。 | ||
|
||
虽然借助插件能实现,但往往得给每一项数据都创建单独页面……颇为麻烦,完全不像 Notion 那样舒适且无感。 | ||
|
||
很久以前我就有一个设想: | ||
|
||
> 如果我有一篇笔记**专门收集某个专题的内容**,想要把它做成一个「小数据库」——不要求编辑,只求汇总展示里面各个内容的特定属性,能行吗? | ||
这个问题困扰了我半年之久,终于在最近,随着对 OB 和 Dataview 插件的学习,我实现了这个陈年需求。 | ||
|
||
## 展示 | ||
|
||
效果展示: | ||
|
||
![](https://cdn.pkmer.cn/images/202409110952965.png!pkmer) | ||
|
||
可以看到,通过笔记内的多个标题,就能自动生成一个「轻量数据库」进行呈现。 | ||
|
||
支持特性: | ||
|
||
1. 点击标题可以直接跳转到对应内容 | ||
2. 只需要一篇笔记就可以创建数据库 | ||
3. 支持多个属性的组合 | ||
4. 根据元数据自适应生成表格列 | ||
5. (大体上)实时更新,具体取决于 Dataview 的刷新间隔 | ||
6. 简单易用:只需要三行代码加一个 JS 脚本文件即可实现 | ||
|
||
## 工作原理 | ||
|
||
### 笔记的书写 | ||
|
||
需要借助 Dataview 插件,以及固定的格式: | ||
|
||
```markdown | ||
## 标题 | ||
[属性::值] [属性2::值] | ||
``` | ||
|
||
1. 元数据行紧挨着标题行写(标题层级不限制——几个井号都行) | ||
2. 可以有多个元数据,使用 `[key::value]` 的格式,一行内的多个属性之间要 **隔开 2 个空格** ⚠️ | ||
3. 同一个属性(例如 `标签` )可以在一个内联属性里用逗号写多个值,也可以一行里写多个同名属性(会自动合并) | ||
- 第一种: `[标签:Obsidian,css]` | ||
- 第二种:`[标签:Obsidian] [标签:css]` | ||
|
||
文章在开始的那张图片,源代码模式下就长这样儿: | ||
|
||
![](https://cdn.pkmer.cn/images/202409110953893.png!pkmer) | ||
|
||
> [!Info] 关于内联属性 | ||
> 详见 Dataview 的文档:[Adding Metadata - Dataview](https://blacksmithgu.github.io/obsidian-dataview/annotation/add-metadata/#inline-fields) | ||
### 数据库的呈现 | ||
|
||
首先,打开 Dataview 插件的设置,确保这几个选项都是打开的: | ||
|
||
![](https://cdn.pkmer.cn/images/202409110953349.png!pkmer) | ||
|
||
然后需要将这个 JS 文件放入你的笔记库,命名为 `liteDatabase.js` : | ||
|
||
````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](https://gist.github.com/Moyf/f2fa6e88c589e70217188e0998e303f0) | ||
> (后续如果有更新会直接传到 Gist 里) | ||
然后,在笔记中插入: | ||
|
||
````markdown | ||
|
||
```dataviewjs | ||
dv.view("liteDatabase") | ||
``` | ||
|
||
```` | ||
|
||
在笔记书写符合规则的情况下,现在你应该就能看到轻量数据库的呈现了! | ||
|
||
### 测试文本 | ||
|
||
如果暂时没写内容,你也可以直接复制这段内容进笔记查看效果: | ||
|
||
````markdown | ||
## 传说之下 | ||
[英文名::Undertale] [标签::感人,剧情向] | ||
|
||
「你落入了地底,然后——」 | ||
关于友情的故事。 | ||
|
||
## 星际拓荒 | ||
[英文名::Outer Wilds] [标签::探索,感人] | ||
|
||
「嘿,尝试过掉进黑洞吗?」 | ||
关于探索的故事。 | ||
|
||
## 生化奇兵:无限 | ||
[标签::好玩] [标签::感人,剧情向] [英文名::BioShock Infinite] | ||
|
||
「过去多少年,我依旧在你身边」 | ||
关于时间的故事。 | ||
```` | ||
|
||
## 拓展 | ||
|
||
因为用的是 JS 语言,所以理论上来讲**形式和内容都有很大的拓展可能性**。 | ||
|
||
比如你也可以不用 Dataview 内联属性,就用自己定义的某种格式。 | ||
|
||
或者你想对获取到的元数据再进行二次处理…… | ||
|
||
修改 JS 文件(或者让 GPT 帮你修改)就可以按自己想要的方式进行定制化! | ||
|
||
## 碎碎念时间 | ||
|
||
### 起源 | ||
|
||
一开始是今年一月份的时候,看 GameOff 的游戏开发比赛发现好多作品非常有趣,然后就一边记录一边想着「打标记」: | ||
|
||
![](https://cdn.pkmer.cn/images/202409110953170.png!pkmer) | ||
|
||
你可以看到,当时我就在尝试类似的「标题 + 下方的标记」这种方式来进行记录,但是苦于 **没有办法把它们汇总到一起展示出来**。 | ||
|
||
(甚至开始怀念起了 Notion 的数据库 lol) | ||
|
||
但是这个问题放到今天,只需要把当时还在探索的「标记方式」替换成 Dataview 的内联属性,就能得到: | ||
|
||
![](https://cdn.pkmer.cn/images/202409110953481.png!pkmer) | ||
|
||
——每个游戏是不是 Godot 开发的,有哪些值得注意的亮点,一目了然! | ||
|
||
### 属性的定义 | ||
|
||
关于元数据定义形式,主要是参考了 Github Badges 的样式: | ||
|
||
![](https://cdn.pkmer.cn/images/202409110953912.jpg!pkmer) | ||
|
||
这样一排小徽标排在一起看着还挺好看的。 | ||
|
||
(应该也可以通过 css 来实现多彩的背景色) | ||
|
||
另外就是……放在同一行也可以让正则表达式的筛选更简化一些。 | ||
|
||
最早其实也尝试过「标签」还有「内联代码」这两种形式。 | ||
|
||
但标签的话……会污染我的标签池,这种内容我还是希望就局限在「这个页面内」;内联代码倒是没啥问题,想用也可以用的。 | ||
|
||
至于多个 KeyValue 中间的 **两个空格**,这个可能是 Dataview 自己的限制?只有一个空格的情况下似乎会有一些识别问题,所以就强行规定两个空格了。 | ||
|
||
(但我自己都经常忘记打两个,嗨呀) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.