在《使用Python和MetaWeblog API发博客》一文中，我介绍了我的博客工作流：使用Obsidian写作，然后切换到命令行执行Python脚本发布。这个工作流用了近4年，运行的非常好。唯一不足就是每次都需要打开命令行运行脚本。最近发现Obsidian有个Python Scripter插件，能直接在Obsidian内运行Python脚本。于是我把之前的脚本稍加修改，实现了在Obsidian内一键发布笔记到Typecho的功能。我把脚本放在Github上，有需要的朋友可以自取。我现在写完文章，只需要CMD + P打开Obsidian的命令面板，执行一个命令就能发布文章，如下图。若文章已经发布过，自动执行更新操作。命令面板调用脚本发布文章我会点Python，有了这个插件，感觉能做非常多的事情。比如自动备份Memos。最后，简单介绍脚本的具体配置：1、在Obsidian插件市场搜索「Python Scripter」插件并安装2、启用并设置Python Scripter插件。其中有两个文件路径的参数，这两个参数可以在Python脚本中引用。Python Scripter插件设置3、在Obsidian的.obsidian目录下创建scripts/python目录，把Github脚本保存在这个目录，需要配置好博客登录名、密码等信息。保存脚本到目录4、在Obsidian的命令面板中置顶该脚本，今后就能通过CMD + P唤起命令面板后，快速发布文章。
最近看到Evernote裁员的消息，今天读到Nira博客这篇关于Evernote历史的文章，有些唏嘘。我从2012年到2020年一直在用Evernote，多次购买会员，算是见证了它的辉煌与没落。2012年的时候，笔记软件的选择不像现在这么多，但回忆起来，当时的Evernote也有非常多的闪光点，是我选择它（至少没有用OneNote）的原因：剪辑网页：保存整个网页副本，包括图片和排版，非常好用。多端同步：免费存储空间对文字记录为主的我，足够使用。移动端体验非常好，真正做到了随时随地快速记录。搜索功能非常强大：支持指定搜索语法，支持搜索word/pdf附件内容，搜索图片中的文字。强大的搜索大大降低了整理笔记的成本。除了这些喜欢的功能，一些博主围绕Evernote开发分享工作流，也是我折腾Evernote的重要原因，当时写Evernote教程的人很多，比如我关注了很久的电脑玩物。和现在很多人折腾Notion写教程是一样的。现在有些工具支持将Notion中的笔记直接发布为博客文章，其实Evernote当年也有（Postach.io）。曾经，围绕Evernote API，有很多好玩的工具，可玩性很高。开头提到的Nira博客的文章，对Evernote的衰落进行了分析，宏观来看有两个：不务正业：做Evernote Hello、Evernote Food，甚至做纸质笔记本。这点我的感触也很深，因为喜欢Evernote，所以它发布的各种新产品我都试用过，但用过之后就很怀疑人生，为啥要做这个，解决了啥问题？错过了云：文中强调的云是「多人协作」，Evernote的多人协作确实很晚才做，但我觉得这个因素没那么重要。很多用户只需要一个纯个人笔记，支持多端同步即可，协作并非刚需。Obsidian至今没有协作功能，不妨碍它发展的很好。Notion的用户中，应该也有大量用户只当个人笔记用，并不协作。在我看来，不务正业是主因。再看看Evernote本身的迭代，作为一款笔记软件，Evernote的编辑器被很多用户吐槽，很长时间不支持表格和标题，更别提Markdown了。不优化编辑体验却增加很多非核心功能（如素材、任务），把软件搞的非常臃肿，稳定性和速度越来越差——最基础的体验得到不到保障。在Evernote的左边栏和菜单栏变的越来越复杂，使用越来越卡顿，编辑器迟迟不支持Markdown的时候，我于2020年转向了Obsidian（期间短暂使用过MWeb），数据迁移的过程，挺痛苦的，总会有些遗漏或不完整，至今我的手机和电脑上仍安装着Evernote，有些笔记在Obsidian上找不到或内容不完整的时候，再去Evernote上找找看（一年也用不到几次）。我也保留了一份Evernote的HTML导出版本，哪怕Evernote彻底关闭了，需要时仍然有数据可查。从用户的角度看，我从Evernote上学到的是：不要把私人笔记存在唯一的云端不要把私人笔记存为某个应用的专用格式这是为什么我现在非常推崇Obsidian和Logseq一类软件，而不是Notion这样的软件，我怕Notion会是下一个Evernote。
今天介绍一个Alfred Workflow，能大幅提升Obsidian的操作效率，作者hauselin，项目地址：https://github.com/hauselin/obsidian-alfred以下根据我自己用到的功能做些介绍，基本覆盖常用功能，深入研究参考上述项目地址。安装首先，你需要安装好Alfred。然后，在这里下载最新的版本，双击Obsidian.alfredworkflow文件完成安装。完成后，Alfred的Workflows菜单中出现「Obsidian」，看下图密密麻麻的节点，就知道多强大了。设置使用前，需要完成一些设置，即在Alfred中执行相关命令，具体包括：添加vault：oaddvault添加日记所在的vault：odailyvault（要求已经安装日记插件并启用）设置日记标题格式：odailyformat datetime_format，比如odailyformat yymmdd~ddd，表示使用格式211207~Tue设置日记默认目录：odailypath设置日记的默认模板：odailytemplate第3步设置日记标题格式碰到一个问题：我的Obsidian是中文的，标题形如「211207~周二」，但Workflow生成的标题是英文，形如「211207~Tue」。在Issues中找到解决方案：在Alfred的Workflow设置界面，找到上图所示节点，双击修改其中的日期变量，支持months、months_short、days、days_short。我的需求，修改days_short即可。var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] var months_short = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] var days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', '星期四', 'Friday', 'Saturday'] var days_short = ['Sun', 'Mon', 'Tue', 'Wed', '周四', 'Fri', 'Sat']功能Workflow功能大多是命令式的，下面介绍常用命令。打开valut：oooo your_vault_name打开vault，vault名称支持自动补全。我只有一个vault，个人不常用。打开日记：od打开今天的日记，如果没有则自动创建。这是非常高频的功能，所以我添加了快捷键。找到下图所示节点，双击即可设置快捷键，全局有效，图中是我的快捷键。我将Caps换键映射为⌃⌥⇧⌘，所以Caps + T就能打开今日日记，非常方便。追加日记内容：odi在今天的日记后面追加内容，内容会被加工为无序列表，且在前面添加格式为yyyymmdd_hhmmss的时间。对日记内容，我有自己的格式，所以修改了时间变量格式及内容处理脚本，位于下图右侧这两个节点，我的脚本见文末附录。这个功能支持使用快捷键将剪切板的内容追加到日记中，上图左侧节点中设置，图中是我的快捷键。然后，当我阅读网页或文档时，有喜欢的段落，只需两步就能保存内容到日记中：Command + C复制选中内容Caps + C追加内容到日记整个过程不用离开阅读界面，太方便了。通知：onotify这个命令打开通知功能，当对笔记进行追加内容操作时，会提示内容添加成功。搜索：os、or、ot（或oss、orr、ott）前三个命令，搜索电脑中所有的markdown文件，如果是vault中的文件，则使用Obsidian打开，如果不是vault中的文件，则使用系统默认编辑器打开。三个命令的区别在于时间范围不同：os：搜索所有markdown文件or：搜索近3天修改过的markdown文件ot：搜索今天修改过的markdown文件oss、orr和ott三个命令类似，区别是只搜索vault中的markdown文件。使用时，输入命令后需要先回车，再输入搜索关键词。Alfred本身就有强大的全局搜索能力，所以全局搜索大可不必使用os、or、ot命令。在vault内的搜索才是高频需求，所以我给oss设置了快捷键，如下图，Caps + F实现vault中搜索。小结Workflow很强大，为减少记忆负担，日常中我就使用下面四个功能：打开今日日记：快捷键Caps + T追加文本到今日日记：命令odi your_text（一般是简短的内容，长内容会打开今日日记再写）将剪切板内容追加到今日日记：快捷键Caps + C在vault中搜索：快捷键Caps + F附录我的日记格式是时间做标题，然后接整段内容，如下图。修改后的脚本：/* cSpell:disable */ ObjC.import('stdlib'); console.log('Running script: oi') // get the current app to access the standard additions app = Application.currentApplication(); app.includeStandardAdditions = true; function interpolate_homepath(path) { homepath = app.pathTo('home folder') return path.replace(/^~/, homepath) } // get inbox note path var inbox_md = $.getenv('dailytodaypath'); inbox_md = interpolate_homepath(inbox_md) // where to add timestamp var inbox_timestamp = $.getenv('inbox_timestamp'); // get timestamp var timestamp = $.getenv('date'); // escape " var query = "{query}"; // query = query.replaceAll('"', '\"'); query = query.replace(/\"/g, '\"'); // github #25 @jamiew if (inbox_timestamp == "append") { query += (" " + timestamp); } else if (inbox_timestamp == "prepend") { query = "\n" + "## " + timestamp + "\n\n" + query; } // 把@NEWLISTITEM@替换为换行 //query = query.split("@NEWLISTITEM@") query = query.replace(/@NEWLISTITEM@/g, '\n\n') console.log("query: " + query) // function to read utf // https://github.com/JXA-Cookbook/JXA-Cookbook/issues/25 ObjC.import('Foundation') const readFile = function (path, encoding) { !encoding && (encoding = $.NSUTF8StringEncoding) const fm = $.NSFileManager.defaultManager const data = fm.contentsAtPath(path) const str = $.NSString.alloc.initWithDataEncoding(data, encoding) return ObjC.unwrap(str) } // read file to append to it var filetext = readFile(inbox_md); // remove newline characters while (filetext.endsWith("\n")) { filetext = filetext.slice(0, filetext.length - 1); } if (filetext == "") { console.log("EMPTY NOTE!") } else { filetext += "\n"; // leave one newline character } console.log("CURRENT TEXT (string)"); console.log(filetext) //var final_text = filetext + query; var final_text = filetext + query; console.log("FINAL TEXT") console.log(final_text) // write file str2write = $.NSString.alloc.initWithUTF8String(final_text); str2write.writeToFileAtomicallyEncodingError(inbox_md, true, $.NSUTF8StringEncoding, null); // notification var oinotify = $.getenv('oinotify'); if (oinotify == "on") { // https://developer.apple.com/library/archive/documentation/LanguagesUtilities/Conceptual/MacAutomationScriptingGuide/DisplayNotifications.html app.displayNotification("Added text to inbox", { withTitle: 'Obsidian Alfred workfow' }) }
我是重度笔记app用户，曾经较长时间使用过的app有印象笔记、OneNote、MWeb、甚至Word，浅尝辄止的数不清了。目前仍然有大量的数据在印象笔记和Word中没有整理出来。最近主力笔记是Obsidian，但经常纠结要不要切Notion，就用这篇文章结束纠结。一、我的笔记诉求我对笔记的核心诉求非常明确，三条：本地存储，或至少能导出数据本地存储或导出的数据格式是开放文本格式，以markdown为优，绝不接受私有数据格式方便发布博客，这里涉及两点：支持图床，发布时本地图片上传图床，并替换为图床URL，同时又不影响本地图片文件链接。支持博文链接，本地博文的互链，发布后能映射为博客的正式链接，同时不影响本地文件链接。二、Obsidian vs Notion前两条，Obsidian和Notion都满足，但Obsidian是本地存储markdown，Notion是云存储支持导出markdown，倾向于Obsidian。第3条，极少App能够满足，我用过很长时间的MWeb，方便发博客是唯一的理由。那段时间也在用印象笔记，二者各司其职。MWeb是本地文件存储，它的发布功能是调用MetaWeblog API实现的，Obsidian也是本地存储markdown，很自然的，我自己尝试用Python写了脚本实现发布功能。（Obsidian支持插件功能，我不会写js代码，否则直接用js写个插件发布博客，就更完美了）于是，三个核心诉求，在Obsidian上都得到了很好的满足。又为什么总对Notion念念不忘呢。一方面，上述三个核心诉求，Notion也都满足，数据能导出为markdown，官方提供API，网上有些开源方案实现博客发布功能。另一方面，它强大的database功能，看着真让人眼馋。我试用过无数次，非常清楚它的逻辑及其强大之处，网上很多教程，把database玩出花样了。甚至于，因为database的存在，Notion直到最近才支持普通表格。最近支持普通表格了但每次试用完，都被一些原因劝退。我对本地存储的偏好特别强烈，而Notion是云存储，并且它的服务位于国外，对大陆用户能否长期稳定使用没有信心现有的存量笔记迁移成本很高，不想迁移，如果有一天大陆无法访问Notion再折腾一次，想想都可怕。Notion最让我心动的database整理门槛非常高，我是个懒人，只想简单记录，然后需要时能被搜索到，就够用了。所以，就继续Obsidian吧，其实Obsidian的Dataview插件，一定程度上能代替Notion的database，但没怎么研究，也是嫌麻烦。三、结语大约去年6月开始使用Obsidian，当时还是非常早期的版本，眼看着Obsidian一步步成长，见证了插件功能的发布，ios和android版上线，接下来的大更新则是所见即所得编辑器。至此，Obsidian已经非常成熟了。另外，Obsidian社区非常活跃，社区+插件系统，有无限的想象空间，有潜力成为笔记界VSCode。工具适合即可，少折腾，多记录。