From cf3ca9c9304cc5ea06e928ab1c969458c2465f75 Mon Sep 17 00:00:00 2001 From: Simeon Vincent Date: Mon, 12 Jul 2021 15:52:23 -0700 Subject: [PATCH 1/3] Text Replacer example --- README.md | 31 ++++-- examples/text-replacer/background.js | 29 +++++ examples/text-replacer/content.js | 47 ++++++++ "examples/text-replacer/icons/Icon\r" | 0 examples/text-replacer/icons/Icon24.png | Bin 0 -> 764 bytes examples/text-replacer/icons/icon128.png | Bin 0 -> 4185 bytes examples/text-replacer/icons/icon16.png | Bin 0 -> 490 bytes examples/text-replacer/icons/icon19.png | Bin 0 -> 578 bytes examples/text-replacer/icons/icon32.png | Bin 0 -> 1045 bytes examples/text-replacer/icons/icon48.png | Bin 0 -> 1648 bytes examples/text-replacer/icons/icon64.png | Bin 0 -> 2147 bytes examples/text-replacer/manifest.json | 34 ++++++ examples/text-replacer/popup.css | 91 ++++++++++++++++ examples/text-replacer/popup.html | 80 ++++++++++++++ examples/text-replacer/popup.js | 130 +++++++++++++++++++++++ 15 files changed, 436 insertions(+), 6 deletions(-) create mode 100644 examples/text-replacer/background.js create mode 100644 examples/text-replacer/content.js create mode 100644 "examples/text-replacer/icons/Icon\r" create mode 100644 examples/text-replacer/icons/Icon24.png create mode 100644 examples/text-replacer/icons/icon128.png create mode 100644 examples/text-replacer/icons/icon16.png create mode 100644 examples/text-replacer/icons/icon19.png create mode 100644 examples/text-replacer/icons/icon32.png create mode 100644 examples/text-replacer/icons/icon48.png create mode 100644 examples/text-replacer/icons/icon64.png create mode 100644 examples/text-replacer/manifest.json create mode 100644 examples/text-replacer/popup.css create mode 100644 examples/text-replacer/popup.html create mode 100644 examples/text-replacer/popup.js diff --git a/README.md b/README.md index 581c2dccaf..1a0d13279e 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,20 @@ Read more on [Getting Started](https://developer.chrome.com/extensions/getstarte + + + My Bookmarks
+ examples/bookmarks + + + + + Page Redder
@@ -56,15 +70,20 @@ Read more on [Getting Started](https://developer.chrome.com/extensions/getstarte - My Bookmarks
- examples/bookmarks + Text Replacer
+ examples/text-replacer diff --git a/examples/text-replacer/background.js b/examples/text-replacer/background.js new file mode 100644 index 0000000000..4e51282679 --- /dev/null +++ b/examples/text-replacer/background.js @@ -0,0 +1,29 @@ +chrome.commands.onCommand.addListener((command, tab) => { + if (command == 'replace-text') { + replaceText(tab.id); + } +}); + +chrome.runtime.onInstalled.addListener(() => { + registerContextMenus(); +}); + +function registerContextMenus() { + chrome.contextMenus.create({ + id: 'replace-text-menuitem', + title: 'Replace text', + }); +} + +chrome.contextMenus.onClicked.addListener((info, tab) => { + if (info.menuItemId == 'replace-text-menuitem') { + replaceText(tab.id); + } +}); + +function replaceText(tabId) { + chrome.scripting.executeScript({ + target: {tabId}, + files: ['content.js'], + }); +} diff --git a/examples/text-replacer/content.js b/examples/text-replacer/content.js new file mode 100644 index 0000000000..98d92bcdba --- /dev/null +++ b/examples/text-replacer/content.js @@ -0,0 +1,47 @@ +// Copyright 2021 Google LLC +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +// Replace text on the page using a static list of patterns +function textReplacer(replacements) { + const replacementPatterns = buildReplacementRegex(replacements); + replaceText(replacementPatterns); +} + +function buildReplacementRegex(source) { + const output = []; + for (var i = 0; i < source.length; i++) { + if (!source[i]) { continue; } + const [find, replace] = source[i]; + const sanitizedMatch = escapeRegExp(find); + const findExp = new RegExp(`\\b${sanitizedMatch}\\b`, 'gi'); + output[i] = [findExp, replace]; + } + return output; +} + +// Use var to avoid "Identifier 'REGEXP_SPECIAL_CHARACTERS' has already been +// declared" errors when running multiple times on the same page. +var REGEXP_SPECIAL_CHARACTERS = /[.(){}^$*+?[\]\\]/g; +/** Sanitize user input to prevent unexpected behavior during RegExp execution */ +function escapeRegExp(pattern) { + return pattern.replace(REGEXP_SPECIAL_CHARACTERS, "\\$&") +} + +/** Iterate through all text nodes and replace */ +function replaceText(replacements) { + let node; + const nodeIterator = document.createNodeIterator(document.body, NodeFilter.SHOW_TEXT); + while (node = nodeIterator.nextNode()) { + for (let [find, replace] of replacements) { + node.nodeValue = node.nodeValue.replace(find, replace); + } + } +} + +// Replace text on the page using a list of patterns loaded from storage +chrome.storage.sync.get(['patterns'], function(data) { + textReplacer(data.patterns); +}); diff --git "a/examples/text-replacer/icons/Icon\r" "b/examples/text-replacer/icons/Icon\r" new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/text-replacer/icons/Icon24.png b/examples/text-replacer/icons/Icon24.png new file mode 100644 index 0000000000000000000000000000000000000000..da0aab2e79c9c1d45ee993cde7520c21a782ad49 GIT binary patch literal 764 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 MJyYE#9jBQ80dwEK@Bjb+ literal 0 HcmV?d00001 diff --git a/examples/text-replacer/icons/icon16.png b/examples/text-replacer/icons/icon16.png new file mode 100644 index 0000000000000000000000000000000000000000..754be60cc62c03fb4d7118860d7b5f6c0c9fe807 GIT binary patch literal 490 zcmVPx$q)9|UR5%f(l1)njQ51&Xv0_Ze(aK!aQbBA}Zi7Nlawi4qqFnfaAfynqXwlDT z(Jx2>SN0oP=uX_URY`H7O+wSqe01kVwGCW9 z9}2;p$$D4`|v{H`mDtoCgQgKPZ>!ZPCDec7{QvGP8JjdmEcqR}fPvXp+R> zmKQMZKZZ>dA;jZ&dwhi2?b58d0JFH3Usx-ZVBgt6pWMHXhiD?iVlY${GU-0LxnV;7 zXt1(^_49K$qfyxR_I}rc4+NkNhmh{>;3*W~I6P!=1`tT0HX7me{vPghnk9T2hN3_x z`=Ap6_3`-I4F)jPRaz~4^m=e565x2AC7FhOcNgx{QyAoXby;RH3Rv=b;fzGUkug@o zVVK>7qa&7N8k2gRE|Jo1Lp(laF$xgLg2;^qT_AIb#<#Z+|BT_iyoA5g0Ur!Ps#Y0b g;csLbFyx%ZC)T%#XH3bx{{R3007*qoM6N<$f}o1u7ytkO literal 0 HcmV?d00001 diff --git a/examples/text-replacer/icons/icon19.png b/examples/text-replacer/icons/icon19.png new file mode 100644 index 0000000000000000000000000000000000000000..7452a65854e1d1d6cb9252bd7c3039c0cd3a8f05 GIT binary patch literal 578 zcmV-I0=@l-P)Px${7FPXR5%f}lfO$sQ546&@AcFqpG%;aB%#(&EkW5{Lxn>&M2l;yLnQqVEe__Q zO*Ix#G>EQ2PC>LpG)QWc4HnNz%^$COFFfn^tU>I+PffuzIf;6qfZEj+>UVd)h6o*xDT)}}+{D2AJl@aFpw?;(U>YBX(c?jJ zYYUZ3MhDepCK!!y?(ai6JVb6`K^I_fyD_x8i()v8>cs`5;bAoLd8X54X5Ze%`}sM_ z$HzL5wjep3a3zxrQa(Asb3D#m+yfX!MnHG|6GSp`M5B=GcJxnAqfDTh=`s_oR|q{5p|(Z`YkRsF@W*tyFjg$W9*O8ttfUN1}&6Htzh@Rm-)O2D0rmW1c}8fvqN*Hns=v@?@wFcCOnF_>p&U?Bx7n9ZD+ zP6}&l$ohO}K0RS@V}tX5!2$u4Pfwv79H6wb1E?wka%wW69U{>CBo89lY@D056K!OA z%==&v^=y_2%(JtQ=>|VKfwmeN4OA~LQ6k0r-Ttr6zVFh_xP52c1pkcr1l`u6`!#z> Q8~^|S07*qoM6N<$g2|=>=>Px# literal 0 HcmV?d00001 diff --git a/examples/text-replacer/icons/icon32.png b/examples/text-replacer/icons/icon32.png new file mode 100644 index 0000000000000000000000000000000000000000..de99ca80d08b84fec09608e1c4789d3a0d9f138f GIT binary patch literal 1045 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 Z^nYUYs(Z(hQXv2U002ovPDHLkV1mk_{qX<* literal 0 HcmV?d00001 diff --git a/examples/text-replacer/manifest.json b/examples/text-replacer/manifest.json new file mode 100644 index 0000000000..dff14e1fff --- /dev/null +++ b/examples/text-replacer/manifest.json @@ -0,0 +1,34 @@ +{ + "name": "Text Replacer", + "version": "1.0.0", + "manifest_version": 3, + "icons": { + "32": "icons/icon32.png", + "48": "icons/icon48.png", + "64": "icons/icon64.png" + }, + "action": { + "default_title": "Show an alert", + "default_icon": { + "16": "icons/icon16.png", + "24": "icons/icon24.png", + "32": "icons/icon32.png" + }, + "default_popup": "popup.html" + }, + "permissions": [ + "scripting", + "activeTab", + "storage", + "commands", + "contextMenus" + ], + "background": { + "service_worker": "background.js" + }, + "commands": { + "replace-text": { + "description": "Replace text on the current page." + } + } +} diff --git a/examples/text-replacer/popup.css b/examples/text-replacer/popup.css new file mode 100644 index 0000000000..fc57133313 --- /dev/null +++ b/examples/text-replacer/popup.css @@ -0,0 +1,91 @@ +/* +Copyright 2021 Google LLC + +Use of this source code is governed by a BSD-style +license that can be found in the LICENSE file or at +https://developers.google.com/open-source/licenses/bsd +*/ + +html, body { + height: 200px; + width: 400px; + margin: 0; + padding: 0; + font-size: 16px; +} + +body { + padding-bottom: 2em; +} + +.form--table { + width: 100%; + border-collapse: collapse; +} + +.form--input { + width: 100%; + padding: .25rem .5rem; + background: #eee; + box-sizing: border-box; + border: 1px solid #0004; + line-height: 1.5; +} + +th { + text-transform: uppercase; + font-size: .7rem; + color: hsl(0, 0%, 30%); +} + +td { + padding: .25rem .1rem; +} + +td:first-child { + padding-left: .25rem; +} +td:last-child { + padding-right: .25rem; +} + +.form--controls { + position: fixed; + display: flex; + bottom: 0; + right: 0; + left: 0; +} +.form--controls > * { + flex: 1; +} + +.form--controls > * { + border: none; + height: 2rem; + margin: .1rem; + cursor: pointer; + border: 1px solid hsla(0, 0%, 0%, .2); +} + +.form--controls > *:first-child { + margin-left: .25rem; +} +.form--controls > *:last-child { + margin-right: .25rem; +} + +#clear { + background: hsl(0, 0%, 100%); +} +#clear:hover, +#clear:focus { + background: hsl(0, 0%, 80%); +} +#submit { + background: hsl(190, 100%, 60%); +} +#submit:hover, +#submit:focus { + background: hsl(190, 80%, 50%); +} diff --git a/examples/text-replacer/popup.html b/examples/text-replacer/popup.html new file mode 100644 index 0000000000..1c4820501d --- /dev/null +++ b/examples/text-replacer/popup.html @@ -0,0 +1,80 @@ + + + + + + + + Document + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FindReplace
+ + + +
+ + + +
+ + + +
+ + + +
+ + + +
+ +
+ + +
+
+ + + + + diff --git a/examples/text-replacer/popup.js b/examples/text-replacer/popup.js new file mode 100644 index 0000000000..7adb45ad02 --- /dev/null +++ b/examples/text-replacer/popup.js @@ -0,0 +1,130 @@ +// Copyright 2021 Google LLC +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +const form = document.querySelector('form'); + +load(['patterns']) + .then(data => data.patterns) + .then(loadFormData); + +form.addEventListener('submit', async (event) => { + event.preventDefault(); + await saveFormData(); + + let currentTab = await getCurrentTab(); + chrome.scripting.executeScript({ + target: {tabId: currentTab.id}, + files: ['content.js'] + }); +}); + +document.getElementById('clear').addEventListener('click', (event) => { + form.querySelectorAll('input[type=text]').forEach(el => el.value = ''); + saveFormData(); +}); + +form.addEventListener('input', debounce(saveFormData, 250)); + +/** + * Populate the form with values from persistent storage. + */ +function loadFormData(patterns) { + const inputs = form.querySelectorAll('input[type=text]'); + const flatPatterns = patterns.flat(); + + inputs.forEach((input, index) => { + input.value = flatPatterns[index] || ''; + }); +} + +/** + * Write the form values to persistent storage. + */ +function saveFormData() { + const inputs = form.querySelectorAll('input[type=text]'); + + const patterns = [...inputs].reduce((acc, input, index) => { + const outerIndex = index >> 1; + const innerIndex = index % 2; + + if (innerIndex === 0) { + // Set the "find" value + acc[outerIndex] = [input.value, '']; + } else { + // Set the "replace" value + acc[outerIndex][innerIndex] = input.value; + } + + return acc; + }, []); + + return save({patterns}); +} + +/** + * Limits how often the supplied callback function will be called. + * + * @see https://developers.google.com/web/fundamentals/performance/rendering/debounce-your-input-handlers + * + * @param {function} fn Callback function that you want to debounce. + * @param {number} wait The amount of time to wait before calling the function. + */ +function debounce(fn, wait = 100) { + let timeout; + return (...args) => { + clearTimeout(timeout); + timeout = setTimeout(() => { + timeout = null; + fn.apply(this, args); + }, wait); + } +} + +/** + * Fetch the currently active tab. + * + * @returns chrome.tabs.Tab instance + */ +async function getCurrentTab() { + let queryOptions = { active: true, currentWindow: true }; + let [tab] = await chrome.tabs.query(queryOptions); + return tab; +} + + +/** + * Minimal promise wrapper for chrome.storage.sync.set(). + * + * @param {object} data Object containing key-value pairs of data to persist. + */ +async function save(data) { + return new Promise((resolve, reject) => { + chrome.storage.sync.set(data, (result) => { + if (chrome.runtime.lastError) { + reject(chrome.runtime.lastError); + } else { + resolve(result); + } + }); + }); +} + +/** + * Minimal promise wrapper for chrome.storage.sync.get(). + * + * @param {string[]} keys Array of keys to retrieve from storage. + */ +function load(keys) { + return new Promise((resolve, reject) => { + chrome.storage.sync.get(keys, (data) => { + if (chrome.runtime.lastError) { + reject(chrome.runtime.lastError); + } else { + resolve(data); + } + }); + }); +} From 0bc7caae29d042fee9f5ffafe6c7b473e3926279 Mon Sep 17 00:00:00 2001 From: Simeon Vincent Date: Mon, 12 Jul 2021 16:26:28 -0700 Subject: [PATCH 2/3] Add basic description --- examples/text-replacer/manifest.json | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/text-replacer/manifest.json b/examples/text-replacer/manifest.json index dff14e1fff..4ca9d1ac92 100644 --- a/examples/text-replacer/manifest.json +++ b/examples/text-replacer/manifest.json @@ -2,6 +2,7 @@ "name": "Text Replacer", "version": "1.0.0", "manifest_version": 3, + "description": "Replace any line of text with another line.", "icons": { "32": "icons/icon32.png", "48": "icons/icon48.png", From e35f5c0a59face6ac7d12a80de5093c2048e00bc Mon Sep 17 00:00:00 2001 From: Simeon Vincent Date: Mon, 12 Jul 2021 16:26:55 -0700 Subject: [PATCH 3/3] Add basic description --- examples/text-replacer/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/text-replacer/manifest.json b/examples/text-replacer/manifest.json index 4ca9d1ac92..90468c2149 100644 --- a/examples/text-replacer/manifest.json +++ b/examples/text-replacer/manifest.json @@ -2,7 +2,7 @@ "name": "Text Replacer", "version": "1.0.0", "manifest_version": 3, - "description": "Replace any line of text with another line.", + "description": "Replace any a line of text on a page with another line of text.", "icons": { "32": "icons/icon32.png", "48": "icons/icon48.png",