Firefox 关键词高亮插件的简单实现

目录

1、配置 manifest.json 文件

2、编写侧边栏结构

3、查找关键词并高亮的方法

3-1) 如果直接使用 innerHTML 进行替换

4、清除关键词高亮

5、页面脚本代码

6、参考


1、配置 manifest.json 文件

{
    "manifest_version": 2,
    "name": "key_word_plugin",
    "version": "1.0",
  
    "description": "find_key_word",
    // 添加权限
    "permissions":[
        "*://*/*",
        "activeTab"
    ],
    "icons": {
      "48": "icons/flower.jpg"
    },
  
    "content_scripts": [
        {
            "matches": ["*://*/*"],
            "js": ["index.js"],
            "run_at":"document_idle"
        }
    ],
    // 侧边栏
    "sidebar_action": {
        "default_title": "My tool",
        "default_panel": "./sidebar/sidebar.html",
        "default_icon": "./sidebar/sidebar_icon.png"
    },
    // 背景脚本
    "background": {
        "scripts": ["bg.js"],
        "persistent": false,
        "type": "module"
    }
}

 

2、编写侧边栏结构

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        /* 略 */
    </style>
    <link rel="stylesheet" href="./top_area.css">
</head>
<body>
    <div class="container">
        <!-- 关键词查找 -->
        <div class="top-area">
            <section class="inp-area">
                <input class="inp" type="text" maxlength="10">
                <button class="find-btn">查找</button>
            </section>
            <section class="result-area">
                <p>共找到</p>
                <p class="count">
                    <!-- 将查找到的结果条目数量写入此处 -->
                </p>
                <p>处;</p>
            </section>
            <section class="btn-area">
                <input type="number" step="1" min="1" class="goto-keyword-inp usable">
                <button class="usable goto-btn">跳转</button>
            </section>
            <section class="btn-area">
                <button class="usable last-btn">上一个</button>
                <button class="usable next-btn">下一个</button>
                <button class="clear">清除所有标记</button>
            </section>
        </div>
    </div>
</body>
<script src="keyword.js"></script>
</html>

        效果图 

 

3、查找关键词并高亮的方法

    // 获取当前激活的标签页面 使用 tabs 需要权限 activeTab,在 manifest 中配置
    browser.tabs.query({active: true, currentWindow: true}).then((logTabs,onError)=>{
        // 然后往当前页面中注入内容脚本,document将是当前页面的 document
        browser.tabs.executeScript({
            code:`
(function action(keyword, nodes){
    Array.from(nodes).forEach(node =>{
        let {nodeType, data : content} = node;
        if(nodeType === 3 && content.includes(keyword)){
            let parentNode = node.parentNode;
			let split_arr = node.data.trim().replaceAll(keyword,'-' + keyword + '-').split('-').filter(e => e);
            for(let item of split_arr){
                if(item === keyword){
                    let strong = document.createElement('strong')
                    strong.innerText = keyword;
                    strong.classList.add('${KEYWORD_CLASS_NAME}')
                    strong.style = '${__style}'
                    parentNode.insertBefore(strong, node)
                }else{
                    let text = document.createTextNode(item);
                    parentNode.insertBefore(text, node)
                }
            }
            parentNode.removeChild(node)
        }else if(nodeType === 1 && node.textContent.includes(keyword)){
            action(keyword, node.childNodes)
        }
    })
})('${keyword}', Array.from(document.body.childNodes).filter((e)=>{
    return (
        e.textContent.includes('${keyword}') &&
        e.tagName !== 'SCRIPT'
    )
}))
document.querySelectorAll('.__keyword_word__').length
            `
        }).then((onExecuted, onError)=>{
// onExecuted[0] 的内容就是document.querySelectorAll('.__keyword_word__').length的结果
            total = onExecuted[0]
        })
    })

        点击查找关键词后,页面脚本向当前的页面注入一段JavaScript代码。该代码包含一个立即执行的函数 和 一个关键词数量的获取。

        该立即执行的函数 action,接收一个 要匹配的关键词 keyword 和 当前搜索节点数组 nodes 作为参数。

       遍历每一个节点,取出节点的类型-->nodeType 和节点的文本内容 -->content。

        如果是纯文本节点,则该节点的 nodeType 为3,如果是元素节点,则为 1。

        如果有纯文本节点,并且该纯文本节点中的内容包含了关键词,那么构造出一个数组,使用该数组来区分非关键词内容和关键词内容,以及他们之间的位置关系。

