65.9K
CodeProject 正在变化。 阅读更多。
Home

Google Chrome 扩展 - CodeProject 模板项

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (28投票s)

2011年8月22日

CPOL

11分钟阅读

viewsIcon

72287

downloadIcon

892

Google Chrome 扩展,提供消息和答案模板。

V2 重要变更

由于 Google 为保护用户免受有害代码侵害而实施的政策变更,该扩展程序需要进行多项更改。

  • 将清单更新到新的 V2 标准。
  • 从 Chrome 网上应用店分发扩展程序
  • 从 HTML 页面中删除内联脚本块。

我还利用这些更改将 jQuery 升级到新版本。

引言

如果您是 CodeProject 的常客,或者可能是文章版主、导师,甚至花时间在问答区回答问题,您有多少次发现自己发布了相同的内容?您有多少次发现自己前往常见问题列表复制文章常见问题链接?一次、两次、十次?那么,为了节省再次输入,而去查找您以前的帖子并复制它再次使用呢?

这就是这个工具和文章诞生的原因。我想一定有更好的方法,考虑过保存一个文本文件,但放弃了这个想法,然后想等等,我再制作一个 Chrome 扩展程序。我已经做过一次了,所以重复那次的成功应该相当容易。

该工具允许用户创建存储在浏览器上下文中的模板项,并一键将选定的模板项直接插入到您的论坛消息或答案中。您甚至可以只保留小片段,例如链接,并能够快速将其添加到您的消息/答案中。

版本 1.1 修复了一些错误,并引入了第三种项目类型以及编辑器和选择器表单中的过滤功能。

涵盖的主题

在本文中,我们将重点关注以下主要内容:

  • Chrome 后台页面和 PageActions
  • 从扩展程序上下文在浏览器页面上下文中注入和执行 Chrome 脚本
  • 一些用于处理对象克隆、项目识别的 JQuery

Chrome 扩展程序 - 它们是什么?

扩展程序是 Google Chrome 浏览器的附加组件,为了避免重复,请查看我的上一篇 Chrome 扩展程序文章,其中解释了基础知识,并提供了 Chrome 扩展程序和 JQuery 开发者页面的链接。文章可以在这里找到:Chrome 扩展程序 - CodeProject 声誉监视器。如果您以前从未接触过扩展程序,我建议您先阅读此文,因为它涵盖了一些关键的扩展程序创建信息,例如扩展程序结构、清单文件、打包和部署。

注意: Google Chrome 已更改了未由 Chrome 网上应用店提供的打包扩展程序的安装方式。这是为了降低安装“可疑”扩展程序的风险。要使用我网站上的打包扩展程序,请点击链接,然后从“工具”菜单切换到“扩展程序”页面,然后将浏览器底部下载栏中的文件拖到“扩展程序”页面上。您将看到“拖放以安装”消息出现。

这个扩展程序怎么样?

这个扩展程序非常相似,关于 JavaScript 文件、JQuery 引用等方面的想法都一样。这个扩展程序与前一个不同之处在于它的操作方式、触发事件以及与用户页面的交互方式。

首先,让我们看一下这个扩展程序的清单文件

{
    "manifest_version": 2,
    "name": "CodeProject Template Items",
    "short_name": "CPTemplates",
    "version": "2.0",
    "content_security_policy": "script-src 'self' https://# 'unsafe-eval'; object-src 'self' ",
    "description": "Chrome Extension to provide CodeProject template items.",
    "icons": {
        "48": "images/bob48.png",
        "128": "images/bob128.png"
    },
    "background": {
        "scripts": [ "js/background.js" ]
    },
    "page_action": {
        "default_icon": "images/bob.png",
        "default_title": "CodeProject: Insert Template Items",
        "default_popup": "selector.html"
    },
    "options_page": "editor.html",
    "permissions": [
        "tabs",
        "http://*.codeproject.com/"
    ]
}

可以看到常见的项,名称、版本、描述等,但有几个不同之处

  • background - 这是在浏览器后台运行的文件,它设置了扩展程序的浏览器集成。在版本 1 中,这是一个带有内联脚本的 html 页面,但对于 V2,需要将其提取到独立的 javascript 文件中。
  • page_action - 这设置了一个可以在适当情况下由用户触发的操作,其中包含的属性设置了显示的图标、工具提示文本和向用户显示的页面。

您还可以看到根据新规则要求进行的'manifest_versions''content_security_policy'更改。

后台脚本 - 它在做什么?

当浏览器启动时,扩展程序的后台会被加载,其中的任何脚本都会被执行。请看下面的页面内容

    // Check if the address contains the Message Editor URL;
    if (tab.url.indexOf(".codeproject.com/script/Forums/Edit.aspx?") > -1) {
        // ... show the page action.
        chrome.pageAction.show(tabId);
    }

    // Check if the address contains the Question URL;
    if (tab.url.indexOf(".codeproject.com/Questions/") > -1) {
        // ... show the page action.
        chrome.pageAction.show(tabId);
    }
};

// Add a handler to listen for Address Bar changes in the browser, set the callback function

chrome.tabs.onUpdated.addListener(checkForTargetMessageEditorUrl);

当脚本加载时,它会向浏览器添加一个监听器。此监听器等待 Tabs 的 onUpdated() 事件,然后触发回调函数 checkForTargetMessageEditorUrl()。Chrome 会将对已更新的浏览器标签页的引用传递给回调。检查标签页的 URL,如果它包含适用网页的地址,在本例中是消息编辑器或问题页面,它将在浏览器地址区域内显示清单文件中定义的页面动作图标。如果 URL 不是要查找的 URL,则不显示页面动作图标。地址栏中的页面动作图标如下所示

CPTemplatesChromeExt/pageActionIcon.png

现在我们有了一个合适的 URL 检查后台代码,它可以向用户显示页面操作。

页面动作

现在,用户可以使用页面操作了,当用户点击图标时,清单文件中定义的弹出窗口会显示给用户,如下图所示

CPTemplatesChromeExt/selector_page.png

用户现在可以点击她/他想要使用的模板项,然后点击选择以将内容注入浏览器页面。我们稍后将介绍此列表中项目的添加和编辑。

内容是如何注入的?

关于 Chrome、用户页面和扩展程序页面,需要了解的关键一点是它们都彼此隔离。扩展程序和网页之间唯一的共同区域是网页 DOM,扩展程序无法访问页面的脚本或变量,页面也无法访问扩展程序的脚本或变量。唯一的共同元素是网页 DOM,当然,还有托管的 Chrome 浏览器。

让我们看看当用户点击模板项并点击Select时执行的代码。

//-----------------------------------------
//     Button Events - Selector
//-----------------------------------------
function buttonSelect_Click() {
    //
    //Get the current window and then the current tab of that window
    //This should relate to the page on which to do the injection
    chrome.windows.getCurrent(function (theWindow) {
        chrome.tabs.getSelected(theWindow.id, function (theTab) { 
            injectToTab(theTab) });
    });
}

click 事件有效地询问 Chrome,“当前活动的 Chrome 窗口是哪个?”结果是一个 Window 对象,它被传递给一个回调函数。然后 Chrome 会被询问,“你刚刚告诉我关于的那个窗口的活动标签页是哪个?”Chrome 会将一个 Tab 对象返回给另一个回调函数,然后该函数用于注入内容。

function injectToTab(tab) {
    //Build Valid Content for remote execution
    var templateItem = parseInt($("#selectTemplates option:selected").val());
    var subject = templateStore[templateItem].Subject;
    var body = templateStore[templateItem].Body;
    
    // Check if the address contains the Message Editor URL;
    if (tab.url.indexOf(".codeproject.com/script/Forums/Edit.aspx?") > -1) {
        
        //Append the subject
        chrome.tabs.executeScript(tab.id, { code:
            "document.getElementById('ctl00_MC_Subject').value += 
				unescape('" + escape(subject) + "');"
        });
        
        //Append the body
        chrome.tabs.executeScript(tab.id, { code:
            "document.getElementById('ctl00_MC_ContentText_MessageText').value += 
				unescape('" + escape(body) + "');"
        });
    }

    // Check if the address contains the Question URL;
    if (tab.url.indexOf(".codeproject.com/Questions/") > -1) {
        //Append the answer
        chrome.tabs.executeScript(tab.id, { code:
            "document.getElementById
		('ctl00_ctl00_MC_AMC_PostEntryObj_Content_MessageText').value += 
		unescape('" + escape(body) + "');"
        });
    }
}

该函数检查模板列表中选择了哪个项目。然后通过内存中模板数组的索引检索该项目。接着,代码根据标签页的 URL 检查我们正在注入到哪个页面。然后,我们构建一个我们想要在页面上下文中执行的脚本的 string 表示,并在其中添加模板项目的相关属性。在开发此功能时,我发现 string 中的换行符无法正确处理,因此我们必须使用 JavaScript 的 escapeunescape 方法才能正确传递数据。

string 构建完成后,Chrome 会收到脚本的 string 表示,然后会在网页的上下文中执行该脚本。此脚本执行并向已通过 ID 识别的元素追加模板数据。

