☰
QOwnNotes 脚本 API 公开类完全指南:Note、NoteSubFolder、Tag 与 MainWindow 的属性和方法详解
2026/10/12 2:17:36 网站建设 项目流程
  • 桌面应用

【免费下载链接】QOwnNotes

QOwnNotes is a plain-text file notepad and todo-list manager with Markdown support and Nextcloud / ownCloud integration.

项目地址:https://gitcode.com/gh_mirrors/qo/QOwnNotes
点击查看免费下载

QOwnNotes 内置 QML/JavaScript 脚本引擎,通过QOwnNotesTypes 1.0模块向脚本暴露了一组核心对象。本文基于官方脚本文档中"公开类"(Exposed classes)一章,逐类讲解Note(笔记)、NoteSubFolder(笔记子文件夹)、Tag(标签)与MainWindow(主窗口)四类对象的全部属性与方法,并结合仓库源码(src/api/noteapi.h、src/api/notesubfolderapi.h、src/api/tagapi.h、src/mainwindow.h 等)揭示其底层实现原理。读完本文,你将能够熟练地在自己的脚本中遍历笔记与标签、操作笔记子文件夹、控制主窗口布局与标签页,并写出可直接运行的 QML 脚本。

前置知识:脚本如何访问这些对象

在动手编写脚本之前,需要理解这些对象是如何进入脚本运行环境的:

  • 引擎通过QQmlEngine::rootContext()把全局对象注入脚本上下文,其中script指向ScriptingService,mainWindow指向主窗口实例,见 src/services/scriptingservice.cpp;
  • 四个核心类型通过qmlRegisterType注册到QOwnNotesTypes模块,脚本顶部必须写import QOwnNotesTypes 1.0才能使用它们,见 src/services/scriptingservice.cpp;
  • 旧版还保留了com.qownnotes.noteapi与com.qownnotes.tagapi两个已弃用的别名注册,新脚本不应再使用。

从源码结构看,NoteApi、NoteSubFolderApi、TagApi均继承自各自的实体类(entities/note.h、entities/notesubfolder.h、entities/tag.h)并通过Q_PROPERTY与Q_INVOKABLE宏把内部数据暴露给 QML/JavaScript,因此脚本中访问属性就像访问普通 JS 对象属性一样简单。

Note:笔记对象

Note(C++ 类名NoteApi)代表一条笔记,是脚本中最常用的对象。它的属性描述了一条笔记在磁盘与数据库中的完整状态。

属性一览

属性类型含义是否只读
idint笔记在内部数据库中的唯一编号只读(CONSTANT)
nameQString笔记名称(通常是标题)可写(WRITE setName)
fileNameQString笔记文件名(含扩展名)只读
fullNoteFilePathQString笔记文件的完整绝对路径只读
fullNoteFileDirPathQString笔记文件所在目录的完整路径只读
relativeNoteFileDirPathQString相对当前笔记文件夹的目录路径只读
noteSubFolderIdint笔记所属子文件夹的 id只读
noteTextQString笔记文本内容(未解密)可写
decryptedNoteTextQString解密后的笔记文本可写
hasDirtyDatabool是否有未保存的脏数据只读
tags对象列表笔记关联的所有 TagApi 对象(QQmlListProperty<TagApi>)只读
fileCreatedQDateTime笔记文件创建时间只读
fileLastModifiedQDateTime笔记文件最后修改时间只读

以上声明均可在 src/api/noteapi.h 中核对。值得注意的是name和noteText是可写的,脚本可以直接给它们赋值来修改笔记名称与内容。

方法与使用示例

Note还提供了一系列可调用方法(均在 src/api/noteapi.h 声明):

方法签名作用
QStringList tagNames()返回笔记关联的所有标签名称
bool addTag(QString tagName)为笔记添加标签(不存在则自动创建)
bool removeTag(QString tagName)从笔记移除标签
bool renameNoteFile(QString newName)重命名笔记文件(newName 不含扩展名)
QString toMarkdownHtml(bool forExport = true)把笔记转成 Markdown 渲染后的 HTML,forExport为 true 时生成导出用 HTML
QString getFileURLFromFileName(QString localFileName)由相对文件名得到文件的绝对 URL
bool allowDifferentFileName()检查是否允许文件名与标题不同
QString getNoteUrlForLinkingToNoteId(int noteId)返回用于链接到指定 noteId 笔记的 Markdown 笔记 URL

结合源码实现可以补充几个关键细节:

  • addTag的幂等行为:它先调用Tag::fetchByName(tagName)查找标签,若不存在则tag.setName(tagName); tag.store()新建标签,再通过tag.linkToNote(note)建立关联,见 src/api/noteapi.cpp;
  • renameNoteFile的主窗口联动:重命名成功后会检查当前打开笔记是否为该笔记,若是则重新拉取并刷新主窗口,避免界面上残留旧文件名的"幽灵笔记",见 src/api/noteapi.cpp;
  • decryptedNoteText的延迟读取:源码注释说明,每次当前笔记变化时不会主动重新解密文本,而是按需读取,见 src/api/noteapi.cpp。
日期属性处理

fileCreated和fileLastModified是 QDateTime 类型,在脚本里可以直接使用 JavaScript 标准Date对象的方法,例如toISOString()、getFullYear()。如需按自定义格式输出,可直接使用 QML 引擎内置的Qt.formatDateTime/Qt.formatDate/Qt.formatTime,占位符规则与 Qt 一致(如yyyy-MM-dd HH:mm,单引号内的文本不作占位符解释),完整说明参见日期与时间格式化。

完整示例
script.log(note.fileCreated.toISOString()); script.log(note.fileLastModified.getFullYear()); // 将笔记文件名重命名为 "new name.md" note.renameNoteFile("new name"); // 检查是否允许笔记文件名与标题不同 script.log(note.allowDifferentFileName());

NoteSubFolder:笔记子文件夹对象

NoteSubFolder(C++ 类名NoteSubFolderApi)表示笔记文件夹体系中的一个子文件夹。它的属性与静态方法如下(见 src/api/notesubfolderapi.h):

属性 / 方法类型 / 签名说明
idint子文件夹 id
nameQString子文件夹名称
notes对象列表该子文件夹下的全部笔记(QQmlListProperty<NoteApi>)
fetchNoteSubFolderById(int id)静态方法按 id 获取子文件夹对象
activeNoteSubFolder()静态方法获取当前活动的子文件夹
fetchNoteSubFoldersByParentId(int parentId)静态方法获取指定父 id 下的全部子文件夹
relativePath()QString相对当前笔记文件夹的路径
fullPath()QString子文件夹的完整磁盘路径

源码层面的补充事实:

  • activeNoteSubFolder的实现:内部调用fetchNoteSubFolderById(NoteSubFolder::activeNoteSubFolderId()),即先取得当前活动子文件夹 id 再封装成 API 对象,见 src/api/notesubfolderapi.cpp;
  • fetchNoteSubFoldersByParentId会跳过排除目录:遍历子文件夹时会调用NoteFolder::isCurrentSubfolderPathExcluded()检查,被排除的子文件夹不会出现在结果中,见 src/api/notesubfolderapi.cpp;
  • relativePath的递归拼接逻辑:父 id 为 0 时返回自身名称,否则递归拼接父级路径与自身名称,见 src/entities/notesubfolder.cpp。

完整示例

// 在脚本中动态创建一个 NoteSubFolder 对象 var noteSubFolderQmlObj = Qt.createQmlObject( "import QOwnNotesTypes 1.0; NoteSubFolder{}", mainWindow, "noteSubFolder", ); // 打印指定父文件夹下的所有子文件夹名称 noteSubFolderQmlObj .fetchNoteSubFoldersByParentId(parentId) .forEach(function (nsf) { script.log(nsf.name); }); // 获取当前活动的子文件夹 var noteSubFolder = noteSubFolderQmlObj.activeNoteSubFolder(); // 打印当前活动子文件夹的完整路径与相对路径 script.log(noteSubFolder.fullPath()); script.log(noteSubFolder.relativePath()); script.log(noteSubFolder.id); script.log(noteSubFolder.name); // 遍历子文件夹中的所有笔记 for (var idx in noteSubFolder.notes) { var note = noteSubFolder.notes[idx]; }

Tag:标签对象

Tag(C++ 类名TagApi)代表一个笔记标签,支持多级(父子)标签体系。其属性与方法声明见 src/api/tagapi.h:

属性 / 方法类型 / 签名说明
idint标签 id
nameQString标签名称
parentIdint父标签 id(0 表示顶级标签)
notes对象列表关联到此标签的全部笔记(QQmlListProperty<NoteApi>)
fetchByName(QString name, int parentId = 0)可调用方法按名称与父 id 查找标签
getParentTagNames()QStringList返回该标签所有父级标签的名称列表

实现层面补充两点:

  • notes属性内部调用tag.fetchAllLinkedNotes()拉取关联笔记,并把每个实体Note封装成NoteApi对象,见 src/api/tagapi.cpp;
  • fetchByName与fetch(int)一样会回填id、name、parentId等内部字段,未找到时各字段保持默认值,见 src/api/tagapi.cpp。

完整示例

// 脚本顶部务必写 "import QOwnNotesTypes 1.0"! // 通过"面包屑列表"获取标签 "home" var tag = script.getTagByNameBreadcrumbList(["home"]); // 获取所有打了该标签的笔记 var notes = tag.notes; // 遍历该标签下的所有笔记 for (var idx in notes) { var note = notes[idx]; script.log(note.name); }

这里用到的script.getTagByNameBreadcrumbList是ScriptingService提供的方法:它接收一个从顶层到目标层的标签名数组(nameList[0]是树中最顶层的标签,parentId为 0),默认会自动创建缺失的中间标签,最终返回最深层标签的TagApi对象,见 src/services/scriptingservice.cpp。

关于TagApi的更多实战用法,可参考官方示例脚本 note-tagging-by-object.qml:它通过noteTaggingByObjectHook钩子实现"在笔记正文中以@tag形式打标签",其中list动作会遍历正文匹配\B@([^\s,]+)正则、用tag.fetchByName(tagName)逐个解析并返回标签 id 列表,对理解TagApi的"按名查找"用法很有帮助。

MainWindow:主窗口对象

MainWindow对象把主窗口的若干能力暴露给脚本,用于控制界面刷新、布局、标签页与标签树。完整方法列表见 src/mainwindow.h:

方法签名作用
void reloadTagTree()重新加载标签树
void reloadNoteSubFolderTree()重新加载笔记子文件夹树
void buildNotesIndexAndLoadNoteDirectoryList(bool forceBuild = false, bool forceLoad = false)重建笔记索引并加载笔记目录列表;forceBuild/forceLoad为 true 时强制重建/强制加载
void focusNoteTextEdit()聚焦笔记文本编辑器
bool createNewNoteSubFolder(QString folderName = "")在当前子文件夹中创建新子文件夹;folderName为空时弹出输入对话框
void insertHtmlAsMarkdownIntoCurrentNote(QString html)把 HTML 作为 Markdown 插入当前笔记,同时会下载远程图片并把data:imageURL 转换为本地媒体目录中的图片
void reloadCurrentNoteByNoteId()按 id 重新加载当前笔记;当当前笔记的路径或文件名发生变化时很有用
QStringList getLayoutUuidList()返回所有布局的 UUID 列表
QString getLayoutUuid(QString layoutName)根据布局名称返回其 UUID
void setCurrentLayout(QString uuid)按 UUID 设置当前布局
bool removeNoteTab(int index)关闭指定索引的笔记标签页(成功返回 true)
QList<int> getNoteTabNoteIdList()返回所有已打开标签页中的笔记 id 列表
bool jumpToTag(int tagId)在标签树中跳转到指定标签

源码层面的补充事实:

  • getLayoutUuid/setCurrentLayout只是把调用转发给_layoutManager(LayoutManager),分别对应 src/mainwindow.cpp 与 src/mainwindow.cpp;
  • buildNotesIndexAndLoadNoteDirectoryList把工作委托给_noteIndexManager(NoteIndexManager),见 src/mainwindow.cpp;
  • removeNoteTab直接调用_noteTabManager->removeNoteTab(index),见 src/mainwindow.cpp;
  • jumpToTag转发给_tagManager->jumpToTag(tagId),见 src/mainwindow.cpp;
  • createNewNoteSubFolder在folderName为空时用QInputDialog::getText弹出文件夹名输入框,见 src/mainwindow.cpp;
  • 布局相关的旧方法getWorkspaceUuid、getWorkspaceUuidList、setCurrentWorkspace仍然保留,只是作为兼容别名的内联转发,见 src/mainwindow.h。

完整示例

// 强制重新加载笔记列表 mainWindow.buildNotesIndexAndLoadNoteDirectoryList(true, true); // 在当前子文件夹中创建名为 "My fancy folder" 的新子文件夹 mainWindow.createNewNoteSubFolder("My fancy folder"); // 把 HTML 作为 Markdown 插入当前笔记 mainWindow.insertHtmlAsMarkdownIntoCurrentNote( "<h2>my headline</h2>some text" ); // 将当前布局切换为 'Edit' 布局 mainWindow.setCurrentLayout(mainWindow.getLayoutUuid("Edit")); // 跳转到标签树中的 "test" 标签 var tag = script.getTagByNameBreadcrumbList(["test"]); mainWindow.jumpToTag(tag.id); // 遍历所有已打开标签页中的笔记 var noteIds = mainWindow.getNoteTabNoteIdList(); noteIds.forEach(function (noteId) { var note = script.fetchNoteById(noteId); // 对笔记做点什么 });

其中script.fetchNoteById(noteId)同样由ScriptingService提供:它通过new NoteApi()并调用fetch(id)返回封装好的笔记对象,见 src/services/scriptingservice.cpp。

综合实战建议

把上述四类对象组合使用,可以写出非常有价值的自动化脚本。下面给出两个可以直接运行在 QOwnNotes 脚本编辑器中的综合思路:

场景一:批量整理标签。通过script.getTagByNameBreadcrumbList(["待整理"])拿到Tag对象,遍历其notes,对每条笔记调用note.tagNames()查看现有标签,再用note.addTag("归档")与note.removeTag("旧标签")完成批量迁移;标签的添加/移除内部会走数据库层面的linkToNote/removeLinkToNote,保证与界面上手动操作的结果一致。

场景二:按子文件夹批量导出。用mainWindow的createNewNoteSubFolder创建归档目录,用NoteSubFolder的静态方法枚举子文件夹与笔记,最后对每条笔记调用note.toMarkdownHtml(true)生成导出 HTML;toMarkdownHtml内部以NoteFolder::currentLocalPath()为基准、宽度 980 渲染(见 src/api/noteapi.cpp),与界面导出的结果保持一致。

编写脚本时注意三点:一是每个脚本顶部必须import QOwnNotesTypes 1.0;二是script.log()用于输出调试信息,可在脚本日志面板查看;三是旧版com.qownnotes.noteapi/com.qownnotes.tagapi模块名已弃用,新脚本一律使用QOwnNotesTypes下的Note、NoteSubFolder、Tag。官方还提供了大量示例脚本(见 docs/scripting/examples),可作为进阶参考。

  • 桌面应用

【免费下载链接】QOwnNotes

QOwnNotes is a plain-text file notepad and todo-list manager with Markdown support and Nextcloud / ownCloud integration.

项目地址:https://gitcode.com/gh_mirrors/qo/QOwnNotes
点击查看免费下载
上一篇:Logto 小米社交连接器深度解析:从版本演进到 OAuth 2.0 授权实现与自定义 Scope 能力
下一篇:easy-vibe 前端基础:Canvas 图形与动画实战指南——从第一根线条到 60 FPS 粒子引擎

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询