适用于为知笔记Windows客户端4.5及以上版本的插件开发API文档

9/12/2019

此內容未以你的語言提供。 以下為簡體中文。

4.4 版本插件开发API文档见:/m/plugin.html

为知笔记从4.5版本开始,升级Chrome内核版本为49,并采用多进程模式。带来的一个影响就是大部分插件需要升级才可以在4.5下面使用。 由于采用多进程模式,插件将无法直接访问笔记渲染浏览器的DOM对象,取而代之的,是通过WizChromeBrowser这样一个对象,和浏览器进行交互,例如执行脚本并获得返回值。

一个例子:在插件代码中访问当前笔记的文字:

4.5之前的版本代码:

var noteDoc = objApp.Window.CurrentDocumentHtmlDocument;    //HTML Document
//4.5之前的版本,我们可以直接获得当前笔记所在浏览器的document对象,并获得body中的文字。
alert(noteDoc.body.innerText);

4.5以及之后的版本代码:

var objBrowser = objApp.Window.CurrentDocumentBrowserObject;
//4.5以及之后的版本,无法获得笔记所在浏览器的对象,而是获得了一个WizChromeBrowser的对象

//通过WizChromeBrowser提供的方法,我们可以要求笔记所在的浏览器,执行一段脚本,并将脚本的返回值返回给我们。
//WizChromeBrowser和浏览器交互的时候,都是异步进行的,结果通过回调函数返回。
objBrowser.ExecuteScript("document.body.innerText", function(text) {
    alert(text);
});

从上面的例子可以看出插件如果要和笔记所在浏览器进行交互,则必须采用发送消息的形式来进行。 下面是WizChromeBrowser的定义:

    interface IWizChromeBrowserObject : IDispatch{
        // 执行一个脚本,异步执行
        [id(9), helpstring("method ExecuteScript")] HRESULT ExecuteScript([in] BSTR bstrScript, [in] VARIANT callback);
        // 执行一个.js文件里的所有脚本,异步执行
        [id(10), helpstring("method ExecuteScriptFile")] HRESULT ExecuteScriptFile([in] BSTR scriptFileName, [in] VARIANT callback);
        // 执行一个函数,无参数,异步执行
        [id(11), helpstring("method ExecuteFunction0")] HRESULT ExecuteFunction0([in] BSTR functionName, [in] VARIANT callback);
        // 执行一个函数,输入参数有一个,异步执行
        [id(12), helpstring("method ExecuteFunction1")] HRESULT ExecuteFunction1([in] BSTR functionName, [in] VARIANT param1, [in] VARIANT callback);
        // 执行一个函数,输入参数有两个,异步执行
        [id(13), helpstring("method ExecuteFunction2")] HRESULT ExecuteFunction2([in] BSTR functionName, [in] VARIANT param1, [in] VARIANT param2, [in] VARIANT callback);
        // 执行一个函数,输入参数有三个,异步执行
        [id(14), helpstring("method ExecuteFunction3")] HRESULT ExecuteFunction3([in] BSTR functionName, [in] VARIANT param1, [in] VARIANT param2, [in] VARIANT param3, [in] VARIANT callback);
        // 执行一个函数,输入参数有四个, 异步执行
        [id(15), helpstring("method ExecuteFunction4")] HRESULT ExecuteFunction4([in] BSTR functionName, [in] VARIANT param1, [in] VARIANT param2, [in] VARIANT param3, [in] VARIANT param4, [in] VARIANT callback);
    };

WizChromeBrowser是为知里面每一个Chrome浏览器所内置的一个对象,该对象可以在脚本中传递。对于笔记所在的浏览器,则可以通过为知的接口获得该对象(例如通过objApp.Window.CurrentDocumentBrowserObject获得当前笔记所在的浏览器对象)。 WizChromeBrowser的主要方法:

  • ExecuteScript:在WizChromeBrowser所属的浏览器内,执行一段脚本。结果通过callback返回。
  • ExecuteScriptFile:和ExecuteScript类似,只不过可以直接执行一个本地js文件里面的脚本。
  • ExecuteFunction等:在WizChromeBrowser所属浏览器内,执行一个函数。返回值通过callback返回。

在插件中更改笔记内容(加粗当前选中的文字): 4.5之前的版本代码:

var doc = objApp.Window.CurrentDocumentHtmlDocument;
doc.execCommand("bold", false);

4.5以及之后的版本代码:

objApp.Window.CurrentDocumentBrowserObject.ExecuteScript("document.execCommand('bold', false);", function(ret){});

获取笔记中特定的元素(大纲插件) 4.5之前的版本代码:

