news 2026/10/14 15:40:35

QOwnNotes 脚本钩子(Hooks)完全指南:从笔记生命周期到编辑器高亮的 22 个扩展点

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
QOwnNotes 脚本钩子(Hooks)完全指南:从笔记生命周期到编辑器高亮的 22 个扩展点
  • 桌面应用

【免费下载链接】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 的脚本钩子(Scripting Hooks)体系:当笔记被打开、保存、插入媒体、生成预览 HTML、加解密、打标签、切换布局等事件发生时,QOwnNotes 脚本引擎会回调你在 QML 脚本中定义的特定函数。读完本文,你将掌握全部 22 个钩子的触发时机、函数签名与参数含义、返回值约定,并结合仓库源码与示例脚本,学会编写属于自己的 QOwnNotes 扩展脚本。

一、钩子机制是如何工作的

QOwnNotes 的脚本使用 QML(Qt Meta-object Language)编写,通过QtQml与QOwnNotesTypes命名空间加载。脚本引擎位于 src/services/scriptingservice.cpp,所有钩子都通过同一套模式被调用:

  1. 引擎启动时加载所有已启用的脚本组件(initComponents,见 scriptingservice.cpp);
  2. 当某个事件发生时,ScriptingService遍历所有ScriptComponent,用methodExistsForObject()检查对象是否定义了对应签名的钩子函数(如noteOpenedHook(QVariant));
  3. 若存在,则通过QMetaObject::invokeMethod()反射调用该函数,并把返回值通过Q_RETURN_ARG(QVariant, ...)拿回;
  4. 每次调用都会用logScriptHookTiming()记录耗时,便于排查性能瓶颈。

例如noteOpenedHook的调用代码(scriptingservice.cpp):

if (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.qml):

function 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.qml):

function 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" : "doesn't 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>", "body><h1>Custom preview for note " + note.name + "</h1><hr />"); }

提示:可用的 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充当标签。一旦某脚本实现了noteTaggingHook,QOwnNotes 的标签处理就会移交给该函数:

/** * 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.cpp),tagmanager.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.qml:

function 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);

参数说明:

  • requestType:"page"(发送整页 HTML)或"selection"(发送选中文本);
  • pageUrl/pageTitle:来源网页的 URL 与标题;
  • rawData:传输的数据,"page"时为 HTML,"selection"时为纯文本;
  • 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 脚本时请遵循以下实践:

  1. 返回值约定:凡是"可改写"钩子(如insertMediaHook、handleNoteNameHook、preNoteToMarkdownHtmlHook、noteTaggingHook等),返回空字符串/空列表表示不做修改;websocketRawDataHook则返回布尔值表示是否已处理。
  2. 多脚本冲突:insertMediaHook、insertAttachmentHook、fetchUrlTitleHook遵循"第一个返回非空字符串的脚本胜出";highlightingHook会汇总所有脚本的返回区间。
  3. 性能意识:highlightingHook每次按键每个可见块都会被调用;noteTaggingHook的list动作会在每次重载笔记文件夹/标签树时对每篇笔记执行。务必保持这两类钩子的实现轻量,能用addHighlightingRule静态规则解决的不要用highlightingHook。
  4. 签名即契约:QOwnNotes 通过 QML 方法的规范化签名(如noteOpenedHook(QVariant))来检测钩子是否存在,因此函数名和参数个数必须与文档完全一致,多参数版本(如带forExport)与旧版兼容。
  5. 调试手段:脚本中用script.log()输出日志;QOwnNotes 的脚本日志面板会记录各钩子的调用耗时(logScriptHookTiming),可据此定位性能热点。
  6. 脚本骨架:每个脚本以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,可作为深入学习的起点。

  • 桌面应用

【免费下载链接】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
点击查看免费下载

相关推荐

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 15:40:00

刑事科学技术论文写作:我按四个环节做了一次工具对比

核心观点&#xff1a;刑技论文每个环节对工具的要求不同——选题文献、数据图表、表述规范、查重降AIGC&#xff1b;四个环节里只有一部分适合交给工具&#xff0c;鉴定结论与专业表述必须自己把关。 环节一&#xff1a;选题与文献整理 刑技的文献分布特殊&#xff1a;一部分在…

作者头像 李华
网站建设 2026/10/14 15:39:45

千千静听5.0.1本地音频工作流:音效插件与皮肤配置实战

简介&#xff1a;这是一份面向怀旧用户与音频爱好者的千千静听5.0.1完整整合包&#xff0c;内含经典版播放器、音效增强插件与大量个性化皮肤&#xff0c;可解决老版本播放器功能单一、界面陈旧的问题&#xff0c;适合追求简洁播放体验或希望重温经典软件的用户。压缩包共63个文…

作者头像 李华
网站建设 2026/10/14 15:39:33

基于IP地址划分VLAN:原理、配置与移动办公实战指南

先说一个我自己的经历。早几年给某公司做网络调整&#xff0c;办公区终端大概两百多台&#xff0c;前面的人图省事把整个二层网络划成一个广播域&#xff0c;结果一到下午视频会议就卡&#xff0c;打印任务频繁超时&#xff0c;排查了半天发现是广播报文在作祟。后来我按部门重…

作者头像 李华
网站建设 2026/10/14 15:39:22

3DMAX随机碳纳米结构RandomCarbonNanostructure建模工具开发教程

一、工具目标与整体思路这个脚本的目标是&#xff1a;在 3ds Max 中随机生成一个类似碳纳米结构的三维模型&#xff0c;包括原子球和原子之间的键柱&#xff0c;并允许用户通过 UI 调整原子数、键长、键角、半径、随机种子等参数。它并不是严格意义上的物理化学模拟&#xff0c…

作者头像 李华
网站建设 2026/10/14 15:39:18

《大话移动通信》读书笔记:从原理到现网落地的工程化解读

1. 这本《大话移动通信》到底在讲什么&#xff0c;为什么值得花时间做笔记&#xff1f;“读书笔记 |《大话移动通信》”这个标题看起来平平无奇&#xff0c;但背后藏着一个非常现实的痛点&#xff1a;通信技术类书籍普遍门槛高、术语密、公式多&#xff0c;很多读者翻不到第三章…

作者头像 李华
网站建设 2026/10/14 15:39:16

实验室原始劳作与现代自动化技术的演进对比

1. 项目概述"时差学者&#xff1a;2015科研日志-第四集&#xff1a;实验室的原始劳作"这个标题让我想起了十年前在分子生物学实验室的日日夜夜。作为一个经历过无数次通宵实验的老科研狗&#xff0c;看到"原始劳作"这个词会心一笑——这确实是对基础科研工…

作者头像 李华