let split_arr = content.trim()
    .replaceAll(keyword, '-' + keyword + '-')
    .split('-')
    .filter(e => e);

        如关键词为 我们 ,纯文本节点的内容为:

我们的征途是星辰大海,请和我们一起,永远相信美好的事情即将发生

        那么构造的数组为:

[ "我们",  "的征途是星辰大海,请和",  "我们",  "一起,永远相信美好的事情即将发生"

    if(nodeType === 3 && content.includes(keyword)){
            let parentNode = node.parentNode;
			let split_arr = content.trim()
                .replaceAll(keyword,'-' + keyword + '-')
                .split('-        ')
                .filter(e => e);
            for(let item of split_arr){
                if(item === keyword){
                    let strong = document.createElement('strong')
                    strong.innerText = keyword;
                    strong.classList.add('${KEYWORD_CLASS_NAME}')
                    strong.style = '${__style}'
                    parentNode.insertBefore(strong, node)
                }else{
                    let text = document.createTextNode(item);
                    parentNode.insertBefore(text, node)
                }
            }
            parentNode.removeChild(node)
        }

          遍历构造的数组中的内容,如果当前值等于关键词,那么构造一个强调标签 Strong 将关键词作为 innerText,并添加指定的样式和样式类名,然后加入到当前所遍历的节点之前;如果该当前值与关键词不相等,则直接构造一个文本节点,将其添加到当前所遍历的节点之前......

        当遍历完构造的数组后,将当前遍历的节点从其父节点中删除。这样就将纯文本节点中的内容全部高亮处理了。

        没有包含关键词的纯文本节点直接跳过。 

        如果该节点不是纯文本结点,那么判断其 textContent 中是否包含关键词,如果是,那么让其所有子节点再参与 action 处理。否则就不用继续递归。

3-1) 如果直接使用 innerHTML 进行替换

如果标签中的属性出现了关键词,则会出现标签结构混乱的问题:

原代码: 

<body>
    <div class="my_name">
        <img src="https://tse1-mm.cn.bing.net/th/id/OIP-C.duz6S7Fvygrqd6Yj_DcXAQHaF7?rs=1&pid=ImgDetMain" alt="我的图片">
        <p>我的图片</p>
        <div>
            你的图片
            <p>我们的图片</p>
            <span>都是</span>
            图片
        </div>
    </div>
    <script>
        document.body.innerHTML = document.body.innerHTML.replaceAll('图片','<strong style="color:red">图片</strong>')
    </script>
</body>

 

4、清除关键词高亮

browser.tabs.query({active: true, currentWindow: true}).then(()=>{
        browser.tabs.executeScript({
            code:`
(function action(keyword){
    document.querySelectorAll('.${KEYWORD_CLASS_NAME}').forEach(e =>{
        let parent = e.parentNode;
        let textNode = document.createTextNode(keyword);
        parent.replaceChild(textNode, e)
    })
})('${keyword}')
            `
        })
    })

        获取到所有 strong 强调标签(根据自定义的 class 名称),然后进行遍历,获取到每一个strong 的父元素。使用 createTextNode 创建一个纯文本节点,其内容就是关键词。然后将该文本节点替换掉 strong 标签即可。

5、页面脚本代码

// 简单封装document.querySelector
const getFirstEle = sign => document.querySelector(sign);

// 关键词
var KEYWORD = '';

// 总共找到多少处
var total = 0;  
const count_ele = getFirstEle('.count')
count_ele.innerText = '____'

const KEYWORD_CLASS_NAME = '__keyword_word__'
const __style = `color: #b60404; background-color: #f9f906; text-decoration: underline; text-decoration-style: double;`
var INDEX = null;               // 当前记录的关键词索引,用于跳转 [1 ~ total]

const find_btn = getFirstEle('.find-btn');
const clear_btn = getFirstEle('.clear');
const last_btn = getFirstEle('.last-btn');
const next_btn = getFirstEle('.next-btn');
const goto_keyword_inp = getFirstEle('.goto-keyword-inp')
const goto_btn = getFirstEle('.goto-btn')

// 控制关键词跳转是否可用
const usables = document.querySelectorAll('.usable');
const set_usable = (res)=>{ usables.forEach(e => { e.disabled = !res; }) }

