在 Obsidian 中集成 GPT-3 提高输入效率

前两天在 Twitter 上发了一个贴子,说如果 [[Obsidian]] 中能继承 [[ChatGPT]] 和 [[GitHub Copilot]] 就好了,我想来虽然也可以在 IntelliJ 和 VSCode 中打开本地的笔记仓库,但是一个礼拜前我尝试了一下用 VSCode 打开,竟然比 Obsidian 还卡,不知道哪里除了问题,所以只能再切换回 Obsidian。好在发帖没多久就发现了 Text Generator 这个插件,用 GTP-3 的 API 驱动的文字生成,并且在调研的过程中又还发现了 Obisidain 下不少能提高输入效率的工具,这里就一切总结一下。

文本生成

使用 Text Generator 生成文本

  • 在 Obsidian 下安装 Text Generator 插件
  • 获取 OpenAI API Key
    • 登录 OpenAI 官网,点击 Account,然后点击 View API keys,或者直接访问 OpenAI API key
  • 使用 cmd+j 快捷键

使用方式

  • 第一种方式,在记笔记的过程中,使用快捷键 cmd+j,让 Text Generator 续写后文内容。
  • 第二种方式是使用左侧边栏上的按钮

文本扩展

Text Expander 类的工具是将用户的输入自动扩展成一个更长的内容,通常用来快速输入某些内容。因为我已经有了很多方法来实现 Text Expander(文本扩展),就没有使用 Obsidian 中的插件。

我个人使用的文本扩展方法主要有两个:

这两个工具一直都秉持我的理念,开源,跨平台,并且可以通过文本的方式配置。

RIME

RIME 是中州韻輸入法引擎 (Rime Input Method Engine) 的缩写,由 RIME 扩展出了不同平台上的输入法,小狼毫,中州韵,鼠须管等等,在 RIME 中可以配置自定义词库,然后在输入法中,通过自定义的映射来实现快速输入。

比如在自定义短语中配置:

MySQL   mysql   100
我看下  wkx     30

就可以实现在输入 mysql 的时候自动修正为 MySQL,在输入 wkx 的时候扩展成 我看下。通过进阶的 lua 脚本也可以实现输入 date 自动扩展成当前日期等等。

Espanso

Espanso 是一款是 Rust 编写的跨平台的 Text Expander。Espanso 可以通过纯文本配置 来设定文本的扩展。

espanso demo

比如演示中输入 :date 自动扩展成日期。为了防止误扩展,一般都会在缩略词前面加上冒号。

  • [[HuggingFace]]

reference


2022-01-03 obsidian , text-generator , gpt-3 , openai , chatgpt , github-copilot

Logseq 第一次试用记录以及发布 Logseq 到网页

早之前在使用 [[Obsidian]] 的时候就有了解过 [[Logseq]] 但一直没有找到机会去尝试一下,毕竟我从 [[WizNote]] 迁移到 Obsidian 之后使用一直没有遇到任何问题,毕竟 Obsidian 够简单,也足够扩展性,并且基于 Markdown 文件的笔记是我过去一直在使用的方式。但现在让我有尝试使用一下 Logseq 的契机是因为在以前我都是使用 Vault 存放所有的笔记,然后使用其中一个 Blog 目录存放我想发布的内容,这样每一次我想发布一个具体的文章的时候就可以直接将文件移动到 Blog/_posts 目录之下,然后 Git 提交即可,但这个发布存在的问题便是其中的特殊双向链接 [[]] 会在页面上有些违和,而我看到 Logseq 可以将页面直接以 HTML 方式发布,并且也可以非常好的处理双向链接的问题,于是想来试一试。

Logseq 官方的文档 便是一个非常好的例子,展示界面几乎和用户的编辑页面是一致的,在线浏览也没有任何卡顿的情况。

Logseq 和 Obsidian 存在的区别

block

上手体验的第一个非常大的区别就在于 Logseq 有 block 的概念,这个概念可能是从 Roam Research 中借用而来,不像 Obsidian 整个文档就是一个 markdown 文件,所以每一行文字其实还是段落,但是 Logseq 中每一次 “Enter” 都创建了一个 block。Logseq 中使用 ((uuid)) 的语法来引用 block。有了 block 的概念之后就会发现其实文档内容的最小单元变成了 block,而在 Obsidian 中我能使用的最小单元也无非是通过 # 来划分出来的页面的段落。这可能是 Logseq 更加灵活的地方,但因为目前我还没有想好怎么使用这个 block 所以之后有了具体的使用场景再来分享。

command

Obsidian 中的 Command 和 Logseq 中的 Command 在使用起来还有一些区别,我设定了 Ctrl + P 来调用 Obsidian 中的 Command Palette,而这其中的命令大部分是对整个文档,或者部分内容进行的调整。而在 Logseq 中,在任何页面中使用 / 都可以进行响应的插入,/ 更像是 Notion 的方式,通过 / 来快捷调用复杂的输入操作,比如插入页面引用、块引用、标题、图片等等。

但我个人觉得在 / 中提示输入标题有些累赘,我个人的习惯一般不会在一篇文章中使用三级标题以上的子标题,那这也就意味着我只会使用 1~3 个 #,那么在纯 markdown 文档中,输入 # 要远比先输入 / 然后搜索对应的标题,选中要来的快,即使是输入三个 # 也会比 / 快。