添加和存储模板项

有两种方法可以访问用于添加/编辑模板项的页面。第一种方法是从 Chrome 扩展程序页面,然后点击选项;第二种方法是右键单击页面操作图标并选择选项

选项页面在此例中在清单文件editor.html中定义,该页面被加载。编辑器入口页面基本上是模板列表,如下图所示

CPTemplatesChromeExt/editor_select.png

 

然后您可以点击添加,或者选择一个现有模板来编辑/删除/克隆该项。

浏览器 LocalStorage 用于存储内存中模板项的 string 表示。JSON 用于将 JavaScript 对象 stringify 并将其放入本地存储,并且 JSON 也用于将 string 转换回 JavaScript 对象。

当编辑器页面加载时,存储被初始化,检查对象是否已经定义,如果未定义则创建一个新的空存储。如果已经存在,则使用 JSON 将对象 parse 回数组。

初始化存储并将对象 parse 回内存的代码如下所示

function initStorage() {
    if (localStorage["TemplateStore"] == undefined) {
        localStorage["TemplateStore="] = "{}";
    }
    else {
        templateStore = JSON.parse(localStorage["TemplateStore"]);
    }
}

使用 JSON 将数组 stringify 到本地存储的代码如下所示

function saveToLocalStorage() {
    //Write out the template store to the local storage
    localStorage["TemplateStore"] = JSON.stringify(templateStore);
}

当编辑器加载时,如果存在现有模板项,该数组会使用 JQuery 作为 option 元素加载到页面选择列表中。加载操作分两个阶段进行,第一阶段检查当前页面(由过滤使用),第二阶段根据当前过滤设置将模板加载到 HTML 中,您还可以看到索引号被写入到 option 元素中。

function loadTemplates() {
    chrome.windows.getCurrent(function (theWindow) {
        chrome.tabs.getSelected(theWindow.id, function (theTab) {
            // Check if the address contains the Message URL;
            if (theTab.url.indexOf(".codeproject.com/script/Forums/Edit.aspx?") > -1) {
                loadTemplatesPart2("Message");
            }
            else {
                // Check if the address contains the Question URL;
                if (theTab.url.indexOf(".codeproject.com/Questions/") > -1) {
                    loadTemplatesPart2("Answer");
                }
                else {
                    loadTemplatesPart2("");
                }
            }
        });
    });
}

function loadTemplatesPart2(urlType) {
    //Clear the list box of any items
    $("#selectTemplates").html(""); //Clear the existing selection box

    if (templateStore.length > 0) {
        //Load Template Items
        $("#templateLoading").html("Template store contains: " + 
			templateStore.length + " item(s).");
        
        //get the filter mode
        var filter = $("#selectFilter option:selected").val();

        //Get each items Title and add it to the list subject to filters
        for (item in templateStore) {
            var addit = false;
            switch (filter) {
                case "All":
                    //Add all items
                    addit = true;
                    break;

                case "Auto":
                    //Always add the snippets regardless of Answer/Message
                    if (templateStore[item].Type == "Snippet") {
                        addit = true;
                    }
                    else {
                        //if the item matches the page (message/answer) then addit
                        addit = (templateStore[item].Type == urlType);
                    }
                    break;

                default:
                    //Add if item == filter type
                    addit = (templateStore[item].Type == filter); 
            }

            if (addit) {
                $("#selectTemplates").html($("#selectTemplates").html() + 
		"<option value="\"" + item.toString() + "\">" + 
		templateStore[item].Title + "</option>");
            }
        }
        $("#templateLoading").html($("#templateLoading").html() + 
	" Displaying: " + $("#selectTemplates").prop("length").toString() + " item(s)");
        $("#templateListing").show();
    }
    else {
        // No items to Load
        $("#templateLoading").html("No items located in local store.");
        $("#templateListing").show();

        if (!templateSelectMode) {
            $("#templateEdit").hide();
            $("#buttonEdit").hide();
            $("#buttonClone").hide();
            $("#buttonDelete").hide();
        }
        else {
            $("#buttonSelect").hide();
        }
    }
    
    //Add always is shown (in editor form).
    if (!templateSelectMode) {
        $("#buttonAdd").show(); 
    }
}

添加和编辑模板项

当用户点击添加时,会显示编辑器表单,用户可以在其中输入各种属性的文本。然后创建一个新的模板对象并将其推入数组并存储到本地存储中。