// 默认不可用
set_usable(false);

// 点击查找关键词
find_btn.addEventListener('click', (e)=>{
    // 获取用户的输入
    let keyword = document.querySelector('.inp').value.trim()
    if(!keyword) return;

    // 获取上次的关键词
    let last_keyword = sessionStorage.getItem('_keyword_');

    // 如果上次查找的关键词存在并且与当前的关键词相等
    if(last_keyword && last_keyword === keyword){ return; }
    // 如果上次的关键词与当前的关键词不相等,那么页面的高亮没有被清理
    // 因为上次的关键词session中没有被清除。先清理页面残留
    else if(last_keyword && last_keyword !== keyword){
        clear_action(last_keyword, false, false, false)
    }

    // 更新关键词
    sessionStorage.setItem('_keyword_', keyword)
    KEYWORD = keyword;
    
    // 获取当前激活的标签页面 使用 tabs 需要权限 activeTab,在 manifest 中配置
    browser.tabs.query({active: true, currentWindow: true}).then((logTabs,onError)=>{
        // 然后往当前页面中注入内容脚本,document将是当前页面的 document
        browser.tabs.executeScript({
            code:`
(function action(keyword, nodes){
    Array.from(nodes).forEach(node =>{
        let {nodeType, textContent : content} = node;
        if(nodeType === 3 && content.includes(keyword)){
            let parentNode = node.parentNode;
			let split_arr = content.trim().replaceAll(keyword,'-' + keyword + '-').split('-').filter(e => e);
            for(let item of split_arr){
                if(item === keyword){
                    let strong = document.createElement('strong')
                    strong.innerText = keyword;
                    strong.classList.add('${KEYWORD_CLASS_NAME}')
                    strong.style = '${__style}'
                    parentNode.insertBefore(strong, node)
                }else{
                    let text = document.createTextNode(item);
                    parentNode.insertBefore(text, node)
                }
            }
            parentNode.removeChild(node)
        }else if(nodeType === 1 && content.includes(keyword)){
            action(keyword, node.childNodes)
        }
    })
})('${keyword}', Array.from(document.body.childNodes).filter((e)=>{
    return (
        e.textContent.includes('${keyword}') &&
        e.tagName !== 'SCRIPT'
    )
}))
document.querySelectorAll('.__keyword_word__').length
            `
        }).then((onExecuted, onError)=>{
            total = onExecuted[0]
            count_ele.innerText = total;
            // 开启跳转功能
            if(total > 0) set_usable(true);
        })
    })
})

// 点击清除按钮 回归页面原始的状态
clear_btn.addEventListener('click', ()=>{
    let keyword = sessionStorage.getItem('_keyword_');
    clear_action(keyword)
})

// 清除关键词标记
const clear_action = (keyword, clear_inp=true, clear_keyword_session=true, clear_count=true)=>{
    browser.tabs.query({active: true, currentWindow: true}).then(()=>{
        browser.tabs.executeScript({
            code:`
(function action(keyword){
    document.querySelectorAll('.${KEYWORD_CLASS_NAME}').forEach(e =>{
        let parent = e.parentNode;
        let textNode = document.createTextNode(keyword);
        parent.replaceChild(textNode, e)
    })
})('${keyword}')
            `
        })
    })
    if(clear_inp) document.querySelector('.inp').value = '';
    if(clear_keyword_session) sessionStorage.setItem('_keyword_', '');
    if(clear_count) count_ele.innerText = '_____';
    set_usable(false)
    KEYWORD = ''
    goto_keyword_inp.value = ''
}

// 跳转到上一个关键词位置
last_btn.addEventListener('click', ()=>{
    if(!INDEX) INDEX = 1;
    else if(INDEX <= 1 ) INDEX = total;
    else if(INDEX >= total) INDEX = total - 1;
    else INDEX --;
    goto_keyword_site(INDEX - 1, KEYWORD);
})

// 跳转到下一个关键词位置
next_btn.addEventListener('click', ()=>{
    if(!INDEX) INDEX = 1;
    else if(INDEX <= 1) INDEX = 2;
    else if(INDEX >= total) INDEX = 1;
    else INDEX ++;
    goto_keyword_site(INDEX - 1, KEYWORD);
})