function getBookmarks(doc){
    if(!doc)
      return ;
    //
    var arrLinks = doc.anchors;
    for(var i = 0; i <arrLinks.length; i++){
        var elem = arraLinks[i];
        var elemName = elem.name;
        …
    }
}

4.5以及之后的版本代码:

/* getBookmarks.js*/
(function(){
    var added = false;
    var arrLinks = document.anchors;
    var arrayBookmarkName = [];
    var jsonelem = [];
    //
    for (var i = 0; i < arrLinks.length; i++) {
        var elem = arrLinks[i];
       // 获取DOM节点的属性值
        var name = elem.name;
        if (name == null || name == "")
            continue;
        //
       …
        //
        arrayBookmarkName.push(name);
    }
    return arrayBookmarkName;
}());

/*Outline.html*/
var scriptFile = pluginPath + "getBookmarks.js";
objBrowser.ExecuteScriptFile(scriptFile, function(ret){
      if(ret && ret.length > 0){
             for(var i = 0; i < ret.length; i++){
                  var elemName = ret[i];  
                  …
             }
      }
});

注意事项: 不管是执行脚本的返回值,还是执行函数的参数,只能是以下几种类型:

  • 基本数据类型,包含数字,字符串,日期,布尔值,空值
  • 基本数据类型组成的数组
  • COM对象

对于DOM对象,例如HTML Document,Window对象,都不能直接作为参数或者返回值传递。例如下面的代码无法执行:

objApp.Window.CurrentDocumentBrowserObject.ExecuteScript("document.body", function(body) {
    alert(body.innerText);
});

因为document.body 不属于上面所允许的类型,因此无法作为返回值传递。 同样,js内部的JSON对象,也不能进行传递。下面的代码同样是非法的:

objApp.Window.CurrentDocumentBrowserObject.ExecuteScript("{'a':'a'}", function(obj) {
    alert(obj.a);
});

如果要传递一个JSON对象,可以将JSON序列化成字符串。在使用的时候再重新解析成一个JSON对象。 示例代码:

function pluginXXX_GetElemAttributes(elemid){
    var elem = document.getElementById(elemid);
    if(elem){
         var jsonelem = {
              "tagName": elem.tagName.toLowerCase(),
              "offsetTop": elem.offsetTop,
              "innerText": elem.innerText
         };
         var json = JSON.stringify(jsonelem);
         return json;
    }
    return null;
}
//将pluginXXX_GetElemAttributes函数注入到目标浏览器内
objBrowser.ExecuteScript(pluginXXX_GetElemAttributes.toString(), function(ret){
       //调用目标浏览器内注入的函数
      objBrowser.ExecuteFunction1("pluginXXX_GetElemAttributes", "test", function(ret){
             if(ret && ret.length > 0){
                  var att = JSON.parse(ret);
                  var tagName = att.tagName;
                  var offsetTop = att.offsetTop;
                  var innerText = att.innerText;
             }
      });
});

其他注意事项: 声明插件支持的版本

  • 在plugin.ini文件的[Common]区段下添加字段“SupportVersion”,此字段值大于等于2的情况下,4.5以及之后版本启动时会加载此插件。

部分接口改变,从传递document对象到传递WizChromeBrowser对象作为参数

  • 定义的一些Wiz笔记响应事件

    function WizOnHtmlDocumentCompleteEx(objBrowser, objWizDocument) { eventsHtmlDocumentCompleteEx.dispatch2(objBrowser, objWizDocument); } function WizOnDocumentBeforeChange(objBrowser, objWizDocumentOld, objWizDocumentNew) { return eventsDocumentBeforeChange.dispatch3(objBrowser, objWizDocumentOld, objWizDocumentNew); } function WizOnDocumentAfterChange(objBrowser, objWizDocumentOld, objWizDocumentNew) { return eventsDocumentAfterChange.dispatch3(objBrowser, objWizDocumentOld, objWizDocumentNew); } function WizOnDocumentBeforeEdit(objBrowser, objWizDocument) { return eventsDocumentBeforeEdit.dispatch2(objBrowser, objWizDocument); } function WizOnDocumentAfterEdit(objBrowser, objWizDocument) { return eventsDocumentAfterEdit.dispatch2(objBrowser, objWizDocument); }