/ 让我觉得最重要的就应该是页面引用和块引用了,通过模糊搜索,在笔记和笔记之间建立关系使用 / 就大大简化的。而在 Obsidian 中我就只能使用 [[ 来进行页面的关联。

vim mode

Obsidian 让我直接上手的一个非常重要的原因就是开箱支持的 Vim 模式,我几乎没有想就启用了,并且一直使用到现在,这无非只是让我在终端的笔记转移到了 Obsidian,并且我熟悉而这个快捷键,命令都可以直接使用,这也让我在终端,IntelliJ IDEA,浏览器 Vimium 达成了统一。而 Logseq 开箱就是所见即所得的界面,当然普通使用起来是没有问题的,但用起来就慢慢地发现有些别扭,比如在上一行插入,或者快速跳转到页尾,以前非常熟悉的 O 或者 G 突然没有了就有些陌生。

然后再看到 Logseq 的 Feature Request ,大家对 Vim-mode 的讨论,要求支持还是挺多,不过截止目前还用不了。

分享

就像上面提到的一样,让我试用 Logseq 最重要的一个原因就是 Logseq 生成在线文档的能力,因为我之前使用 Jekyll 的文档分享部分 Obsidian Vault 中的内容就没有购买 Obsidian Publish 服务,因为一来我觉得 Jekyll 够用,然后 Obsidian Publish 服务的页面最初的时候访问优点慢,并且早鸟价 8$ 一个月的价格也有些高。但后来在使用的过程中还是会有一些些的不便,毕竟双向链接如果没有特殊的处理会显得有些奇怪。

Tag 使用

在 Logseq 中使用 #tag 新建 tag,当点击 tag 的时候会创建新的页面,而在 Obsidian 中 #tag 就只是页面的一个元数据,标签。在 Logseq 中可以点击 tag 页面来查看所有该标签下的页面和块,而在 Obsidian 中我通常是使用搜索。

Logseq 中特殊的语法

Logseq 中使用 key:: value 格式来对页面或块进行属性设置,页面的属性在页面的第一个块定义,作为 frontmatter,块属性可以在任何块中定义。

Logseq 中的 Properties 的两大作用:

  • Query,可以通过查询语法来将带有 property 的内容检索出来
  • 定义 Page/Block 的通用属性,比如想要有一个模板,当记录一本书的时候自动会有相应的属性

Obsidian 搭配 Logseq 使用

在上面的使用过程中,我一直使用 Git 来追踪 Logseq 界面中修改了文档之后在原始文档中的体现,大体来看如果没有使用 Logseq 自身的特殊语法,大致还是兼容 markdown 语法的。

所以我想到了如果将 Logseq 仓库存放在 Obsidian Vault 中,那么其实和我之前使用 Jekyll 发布内容的方式就是一致的了,我只需要把需要发布的内容移动到 Logseq 文件夹下即可。比如说我的 Logseq 仓库叫做 notes,那么他下面会有一个 pages 文件夹,存放的都是 Logseq 中的笔记原始文件。那么我将整个 notes 作为我 Obsidian Vault 的一部分,那么我既可以在 Obsidian 中编辑这些笔记,也可以用 Logseq 打开这个子文件夹来编辑。我只需要使用 Logseq 兼容的语法,那么每一次提交,然后推送到 GitHub 之后,就可以利用 GitHub Actions 自动发布。

Obsidian Vault 是一个独立的仓库,然后使用 git submodule 将 Logseq 仓库添加到其中,还可以利用版本控制来管理。

将 Logseq 发布到 GitHub Pages

这里使用 pengx17 的 Logseq-publish 的 GitHub Action,在个人仓库下创建 .github/workflows/main.yml 文件,根据自己的情况填入配置:

name: CI

# Controls when the workflow will run
on:
  push:
    branches: [master]

  # Allows you to run this workflow manually from the Actions tab
  workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  build:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - uses: actions/checkout@v2
      - name: Logseq Publish
        uses: pengx17/logseq-publish@main
      - name: add a nojekyll file
        run: touch $GITHUB_WORKSPACE/www/.nojekyll
      - name: Deploy
        uses: JamesIves/github-pages-deploy-action@v4
        with:
          branch: gh-pages # The branch the action should deploy to.
          folder: www # The folder the action should deploy.
          clean: true
          single-commit: true

然后每一次 push 之后会自动触发 build,将静态 HTML 文件发布到 gh-pages 分支中。然后在 GitHub 仓库设置界面 Pages 中设置域名即可。

我发布的内容 https://notes.einverne.info


2022-01-03 logseq , notes , backlink , markdown

2021 年读书笔记

根据豆瓣的记录,今年读了 48 本书,没去年读哲学、金融那么入迷,所以大部分的读书记录还是集中在了前两个季度。

之间几年的读书笔记:

哲学

因为去年政治学的著作,所以想要开始读一些西方哲学史相关的内容,但柏拉图、苏格拉底等著作目前又无法直接读懂,需要借助大量的二手著作,二次解读,所以经过了去年大量[[洛克]]的作品之后,今年第一季度就读了很多[[卢梭]]的作品:

  • [[社会契约论]],这是卢梭关于政治学的一本著作,其大名应该很多人都知道,尤其是其中最著名的一句话「人生而自由,却无往不在枷锁之中。自以为能掌控一切的人,却比其他任何人更是奴隶」。
  • [[论人类不平等的起源和基础]],卢梭认为人类不平等的起源是人的虚荣心和私有制。人在[[自然状态]]下是平等的,只是进入了社会之后才不平等。这本书只是卢梭为第戎学院写的一个征文内容,但其观点却另当时的社会为之一振。
  • 接下来读的就是[[忏悔录]],这是一部卢梭的自传,卢梭毫无顾忌地把自己一生中不堪、肮脏都放到了读者面前。
  • [[爱弥尔]] 这一本书并没有消化完全,虽然我知道这本书是卢梭想要教育出一个合格公民而写的一部关于教育的书,但我因为缺乏对那个时期的一些认识,可能需要再补充一些历史背景之后再读。
  • [[通过知识获得解放]],这是 2021 年重读的一本书,这是[[波普尔]]的一本论文集,从知识,社会科学,到书籍思想,到文化,在这本书里面波普尔探讨了非常多的问题以及思考。最让我印象深刻的是破普尔对于科学知识的认识,[[我们的知识是建立在暂时性和尝试性解决办法之上]],我们当前的科学知识正确的唯一原因就是其是暂时经受了猛烈的批评。自然科学的方法和社会科学一样,通过实验得出尝试性解决方法,如果经得住批评就接受,如果被驳倒就尝试另外一个方法。

社科

  • [[人类理解论]]
  • [[人类大瘟疫 一个世纪以来的全球性流行]]
  • [[生活在宋朝]]
  • [[牛津通识读本:大卫休谟]]
  • [[刘擎西方现代思想讲义]],非常容易读,作为一本哲学入门书籍不错,并且对西方哲学的流派和哲学家都有一个介绍和总结,如果对其中的某些思想或哲学家感兴趣不妨再找其他著作阅读
  • [[做一个清醒的现代人]]
  • [[马克思韦伯 跨越时代的人生]]
  • [[货币的非国家化]],这是一本读着读着就不自觉想起 Web 3,区块链,比特币的书,[[哈耶克]] 在其中主张废除中央银行,允许私人发币,货币也可以自由竞争。
  • [[文明 现代化 价值投资与中国]] 这是投资人李录关于「价值投资」,现代化,文明和中国的思考,在我看来是一本非常容易读的书,李录在书中也就很多问题展开了自己的论述,关于「文明的传播」,「现代化是否有可能在中国诞生」,「现代化的传播和现代化的道路之争」,「现代化的本质和铁律」,「对未来中国的预测」 等等问题。在投资上,我至始至终都坚信的是认识到了才能赚到超额的收益,而如何提高自己的认识,去读伟大的投资家是如何思考问题的,他们就是学习的对象。

投资 期权

  • [[投资中最重要的事]] 算是一本投资经典书吧,作者是 [[霍华德 马克斯]],作者在其中阐述了自己的投资逻辑,如何看待市场,判断估值,理解风险,关注周期,等等,是一部非常不错的投资书籍,并且通过这一本书,可以了解非常多的观点,就这些观点也可以去发现更多的经典著作。
  • [[简易期权]],算是期权的入门书吧,看名字就大概能理解,这算是一本非常简单介绍期权的著作,不是非常推荐,但如果要看可以快速的根据目录来了解一下什么期权,以及各种期权的种类
  • [[每天学一点金融投资学]]
  • [[投资 一部历史]]
  • [[彼得林奇教你理财]],彼得林奇也是在投资历史上留下了辉煌的一笔
  • [[对赌]]
  • [[期权交易:核心策略与技巧解析]]
  • [[漫步华尔街]],个人非常推荐的一本投资书,因为通过这一本书让我了解了更多的投资理论,[[价值投资]],[[技术分析]],[[基本面分析]],[[随机漫步理论]],[[现代投资组合理论]],[[行为金融学]] 等等理论,没有哪一个理论是绝对的正确,也不是说坚守哪一个理论就能百分之百超越市场
  • [[麦克米伦谈期权]]
  • [[暗池]]

人物传记

  • [[算法帝国]] 当时了解到盈透证券,偶然间获知了此书,书中的第一章关于[[Interactive Brokers 盈透证券]] 创始人的故事非常精彩,读完了这部分后面就略读过去了
  • [[将心注入 舒尔茨]] 这是星巴克创始人[[霍华德 舒尔茨]]的第一本个人传记,这也是舒尔茨关于创办星巴克的故事。故事也非常精彩,读完之后对星巴克又有了全新的认识,它在我这里便不再只是一个咖啡品牌了。
  • [[一路向前]] 这是舒尔茨再次担任 CEO 之后的著作,星巴克在丢失了创始人舒尔茨之后一度曾经走上了歧路
  • [[大投机家]],这是德国投资家科斯托拉尼的自传,在他的历史时代,他有着非常超前的思考,对「投机」也有自己的行事逻辑。不借钱投资(不加杠杆),构建富有想象力的投资方案,耐心。书中对股市的长期,短期,已经外部影响因素都有一定的讨论。这算不上一本投资的经典书籍,但作为业余读物却也能在其中收获不少。

小说

  • [[希望之线]] 东野圭吾 2021 最新的小说
  • [[无名之町]] 疫情下的海边小镇发生的一起命案,死者是受人尊敬的老师

纪实文学

  • [[巨浪下的小学]] 海啸过后
  • [[无规则游戏:阿富汗屡被中断的历史]]

社科

  • [[繁荣与衰退]] 美联储前主席[[格林斯潘]] 的著作
  • [[全球房地产]],正如其名,就是介绍了世界各个主流市场的房地产经济,包括了香港的,德国的,美国的等等。我没有精读,大致留下了一个印象。

其他

  • [[费曼学习法]],以输出来促使输入,通过教别人来学习。费曼学习法可以简化成四个步骤,理解概念,教给别人,回顾,简化内容。
  • [[不购买的习惯]],法语中并不存在「穷」这个说法,法国人不说「穷」,而是说现在没有钱,也就是说「穷」不是一种属性,而是一种状态。只允许拥有少量的物品,就促使我们去好好利用,好好相处。

专业

  • [[Reading-2021]]

2021-12-31 reading , notes , collection , book

Bash 中的 Parameter Expansion

Parameter Expansion 是一个用来描述命令行中部分参数被展开(内容被替换)的术语。在大部分的场景中,被展开的参数通常会带有 $ 符号,在一些特定的场景中,额外的花括号(curly braces)也是必须的。

比如:

echo "'$USER', '$USERs', '${USER}s'"
'testuser', '', 'testusers'

上面的例子展示了基本的 parameter expansions(PE) 是什么,第二个 PE 结果是一个空字符串,那是因为参数 USERs 是空的。其实 s 不是参数的一部分,但是因为 bash 无法分辨这一点,所以我们需要使用 {} 来限定参数的边界(前后)。

Parameter Expansion 也可以让我们去修改会被展开的字符串,这个操作会使得一些修改变得非常方便:

for file in *.JPG *.jepg
do mv -- "$file" "${file%.*}.jpg"
done

上述的代码会重命名所有扩展名为 .JPG.jepg 的 JPEG 文件到 .jpg 扩展名。然后 ${file%.*} 表达式则会截取 file 文件从开头到最后一个 . 的内容。

其他的一些 PE tricks

  • ${parameter:-word}, Use Default Value 如果当 parameter 未设置或为null时,使用默认值 word,否则直接使用 parameter 的值
  • ${parameter:=word}Assign Default Value 赋值,当 parameter 未设置或为 null 时,word 会被赋值给 parameter,然后 parameter 的值会被展开
  • ${parameter:+word}Use Alternate Value 如果 parameter 是 null 或者 未设置,那么结果没有任何被替换,如果 parameter 有值,则会被替换成 word
  • ${parameter:offset:length} Substring Expansion,使用 offset 和 length 限定的字串展开,序号从0开始。
  • ${#parameter} 使用 parameter 的长度展开,如果 parameter 是一个数组名,则展开其中元素个数
  • ${parameter#pattern} pattern 会从 parameter 值开始匹配,最短的 match 会从 parameter 中被删除然后剩余的被展开
  • ${parameter##pattern}## 后面的 pattern 会把最长的 match 删掉
  • ${parameter%pattern},pattern 从 parameter 后往前匹配,最短的匹配被删除,剩余部分展开
  • ${parameter%%pattern},从后往前,最长的 match 会被删除
  • ${parameter/pat/string},parameter 值中的第一个出现的 pat 会被替换为 string
  • ${parameter//pat/string}, 每一个出现的 pat 都会被替换
  • ${parameter/#pat/string}
  • ${parameter/%pat/string}

reference


2021-12-29 linux , bash

使用 HandBrake 压缩转码视频

HandBrake 是一款开源的全平台的视频转码压制工具,基于 [[FFmpeg]],可以算作一个 GUI 版本的 FFmpeg。

官网: https://handbrake.fr/

安装

macOS:

brew install --cask handbrake

基础知识复习

分辨率 resolution

分辨率,也称为解析度,指的是视频中像素点的个数。

帧率 frame rate

帧率指的是每秒在屏幕上刷新的画面个数。

需要显示器硬件支持,大多数屏幕的刷新率在 60Hz 左右。

通常情况下 30FPS 已经能够保证流畅。

码率 bit rate

码率指的是单位时间内文件包含的数据量。

相同分辨率,码率越高约清晰。但是超过一定范围之后,清晰度变不会显著提高。

文件体积 = 总码率 * 时长。

媒体封装格式

通常见到的媒体封装格式包括:

  • MKV
  • MP4
  • WebM

编解码格式

视频、音频数据的压缩方式。

常见的有:

  • H.264 / H.265(HEVC)

预估文件的大小

通常可以通过分辨率大小、码率、帧率来预估一个视频文件的大小:

  • 720P / 1 Mbps / 30 FPS / x264 / AAC = 1 小时视频大小在 450M 左右
  • 1080P / 2 Mbps / 30 FPS / x264 / AAC = 1 小时视频大小在 900M 左右

使用

当了解了视频编解码的基本信息之后再去使用 HandBrake 就简单很多了。

使用 HandBrake 的使用场景通常有:

  • 压缩原始文件,如果想要在互联网上分发音视频文件,可以通过 HandBrake 压缩到一个合适的大小之后再分发
  • 视频转码,将视频文件转码成各个平台都兼容的格式,比如 MP4 等
  • 给视频文件增加字幕,音轨

软件界面:

handbrake ui


2021-12-24 handbrake , video , ffmpeg , codec , open-source , transcoder

不丢失数据 降级 Android 应用版本

之前一次不消息把 Google Play Store 中的自动更新启用了,之后一个夜里把所有应用都更新了,不过有些应用本来就不想升级的,比如网易云音乐(有一些低版本没有广告,没有乱七八糟的直播什么的),微信。所以想着能不能在不丢失数据的情况下降级应用。简单的搜索了一下果然可以。

这里需要使用到 adb 命令,不同的系统直接安装即可,我现在在 Linux 下之前就已经安装过。

macOS 下:

brew cask install android-platform-tools

执行:

adb devices

查看是否连接,如果出现了设备 ID,则表明连接成功了。

然后准备好特定版本的 apk。

adb push wechat_7.0.0.apk /sdcard/Download/wechat_7.0.0.apk

然后进入下一步:

adb shell

进入系统的 shell 环境。

pm install -d -r /sdcard/Download/wechat_7.0.0.apk

说明:

  • -d 表示运行降级安装
  • -r 表示保存数据重新安装现有应用

如果无法安装报错:

255|OnePlus7Pro:/ $ pm install -d -r /sdcard/Download/wechat_7.0.0.apk                                                                                                                                                                       
avc:  denied  { read } for  scontext=u:r:system_server:s0 tcontext=u:object_r:fuse:s0 tclass=file permissive=0
System server has no access to read file context u:object_r:fuse:s0 (from path /sdcard/Download/wechat_7.0.0.apk, context u:r:system_server:s0)
Error: Unable to open file: /sdcard/Download/wechat_7.0.0.apk
Consider using a file under /data/local/tmp/
Error: Can't open file: /sdcard/Download/wechat_7.0.0.apk

Exception occurred while executing 'install':
java.lang.IllegalArgumentException: Error: Can't open file: /sdcard/Download/wechat_7.0.0.apk
	at com.android.server.pm.PackageManagerShellCommand.setParamsSize(PackageManagerShellCommand.java:520)
	at com.android.server.pm.PackageManagerShellCommand.doRunInstall(PackageManagerShellCommand.java:1283)
	at com.android.server.pm.PackageManagerShellCommand.runInstall(PackageManagerShellCommand.java:1249)
	at com.android.server.pm.PackageManagerShellCommand.onCommand(PackageManagerShellCommand.java:185)
	at android.os.BasicShellCommandHandler.exec(BasicShellCommandHandler.java:98)
	at android.os.ShellCommand.exec(ShellCommand.java:44)
	at com.android.server.pm.PackageManagerService.onShellCommand(PackageManagerService.java:22322)
	at android.os.Binder.shellCommand(Binder.java:940)
	at android.os.Binder.onTransact(Binder.java:824)
	at android.content.pm.IPackageManager$Stub.onTransact(IPackageManager.java:4644)
	at com.android.server.pm.PackageManagerService.onTransact(PackageManagerService.java:4515)
	at android.os.Binder.execTransactInternal(Binder.java:1170)
	at android.os.Binder.execTransact(Binder.java:1134)

那么需要将 apk 移动到 /data/local/tmp/:

mv /sdcard/Download/wechat_7.0.0.apk /data/local/tmp/

然后再安装:

pm install -d -r /data/local/tmp/wechat_7.0.0.apk

如果还不行,那么可以保留数据卸载应用然后重新安装:

pm uninstall -k com.tencent.mm
pm install -d /data/local/tmp/wechat_7.0.0.apk

2021-12-23 android , apk , adb

使用 Swizzin 从零开始构建自己的 Seedbox

[[Swizzin]] 是一个轻量级、模块化的 [[Seedbox]] 解决方案,可以安装在 Debian 9/10 或 Ubuntu 16.04/18.04/20.04 上。QuickBox 的软件包已经被移植过来,包括 Webmin 控制面板。

Swizzin 可以安装如下的组件:

  • BT/PT 客户端:qBittorrent, rTorrent/ruTorrent, flood, Transmission, Deluge
  • 影音播放:Plex, Emby, ffmpeg
  • 资料同步:rclone, btsync(resilio sync), syncthing
  • 其他影音相关:bazarr, headphones, [[Jackett]], lidarr, quassel, sickchill, [[Sonarr]], subsonic, ombi, 等等
  • 资源获取:Couchpotato, rapidleech, sickgear, 等等
  • 文件管理:shellinabox, filebrowser, vsftpd, 等等
  • 网盘:[[NextCloud]]
  • Usenet: nzbget, nzbhydra, 等等
  • Linux 桌面环境:X2Go
  • 聊天室:ZNC, the Lounge
  • Web 服务器:Nginx, LEMP (Webmin), Let’s Encrypt

后台界面:

swizzin dashboard

项目地址 GitHub: https://github.com/swizzin/swizzin

支持的操作系统

  • Debian 9/10
  • Ubuntu 16.04/18.04/20.04

一键安装脚本

wget

bash <(wget -qO - git.io/swizzin) && . ~/.bashrc

curl

bash <(curl -sL git.io/swizzin) && . ~/.bashrc

如果在 Ubuntu 下,以 sudo 来执行上面的安装脚本,需要在命令中包含 -H 参数,以确保你的主目录在 sudo 启动时被修改为 /root 。安装程序会自动处理这个问题:

sudo -H su -c 'bash <(wget -O- -q https://git.io/swizzin-setup)'

更进阶的设置可以参考 这里

box 命令

box 有如下子命令:

help
install
remove
update
upgrade
adduser
deluser
chpasswd
panel fix-disk
list
test

Swizzin 更改用户名密码

Swizzin 的密码是在执行初始化脚本的时候设定的,可以使用如下命令重置密码:

sudo box chpasswd your_name

更改 rtorrent 默认下载位置

修改 vi ~/.rtorrent.rc:

directory.default.set = /mnt/storage/torrents/rtorrent

重启 rtorrent 服务

sudo systemctl restart rtorrent@<yourusername>
sudo systemctl status rtorrent@<yourusername>

重启 Syncthing

sudo systemctl restart syncthing@yourname.service

重启 Plex

sudo systemctl restart plexmediaserver

修改 ruTorrent 主题

ruTorrent 的安装地址在

/srv/rutorrent/plugins/theme/themes

找到合适的主题克隆到该目录下即可。

Transdroid/Transdrone 配置

[[Transdroid]] 是一款 Android 上用来管理 BT 客户端的软件,Swizzin 安装的 ruTorrent 需要按照如下方式配置才能连接上。

Name: rtorrent (or whatever you like)
Server type: rtorrent
IP or host name: <the hostname of your server>
Username: <your username>
Password: <your password>
Advanced Settings:
    SCGI mount point: /rutorrent/plugins/httprpc/action.php OR /<username>
    Use SSL: ON

2021-12-20 swizzin , seedbox , seed , rtorrent , rutorrent , transmission , syncthing , linux , ubuntu , webmin , bittorrent , torrent

每天学习一个命令:通过 arp 学习地址解析协议

ARP( Address Resolution Protocol) 地址解析解析,用来将 IP 地址解析为 MAC 地址的协议。

主机会维护一张 ARP 表,存储 IP 地址和 MAC 地址映射关系。

arp

直接执行 arp 会列出系统当前的 arp 记录:

arp
Address                  HWtype  HWaddress           Flags Mask            Iface
10.236.151.50            ether   50:9a:4c:2d:--:--   C                     eth0
172.17.0.2               ether   02:42:ac:11:00:02   C                     docker0
10.236.151.83            ether   ac:87:a3:17:72:26   C                     eth0
172.25.0.2               ether   02:42:ac:19:00:02   C                     br-8ff872eb50bd

如果有多张网卡,可以使用 -i 指定网卡:

arp -i eth0

使用 -a 参数使用 BSD 风格输出:

$ arp -a
? (10.236.151.19) at 50:9a:4c:2d:--:-- [ether] on eth0
? (10.236.151.200) at e4:54:e8:cd:--:-- [ether] on eth0

删除 ARP 记录,可以使用 -d 参数:

sudo arp -d 192.168.0.1

删除之后 ARP 记录就会显示为 incomplete。

添加 ARP 记录:

sudo arp -s 192.168.0.1 12:34:56:78:90:ab

如果要批量添加,可以从文件中读取添加,可以先准备好一个文件,然后其中添加 IP 和 MAC 地址:

192.168.0.201 11:22:33:44:55:66
192.168.0.202 12:23:34:45:56:67

最后使用 -f 参数:

sudo arp -f arp_list.txt

2021-12-19 linux , arp , ip , ipv4

Azure Cosmos DB 简单使用入门

Azure Cosmos DB 是 Azure 提供的一个分布式 NoSQL 数据库,Cosmos DB 提供一定的关系型数据库的能力,并且可以无缝地扩容。

Azure 提供了 30 天免费的试用时长,可以注册一个 Azure 帐号体验一下。该帐户中获得前 1000 RU/s 的免费吞吐量和 25 GB 的免费存储。

Cosmos DB 和其他非关系型数据库区别

Cosmos DB 是微软提供的多区域分布式的数据库,可以根据需要自动缩放吞吐量,自动扩容。

Cosmos DB 的优势

  • 多地区,可以在全球范围能有不错的响应时间,Cosmos DB 可以自行复制副本,用户可以与最近的副本进行交互
  • 高吞吐和弹性存储,透明水平分区和多主数据库复制,任何区域内,只需发出一次 API 调用,即可将每秒数千个请求弹性扩容到数百个请求,用户只需为实际使用的吞吐量和存储付费
  • 一致性,用户无需关心多个地区的数据一致性问题
  • 用户无需关心索引,数据库架构,Cosmos DB 会自动为所有数据构建索引
  • SLA 可用性保证,Azure 托管 Cosmos DB,用户不需要管理和操作复杂的多数据中心和部署软件的升级
  • 支持多种数据库模型
    • SQL
    • MongoDB
    • Cassandra

Azure Cosmos DB 账户结构

  • 一个数据库账户下面有零个或多个数据库
  • 一个数据库下面有零个或多个容器
  • 一个容器包括零个或多个项
    • 容器用于存储数据
    • 创建容器时需要提供分区键,文档中选择的要存储的属性,用于路由到要写入,更新或删除的分区
    • 容器会实现集合,表,图等

下面就重点对 Cosmos DB 中的每个概念介绍一下

Azure Cosmos DB databases

在 Azure Cosmos DB 中数据库类似于 namespace。数据库是一组 container。

Azure Cosmos DB containers

Azure Cosmos DB container (容器)就是数据真正存放的地方。和关系型数据库不同的是,当发生扩容的时候,不是扩展 VM 的存储空间, Cosmos DB 会横向扩展。数据会存储在一台或多肽服务器中,称为分区(partitions)。为了增加吞吐量或存储,需要添加更多的分区。这为容器提供了无限的吞吐量和存储。当创建容器的时候,需要添加分区键(partition key)。这个分区键需要从文档的属性中选择。该属性将用于将数据路由到要写入、更新或删除的分区。也可以被用于 WHERE 查询子句,以实现高效的数据检索。

Cosmos DB 中数据的基础存储机制称为物理分区,可以拥有高达 10000 RU/s 和多大 50 GB 的数据。Azure Cosmos DB 使用可存储多达 20 GB 数据的逻辑分区对此进行抽象。随着添加更多分区,逻辑分区允许服务为基础物理分区上的数据提供更大的弹性和更好的管理。 若要详细了解分区和分区键,请参阅将数据分区

创建容器的时候,会需要配置吞吐量:

  • 专用吞吐量:专门留给该容器使用
    • 有两种类型的专用吞吐量:标准和自动缩放
  • 共享吞吐量:
    • 在数据库级别指定,与数据库中最多 25 个容器共享。

容器与 Schema 无关,容器中的项可以具有任意 Schema 或不同的实体,只要它们共享相同的分区键。默认情况下,容器中的所有数据都会自动编制索引,无需显式索引。

容器中的数据必需具有每个逻辑分区键值唯一的 id

Azure Cosmos DB 容器的属性

Cosmos DB 容器具备一组系统定义的属性。这些属性值会根据不同的 API 附加在数据上,比如常见的:

  • _rid,容器唯一标识符
  • _etag,用于乐观并发控制的实体标记
  • _ts,容器上次更新的时间戳
  • _self,容器的可寻址 URI
  • id,用户配置,容器的名字
  • indexingPolicy,用户可配置,提供更改索引的功能
  • TimeToLive,用户配置,从容器自动删除
  • changeFeedPolicy,用户配置,用于读取对容器中的项所做的更改
  • uniqueKeyPolicy,用户配置,确保逻辑分区中一个或多个值的唯一性
  • AnalyticalTimeToLive,用户配置,在设置的时间段后从容器中自动删除功能

Azure Cosmos DB items

数据在 Cosmos DB 中表现为一个项,集合中的一个文件,表格的一行,或者图形中的一个节点或边缘。

item 的属性

  • _rid,项的唯一标识符
  • _etag,乐观并发控制的实体标记
  • _ts,上次更新的事件戳
  • _self,项的可寻址 URI
  • id,逻辑分区中用户定义的唯一名称

建模和分区

嵌入数据

在关系型数据库中,通常是将数据规范化,将数据规范化通常就是将数据拆分成不同的不同的实体。比如一个人的信息可以拆分成联系人,多条地址记录,多条联系人记录等等。通常可以通过类型来进一步区分,比如地址可以是家庭地址,或工作地址等。

规范数据的前提是为了避免冗余记录。而在 Cosmos DB 中可以使用 嵌入的方式,将此人的相关信息嵌入到一个文档中。

{
    "id": "1",
    "firstName": "Thomas",
    "lastName": "Andersen",
    "addresses": [
        {
            "line1": "100 Some Street",
            "line2": "Unit 1",
            "city": "Seattle",
            "state": "WA",
            "zip": 98012
        }
    ],
    "contactDetails": [
        {"email": "thomas@andersen.com"},
        {"phone": "+1 555 555-5555", "extension": 5555}
    ]
}

这样在创建或更新此人的信息时都是单个写入操作。

什么时候使用嵌入

通常是如下的场景:

  • 实体之间存在包含关系
  • 实体之间存在一对多关系
  • 嵌入的数据不经常更改
  • 嵌入 ude 数据在未绑定的情况下不会增长
  • 嵌入的数据会频繁地统一查询

什么时候不嵌入

如果文档中有一个无限制增长的数组,那么最好不要嵌入此数组。

可以考虑将数组拆分开,并在数组的元素中加入原始引用的 ID。

引用数据

通过在文档中包含 ID 的方式来完成引用。

比如下面的文档设计为一个人的股票持仓。

Person document:
{
    "id": "1",
    "firstName": "Thomas",
    "lastName": "Andersen",
    "holdings": [
        { "numberHeld":  100, "stockId": 1},
        { "numberHeld":  50, "stockId": 2}
    ]
}

Stock documents:
{
    "id": "1",
    "symbol": "zbzb",
    "open": 1,
    "high": 2,
    "low": 0.5,
    "vol": 11970000,
    "mkt-cap": 42000000,
    "pe": 5.89
},
{
    "id": "2",
    "symbol": "xcxc",
    "open": 89,
    "high": 93.24,
    "low": 88.87,
    "vol": 2970200,
    "mkt-cap": 1005000,
    "pe": 75.82
}

这种方法的缺点就是当要显示一个人的投资组合的时候,应用程序需要多次访问数据库来加载每个股票的信息。

什么时候使用引用

  • 表示一对多关系。
  • 表示多对多关系。
  • 相关数据频繁更改
  • 引用的数据可能没有限制

尽量避免使用可能较大的可变即集合。而通过将 ID 分散在单个文档中的方式来保存关系。

多对多关系

在关系型数据库中如果要处理多对多的关系,通常是引入一张关系表,保存 ID 对 ID 的关系。

在文档数据库中,可以通过分别在实体中冗余关系的方式来记录多对多的关系。

比如作者,作者图书关系,图书,三个实体之间的联系。一本书可能有多个作者,而一个作者也可能写很多本书。

这个时候就可以考虑在作者的文档中加入 books 数组,在图书的文档中加入 authors 数组来表示。这样就可以不用关系表来保存多对多的关系了。同时也可以减少应用程序需要访问服务器的次数。

创建样例数据

可以使用 New item 菜单添加新的 item

{
    "id": "1",
    "category": "personal",
    "name": "groceries",
    "description": "Pick up apples and strawberries.",
    "isComplete": false
}

其中的 id 为数据的唯一 ID。

Cosmos DB 常用查询语法

最基本的查询语法,比如查询全部文档

SELECT * FROM c

指定顺序

SELECT * FROM c ORDER BY c._ts desc

查询条件

TTL

TTL,生存时间,Cosmos DB 能够在一段时间之后自动将项从容器中删除。可以在容器级别设置 TTL,系统会基于 TTL 值自动删除过期的项,不需要客户端应用程序显式的发出删除请求。TTL 的最大值是 2147483647 秒,大约 24855 天或 68 年。

删除过期项是一个后台任务,使用剩余的请求单元,即用户请求没有使用的请求单元。TTL 过期后,如果容器出现请求过载的情况,并且没有足够的 RU 使用,也会延迟数据删除操作。但是任何查询都不会通过接口返回 TTL 过期后的数据。

  • 容器的生存时间 (DefaultTimeToLive
    • 缺失,项不回自动过期
    • 如果设置为 -1,默认情况下,项不回过期
    • 如果设置为非零数字,项将在上次修改后 n 秒后过期
  • 项的生存时间 (ttl
    • 仅当父容器的 DefaultTimeToLive 存在且不是设置为 null 时,此属性适用
    • 如果存在,将替代父容器的 DefaultTimeToLive

reference


2021-12-16 azure , cosmos-db , no-sql , microsoft , database

Obsidian 插件篇之 Dataview

在过去的一年里面,我基本上只用 Obsidian 来记录,并且我也将我过去几年的 Jekyll 的内容作为 submodule 引入到了 Obsidian Vault,所以现在这个仓库中有非常多的笔记。

前些天在 Twitter 上看到有人说 Obsidian 基于 Markdown 的管理方式没有 Roam Research,和 [[Logseq]] 那样基于块的灵活,并且无法动态展示内容,比如有人用 Notion 那种表格的形式来管理读书记录,观影记录,用 Obsidian 就没有太好的办法去做到,但其实只要使用 Dataview 这一个插件就可以实现。

在过去的使用过程中,我并没有太多的需求要去使用这一款插件,大部分的场景我都会直接使用搜索(Ctrl+Shift+F)关键字或标签来做到。但随着现在笔记越来越多,我产生了一个需求,比如我想要查看我笔记中打了某个标签的笔记列表,我想查看某个时间段中我记录的关于某个人物的笔记,又或者我会在笔记中将我看过的影视作品记录下来,我想查看过去我看过的某个导演的作品列表,这一些都可以通过 Dataview 结合 Obsidian 来达到。

这样的需求和我之前在了解 [[Zettelkasten 笔记法]] 中关于 [[202008261820-Zettel-笔记中间层]] 的概念不谋而合。当原子的笔记变得越来越多的时候,为了将这些原子的笔记集合到某一个主题下,通常我们会使用分类、标签等等方式来组织,那么使用一个笔记中间层来管理就变得顺理成章,而在过去我通常都是使用手工的方式来组织,比如我在管理读书笔记的时候,我会新建一个年度的笔记 [[Reading-2021]] ,然后在其中以季度为标题,然后记录每一本书。同样观影记录也类似。

而当我得知了 Dataview 插件之后,我发现这样的中间层可以通过组织数据而自动产生,只需要我在每一个笔记中加上 YAML Front matter,然后用适当的查询语句就可以做到。下面就直接来介绍一下这个插件。

基本概念

在 Obsidian 中可以用不同的方式对数据(笔记)进行标记,Dataview 会追踪所有 Markdown 文件中标记的数据。

Dataview 插件中几个重要的概念:

  • YAML Front Matter,Markdown 文件开头标记元数据部分
  • Inline Field, 行内标记字段
  • Implicit Field, 隐式字段

YAML Front Matter

Obsidian 的 metadata 使用 YAML front matter,一般写在文件的最上面,使用 key-value 结构,既对人友好,也对 Obsidian 可读。YAML 是 “Yet Another Markup Language” 的缩写。

Front matter 一般是纯文本文件从第一行开始的一块区域。这是 Markdown 文件通常用来添加 metadata 的方式。Jekyll, Hugo, Gatsby 等等静态网站生成器都使用这个方式。

这个区块就可以对这个 Markdown 文件添加额外的描述信息。

YAML 区块需要三条 --- 短横线标记开始和结束。并且这个区块需要在文件的最上面。

比如:

---
key: value
key2: value2
key3: [one, two, three]
key4:
- four
- five
- six
---

从 Obsidian 0.12.12 开始,有四个原生支持的标签:

  • tags
  • aliases
  • cssclass
  • publish

后两个我一般不怎么使用,所以我创建了一个模板,每一次创建新的笔记,都会自动包含上述两个标签:

---
category: 
aliases:
tags:
time:  
---

你当然还可以用 Templater 插件针对不同的笔记内容插入不同的 YAML 头,比如去记录看过的电影,我就会多加上 rating, comment 字段,简单的记录评分和短评。

Inline Fields

在页面中使用行内字段

除了上面显式地使用 YAML 来对文件进行标记,也还可以在内容中使用行内的语法对 markdown 文件进行标记,Dataview 支持 Key:: Value 这样的格式:

# Markdown Page

One Field:: Value
**Bold Field**::  Nice!

或者也可以写在一行内:

刚刚看完[[浪漫的体质 Melo 体质]],可以打 [rating:: 5] 分,这是一部让人看了非常[mood:: 轻松愉悦]的[category:: 喜剧片]

上面一个句子同样达到了给当前这个文档加上了如下的标签:

  • rating: 5
  • mood: 轻松愉悦
  • category: 喜剧片

在之后的检索中直接可以使用。

在 Task 中使用行内字段

同样在 Task 语法中也可以使用,通常在 Markdown 中我们会使用 -[ ] blah blah 来标记一个任务,比如:

- [ ] todo task [metadata key:: value]
- [x] finished task [completion:: 2021-12-30]

同样可以使用行内的标记。在 Dataview 官方的说明中我们也可以使用 emoji 来分别表示,到期(🗓️YYYY-MM-DD),完成(✅YYYY-MM-DD),创建(➕YYYY-MM-DD)的日期。如果不想使用 emoji 也可以直接用文字 [due:: ], [create:: ], [completion:: ]

行内字段的类型

所有 Dataview 中的字段都有类型,这决定了在渲染的时候的顺序。

如果没有匹配上任何类型,就是字符串:

FieldName:: This is a demo text.

数字类型:

FieldName:: 6
FieldName:: 2.4
FieldName:: -10

布尔值:true/false

FieldName:: true
FieldName:: false

日期:ISO8601 标准,YYYY-MM[-DDTHH:mm:ss.nnn+ZZ]

Example:: 2021-04-18
Example:: 2021-04-18T04:19:35.000
Example:: 2021-04-18T04:19:35.000+06:30

时长:格式 <time> <unit>,比如 6 hours 或者 4 minutes

Example:: 7 hours
Example:: 4min
Example:: 16 days
Example:: 9 years, 8 months, 4 days, 16 hours, 2 minutes
Example:: 9 yrs 8 min

链接:Obsidian 的格式,如果要在 Front matter 中使用,则需要使用双引号

Example:: [[A Page]]
Example:: [[Some Other Page|Render Text]]

数组:

Example:: 1, 2, 3
Example:: "yes", "or", "no"

对象,字典:

field:
  value1: 1
  value2: 2
  ...

字段 Fields

页面中的隐式字段

Dataview 会给每一个文件都自动添加一些默认的 metadata 信息,这些字段在后面的检索语法中都可以使用。

Implicit Field 字段1

  • file.name: 文件标题(字符串)
  • file.folder: 文件所属文件夹路径
  • file.path: 文件路径
  • file.size: (in bytes) 文件大小
  • file.ctime: 文件的创建时间(包含日期和时间)
  • file.mtime: 文件的修改时间
  • file.cday: 文件创建的日期
  • file.mday: 文件修改的日期
  • file.tags: 笔记中所有标签数组,子标签会按照每一个层级分别展开存储,比如 #Tag/A/1,会有三个 [#Tag, #Tag/A, #Tag/A/1]
  • file.etags: 除去子标签的数组
  • file.inlinks: 指向此文件的所有传入链接的数组
  • file.outlinks: 此文件所有出站的链接数组
  • file.aliases: 文件别名数组
  • file.day: 如果文件名中有日期,那么会以这个字段显示。比如文件名中包含 yyyy-mm-dd(年-月-日,例如2021-03-21),那么就会存在这个 metadata。

任务中的隐式字段 implicit field in task

同样对于 Tasks,Dataview 也会自动创建一些隐式的字段。

每一个任务都会有如下的字段:

  • Task 会继承所在文件的所有字段,比如 Task 所在的页面中已经包含了 rating 信息了,那么 task 也会有
  • completed 任务是否完成
  • fullyCompleted: 任务以及所有的子任务是否完成
  • text: 任务名
  • line: task 所在行
  • path: task 所在路径
  • section: 连接到任务所在区块
  • link: 连接到距离任务最近的可连接的区块
  • subtasks: 子任务
  • real: 如果为 true, 则是一个真正的任务,否则就是一个任务之前或之后的元素列表
  • completion: 任务完成的日期
  • due: 任务到期时间
  • created: 创建日期
  • annotated: 如果任务有自定义标记则为 True,否则为 False

Dataview Query Language

一旦定义了上述的字段(标签、属性)就可以用 Dataview 定义的查询语法来检索并展示内容。这是一个类 SQL 的语法,一个最简单的例子:

```dataview
TABLE rating AS "Rating", summary AS "Summary" FROM #games
SORT rating DESC
```

Dataview 插件提供了三种样式的展现形式:

  • table, 检索内容以表格形式展现
  • list, 列表形式展现
  • task, 检索内容中的任务

一个标准的语法是这样的:

```dataview
[list|table|task] field1, (field2 + field3) as myfield, ..., fieldN
from #tag or "folder" or [[link]] or outgoing([[link]])
where field [>|>=|<|<=|=|&|'|'] [field2|literal value] (and field2 ...) (or field3...)
sort field [ascending|descending|asc|desc] (ascending is implied if not provided)
```

说明:

  • dataview 告诉 Obsidian 这个地方需要使用 Dataview 插件
  • list|table|task 告诉 Dataview 插件展现形式
  • from 则告诉 Dataview 包含什么文件,如果是 #tag 获取标签,获取文件夹,等等
  • Where:筛选 from 中的内容,进行过滤,可以指定从某个标签(在yaml字段中的标签),或某个文件夹中检索信息。
  • sort:排序:可以按某个字段进行排序,可以升序(使用参数ascending、asc)或降序(使用参数descending、desc)

table

个人最常使用的一个展现形式,可以以表格的形式展示多列。

```dataview
table field
from #tag 
sort file.ctime desc
```

表示过滤出所有包含 #tag 的文件,并显示 field 列,最后以表格形式,并以文件创建时间倒序渲染展示。

进阶用法

除了上面提到的 Query 语法,Dataview 插件还提供了另外两种查询语法。

  • 行内查询
  • Dataview JS 进阶查询

行内查询 Inline DQL

Dataview 提供的查询可以直接在行内使用,比如

# 查看当前文件名
`= this.file.name`
# 今天日期
`= date(today)`
# 距离某个日期时长
`=(date(2023-12-31)-date(today))`

可以在 Dataview 设置中自定义行内查询的前缀。

Dataview JS

Dataview JS 给了用户进一步查询的能力,可以直接使用 JavaScript , Dataview JS 定义了一些预置函数可以直接使用。

使用 dataviewjs 代码块:

```dataviewjs
let pages = dv.pages("#books and -#books/finished").where(b => b.rating >= 4);
for (let group of pages.groupBy(b => b.genre)) {
   dv.header(group.key);
   dv.list(group.rows.file.name);
}
```

在 Dataview JS 中需要使用 dv 变量,插件定义了一些默认的函数。

检索页面

通过 dv.pages() 函数:

```dataviewjs
dv.pages("#korean-drama") //返回所有带有标签 books 的页面
dv.pages('"folder"') //返回所有在 folder 文件夹的页面
dv.pages("#yes or -#no") //返回所有带有标签 yes 或者没有标签 no 的页面
dv.pages("") //返回所有页面
```

检索路径

返回文件的路径:

```dataviewjs
dv.pagePaths("#books") //返回所有带有标签 books 的页面路径
dv.pagePaths('"folder"') //返回所有在 folder 文件夹的页面路径
dv.pagePaths("#yes or -#no") //返回所有带有标签 yes 或者没有标签 no 的页面路径
```

返回单个页面

```dataviewjs
dv.page("Index") //返回名称为 Index 的页面
dv.page("books/The Raisin.md") //返回所有在 books 文件夹下的 The Raisin 文件的页面
```

渲染

通过 header 来设置标题,比如 level=1 就是设置一级标题:

dv.header(level, text)

列表

```dataviewjs
dv.list([1, 2, 3]) //生成一个1,2,3的列表
dv.list(dv.pages().file.name)  //生成所有文件的文件名列表
dv.list(dv.pages().file.link)  //生成所有文件的文件链接列表,即双链
dv.list(dv.pages("").file.tags.distinct()) //生成所有标签的列表
dv.list(dv.pages("#book").where(p => p.rating > 7)) //生成在标签 book 内所有评分大于 7 的书本列表
```

任务列表

```dataviewjs
// 从所有带有标签 project 的页面中获取所有的任务生成列表
dv.taskList(dv.pages("#project").file.tasks)

// 从所有带有标签 project 的页面中获取所有的未完成任务生成列表
dv.taskList(dv.pages("#project").file.tasks
    .where(t => !t.completed))

// 从所有带有标签 project 的页面中获取所有的带有特定字符的任务生成列表
// 可以设置为特定日期
dv.taskList(dv.pages("#project").file.tasks
    .where(t => t.text.includes("#tag")))

// 将所有未完成且带有字符串的任务列出
dv.taskList(
    dv.pages("").file.tasks
    .where(t => t.text.includes("#todo") && !t.completed),1)
```

表格

```dataviewjs
// 根据标签 book 对应的页面的 YAML 生成一个简单的表格,其中 map 为对应的内容所对应的表头,按顺序填入即可。
// b 可以是任意值,只是代表当前传入的文件为 b
dv.table(["File", "Genre", "Time Read", "Rating"], dv.pages("#book")
    .sort(b => b.rating)
    .map(b => [b.file.link, b.genre, b["time-read"], b.rating]))
```

同样也可以在行内使用 Dataview JS:

`$= dv.current().file.mtime`

总结

再回到我最初的需求,比如我想查看我笔记中所有包含了 korean-drama 标签的笔记,以及我的打分。只要我的笔记中之前已经包含了 Front Matter,就可以用下面的代码直接检索出来。

```dataview
table file.ctime as "Create Time", rating as "Score"
from #korean-drama
sort rating desc
```

结果:

obsidian dataview query result

再比如我想知道我笔记中包含了洛克这个单词的所有笔记,以及出现的行内容。

那么可以用这一段代码:

```dataviewjs
const files = app.vault.getMarkdownFiles()
const prompt = "洛克"

const fileObject = files.map(async (file) => {
    const fileLink = "[["+file.name.split(".")[0]+"]]"
    const content = await app.vault.cachedRead(file)
    return {fileLink, content}
})

Promise.all(fileObject).then(files => {

let values = new Set(files.reduce((acc, file) => {
    const lines = file.content.split("\n").filter(line => line.match(new RegExp(prompt, "i")))
    if (lines[0] && !file.fileLink.includes("Untitled")) {
        if (acc[0]) {
            return [...acc, [file.fileLink, lines.join("\n")]]
        } else {
            return [[file.fileLink, lines.join("\n")]]
        }
    }
    return acc
}, []))

dv.header(1, prompt)
dv.table(["file", "lines"], Array.from(values))

})
```

reference


2021-12-13 obsidian , note-taking , dataview , metadata , query , obsidian-plugin

电子书

本站提供服务

最近文章