如果用户选择一个现有项,它将被加载到编辑器中,用户可以对其进行更新,然后将其推回数组并写入本地存储。编辑表单只是一个隐藏的 DIV,它与模板选择器 DIV 交换。这个编辑表单如下所示

CPTemplatesChromeExt/editor_page.png

 

以下代码演示了如何识别列表中选择用于编辑的项,从存储数组中取出并用现有详细信息更新表单。

function buttonEdit_Click() {
    templateEditMode = true;
    templateEditModeItem = parseInt($("#selectTemplates option:selected").val());

    //V1.1 Change Switch block replaces If and added Snippet handling
    switch (templateStore[templateEditModeItem].Type) {
        case "Message":
            $("#RadioTypeMessage").prop('checked', true);
            break;

        case "Answer":
            $("#RadioTypeAnswer").prop('checked', true);
            break;

        case "Snippet":
            $("#RadioTypeSnippet").prop('checked', true);
            break;
    }

    $("#TextTitle").val(templateStore[templateEditModeItem].Title);
    $("#TextSubject").val(templateStore[templateEditModeItem].Subject);
    $("#TextBody").val(templateStore[templateEditModeItem].Body);

    $("#templateListing").hide();
    $("#templateEdit").show();
}

以下代码是执行的保存操作,用于添加新项目或编辑现有项目。当一个项目被编辑时,会创建一个新项目,旧项目在存储数组中被替换。然后将更新后的数组写入本地存储。

function buttonSave_Click() {
    //Create new item
    var item = new Template();

    //Add item properties
    item.Type = $("input[name=RadioType]:checked").val();
    item.Title = $("#TextTitle").val().toString();
    item.Subject = $("#TextSubject").val().toString();
    item.Body = $("#TextBody").val().toString();

    if (templateEditMode) {
        //Edit Mode
        templateStore[templateEditModeItem] = item;
        templateEditMode = false;
    }
    else {
        //Add Mode
        templateStore.push(item);
    }

    //Save to local storage
    saveToLocalStorage();

    //Restart
    initEditorView();
}

正如您在上面的代码中看到的,我们通过执行 new Template() 创建一个新模板。Template 实际上是一个返回合适格式对象的函数。执行此操作的函数是

//Template Object Constructor
function Template() {
    this.Type = "";
    this.Title = "";
    this.Subject = "";
    this.Body = "";
}

您可能还在编辑器选择器表单上注意到了一个克隆按钮。这允许我们复制一个现有项目。为此,我们使用 JQuery 对现有对象进行深度复制到一个新对象中,首先我们从数组中获取现有项目,将其克隆到一个新对象中,将新对象添加到数组中并将更新写入本地存储。代码如下所示

function buttonClone_Click() {
    //Get the current selected item
    var original = templateStore[parseInt($("#selectTemplates option:selected").val())];
    
    //Perform a deep copy of the original using JQuery
    var copy = $.extend(true, {}, original);

    //Push it into the store at the end
    templateStore.push(copy);

    //save
    saveToLocalStorage();

    initEditorView();
}

过滤功能

版本 1.1 在编辑器表单和选择器表单上引入了过滤功能。这允许用户在有多种不同类型(消息/答案/片段)的项目时,向下筛选以处理项目。

选择器表单还具有自动过滤器(默认选择)。它的作用是检查用户正在浏览哪个 CodeProject 页面,并相应地过滤列表。如果用户在消息编辑器上,列表会自动过滤消息和片段。如果用户在问题页面上,则列表会自动过滤答案和片段。

通过将模板数组项索引作为选项的值属性存储在 HTML 标记中,并仅将相关项添加到列表中,实现过滤。然后读取该值以识别存储数组中的源索引。

我还需要知道什么?

标题字段是您自己用于识别模板项及其内容的可自由编辑文本,主题字段仅用于论坛消息,正文用于论坛消息、答案和片段。

还有其他吗?接下来是什么?

有关打包、部署和托管等详细信息,请参阅之前的扩展程序链接。

文章顶部的链接是未打包的源代码以及打包分发服务器的链接。如果您使用此功能,Chrome 将自动获取任何未来的更新。

未来

如果您有任何想法,或者发现任何问题,请在下面留言,我会尽力解决。

好了,暂时就到这里,希望您觉得这很有用。

参考链接

历史

  • 2014年8月29日 - 更新至V2,以支持新的Google政策要求。
  • 2013年10月23日 - 补充了关于在Chrome网上应用商店之外安装的注意事项
  • 2011年9月16日 - V1.1,添加了片段类型、过滤功能并修复了添加项错误
  • 2011年8月22日 - V1.0,首次发布
© . All rights reserved.