eventsDocumentBeforeEdit 和 eventsDocumentAfterEdit 消息在Wiz 4.5.8以及之后版本中失效

  • IWizExplorerApp

    HRESULT GetPluginAppPath([in] IDispatch* pBrowserObjDisp, [out, retval] BSTR* pVal); HRESULT GetPluginAppGUID ([in] IDispatch* pBrowserObjDisp, [out, retval] BSTR* pVal); HRESULT GetHtmlDocumentPath([in] IDispatch* pBrowserObjDisp, [out, retval] BSTR* pVal); // 以下三种方法均为异步执行 HRESULT PluginLocalizeHtmlDialog([in] IDispatch* pBrowserObjDisp); HRESULT LocalizeHtmlDocument([in] BSTR bstrLanguageFileName, [in] IDispatch* pBrowserObjDisp); //同LocalizeHtmlDocument方法,返回值通过callback返回 HRESULT LocalizeHtmlDocument2([in] BSTR bstrLanguageFileName, [in] IDispatch* pBrowserObjectDisp, [in] VARIANT callback); //添加方法 SetNoteModifiedByPlugin, 插件修改笔记后调用此方法,作为修改标志 HRESULT SetNoteModifiedByPlugin(); //添加方法 IsCurrentDocumentEditing, 获取当前显示的笔记是否处于编辑状态 HRESULT IsCurrrentDocumentEditing([out, retval] VARIANT_BOOL* pVb);

一个例子:对话框类型插件本地化 4.5之前版本代码:

var iniPath = pluginPath +"plugin.ini";
LocalizeHtmlDocument(iniPath, document);  //同步执行

4.5以及之后的版本代码:

LocalizeHtmlDocument(iniPath, WizChromeBrowser);   //异步执行
  • IWizExplorerWindow

    // 属性 CurrentDocumentHtmlDocument不再使用, 改为使用 CurrentDocumentBrowserObject, 获取当前正在显示的笔记所在浏览器的 WizChromeBrowser 类型对象 HRESULT CurrentDocumentBrowserObject([out, retval] IDispatch** pVal); // 方法 ShowHtmlDialog 与 ShowHtmlDialog2 不再使用,改为使用 ShowHtmlDialogEx。 // vbModal 必为 false,vParam为传给对话框的参数,可通过方法 GetHtmlDialogParam 来获得。 此函数为异步调用,结果通过vCallBack返回。 HRESULT ShowHtmlDialogEx([in] VARIANT_BOOL vbModal, [in] BSTR bstrTitle, [in] BSTR bstrURL, [in] LONG nWidth, [in] LONG nHeight, [in] BSTR bstrExtOptions, [in] VARIANT vParam, [in] VARIANT vCallback); HRESULT CloseHtmlDialog([in] IDispatch* pBrowserObjDisp, [in] VARIANT vRet); HRESULT GetHtmlDialogParam([in] IDispatch* pBrowserObjDisp, [out, retval] VARIANT* pvParam); HRESULT GetHtmlDialogHWND([in] IDispatch* pBrowserObjDisp, [out, retval] OLE_HANDLE* phHWND); HRESULT SetDialogResult([in] IDispatch* pBrowserObjDisp, [in] VARIANT vRet); HRESULT CloseSelectorWindow([in] IDispatch* pdispSelectorBrowserDisp);

一个例子:在对话框插件中打开另一个html页面(自定义快速搜索) 4.5以前版本代码:

/*add_quick_search.htm*/
…
var retString;
//填充 retString
…
//
objWindow.CloseHtmlDialog(document, retString);
…
/*customize_quick_searches.htm*/
…
// ret即为add_quick_search.htm中retString变量
var ret = objApp.Window.ShowHtmlDialog("", objApp.AppPath + "files\\customize_quick_searches\\add_quick_search.htm", 500, 500, "");
//
…

4.5以及之后版本代码:

/*add_quick_search.htm*/
…
var retString;
//填充 retString
…
//
objWindow.CloseHtmlDialog(WizChromeBrowser, retString);
…
/*customize_quick_searches.htm*/
…
// ret即为add_quick_search.htm中retString变量, ShowHtmlDialogEx 第一个参数必为false, 弹出非模态对话框
objApp.Window.ShowHtmlDialogEx(false, "", objApp.AppPath + "files\\customize_quick_searches\\add_quick_search.htm", 500, 500, "", null, function(ret){
      //
      if(ret && ret.length > 0){
      …
      }
});
//
…
  • IWizHtmlEditorApp

    // 属性 EditorDocument不再使用, 改为使用 EdtorBrowserObject, 获取当前正在显示的笔记所在浏览器的 WizChromeBrowser 类型对象 HRESULT EditorBrowserObject([out, retval] IDispatch** pVal); HRESULT LocalizeHtmlDocument([in] BSTR bstrLanguageFileName, [in] IDispatch* pBrowserObjectDisp); HRESULT GetHtmlDocumentPath([in] IDispatch* pBrowserObjectDisp, [out, retval] BSTR* pVal);

双浏览器变单浏览器

  • 4.5以前版本,使用一个浏览器加载阅读状态下的笔记, external 对象即 WizExplorerApp 对象;使用另一个浏览器加载编辑状态下的笔记, external 对象为 IWizHtmlEditorApp 类型,所属文件 WizTools.idl。4.5以及之后版本,改为使用一个浏览器来加载阅读状态与编辑状态下的笔记,external 对象类型为 IWizHtmlEditorApp。

一个例子:阅读状态下,获取当前数据库的路径(为知助手) 4.5以前的版本代码:

var objDatabase = external.Database;
var databasePath = objDatabse.DatabasePath;

4.5以及之后的版本代码:

/* 注入脚本文件: KMContent.js*/
var objKMHelperApp;
var objKMHelperDatabse;
var objKMHelperPluginBrowser;
//
function KMInit(app, pluginBrowser) {
    if (!app || !pluginBrowser)
        return;
    //
    objKMHelperApp = app;
    objKMHelperDatabase = app.Database;
    var databasePath = objKMHelperDatabase.DatabasePath;
    //
    objKMHelperPluginBrowser = pluginBrowser;
    …
}

/* 插件文件:KMHelper.js*/
function initEvents() {
/*
向Wiz注册一个事件,响应文档完成的消息。在Wiz内打开一个html文件的时候(例如阅读文档),如果Html文件打开完成,则调用这个方法。
*/
    eventsHtmlDocumentComplete.add(KMOnHtmlDocumentComplete);
    …
}
// 笔记加载完成, objBrowser 为当前显示的笔记所在浏览器的 WizChromeBrowser 对象
function KMOnHtmlDocumentComplete(objBrowser) {
    if (!objBrowser)
        return;
    //
    var pluginPath = objApp.GetPluginPathByScriptFileName("KMHelper.js");
    var contentScriptPath = pluginPath + "KMContent.js";
    //
    objBrowser.ExecuteScriptFile(contentScriptPath, function (ret) {
        // 执行注入脚本的 KMInit 方法, 将 WizExplorerApp, WizChromeBrowser 对象传到笔记所在浏览器中
        objBrowser.ExecuteFunction2("KMInit", objApp, WizChromeBrowser, function (ret) {
              …
        });
    });
}

笔记所在浏览器的 external 对象没有获取 IWizDatabase 对象的方法。从上例可看出,可调用注入脚本的函数,将 WizExplorerApp 对象传到笔记所在浏览器中,再获取数据库对象,由此也可以使用 WizExplorerApp 对象的其他方法。 上例中还将插件所在浏览器的 WizChromeBrowser 对象传到笔记所在浏览器中,这样可以通过此对象执行插件环境下的脚本。 一个例子: 鼠标移开后自动关闭目录设置界面(为知助手)

/*注入脚本文件 : KMContent.js*/
function KMOnSetAsContentClick() {      //点击设置目录图标,弹出目录设置界面
    // 设置目录逻辑
    …
    // 添加自动关闭窗口的计时器, objKMHelperPluginBrowser为插件所在浏览器的 WizChromeBrowser 对象
    objKMHelperPluginBrowser.ExecuteFunction1("KMAutoCloseContentWindow", true, null);
} 
function KMAutoCloseContentWindow() {
    //关闭目录设置窗口逻辑
    …
    //去除计时器, objKMHelperPluginBrowser为插件所在浏览器的 WizChromeBrowser 对象
    objKMHelperPluginBrowser.ExecuteFunction1("KMAutoCloseContentWindow", false, null);
}

/*插件文件: KMHelper.js*/
function KMAutoCloseContentWindow(isAddAfterRemove) {
    //
    objWindow.RemoveTimer("KMAutoCloseContentWindowTimer");
    if (isAddAfterRemove) {
        objWindow.AddTimer("KMAutoCloseContentWindowTimer", 1000);  //KMAutoCloseContentWindowTimer, 计时器回调函数名
    }
}
function KMAutoCloseContentWindowTimer() { 
    var objBrowser = objWindow.CurrentDocumentBrowserObject;
    if (!objBrowser)
        return;
    //
    objBrowser.ExecuteFunction0("KMAutoCloseContentWindow", null);
}

一个例子: 阅读状态下,插件对笔记的修改与保存(为知助手) 4.5以前版本代码:

