完全指南:从笔记生命周期到编辑器高亮的 22 个扩展点)
桌面应用【免费下载链接】QOwnNotesQOwnNotes 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 的脚本钩子Scripting Hooks体系当笔记被打开、保存、插入媒体、生成预览 HTML、加解密、打标签、切换布局等事件发生时QOwnNotes 脚本引擎会回调你在 QML 脚本中定义的特定函数。读完本文你将掌握全部 22 个钩子的触发时机、函数签名与参数含义、返回值约定并结合仓库源码与示例脚本学会编写属于自己的 QOwnNotes 扩展脚本。一、钩子机制是如何工作的QOwnNotes 的脚本使用 QMLQt Meta-object Language编写通过QtQml与QOwnNotesTypes命名空间加载。脚本引擎位于 src/services/scriptingservice.cpp所有钩子都通过同一套模式被调用引擎启动时加载所有已启用的脚本组件initComponents见 scriptingservice.cpp当某个事件发生时ScriptingService遍历所有ScriptComponent用methodExistsForObject()检查对象是否定义了对应签名的钩子函数如noteOpenedHook(QVariant)若存在则通过QMetaObject::invokeMethod()反射调用该函数并把返回值通过Q_RETURN_ARG(QVariant, ...)拿回每次调用都会用logScriptHookTiming()记录耗时便于排查性能瓶颈。例如noteOpenedHook的调用代码scriptingservice.cppif (methodExistsForObject(object, QStringLiteral(noteOpenedHook(QVariant)))) { QMetaObject::invokeMethod(object, noteOpenedHook, Q_ARG(QVariant, QVariant::fromValue(note))); logScriptHookTiming(scriptComponent.script.getName(), QStringLiteral(noteOpenedHook), timer.elapsed()); }值得注意的是QML 中参数类型NoteApi、TagApi等在 C 侧都归一化为QVariant传递因此钩子的实际 C 签名是诸如noteOpenedHook(QVariant)这样的规范化形式。通用的返回值约定绝大多数可改写钩子遵循同一条约定返回空字符串 / 空列表表示不做任何修改返回非空值则用返回值替换默认行为。多个脚本同时定义同一钩子时通常第一个返回非空字符串的脚本胜出见insertMediaHook、insertAttachmentHook、fetchUrlTitleHook的说明。二、笔记生命周期钩子onNoteStored笔记写入磁盘时在笔记被保存到磁盘后触发。你无法修改刚保存的笔记——因为用户很可能正同时用手编辑它强行修改会造成混乱。/** * This function is called when a note gets stored to disk * You cannot modify stored notes, that would be a mess since * you are most likely editing them by hand at the same time * * param {NoteApi} note - the note object of the stored note */ function onNoteStored(note);典型用法是在保存后记录笔记信息。参考示例 on-note-opened.qml 与 example.qml后者展示了读取note.id、note.name、note.fileName以及note.tagNames()function onNoteStored(note) { script.log(fileName was stored!); script.log(note.id); script.log(note.name); script.log(note.fileName); var tagNameList note.tagNames(); script.log(tagNameList); }C 侧在 scriptingservice.cpp 中通过onNoteStored(QVariant)签名检测并连接信号槽。noteOpenedHook笔记被打开后笔记被打开后触发常用于记录打开行为、统计或联动外部工具/** * This function is called after a note was opened * * param {NoteApi} note - the note object that was opened */ function noteOpenedHook(note);最小实现on-note-opened.qmlfunction noteOpenedHook(note) { script.log(note.fullNoteFilePath); }noteDoubleClickedHook笔记被双击后笔记被双击后触发常用来拦截双击行为并打开外部程序/** * This function is called after a note was double clicked * * param {NoteApi} note - the note object that was clicked */ function noteDoubleClickedHook(note);参考示例 external-note-open.qml。handleNoteNameHook改写笔记在列表中的显示名当 QOwnNotes 需要确定笔记名称时触发。笔记名称主要用于在列表中显示。返回空字符串表示不修改/** * This function is called when the note name is determined for a note * * It allows you to modify the name of the note that is viewed * * Return an empty string if the name of the note should not be modified * * param {NoteApi} note - the note object of the stored note * return {string} the name of the note */ function handleNoteNameHook(note);注意如果设置中启用了用文件名作为笔记名使用此钩子可能不是个好主意。从源码看noteindexmanager.cpp 会先通过ScriptingService::instance()-handleNoteNameHookExists()判断是否存在该钩子handleNoteNameHook(QVariant)签名检测见 scriptingservice.cpp再决定是否调用。handleNoteTextFileNameHook改写笔记文件名仅当设置中启用了Allow note file name to be different from headline允许笔记文件名与标题不同时笔记写入磁盘前会调用此钩子。它允许你修改笔记文件的名称但重复文件名需要你自己负责。返回空字符串表示不改动文件名/** * This function is called when a note gets stored to disk if * Allow note file name to be different from headline is enabled * in the settings * * It allows you to modify the name of the note file * Keep in mind that you have to care about duplicate names yourself! * * Return an empty string if the file name of the note should * not be modified * * param {NoteApi} note - the note object of the stored note * return {string} the file name of the note */ function handleNoteTextFileNameHook(note);经典实战把标签名拼进文件名use-tag-names-in-filename.qmlfunction handleNoteTextFileNameHook(note) { // the current note name is the fallback var fileName note.name; // get a list of note text lines var noteLines note.noteText.split(\n); // set the first line of the note as base for the file name if (noteLines.length 0) { // you maybe also want to exclude some disallowed characters here fileName noteLines[0]; } // get a list of all assigned tag names var tagNameList note.tagNames(); // add the tag names to the filename if (tagNameList.length 0) { fileName [ tagNameList.join(] [) ]; } script.log(note file name: fileName); var noteExists script.noteExistsByFileName(fileName .md, note.id); script.log(note (noteExists ? exists : doesnt exist)); return fileName; }C 侧实现位于 scriptingservice.cpp。handleNewNoteHeadlineHook新建笔记时改写标题在新建笔记之前触发允许你改写即将生成的笔记标题常用于实现笔记模板。注意你必须保证新笔记名唯一否则新笔记不会被创建只会出现在笔记列表中/** * This function is called before a note is created * * It allows you to modify the headline of the note before it is created * Note that you have to take care about a unique note name, otherwise * the new note will not be created, it will just be found in the note list * * You can use this function for creating note templates * * param headline text that would be used to create the headline * return {string} the headline of the note */ function handleNewNoteHeadlineHook(headline);示例 custom-new-note-headline.qml 为每个新笔记自动附加一个用符号绘制的下划线标题function handleNewNoteHeadlineHook(headline) { var text headline - my custom note name \n; var underline ; // add the underline for (var i 0; i (text.length - 1); i) { underline ; } return text underline; }三、笔记内容、媒体与预览钩子insertingFromMimeDataHook粘贴内容拦截当通过Ctrl Shift V将 HTML 或媒体文件粘贴到笔记时触发。你可以返回一个字符串来替换从QMimeData对象中取出的文本/** * This function is called when html or a media file is pasted to a note with Ctrl Shift V * * param text text of the QMimeData object * param html html of the QMimeData object * returns the string that should be inserted instead of the text from the QMimeData object */ function insertingFromMimeDataHook(text, html);参考示例example.qml、insert-headline-with-link-from-github-url.qml将 GitHub URL 转换为带标题的链接头、note-text-from-5pm-mail.qml。C 调用位于 scriptingservice.cpp。insertMediaHook插入媒体文件时改写 Markdown当媒体文件被插入当前笔记时触发。若在多个脚本中定义第一个返回非空字符串的脚本胜出/** * param fileName string the file path of the source media file before it was copied to the media folder * param markdownText string the markdown text of the media file, e.g. my-image * return string the new markdown text of the media file */ function insertMediaHook(fileName, markdownText);markdownText形如my-image返回值将替换默认插入的 Markdown 文本。参考 example.qml。insertAttachmentHook插入附件时改写 Markdown与insertMediaHook平行针对附件文件/** * param fileName string the file path of the source attachment file before it was copied to the attachment folder * param markdownText string the markdown text of the attachment file, e.g. my-file.txt * return string the new markdown text of the attachment file */ function insertAttachmentHook(fileName, markdownText);markdownText形如my-file.txt。同样遵循首个非空返回值胜出规则。C 调用位于 scriptingservice.cpp。fetchUrlTitleHook接管链接标题抓取在链接对话框中、QOwnNotes 尝试从 URL 抓取网页标题之前调用。若多个脚本都定义了此函数第一个返回非空字符串的脚本胜出/** * param url string the URL whose title should be fetched * return string the title for the URL, or an empty string to let QOwnNotes fetch it */ function fetchUrlTitleHook(url);典型应用是用外部 CLI 工具抓取标题例如 fetch-url-title-with-kanboard-cli.qml。preNoteToMarkdownHtmlHook转换前改写 Markdown在笔记的 Markdown HTML生成之前触发允许你修改将要传给Markdown 转 HTML转换器的内容。典型用途在多个脚本中把代码块如 LaTeX 数学公式或 Mermaid 图表转换为预览所需的图形表示。此过程不会修改笔记本身/** * This function is called before the markdown html of a note is generated * * It allows you to modify what is passed to the markdown to html converter * * The function can for example be used in multiple scripts to render code (like LaTeX math or mermaid) * to its graphical representation for the preview * * The note will not be changed in this process * * param {NoteApi} note - the note object * param {string} markdown - the markdown that is about to being converted to html * param {bool} forExport - true if the html is used for an export, false for the preview * return {string} the modified markdown or an empty string if nothing should be modified */ function preNoteToMarkdownHtmlHook(note, markdown, forExport);forExport参数用于区分预览false与导出true两种场景。参考示例 preview-styling.qml。C 侧同时兼容新旧两种签名preNoteToMarkdownHtmlHook(note, markdown)与带forExport的三参数版本见 scriptingservice.cpp。noteToMarkdownHtmlHook改写最终预览 HTML在笔记的 Markdown HTML 生成后触发允许你直接修改即将渲染的 HTML。例如笔记预览在渲染前会调用此钩子。可结合多个脚本共同修改预览 HTML/** * This function is called when the markdown html of a note is generated * * It allows you to modify this html * This is for example called before by the note preview * * The function can be used in multiple scripts to modify the html of the preview * * param {NoteApi} note - the note object * param {string} html - the html that is about to being rendered * param {bool} forExport - true if the html is used for an export, false for the preview * return {string} the modified html or an empty string if nothing should be modified */ function noteToMarkdownHtmlHook(note, html, forExport);例如 example.qml 在预览顶部插入自定义标题和分隔线function noteToMarkdownHtmlHook(note, html, forExport) { // show a custom headline and a horizontal ruler on top of the preview return html.replace(body, bodyh1Custom preview for note note.name /h1hr /); }提示可用的 CSS 样式受 Qt 渲染的Supported HTML Subset限制编写自定义样式前建议先查阅该子集文档中列出的受支持样式。C 实现同样兼容两参数/三参数两种签名见 scriptingservice.cpp。四、加密钩子encryptionHook自定义笔记加密当文本需要加密或解密时触发。QOwnNotes 内置加密之外你可以通过此钩子接入任意加密方案如 Keybase、PGP、ROT13/** * This function is called when text has to be encrypted or decrypted * * param text string the text to encrypt or decrypt * param password string the password * param decrypt bool if false encryption is demanded, if true decryption is demanded * return the encrypted decrypted text */ function encryptionHook(text, password, decrypt);完整可运行的 ROT13 示例encryption-rot13.qml——注意它同时用script.encryptionDisablePassword()禁用密码对话框function init() { // disable the password dialog script.encryptionDisablePassword(); } function encryptionHook(text, password, decrypt) { // do the rot13 transformation text text.replace(/[a-zA-Z]/g, function(c){ return String.fromCharCode((c Z ? 90 : 122) (c c.charCodeAt(0) 13) ? c : c - 26); }); return text; }更多参考encryption-keybase.qml、encryption-pgp.qml。C 调用位于 scriptingservice.cpp。五、标签系统钩子noteTaggingHook自定义笔记打标签机制你可以实现自己的打标签机制例如用笔记中的特殊文本tag1、tag2、tag3充当标签。一旦某脚本实现了noteTaggingHookQOwnNotes 的标签处理就会移交给该函数/** * Handles note tagging for a note * * This function is called when tags are added to, removed from or renamed in * a note or the tags of a note should be listed * * param note * param action can be add, remove, rename or list * param tagName tag name to be added, removed or renamed * param newTagName tag name to be renamed to if action rename * return note text string or string-list of tag names (if action list) */ function noteTaggingHook(note, action, tagName, newTagName);action的四种取值对应四类操作返回值语义如下action触发时机返回值add给笔记添加标签修改后的笔记文本空串表示无变化remove从笔记移除标签修改后的笔记文本空串表示无变化rename重命名笔记中的标签修改后的笔记文本空串表示无变化list列出笔记的标签标签名字符串列表启用后通过 QOwnNotes 界面完成的以下操作都会由该函数接管初始导入笔记中类似tag的标签并覆盖当前的标签分配——你不会丢失标签树只是丢失原先对笔记的分配仍可把标签移入其他标签若标签树中存在同名标签则分配第一个命中的标签向笔记添加标签 → 把标签写入笔记文本从笔记移除标签 → 从笔记文本删除该标签在标签列表中删除/重命名标签 → 同步删除/重命名笔记文本中的标签在笔记列表中批量打标签/批量移除标签 → 同步写入/移除笔记文本在标签面板中移动标签时应用会对所有选中标签及其子标签在所有笔记上触发一系列add和remove操作。完整的自定义实现见 note-tagging.qml其核心逻辑是维护noteText中tagName形式的文本function noteTaggingHook(note, action, tagName, newTagName) { var noteText note.noteText; var tagRegExp RegExp(\\B escapeRegExp(tagName) \\b); switch (action) { case add: if (noteText.search(tagRegExp) 0) { return ; } noteText \n tagName; return noteText; break; case remove: noteText noteText.replace(tagRegExp, ); return noteText; break; case rename: noteText noteText.replace(tagRegExp, newTagName); return noteText; break; case list: var re new RegExp(\\B([^\\s,]), gi), result, tagNameList []; while ((result re.exec(noteText)) ! null) { var tagName result[1]; if (tagNameList.indexOf(tagName) -1) { tagNameList.push(tagName); } } return tagNameList; break; } return ; }性能警告务必保证list动作足够快——每当笔记文件夹或标签树被重载时list都会对每一篇笔记执行一次。换句话说每次你在 mainwindow.cpp 中搜索到reloadTagTree时它都会被触发。C 侧在 tagmanager.cpp 中通过ScriptingService::instance()-noteTaggingHookExists()判断是否启用脚本引擎接管见 tagmanager.cpp并在多处调用noteTaggingHook(QVariant,QVariant,QVariant,QVariant)。noteTaggingByObjectHook基于标签对象的打标签与noteTaggingHook类似但不再局限于标签树根部的标签名可以使用整棵标签树。区别在于参数不再是tagName字符串而是一个TagApi对象list动作的返回值是标签 ID 列表而非标签名列表。这也意味着你需要自行创建缺失的标签才能在list时提供已存在标签的 ID 列表/** * Handles note tagging for a note * * This function is called when tags are added to, removed from or renamed in * a note or the tags of a note should be listed * * param note * param action can be add, remove, rename or list * param tag to be added, removed or renamed * param newTagName tag name to be renamed to if action rename * return note text string or string-list of tag ids (if action list) */ function noteTaggingByObjectHook(note, action, tag, newTagName);参考实现 note-tagging-by-object.qml它通过tag.name读写笔记文本在list时用script.getTagByNameBreadcrumbList()和tag.fetchByName()把标签名解析为 ID。noteTaggingHookExists()同时检测两种钩子的签名见 scriptingservice.cpptagmanager.cpp 会先尝试noteTaggingHook无结果再尝试noteTaggingByObjectHook。六、编辑器与界面钩子autocompletionHook向自动补全列表注入条目当自动补全被唤起时例如按Ctrl Space返回一个字符串列表这些字符串会被加入补全列表/** * Calls the autocompletionHook function for all script components * This function is called when autocompletion is invoked in a note * * return QStringList of text for the autocomplete list */ function callAutocompletionHook();示例 autocompletion.qml 实现输入时按标签名补全function autocompletionHook() { // get the current word plus non-word-characters before the word to also get the -character var word script.noteTextEditCurrentWord(true); if (!word.startsWith()) { return []; } // cut the off of the string and do a substring search for tags var tags script.searchTagsByName(word.substr(1)); return tags; }C 侧实现位于 scriptingservice.cpp。highlightingHook动态、上下文感知的高亮语法高亮期间编辑器的每个文本块都会调用此钩子用于实现超越静态正则规则addHighlightingRule的动态、上下文感知高亮。返回一个高亮区间对象数组/** * This function is called for each text block during syntax highlighting. * It allows context-aware, dynamic highlighting. * * param text {QString} the text of the current block being highlighted * param previousBlockState {int} the highlighter state of the previous block * (-1 if this is the first block) * return {Array} an array of highlight range objects, each with: * start {int} - start position in the text * length {int} - number of characters to highlight * state {int} - the HighlighterState to use (optional, -1 for custom only) * foregroundColor {string} - foreground color name or hex (optional) * backgroundColor {string} - background color name or hex (optional) * bold {bool} - bold font weight (optional) * italic {bool} - italic font style (optional) * underline {bool} - underline the text (optional) * fontSize {int} - font point size (optional) */ function highlightingHook(text, previousBlockState) { var highlights []; // Example: highlight all occurrences of FIXME with red bold underline var re /FIXME/g; var match; while ((match re.exec(text)) ! null) { highlights.push({ start: match.index, length: match[0].length, foregroundColor: #ff0000, underline: true, bold: true, }); } return highlights; }性能警告此钩子调用极其频繁——每次按键、每个可见文本块都会触发因此实现必须保持高效。如果只需要静态正则高亮应优先使用addHighlightingRule包括带自定义颜色的版本。从源码看QOwnNotes 对此做了专门优化ScriptingService在initComponents()中缓存_highlightingHookExists标志见 scriptingservice.cpp只有至少一个脚本提供该钩子时才发起逐块 QML 调用高亮器通过highlightScriptingHook()把每个文本块交给脚本并应用返回的区间见 qownnotesmarkdownhighlighter.cpp。可参考示例 highlighting.qml含addHighlightingRule静态规则与highlightingHook动态高亮的对比与 custom-highlighting.qml。windowStateChangedHook窗口状态变化通知每次WindowStateChange事件触发后调用/** * This function is called after a WindowStateChange event was triggered * * param {QString} windowState - the new window state, parameter value can be minimized, maximized, fullscreen, active or nostate */ function windowStateChangedHook(windowState);windowState取值可为minimized、maximized、fullscreen、active或nostate。示例 window-state-changed.qmlfunction windowStateChangedHook(windowState) { script.log(Window state changed: windowState); }layoutSwitchedHook布局切换通知当 QOwnNotes 的布局被切换时调用/** * This function is called when layouts are switched * * param oldUuid old uuid of layout * param newUuid new uuid of layout */ function layoutSwitchedHook(oldUuid, newUuid);两个参数分别是旧布局与新布局的 UUID可用于保存/恢复与布局关联的脚本状态。参考示例 layouts.qml。C 调用位于 scriptingservice.cpp。七、外部进程与 Web 集成钩子onDetachedProcessCallback后台进程完成回调当 startDetachedProcess 启动的脚本线程执行完毕时触发。thread[1] 0可以帮助判断某一标识符下启动的一批进程是否全部完成/** * This function is called when a script thread is done executing. * Hint: thread[1]0 helps to determine if a bulk of started processes for a certain identifier is done. * * param {QString} callbackIdentifier - the provided id when calling startDetachedProcess() * param {QString} resultSet - the result of the process * param {QVariantList} cmd - the entire command array [0-executablePath, 1-parameters, 2-exitCode] * param {QVariantList} thread - the thread information array [0-passed callbackParameter, 1-remaining threads for this identifier] */ function onDetachedProcessCallback(callbackIdentifier, resultSet, cmd, thread);参数结构说明cmd数组[0]可执行文件路径、[1]参数、[2]退出码thread数组[0]传入的callbackParameter、[1]该标识符剩余的线程数。示例 callback.qml 用startDetachedProcess并发启动 100 个sleep进程并在每个回调里打印剩余线程数function init() { for (var i 0; i 100; i) { var dur Math.floor(Math.random() * 10) 1; script.startDetachedProcess(sleep, [${dur}s], callback-example, i); } } function onDetachedProcessCallback(callBackIdentifier, resultSet, cmd, thread) { if (callBackIdentifier callback-example) { log(#${thread[1]} i[${thread[0]}] t${cmd[1]}); } }C 侧实现位于 scriptingservice.cpp。websocketRawDataHook接收浏览器扩展数据当 QOwnNotes Web Companion 浏览器扩展通过浏览器右键菜单发送数据时调用。返回true表示数据已被钩子处理/** * param requestType can be page or selection * param pageUrl the url of the webpage where the request was made * param pageTitle the page title of the webpage where the request was made * param rawData the data that was transmitted, html for requestType page or plain text for requestType selection * param screenshotDataUrl the data url of the screenshot of the webpage where the request was made * return true if data was handled by a hook */ function callHandleWebsocketRawDataHook(requestType, pageUrl, pageTitle, rawData, screenshotDataUrl);参数说明requestTypepage发送整页 HTML或selection发送选中文本pageUrl/pageTitle来源网页的 URL 与标题rawData传输的数据page时为 HTMLselection时为纯文本screenshotDataUrl网页截图的数据 URL。示例 websocket-raw-data-selection-in-note.qml 把网页选中的文本直接写入当前笔记function websocketRawDataHook(requestType, pageUrl, pageTitle, rawData, screenshotDataUrl) { // we only want to handle selection requests, page requests are ignored if (requestType ! selection) { return false; } // write selected text from the page to the current note script.noteTextEditWrite(rawData); return true; }C 侧在 scriptingservice.cpp 中遍历脚本组件调用websocketRawDataHook(QVariant,QVariant,QVariant,QVariant,QVariant)一旦某个脚本返回true立即终止遍历该调用由 websocketserverservice.cpp 触发。另可参考 layouts.qml。八、AI 后端钩子openAiBackendsHook注册自定义 OpenAI 兼容后端当 OpenAI 服务配置被重载时调用例如脚本引擎重载时也会触发。你可以利用它为自定义 OpenAI API 兼容后端提供配置例如自建的 ChatGPT、Claude 或 Ollama 等系统/** * This function is called when the OpenAI service config is reloaded * It returns a list of objects with config parameters for new OpenAI backends */ function openAiBackendsHook() { return [ { id: my-custom-ai, name: My Custom AI, baseUrl: http://localhost:5000, apiKey: kDFJkjk3asdm, models: [gpt-3.5-turbo, gpt-4.0-turbo], }, { id: my-custom-ai2, name: My Custom AI 2, baseUrl: http://localhost:5001, apiKey: lOikf7eNdb9, models: [gpt-3.5-turbo2, gpt-4.0-turbo2], }, ]; }每个后端对象包含四个配置字段字段说明id后端唯一标识name界面中显示的名称baseUrlAPI 端点地址apiKeyAPI 密钥models可用模型列表提示可以使用脚本设置变量Registering script settings variables见 webpage/src/scripting/methods-and-objects.md把 OpenAI 后端设置保存到脚本设置中实现免编辑脚本的配置管理。完整示例见 custom-openai-backends.qml它还演示了接入真实第三方服务如 Groq 的https://api.groq.com/openai/v1/chat/completions与llama3-70b-8192模型的写法。C 调用位于 scriptingservice.cpp。九、钩子编写的通用规范与调试建议综合以上全部钩子编写 QOwnNotes 脚本时请遵循以下实践返回值约定凡是可改写钩子如insertMediaHook、handleNoteNameHook、preNoteToMarkdownHtmlHook、noteTaggingHook等返回空字符串/空列表表示不做修改websocketRawDataHook则返回布尔值表示是否已处理。多脚本冲突insertMediaHook、insertAttachmentHook、fetchUrlTitleHook遵循第一个返回非空字符串的脚本胜出highlightingHook会汇总所有脚本的返回区间。性能意识highlightingHook每次按键每个可见块都会被调用noteTaggingHook的list动作会在每次重载笔记文件夹/标签树时对每篇笔记执行。务必保持这两类钩子的实现轻量能用addHighlightingRule静态规则解决的不要用highlightingHook。签名即契约QOwnNotes 通过 QML 方法的规范化签名如noteOpenedHook(QVariant)来检测钩子是否存在因此函数名和参数个数必须与文档完全一致多参数版本如带forExport与旧版兼容。调试手段脚本中用script.log()输出日志QOwnNotes 的脚本日志面板会记录各钩子的调用耗时logScriptHookTiming可据此定位性能热点。脚本骨架每个脚本以import QtQml 2.0和import QOwnNotesTypes 1.0开头主体是一个QtObject或Script对象内部定义钩子函数参见上述所有示例。本文涉及的全部示例脚本均位于 docs/scripting/examples 目录钩子的 C 调用实现集中在 src/services/scriptingservice.cpp高亮钩子与标签钩子的消费方分别在 src/helpers/qownnotesmarkdownhighlighter.cpp 与 src/managers/tagmanager.cpp可作为深入学习的起点。赞分享桌面应用【免费下载链接】QOwnNotesQOwnNotes 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 脚本 Hooks 完全指南从笔记生命周期到动态高亮与自定义 AI 后端的 21 个挂钩点QOwnNotes 脚本 Hooks 完全指南从笔记生命周期到动态高亮与自定义 AI 后端的 21 个挂钩点 QOwnNotes 内置的 QML 脚本引擎为桌面应用QOwnNotes 脚本钩子Hook体系全解析从笔记生命周期到自定义高亮与 AI 后端QOwnNotes 脚本钩子Hook体系全解析从笔记生命周期到自定义高亮与 AI 后端 QOwnNotes 的脚本系统以 JavaScriptQML 环桌面应用QOwnNotes 脚本 Hook 全指南深入掌握笔记生命周期、编辑器、标签与 AI 后端的全部扩展点QOwnNotes 脚本 Hook 全指南深入掌握笔记生命周期、编辑器、标签与 AI 后端的全部扩展点 导读 本文围绕 QOwnNotes 脚本系统中最核心的桌面应用创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考