// 跳转到指定的位置
goto_btn.addEventListener('click', ()=>{
    let index = parseInt(goto_keyword_inp.value)
    if(!index) return;
    if(index > total) index = total;
    else if(index < 1) index = 1;
    goto_keyword_site(index - 1)
    INDEX = index;
})


// 跳转到具体的关键词位置
const goto_keyword_site = (index) =>{
    goto_keyword_inp.value = index + 1;
    browser.tabs.query({active: true, currentWindow: true}).then((logTabs,onError)=>{
        // 然后往当前页面中注入内容脚本,document将是当前页面的 document
        browser.tabs.executeScript({
            code:`
document.querySelectorAll(".${KEYWORD_CLASS_NAME}")[${index}].scrollIntoView({
        behavior:'smooth'
})            
            `
        })
    })
}



6、参考

[1]: 扩展是什么? - Mozilla | MDN

[2]: Firefox插件(拓展)开发_火狐浏览器插件开发-CSDN博客 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/515219.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

配置zookeeper的时候三个节点都启动了但是查询zookeeper的角色的时候显示没启动成功

场景 搭建了一个音乐平台数仓&#xff0c;一共有五个节点&#xff0c;其中三个节点配置zookeeper&#xff0c;我的操作是先把这三个节点的zookeeper全部启动&#xff0c;然后再分别查询各自zookeeper的角色。出现了一下问题&#xff1a; Error contacting service. It is proba…

网络层

网络层主要负责两方面的事情 1.地址管理&#xff1a;制定一系列的规则&#xff0c;通过地址&#xff0c;描述出网络中的设备的位置 2.路由选择&#xff1a;网络环境是非常复杂的。从一个节点到另外一个节点之间&#xff0c;存在很多条不同的路径&#xff0c;通过路由选择来筛…

【nc工具信息传输】

nc&#xff0c;全名叫 netcat&#xff0c;它可以用来完成很多的网络功能&#xff0c;譬如端口扫描、建立TCP/UDP连接&#xff0c;数据传输、网络调试等等&#xff0c;因此&#xff0c;它也常被称为网络工具的 瑞士军刀 。 nc [-46DdhklnrStUuvzC] [-i interval] [-p source_po…

3.恒定乘积自动做市商算法及代码

中心化交易所的安全风险 在中心化交易所中注册账户时&#xff0c;是由交易所生成一个地址&#xff0c;用户可以向地址充币&#xff0c;充到地址之后交易所就会根据用户充币的数量显示在管理界面中。但是充币的地址是掌管在交易所之中的&#xff0c;资产的控制权还是在交易所。…

【Java多线程(4)】案例:设计模式

目录 一、什么是设计模式&#xff1f; 二、单例模式 1. 饿汉模式 2. 懒汉模式 懒汉模式-第一次改进 懒汉模式-第二次改进 懒汉模式-第三次改进 一、什么是设计模式&#xff1f; 设计模式是针对软件设计中常见问题的通用解决方案。它们提供了一种被广泛接受的方法来解决…

【2024系统架构设计】案例分析- 5 Web应用

目录 一 基础知识 二 真题 一 基础知识 1 Web应用技术分类 大型网站系统架构的演化:高性能、高可用、可维护、应变、安全。 从架构来看:MVC,MVP,MVVM,REST,Webservice,微服务。

从头开发一个RISC-V的操作系统(一)计算机系统漫游

文章目录 前提计算机的硬件组成程序的存储与执行操作系统 目标&#xff1a;通过这一个系列课程的学习&#xff0c;开发出一个简易的在RISC-V指令集架构上运行的操作系统。 前提 这个系列的大部分文章和知识来自于&#xff1a;[完结] 循序渐进&#xff0c;学习开发一个RISC-V上…

element-ui divider 组件源码分享

今日简单分享 divider 组件&#xff0c;主要有以下两个方面&#xff1a; 1、divider 组件页面结构 2、divider 组件属性 一、组件页面结构 二、组件属性 2.1 direction 属性&#xff0c;设置分割线方向&#xff0c;类型 string&#xff0c;horizontal / vertical&#xff0…

AtCoder Beginner Contest 347 A~F

AtCoder Beginner Contest 347 A~F - 知乎 (zhihu.com) Tasks - AtCoder Beginner Contest 347 A.Divisible(循环) 代码 #include<bits/stdc.h> using namespace std;void solve() {int n, k;cin >> n >> k;for (int i 0; i < n; i) {int a;cin >…

如何申请Telegram机器人 | 推送通知

一、前言 利用Telegram机器人推送通知&#xff0c;需要在环境变量填入正确的TG_BOT_TOKEN以及TG_USER_ID&#xff0c;以下教程简明阐述如何获取Token以及UserID 二、获取步骤 1、首先在Telegram上搜索BotFather机器人。需要注意的是&#xff0c;搜索结果中选择ID为BotFather…

基于向量数据库搭建自己的搜索引擎

前言【基于chatbot】 厌倦了商业搜索引擎搜索引擎没完没了的广告&#xff0c;很多时候&#xff0c;只是需要精准高效地检索信息&#xff0c;而不是和商业广告“斗智斗勇”。以前主要是借助爬虫工具&#xff0c;而随着技术的进步&#xff0c;现在有了更多更方便的解决方案&…

Maven--lib分离的打包方式

就是把lib包和source源码分开打包。优势就是&#xff0c;面对频繁更新的应用场景时&#xff0c;可以只更新源码包&#xff08;当然&#xff0c;前提是你的依赖没有增减&#xff09;。尤其是使用jenkins更新项目时&#xff0c;会省去很多时间吧&#xff1f; 不同项目的 lib之间不…

【C++练级之路】【Lv.18】哈希表(哈希映射,光速查找的魔法)

快乐的流畅&#xff1a;个人主页 个人专栏&#xff1a;《算法神殿》《数据结构世界》《进击的C》 远方有一堆篝火&#xff0c;在为久候之人燃烧&#xff01; 文章目录 引言一、哈希1.1 哈希概念1.2 哈希函数1.3 哈希冲突 二、闭散列2.1 数据类型2.2 成员变量2.3 默认成员函数2.…

保研线性代数复习3

一.基底&#xff08;Basis&#xff09; 1.什么是生成集&#xff08;Generating Set&#xff09;&#xff1f;什么是张成空间&#xff08;Span&#xff09;&#xff1f; 存在向量空间V(V&#xff0c;&#xff0c;*)&#xff0c;和向量集&#xff08;xi是所说的列向量&#xff…

【软件工程】概要设计

1. 导言 1.1 目的 该文档的目的是描述学生成绩管理系统的概要设计&#xff0c;其主要内容包括&#xff1a; 系统功能简介 系统结构简介 系统接口设计 数据设计 模块设计 界面设计 本文的预期读者是&#xff1a; 项目开发人员 项目管理人员 项目评测人员&#xff08;…

算法设计与分析实验报告python实现(串匹配问题、采用分治法求解最大连续子序列和问题、用分治策略求众数问题、最近点对问题)

一、 实验目的 1&#xff0e;加深学生对算法设计方法的基本思想、基本步骤、基本方法的理解与掌握&#xff1b; 2&#xff0e;提高学生利用课堂所学知识解决实际问题的能力&#xff1b; 3&#xff0e;提高学生综合应用所学知识解决实际问题的能力。 二、实验任务 1、串匹配问…

Docker安装mysql并且设置主从

Docker安装部署mysql 下载镜像 docker pull mysql:5.7.35查看镜像 docker images启动 直接启动不挂载文件 docker run -p 3306:3306 --name mysql -e MYSQL_ROOT_PASSWORD123456 -d mysql:5.7.35挂载文件 docker run -p 3306:3306 --name mysql \ -v /usr/local/docker/m…

正则表达式与JSON序列化:去除JavaScript对象中的下划线键名

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

【Java EE】关于Maven

文章目录 &#x1f38d;什么是Maven&#x1f334;为什么要学Maven&#x1f332;创建⼀个Maven项目&#x1f333;Maven核心功能&#x1f338;项目构建&#x1f338;依赖管理 &#x1f340;Maven Help插件&#x1f384;Maven 仓库&#x1f338;本地仓库&#x1f338;私服 ⭕总结 …

STM32G系 编程连接不上目标板,也有可能是软件不兼容。

由于一直用的老版本STM32 ST-LINK Utility 4.20 &#xff0c;找遍了所有问题&#xff0c;SWD就是连不上目标板。 电源脚 VDDA 地线&#xff0c;SWD的四条线&#xff0c;还是不行&#xff0c;浪费了一天&#xff0c;第二天才想起&#xff0c;是不是G系升级了 SWD协议。结果下载…