this.colorword = function (doc, node, keyword, callback) {
        if (node.childNodes == undefined)
            return false;
        //
        if (node.id == "wizKMHighlighterSpan_t_t")
            return false;
        //
        for (var i = 0; i < node.childNodes.length; i++) {
            var childNode = node.childNodes[i];
            if (childNode.nodeType == 3) {
                //childNode is #text
               …
                //
                re = new RegExp('(' + keyword.word + ')', 'i');
                //
                var forkNode = doc.createElement("span");
                forkNode.id = "wizkm_highlight_tmp_span";
                forkNode.innerHTML = childNode.data.replace(re, '<div id="wizKMHighlighterSpan_t_t" style="background-color:' + keyword.bgColor + ';color:' + keyword.foreColor + '; cursor:pointer; border-bottom: 1px #00c dashed;">$1</div>');
                node.replaceChild(forkNode, childNode);
                //
                …
                //
               return true;
            } 
            …
        }
        return false;
    } 
    //
    …
    // 
   function KMSetDocumentModified(doc) {
        if (!doc)
              return;
        var body = doc.body;
        if (!body)
              return;
        //
        body.setAttribute(g_KMDocumentModifiedAttributeName, "1", 0);
   }
   …
   function KMIsDocumentModified(doc){
        if(!doc)
             return;
       //  
        var body = doc.body;
        if (!body)
              return;
        //   
        return body.getAttribute(g_KMDocumentModifiedAttributeName, 0) == "1";
   }
   function KMSaveDocument(objHtmlDocument, objWizDocument) {
       if (!objWizDocument)
           return;
       if (!KMIsDocumentModified(objHtmlDocument))
           return;
       //
    …
    }

4.5以及之后版本代码:

/*KMContent.js*/  
//注入脚本 
this.colorword = function (node, keyword, callback) {
        if (node.childNodes == undefined)
            return false;
        //
        if (node.id == "wizKMHighlighterSpan_t_t")
            return false;
        //
        for (var i = 0; i < node.childNodes.length; i++) {
            var childNode = node.childNodes[i];
            if (childNode.nodeType == 3) {
                //childNode is #text
               …
                //
                re = new RegExp('(' + keyword.word + ')', 'i');
                //
                var forkNode = document.createElement("span");
                forkNode.id = "wizkm_highlight_tmp_span";
                forkNode.innerHTML = childNode.data.replace(re, '<wiz_tmp_plugin_tag id="wizKMHighlighterSpan_t_t" style="background-color:' + keyword.bgColor + ';color:' + keyword.foreColor + '; cursor:pointer; border-bottom: 1px #00c dashed;">$1</wiz_tmp_plugin_tag>');
                node.replaceChild(forkNode, childNode);
                //
                …
                //
               return true;
            } 
            …
        }
        return false;
    } 
    //
    …
    //
    function KMSetDocumentModified(){
           objKMHelperApp.SetNoteModifiedByPlugin();
     }
     …
  • 4.5以前版本,在为知笔记默认编辑器下使用的插件,其保存操作是同步执行,各个插件按顺序对笔记进行保存。4.5以及之后版本,插件的保存操作是异步执行,各插件按顺序对笔记进行保存时,可能会丢失其他插件对笔记的修改。所以从4.5版本开始,保存操作由内部编辑器来完成, 插件修改笔记之后,只需调用 WizExplorerApp 对象的SetNoteModifiedByPlugin方法,设置修改标志即可。

注意: SetNoteModifiedByPlugin 方法应用于阅读状态下插件对笔记做出修改时进行标记。编辑状态下为知笔记编辑器会对DOM修改进行捕获,所以不需插件单独设置修改标记。

  • 插件在操作笔记时可能会添加一些临时DOM节点,需在保存时进行删除, 像上例中创建的高亮节点。插件可以使用为知笔记定义的自定义标签来创建此类节点,内部编辑器保存时会将此类节点删除。根据删除类型,定义了以下两种自定义标签:
  1. 保存时删除整个临时节点 使用标签<wiz_tmp_tag></wiz_tmp_tag>创建临时节点的顶层节点,字节点不需要再使用此标签创建。
  2. 保存时删除整个临时节点,但保留节点文本 使用标签<wiz_tmp_plugin_tag></wiz_tmp_plugin_tag>来创建临时节点的顶层节点,字节点不需要再使用此标签创建。

此上两种标签不可嵌套使用,否则会产生未知错误,只需用这两种标签创建相应的顶层节点即可。 笔记所在浏览器中, 避免全局脚本命名冲突

  • 所有的插件注入脚本都在笔记所在浏览器下执行,避免出现变量和函数名的命名冲突,从而导致发生不可预期的错误,建议对变量及函数的名称添加前缀或者后缀。

来自为知笔记(Wiz)