QOwnNotes 脚本钩子(Hooks)完全指南:从笔记生命周期到编辑器高亮的 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),仅供参考

相关新闻

Python PyQt6金价监控气泡工具开发实战

Python PyQt6金价监控气泡工具开发实战

简介:这是一款面向Windows桌面用户的轻量级金价监控小工具,专为关注黄金价格变动的投资者、理财爱好者及日常需要快速查看金价的办公人群设计,解决手动刷新网页或切换小程序获取实时金价的繁琐问题。资源包共2个文件,含1个Python源…

2026/10/14 15:40:36 阅读更多 →
实战分享:广州24小时自助健身房解决方案开发全流程及落地要点

实战分享:广州24小时自助健身房解决方案开发全流程及落地要点

实战分享:广州24小时自助健身房解决方案开发全流程及落地要点 广州24小时自助健身房解决方案是针对广州本地健身用户时间碎片化、传统健身房营业时间受限的痛点,打造的集无人值守、在线预约、智能管控、安全防护、运营管理于一体的全链路技术方案&#x…

2026/10/14 15:40:36 阅读更多 →
MATLAB实现纳什均衡求解:纯策略枚举与混合策略梯度投影

MATLAB实现纳什均衡求解:纯策略枚举与混合策略梯度投影

简介:本资源是一套面向高校博弈论课程学习者、运筹学研究者及MATLAB初学者的纳什均衡计算实践工具包,聚焦于纯策略与混合策略纳什均衡的建模、求解与可视化验证。压缩包共6个文件(4个MATLAB源码文件、1个PDF理论文档、1个TXT公式与流程说明&a…

2026/10/14 15:39:36 阅读更多 →

最新新闻

ITE V12.4 Update固件包刷写指南:增量升级、参数配置与故障排查

ITE V12.4 Update固件包刷写指南:增量升级、参数配置与故障排查

简介:ITE_V12.4_Update.rar 是嵌入式控制器(EC)V12.4 版本源代码,主要面向嵌入式系统开发者、软硬件协同工程师及相关专业学生,用于剖析 EC 在电源管理、风扇控制、键盘输入、系统安全和主处理器通信等底层硬件管理中的…

2026/10/14 16:22:34 阅读更多 →
Java GUI实战:Swing实现算术题生成与在线批改完整方案

Java GUI实战:Swing实现算术题生成与在线批改完整方案

简介:这是一套基于Java GUI技术开发的算术题在线练习与批改项目,适合Java初学者、学生或课程设计者用于理解图形界面编程、随机出题、文件读写与数据持久化。压缩包共55个文件,主体包含33个class编译文件与17个java源文件,另有2个…

2026/10/14 16:22:34 阅读更多 →
Android多列表嵌套滑动冲突终结者:ConsecutiveScrollerLayout原理与实战

Android多列表嵌套滑动冲突终结者:ConsecutiveScrollerLayout原理与实战

1. 这个布局到底解决了什么痛点做过Android复杂详情页或者多列表嵌套的兄弟应该都有过这种体验:一个页面里塞了Banner轮播、横向商品列表、纵向评论列表、底部推荐流,每个模块单独拿出来滑动都没问题,可一旦拼在同一个页面里,手指…

2026/10/14 16:22:34 阅读更多 →
八皇后问题的工程化实现:DFS剪枝与Java性能优化

八皇后问题的工程化实现:DFS剪枝与Java性能优化

1. 项目概述:一道经典题目的真实分量“八皇后”这三个字,在算法入门圈子里,几乎等同于“递归初体验”“回溯第一课”“DFS手把手教学”。但很多人刷完洛谷P1219后只记得“输出前三个解总解数”,却没意识到——这道标着“java&…

2026/10/14 16:22:34 阅读更多 →
Grin 区块与区块头处理全解析:正常接收、追赶同步与分叉重组的源码级指南

Grin 区块与区块头处理全解析:正常接收、追赶同步与分叉重组的源码级指南

区块链Web3 【免费下载链接】grin Minimal implementation of the Mimblewimble protocol. 项目地址: https://gitcode.com/gh_mirrors/gr/grin 点击查看 免费下载 本篇技术指南以 Grin 仓库中的《Blocks and Block headers》文档为骨架,系统讲解 Mimbl…

2026/10/14 16:22:34 阅读更多 →
如何靠真实预览图选风格:ppt-image-first首页、目录页、正文页三页预览法完全解析

如何靠真实预览图选风格:ppt-image-first首页、目录页、正文页三页预览法完全解析

AI 技能工作流自动化AI 应用 【免费下载链接】ppt-image-first PPT image-first skill for Codex/Claude Code/Opencode CLI 项目地址: https://gitcode.com/gh_mirrors/ppt/ppt-image-first 点击查看 免费下载 ppt-image-first 是一个 image-first 的 PPT 工作流 …

2026/10/14 16:21:33 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →