新增備份、恢復、右鍵替換詞語功能
This commit is contained in:
parent
db0a715fc8
commit
dc6c364f29
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 149 KiB |
12
readme.md
12
readme.md
|
|
@ -1,2 +1,14 @@
|
||||||
# 支語寶
|
# 支語寶
|
||||||
將所有網頁中的指定詞語替換為其他詞語
|
將所有網頁中的指定詞語替換為其他詞語
|
||||||
|
|
||||||
|
## 設定畫面
|
||||||
|
提供新增刪除,以及匯出匯入功能
|
||||||
|
|
||||||
|
## 使用方式
|
||||||
|
1. 可進入設定畫面新增詞彙
|
||||||
|

|
||||||
|
2. 反白想替換的文字,並選擇「替換詞語」
|
||||||
|

|
||||||
|
|
||||||
|
## 其他
|
||||||
|
所有程式碼皆為Chatgpt撰寫,並手動修改部分用詞
|
||||||
|
|
@ -8,4 +8,39 @@ chrome.runtime.onInstalled.addListener(() => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
// 創建右鍵選單
|
||||||
|
chrome.runtime.onInstalled.addListener(() => {
|
||||||
|
chrome.contextMenus.create({
|
||||||
|
id: "replaceWord",
|
||||||
|
title: "替換詞語",
|
||||||
|
contexts: ["selection"] // 僅在選取文字時顯示
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 處理選單點擊事件
|
||||||
|
chrome.contextMenus.onClicked.addListener((info, tab) => {
|
||||||
|
if (info.menuItemId === "replaceWord" && info.selectionText) {
|
||||||
|
const selectedText = info.selectionText;
|
||||||
|
// 提示用戶輸入替換詞語
|
||||||
|
chrome.scripting.executeScript({
|
||||||
|
target: { tabId: tab.id },
|
||||||
|
func: (selectedText) => {
|
||||||
|
const newWord = prompt(`將「${selectedText}」替換為:`, "");
|
||||||
|
return { selectedText, newWord };
|
||||||
|
},
|
||||||
|
args: [selectedText]
|
||||||
|
}, (results) => {
|
||||||
|
const { selectedText, newWord } = results[0].result;
|
||||||
|
if (newWord && newWord !== selectedText) {
|
||||||
|
// 儲存到 replacements 中
|
||||||
|
chrome.storage.local.get("replacements", (data) => {
|
||||||
|
const replacements = data.replacements || {};
|
||||||
|
replacements[selectedText] = newWord;
|
||||||
|
chrome.storage.local.set({ replacements });
|
||||||
|
alert(`已將「${selectedText}」替換為「${newWord}」`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -1,35 +1,42 @@
|
||||||
// 替換文字的函數
|
// 替換文字的函數
|
||||||
function replaceText(node, replacements) {
|
function replaceText(node, replacements) {
|
||||||
if (node.nodeType === Node.TEXT_NODE) {
|
if (node.nodeType === Node.TEXT_NODE) {
|
||||||
let text = node.nodeValue;
|
let text = node.nodeValue;
|
||||||
for (let [key, value] of Object.entries(replacements)) {
|
for (let [key, value] of Object.entries(replacements)) {
|
||||||
const regex = new RegExp(key, "g");
|
const regex = new RegExp(key, "g");
|
||||||
text = text.replace(regex, value);
|
text = text.replace(regex, value);
|
||||||
}
|
|
||||||
node.nodeValue = text;
|
|
||||||
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
|
||||||
node.childNodes.forEach((child) => replaceText(child, replacements));
|
|
||||||
}
|
}
|
||||||
|
node.nodeValue = text;
|
||||||
|
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
||||||
|
node.childNodes.forEach((child) => replaceText(child, replacements));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// 初始化替換
|
|
||||||
function initializeReplacements() {
|
// 初始化替換
|
||||||
chrome.storage.local.get("replacements", (data) => {
|
function initializeReplacements() {
|
||||||
const replacements = data.replacements || {};
|
chrome.storage.local.get("replacements", (data) => {
|
||||||
replaceText(document.body, replacements);
|
const replacements = data.replacements || {};
|
||||||
|
replaceText(document.body, replacements);
|
||||||
// 監控動態內容
|
|
||||||
const observer = new MutationObserver((mutations) => {
|
// 監控動態內容
|
||||||
mutations.forEach((mutation) => {
|
const observer = new MutationObserver((mutations) => {
|
||||||
mutation.addedNodes.forEach((node) => {
|
mutations.forEach((mutation) => {
|
||||||
if (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.TEXT_NODE) {
|
mutation.addedNodes.forEach((node) => {
|
||||||
replaceText(node, replacements);
|
if (node.nodeType === Node.ELEMENT_NODE || node.nodeType === Node.TEXT_NODE) {
|
||||||
}
|
replaceText(node, replacements);
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
observer.observe(document.body, { childList: true, subtree: true });
|
|
||||||
});
|
});
|
||||||
|
observer.observe(document.body, { childList: true, subtree: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 監聽 storage 更新,動態更新替換內容
|
||||||
|
chrome.storage.onChanged.addListener((changes) => {
|
||||||
|
if (changes.replacements) {
|
||||||
|
initializeReplacements();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
initializeReplacements();
|
|
||||||
|
initializeReplacements();
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "支語寶",
|
"name": "支語寶",
|
||||||
"version": "0.1.0 alpha",
|
"version": "0.1.0",
|
||||||
"description": "將所有網頁中的指定詞語替換為其他詞語。",
|
"description": "將所有網頁中的指定詞語替換為其他詞語。",
|
||||||
"permissions": ["storage", "activeTab", "scripting"],
|
"permissions": ["storage", "activeTab", "scripting", "contextMenus"],
|
||||||
"host_permissions": ["<all_urls>"],
|
"host_permissions": ["<all_urls>"],
|
||||||
"background": {
|
"background": {
|
||||||
"service_worker": "background.js"
|
"service_worker": "background.js"
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
<title>支語寶</title>
|
<title>支語寶</title>
|
||||||
<style>
|
<style>
|
||||||
body { font-family: Arial, sans-serif; padding: 20px; }
|
body { font-family: Arial, sans-serif; padding: 20px; }
|
||||||
table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
|
table { width: 600px; border-collapse: collapse; margin-bottom: 20px; }
|
||||||
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
||||||
th { background-color: #f4f4f4; }
|
th { background-color: #f4f4f4; }
|
||||||
button { margin-right: 10px; }
|
button { margin-right: 10px; }
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>支語寶</h1>
|
<h1>支語寶</h1>
|
||||||
|
<h2>設定詞語</h2>
|
||||||
<table id="replacementsTable">
|
<table id="replacementsTable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
@ -27,6 +28,11 @@
|
||||||
<input type="text" id="original" placeholder="原始詞語">
|
<input type="text" id="original" placeholder="原始詞語">
|
||||||
<input type="text" id="replacement" placeholder="替換詞語">
|
<input type="text" id="replacement" placeholder="替換詞語">
|
||||||
<button id="addButton">新增</button>
|
<button id="addButton">新增</button>
|
||||||
|
<br /><br />
|
||||||
|
<h2>備份/恢復</h2>
|
||||||
|
<button id="exportButton">匯出</button>
|
||||||
|
<input type="file" id="importFile" style="display: none;" />
|
||||||
|
<button id="importButton">匯入</button>
|
||||||
|
|
||||||
<script src="options.js"></script>
|
<script src="options.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
159
src/options.js
159
src/options.js
|
|
@ -1,58 +1,105 @@
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
const tableBody = document.querySelector("#replacementsTable tbody");
|
const tableBody = document.querySelector("#replacementsTable tbody");
|
||||||
const originalInput = document.getElementById("original");
|
const originalInput = document.getElementById("original");
|
||||||
const replacementInput = document.getElementById("replacement");
|
const replacementInput = document.getElementById("replacement");
|
||||||
const addButton = document.getElementById("addButton");
|
const addButton = document.getElementById("addButton");
|
||||||
|
const exportButton = document.getElementById("exportButton");
|
||||||
// 載入詞語對
|
const importButton = document.getElementById("importButton");
|
||||||
function loadReplacements() {
|
const importFileInput = document.getElementById("importFile");
|
||||||
chrome.storage.local.get("replacements", (data) => {
|
|
||||||
const replacements = data.replacements || {};
|
// 載入詞語對
|
||||||
tableBody.innerHTML = "";
|
function loadReplacements() {
|
||||||
for (const [key, value] of Object.entries(replacements)) {
|
chrome.storage.local.get("replacements", (data) => {
|
||||||
addRow(key, value);
|
const replacements = data.replacements || {};
|
||||||
}
|
tableBody.innerHTML = "";
|
||||||
});
|
for (const [key, value] of Object.entries(replacements)) {
|
||||||
}
|
addRow(key, value);
|
||||||
|
}
|
||||||
// 新增表格行
|
|
||||||
function addRow(original, replacement) {
|
|
||||||
const row = document.createElement("tr");
|
|
||||||
row.innerHTML = `
|
|
||||||
<td>${original}</td>
|
|
||||||
<td>${replacement}</td>
|
|
||||||
<td>
|
|
||||||
<button class="deleteButton">刪除</button>
|
|
||||||
</td>
|
|
||||||
`;
|
|
||||||
row.querySelector(".deleteButton").addEventListener("click", () => {
|
|
||||||
deleteReplacement(original);
|
|
||||||
});
|
|
||||||
tableBody.appendChild(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增詞語對
|
|
||||||
addButton.addEventListener("click", () => {
|
|
||||||
const original = originalInput.value.trim();
|
|
||||||
const replacement = replacementInput.value.trim();
|
|
||||||
if (!original || !replacement) return alert("請輸入有效的詞語!");
|
|
||||||
chrome.storage.local.get("replacements", (data) => {
|
|
||||||
const replacements = data.replacements || {};
|
|
||||||
replacements[original] = replacement;
|
|
||||||
chrome.storage.local.set({ replacements }, loadReplacements);
|
|
||||||
});
|
|
||||||
originalInput.value = "";
|
|
||||||
replacementInput.value = "";
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
// 刪除詞語對
|
|
||||||
function deleteReplacement(original) {
|
// 新增表格行
|
||||||
chrome.storage.local.get("replacements", (data) => {
|
function addRow(original, replacement) {
|
||||||
const replacements = data.replacements || {};
|
const row = document.createElement("tr");
|
||||||
delete replacements[original];
|
row.innerHTML = `
|
||||||
chrome.storage.local.set({ replacements }, loadReplacements);
|
<td>${original}</td>
|
||||||
});
|
<td>${replacement}</td>
|
||||||
}
|
<td>
|
||||||
|
<button class="deleteButton">刪除</button>
|
||||||
loadReplacements();
|
</td>
|
||||||
});
|
`;
|
||||||
|
row.querySelector(".deleteButton").addEventListener("click", () => {
|
||||||
|
deleteReplacement(original);
|
||||||
|
});
|
||||||
|
tableBody.appendChild(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增詞語對
|
||||||
|
addButton.addEventListener("click", () => {
|
||||||
|
const original = originalInput.value.trim();
|
||||||
|
const replacement = replacementInput.value.trim();
|
||||||
|
if (!original || !replacement) return alert("請輸入有效的詞語!");
|
||||||
|
chrome.storage.local.get("replacements", (data) => {
|
||||||
|
const replacements = data.replacements || {};
|
||||||
|
replacements[original] = replacement;
|
||||||
|
chrome.storage.local.set({ replacements }, loadReplacements);
|
||||||
|
});
|
||||||
|
originalInput.value = "";
|
||||||
|
replacementInput.value = "";
|
||||||
|
});
|
||||||
|
|
||||||
|
// 刪除詞語對
|
||||||
|
function deleteReplacement(original) {
|
||||||
|
chrome.storage.local.get("replacements", (data) => {
|
||||||
|
const replacements = data.replacements || {};
|
||||||
|
delete replacements[original];
|
||||||
|
chrome.storage.local.set({ replacements }, loadReplacements);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 匯出詞語對
|
||||||
|
exportButton.addEventListener("click", () => {
|
||||||
|
chrome.storage.local.get("replacements", (data) => {
|
||||||
|
const replacements = data.replacements || {};
|
||||||
|
const blob = new Blob([JSON.stringify(replacements, null, 2)], { type: "application/json" });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = url;
|
||||||
|
a.download = "replacements.json";
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 匯入詞語對
|
||||||
|
importButton.addEventListener("click", () => {
|
||||||
|
importFileInput.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
importFileInput.addEventListener("change", (event) => {
|
||||||
|
const file = event.target.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
try {
|
||||||
|
const importedReplacements = JSON.parse(e.target.result);
|
||||||
|
if (typeof importedReplacements !== "object") throw new Error();
|
||||||
|
|
||||||
|
chrome.storage.local.get("replacements", (data) => {
|
||||||
|
const currentReplacements = data.replacements || {};
|
||||||
|
const mergedReplacements = { ...currentReplacements, ...importedReplacements };
|
||||||
|
chrome.storage.local.set({ replacements: mergedReplacements }, loadReplacements);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
alert("匯入失敗,請確保檔案格式正確!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsText(file);
|
||||||
|
importFileInput.value = ""; // 重置 input,允許重複上傳同一檔案
|
||||||
|
});
|
||||||
|
|
||||||
|
loadReplacements();
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue