// ==UserScript==
// @name 随手记
// @namespace http://tampermonkey.net/
// @version 1.0.0
// @description 随手记 用来记录网站常用数据减少查找时间
// @author wangshiwei
// @match https://*/*
// @match http://*/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant GM_setValue
// @grant GM_getValue
// ==/UserScript==
(function() {
'use strict';
const defaultData = [
{
name: 'test',
value: 'test',
note: 'test'
}
];
const defaultAllowedDomains = [];
function getAllowedDomains() {
const saved = GM_getValue('planMappingAllowedDomains', null);
if (saved) {
return JSON.parse(saved);
}
return defaultAllowedDomains;
}
function saveAllowedDomains(domains) {
GM_setValue('planMappingAllowedDomains', JSON.stringify(domains));
}
function getData() {
const saved = GM_getValue('planMappingData', null);
if (saved) {
return JSON.parse(saved);
}
GM_setValue('planMappingData', JSON.stringify(defaultData));
return defaultData;
}
function parseArrayData(inputText) {
try {
const data = JSON.parse(inputText);
if (!Array.isArray(data)) {
throw new Error('数据必须是数组格式');
}
const validData = data.filter(item => {
return item &&
typeof item === 'object' &&
item.name &&
item.value &&
item.note;
});
if (validData.length === 0) {
throw new Error('未找到有效的数据项。每项必须包含 name、value 和 note 字段');
}
if (validData.length < data.length) {
console.warn(`部分数据无效,已过滤 ${data.length - validData.length} 条无效数据`);
}
return validData;
} catch (error) {
try {
let cleaned = inputText.trim();
const arrayMatch = cleaned.match(/\[[\s\S]*\]/);
if (arrayMatch) {
cleaned = arrayMatch[0];
}
const parsedData = new Function('return ' + cleaned)();
if (!Array.isArray(parsedData)) {
throw new Error('数据必须是数组格式');
}
const validData = parsedData.filter(item => {
return item &&
typeof item === 'object' &&
item.name &&
item.value &&
item.note;
});
if (validData.length === 0) {
throw new Error('未找到有效的数据项。每项必须包含 name、value 和 note 字段');
}
return validData;
} catch (parseError) {
throw new Error(`解析失败: ${error.message || parseError.message}`);
}
}
}
function saveData(data) {
GM_setValue('planMappingData', JSON.stringify(data));
}
function copyToClipboard(text) {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
return true;
} catch (err) {
console.error('复制失败:', err);
return false;
} finally {
document.body.removeChild(textarea);
}
}
function createTable(data, onDelete) {
const tableWrapper = document.createElement('div');
tableWrapper.style.cssText = `
position: relative;
width: 100%;
margin-top: 8px;
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
background-color: white;
isolation: isolate;
`;
const scrollContainer = document.createElement('div');
scrollContainer.style.cssText = `
height: 360px;
overflow-y: auto;
overflow-x: hidden;
position: relative;
`;
const style = document.createElement('style');
style.textContent = `
#plan-mapping-modal [data-table-wrapper] > div::-webkit-scrollbar {
width: 8px;
}
#plan-mapping-modal [data-table-wrapper] > div::-webkit-scrollbar-track {
background: #f1f1f1;
}
#plan-mapping-modal [data-table-wrapper] > div::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
#plan-mapping-modal [data-table-wrapper] > div::-webkit-scrollbar-thumb:hover {
background: #555;
}
#plan-mapping-modal [data-table-wrapper] thead {
position: sticky !important;
top: 0 !important;
z-index: 100 !important;
background-color: #f2f2f2 !important;
}
#plan-mapping-modal [data-table-wrapper] thead th {
background-color: #f2f2f2 !important;
position: relative !important;
z-index: 101 !important;
box-shadow: 0 2px 0 #f2f2f2;
}
#plan-mapping-modal [data-table-wrapper] thead tr {
background-color: #f2f2f2 !important;
position: relative !important;
z-index: 100 !important;
}
#plan-mapping-modal [data-table-wrapper] tbody {
position: relative;
z-index: 1;
}
#plan-mapping-modal [data-table-wrapper] tbody tr {
position: relative;
z-index: 1;
}
#plan-mapping-modal [data-table-wrapper] tbody td {
position: relative;
z-index: 1;
}
#plan-mapping-modal [data-table-wrapper] tbody button {
position: relative;
z-index: 1;
}
#plan-mapping-modal [data-table-wrapper] thead {
box-shadow: 0 2px 4px rgba(242, 242, 242, 1);
}
#plan-mapping-modal [data-table-wrapper] thead tr {
box-shadow: 0 2px 4px rgba(242, 242, 242, 1);
}
#plan-mapping-modal [data-table-wrapper] table {
background-color: white;
}
#plan-mapping-modal [data-table-wrapper] tbody td {
background-color: inherit;
}
#plan-mapping-modal [data-table-wrapper] tbody {
display: table-row-group;
}
#plan-mapping-modal [data-table-wrapper] table {
display: table;
}
`;
if (!document.getElementById('plan-mapping-scrollbar-style')) {
style.id = 'plan-mapping-scrollbar-style';
document.head.appendChild(style);
}
const table = document.createElement('table');
table.style.cssText = `
width: 100%;
border-collapse: collapse;
margin: 0;
background-color: white;
table-layout: fixed;
`;
const thead = document.createElement('thead');
thead.style.cssText = `
position: sticky;
top: 0;
z-index: 100;
background-color: #f2f2f2;
display: table-header-group;
`;
const headerRow = document.createElement('tr');
headerRow.style.cssText = `
background-color: #f2f2f2 !important;
position: relative;
z-index: 100;
box-shadow: 0 2px 4px rgba(242, 242, 242, 1);
`;
const headers = ['名称', '值', '备注', '操作'];
headers.forEach(headerText => {
const th = document.createElement('th');
th.textContent = headerText;
th.style.cssText = `
padding: 12px;
text-align: left;
border: 1px solid #ddd;
background-color: #f2f2f2 !important;
position: relative;
z-index: 101;
box-shadow: 0 2px 0 #f2f2f2;
`;
headerRow.appendChild(th);
});
thead.appendChild(headerRow);
table.appendChild(thead);
const tbody = document.createElement('tbody');
tbody.style.cssText = `
position: relative;
z-index: 1;
min-height: 300px;
`;
if (data.length === 0) {
const emptyRow = document.createElement('tr');
emptyRow.style.cssText = `
height: 300px;
`;
const emptyCell = document.createElement('td');
emptyCell.colSpan = 4;
emptyCell.style.cssText = `
padding: 0;
text-align: center;
vertical-align: middle;
border: none;
height: 300px;
`;
const emptyState = document.createElement('div');
emptyState.style.cssText = `
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #999;
`;
const emptyIcon = document.createElement('div');
emptyIcon.style.cssText = `
font-size: 48px;
margin-bottom: 16px;
opacity: 0.5;
`;
emptyIcon.textContent = '📭';
const emptyText = document.createElement('div');
emptyText.style.cssText = `
font-size: 14px;
color: #999;
`;
emptyText.textContent = '暂无数据,请点击"导入数据"或"+ 新增"添加';
emptyState.appendChild(emptyIcon);
emptyState.appendChild(emptyText);
emptyCell.appendChild(emptyState);
emptyRow.appendChild(emptyCell);
tbody.appendChild(emptyRow);
} else {
data.forEach((item, index) => {
const row = document.createElement('tr');
row.style.backgroundColor = index % 2 === 0 ? '#fff' : '#f9f9f9';
row.style.position = 'relative';
row.style.zIndex = '1';
const td1 = document.createElement('td');
td1.textContent = item.name;
td1.style.padding = '10px';
td1.style.border = '1px solid #ddd';
row.appendChild(td1);
const td2 = document.createElement('td');
td2.textContent = item.value;
td2.style.padding = '10px';
td2.style.border = '1px solid #ddd';
row.appendChild(td2);
const td3 = document.createElement('td');
td3.textContent = item.note;
td3.style.padding = '10px';
td3.style.border = '1px solid #ddd';
row.appendChild(td3);
const td4 = document.createElement('td');
td4.style.padding = '10px';
td4.style.border = '1px solid #ddd';
td4.style.position = 'relative';
td4.style.zIndex = '1';
const btnContainer = document.createElement('div');
btnContainer.style.display = 'flex';
btnContainer.style.gap = '8px';
btnContainer.style.position = 'relative';
btnContainer.style.zIndex = '1';
const copyBtn = document.createElement('button');
copyBtn.textContent = '复制';
copyBtn.style.padding = '5px 15px';
copyBtn.style.cursor = 'pointer';
copyBtn.style.backgroundColor = '#4CAF50';
copyBtn.style.color = 'white';
copyBtn.style.border = 'none';
copyBtn.style.borderRadius = '4px';
copyBtn.onmouseover = () => copyBtn.style.backgroundColor = '#45a049';
copyBtn.onmouseout = () => copyBtn.style.backgroundColor = '#4CAF50';
copyBtn.onclick = () => {
if (copyToClipboard(item.value)) {
const originalText = copyBtn.textContent;
copyBtn.textContent = '已复制!';
copyBtn.style.backgroundColor = '#2196F3';
setTimeout(() => {
copyBtn.textContent = originalText;
copyBtn.style.backgroundColor = '#4CAF50';
}, 2000);
}
};
btnContainer.appendChild(copyBtn);
const deleteBtn = document.createElement('button');
deleteBtn.textContent = '删除';
deleteBtn.style.padding = '5px 15px';
deleteBtn.style.cursor = 'pointer';
deleteBtn.style.backgroundColor = '#f44336';
deleteBtn.style.color = 'white';
deleteBtn.style.border = 'none';
deleteBtn.style.borderRadius = '4px';
deleteBtn.onmouseover = () => deleteBtn.style.backgroundColor = '#da190b';
deleteBtn.onmouseout = () => deleteBtn.style.backgroundColor = '#f44336';
deleteBtn.onclick = () => {
if (confirm(`确定要删除 "${item.name} - ${item.value}" 这条记录吗?`)) {
onDelete(index);
}
};
btnContainer.appendChild(deleteBtn);
td4.appendChild(btnContainer);
row.appendChild(td4);
tbody.appendChild(row);
});
}
table.appendChild(tbody);
scrollContainer.appendChild(table);
tableWrapper.appendChild(scrollContainer);
return tableWrapper;
}
function createAddModal(onAdd) {
const overlay = document.createElement('div');
overlay.id = 'plan-mapping-add-overlay';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 100000;
display: none;
`;
const modal = document.createElement('div');
modal.id = 'plan-mapping-add-modal';
modal.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
max-width: 500px;
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
z-index: 100001;
display: none;
flex-direction: column;
`;
const header = document.createElement('div');
header.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
border-bottom: 1px solid #ddd;
background-color: #f5f5f5;
`;
const title = document.createElement('h2');
title.textContent = '添加新映射';
title.style.margin = '0';
title.style.fontSize = '18px';
header.appendChild(title);
const closeBtn = document.createElement('button');
closeBtn.textContent = '×';
closeBtn.style.cssText = `
background: none;
border: none;
font-size: 28px;
cursor: pointer;
color: #666;
padding: 0;
width: 30px;
height: 30px;
line-height: 30px;
text-align: center;
`;
closeBtn.onmouseover = () => closeBtn.style.color = '#000';
closeBtn.onmouseout = () => closeBtn.style.color = '#666';
closeBtn.onclick = () => {
overlay.style.display = 'none';
modal.style.display = 'none';
};
header.appendChild(closeBtn);
modal.appendChild(header);
const content = document.createElement('div');
content.style.cssText = `
padding: 20px;
`;
const fields = [
{ label: '名称', id: 'name', placeholder: '请输入名称' },
{ label: '值', id: 'value', placeholder: '请输入值' },
{ label: '备注', id: 'note', placeholder: '请输入备注' }
];
const inputs = {};
fields.forEach(field => {
const label = document.createElement('label');
label.textContent = field.label + ': ';
label.style.display = 'block';
label.style.marginTop = '15px';
label.style.marginBottom = '5px';
label.style.fontWeight = '500';
content.appendChild(label);
const input = document.createElement('input');
input.type = 'text';
input.id = field.id;
input.placeholder = field.placeholder;
input.style.width = '100%';
input.style.padding = '10px';
input.style.marginBottom = '5px';
input.style.border = '1px solid #ddd';
input.style.borderRadius = '4px';
input.style.boxSizing = 'border-box';
input.style.fontSize = '14px';
content.appendChild(input);
inputs[field.id] = input;
});
const btnContainer = document.createElement('div');
btnContainer.style.cssText = `
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 25px;
`;
const cancelBtn = document.createElement('button');
cancelBtn.textContent = '取消';
cancelBtn.style.cssText = `
padding: 8px 20px;
cursor: pointer;
background-color: #f5f5f5;
color: #333;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
`;
cancelBtn.onmouseover = () => cancelBtn.style.backgroundColor = '#e0e0e0';
cancelBtn.onmouseout = () => cancelBtn.style.backgroundColor = '#f5f5f5';
cancelBtn.onclick = () => {
overlay.style.display = 'none';
modal.style.display = 'none';
inputs.name.value = '';
inputs.value.value = '';
inputs.note.value = '';
};
btnContainer.appendChild(cancelBtn);
const addBtn = document.createElement('button');
addBtn.textContent = '添加';
addBtn.style.cssText = `
padding: 8px 20px;
cursor: pointer;
background-color: #2196F3;
color: white;
border: none;
border-radius: 4px;
font-size: 14px;
`;
addBtn.onmouseover = () => addBtn.style.backgroundColor = '#0b7dda';
addBtn.onmouseout = () => addBtn.style.backgroundColor = '#2196F3';
addBtn.onclick = () => {
const newItem = {
name: inputs.name.value.trim(),
value: inputs.value.value.trim(),
note: inputs.note.value.trim()
};
if (!newItem.name || !newItem.value || !newItem.note) {
alert('请填写所有字段');
return;
}
onAdd(newItem);
inputs.name.value = '';
inputs.value.value = '';
inputs.note.value = '';
overlay.style.display = 'none';
modal.style.display = 'none';
};
btnContainer.appendChild(addBtn);
content.appendChild(btnContainer);
modal.appendChild(content);
overlay.onclick = (e) => {
if (e.target === overlay) {
overlay.style.display = 'none';
modal.style.display = 'none';
inputs.name.value = '';
inputs.value.value = '';
inputs.note.value = '';
}
};
document.body.appendChild(overlay);
document.body.appendChild(modal);
return {
show: () => {
overlay.style.display = 'block';
modal.style.display = 'flex';
setTimeout(() => {
inputs.name.focus();
}, 100);
},
hide: () => {
overlay.style.display = 'none';
modal.style.display = 'none';
}
};
}
function createImportModal(onImport) {
const overlay = document.createElement('div');
overlay.id = 'plan-mapping-import-overlay';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 100000;
display: none;
`;
const modal = document.createElement('div');
modal.id = 'plan-mapping-import-modal';
modal.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
max-width: 600px;
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
z-index: 100001;
display: none;
flex-direction: column;
max-height: 80vh;
`;
const header = document.createElement('div');
header.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
border-bottom: 1px solid #ddd;
background-color: #f5f5f5;
`;
const title = document.createElement('h2');
title.textContent = '导入数据';
title.style.margin = '0';
title.style.fontSize = '18px';
header.appendChild(title);
const closeBtn = document.createElement('button');
closeBtn.textContent = '×';
closeBtn.style.cssText = `
background: none;
border: none;
font-size: 28px;
cursor: pointer;
color: #666;
padding: 0;
width: 30px;
height: 30px;
line-height: 30px;
text-align: center;
`;
closeBtn.onmouseover = () => closeBtn.style.color = '#000';
closeBtn.onmouseout = () => closeBtn.style.color = '#666';
closeBtn.onclick = () => {
overlay.style.display = 'none';
modal.style.display = 'none';
};
header.appendChild(closeBtn);
modal.appendChild(header);
const content = document.createElement('div');
content.style.cssText = `
padding: 20px;
overflow-y: auto;
flex: 1;
`;
const description = document.createElement('div');
description.style.cssText = `
margin-bottom: 15px;
padding: 10px;
background-color: #f0f7ff;
border-left: 3px solid #2196F3;
border-radius: 4px;
`;
description.innerHTML = `
数据格式说明:
请粘贴数组格式的数据,每项必须包含 name、value 和 note 字段。
支持 JSON 格式或 JavaScript 数组格式。
`;
content.appendChild(description);
const exampleText = document.createElement('div');
exampleText.style.cssText = `
margin-bottom: 15px;
padding: 10px;
background-color: #f9f9f9;
border-radius: 4px;
font-size: 12px;
font-family: monospace;
color: #666;
max-height: 120px;
overflow-y: auto;
`;
exampleText.textContent = `示例格式:\n[\n {\n name: '示例名称',\n value: '示例值',\n note: '示例备注'\n }\n]`;
content.appendChild(exampleText);
const label = document.createElement('label');
label.textContent = '粘贴数据:';
label.style.display = 'block';
label.style.marginBottom = '8px';
label.style.fontWeight = '500';
content.appendChild(label);
const textarea = document.createElement('textarea');
textarea.id = 'import-data-textarea';
textarea.placeholder = '请粘贴数组数据...';
textarea.style.cssText = `
width: 100%;
min-height: 200px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: monospace;
font-size: 13px;
resize: vertical;
box-sizing: border-box;
`;
content.appendChild(textarea);
const errorMsg = document.createElement('div');
errorMsg.id = 'import-error-msg';
errorMsg.style.cssText = `
margin-top: 10px;
padding: 10px;
background-color: #ffebee;
border-left: 3px solid #f44336;
border-radius: 4px;
color: #c62828;
font-size: 13px;
display: none;
`;
content.appendChild(errorMsg);
const btnContainer = document.createElement('div');
btnContainer.style.cssText = `
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
`;
const cancelBtn = document.createElement('button');
cancelBtn.textContent = '取消';
cancelBtn.style.cssText = `
padding: 8px 20px;
cursor: pointer;
background-color: #f5f5f5;
color: #333;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
`;
cancelBtn.onmouseover = () => cancelBtn.style.backgroundColor = '#e0e0e0';
cancelBtn.onmouseout = () => cancelBtn.style.backgroundColor = '#f5f5f5';
cancelBtn.onclick = () => {
overlay.style.display = 'none';
modal.style.display = 'none';
textarea.value = '';
errorMsg.style.display = 'none';
};
btnContainer.appendChild(cancelBtn);
const importBtn = document.createElement('button');
importBtn.textContent = '导入';
importBtn.style.cssText = `
padding: 8px 20px;
cursor: pointer;
background-color: #2196F3;
color: white;
border: none;
border-radius: 4px;
font-size: 14px;
`;
importBtn.onmouseover = () => importBtn.style.backgroundColor = '#0b7dda';
importBtn.onmouseout = () => importBtn.style.backgroundColor = '#2196F3';
importBtn.onclick = () => {
const inputText = textarea.value.trim();
if (!inputText) {
errorMsg.textContent = '请输入数据';
errorMsg.style.display = 'block';
return;
}
try {
const parsedData = parseArrayData(inputText);
if (parsedData.length === 0) {
errorMsg.textContent = '未找到有效数据';
errorMsg.style.display = 'block';
return;
}
onImport(parsedData);
overlay.style.display = 'none';
modal.style.display = 'none';
textarea.value = '';
errorMsg.style.display = 'none';
} catch (error) {
errorMsg.textContent = error.message || '解析失败,请检查数据格式';
errorMsg.style.display = 'block';
}
};
btnContainer.appendChild(importBtn);
content.appendChild(btnContainer);
modal.appendChild(content);
overlay.onclick = (e) => {
if (e.target === overlay) {
overlay.style.display = 'none';
modal.style.display = 'none';
textarea.value = '';
errorMsg.style.display = 'none';
}
};
document.body.appendChild(overlay);
document.body.appendChild(modal);
return {
show: () => {
overlay.style.display = 'block';
modal.style.display = 'flex';
setTimeout(() => {
textarea.focus();
}, 100);
},
hide: () => {
overlay.style.display = 'none';
modal.style.display = 'none';
}
};
}
function createModal(configModal) {
const overlay = document.createElement('div');
overlay.id = 'plan-mapping-overlay';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 99998;
display: none;
`;
const modal = document.createElement('div');
modal.id = 'plan-mapping-modal';
modal.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
max-width: 1000px;
max-height: 90vh;
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
z-index: 99999;
display: none;
overflow: hidden;
flex-direction: column;
`;
const header = document.createElement('div');
header.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
border-bottom: 1px solid #ddd;
background-color: #f5f5f5;
`;
const titleContainer = document.createElement('div');
titleContainer.style.cssText = `
display: flex;
flex-direction: row;
align-items: center;
gap: 4px;
`;
const title = document.createElement('h2');
title.textContent = '随手记列表';
title.style.margin = '0';
title.style.fontSize = '20px';
titleContainer.appendChild(title);
const description = document.createElement('div');
description.style.cssText = `
font-size: 12px;
color: #666;
font-weight: normal;
`;
description.textContent = '默认匹配所有域名,可以自行在配置按钮中配置域名配置规则';
titleContainer.appendChild(description);
header.appendChild(titleContainer);
const headerBtnGroup = document.createElement('div');
headerBtnGroup.style.cssText = `
display: flex;
align-items: center;
gap: 10px;
`;
if (configModal) {
const configBtn = document.createElement('button');
configBtn.textContent = '⚙️ 配置';
configBtn.style.cssText = `
padding: 6px 12px;
cursor: pointer;
background-color: #f5f5f5;
color: #333;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 13px;
`;
configBtn.onmouseover = () => configBtn.style.backgroundColor = '#e0e0e0';
configBtn.onmouseout = () => configBtn.style.backgroundColor = '#f5f5f5';
configBtn.onclick = () => {
configModal.show();
};
headerBtnGroup.appendChild(configBtn);
}
const closeBtn = document.createElement('button');
closeBtn.textContent = '×';
closeBtn.style.cssText = `
background: none;
border: none;
font-size: 28px;
cursor: pointer;
color: #666;
padding: 0;
width: 30px;
height: 30px;
line-height: 30px;
text-align: center;
`;
closeBtn.onmouseover = () => closeBtn.style.color = '#000';
closeBtn.onmouseout = () => closeBtn.style.color = '#666';
closeBtn.onclick = () => {
overlay.style.display = 'none';
modal.style.display = 'none';
};
headerBtnGroup.appendChild(closeBtn);
header.appendChild(headerBtnGroup);
modal.appendChild(header);
const content = document.createElement('div');
content.id = 'plan-mapping-content';
content.style.cssText = `
padding: 5px 20px 20px 20px;
overflow-y: auto;
flex: 1;
`;
const tableContainer = document.createElement('div');
tableContainer.style.cssText = `
position: relative;
`;
const tableHeader = document.createElement('div');
tableHeader.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
`;
const tableTitle = document.createElement('h3');
tableTitle.textContent = 'note列表';
tableTitle.style.margin = '0';
tableHeader.appendChild(tableTitle);
const btnGroup = document.createElement('div');
btnGroup.style.cssText = `
display: flex;
gap: 10px;
align-items: center;
`;
const importBtn = document.createElement('button');
importBtn.textContent = '📥 导入数据';
importBtn.style.cssText = `
padding: 8px 20px;
cursor: pointer;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
font-size: 14px;
`;
importBtn.onmouseover = () => importBtn.style.backgroundColor = '#45a049';
importBtn.onmouseout = () => importBtn.style.backgroundColor = '#4CAF50';
const addNewBtn = document.createElement('button');
addNewBtn.textContent = '+ 新增';
addNewBtn.style.cssText = `
padding: 8px 20px;
cursor: pointer;
background-color: #2196F3;
color: white;
border: none;
border-radius: 4px;
font-size: 14px;
`;
addNewBtn.onmouseover = () => addNewBtn.style.backgroundColor = '#0b7dda';
addNewBtn.onmouseout = () => addNewBtn.style.backgroundColor = '#2196F3';
const addModal = createAddModal((newItem) => {
const data = getData();
data.push(newItem);
saveData(data);
renderTable();
});
const importModal = createImportModal((importedData) => {
saveData(importedData);
renderTable();
});
importBtn.onclick = () => {
importModal.show();
};
addNewBtn.onclick = () => {
addModal.show();
};
btnGroup.appendChild(importBtn);
btnGroup.appendChild(addNewBtn);
tableHeader.appendChild(btnGroup);
tableContainer.appendChild(tableHeader);
const renderTable = () => {
const data = getData();
const oldTableWrapper = tableContainer.querySelector('[data-table-wrapper]');
if (oldTableWrapper) {
oldTableWrapper.remove();
}
const tableWrapper = createTable(data, (index) => {
const data = getData();
data.splice(index, 1);
saveData(data);
renderTable();
});
tableWrapper.setAttribute('data-table-wrapper', 'true');
tableContainer.appendChild(tableWrapper);
};
renderTable();
content.appendChild(tableContainer);
modal.appendChild(content);
overlay.onclick = (e) => {
if (e.target === overlay) {
overlay.style.display = 'none';
modal.style.display = 'none';
}
};
document.body.appendChild(overlay);
document.body.appendChild(modal);
return {
show: () => {
overlay.style.display = 'block';
modal.style.display = 'flex';
},
hide: () => {
overlay.style.display = 'none';
modal.style.display = 'none';
}
};
}
function createFloatingButton(modal, configModal) {
const floatBtn = document.createElement('div');
floatBtn.id = 'plan-mapping-float-btn';
floatBtn.textContent = '随手记';
floatBtn.style.cssText = `
position: fixed;
right: 90px;
top: 0px;
width: 60px;
height: 60px;
background-color: #52c41a!important;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
z-index: 99997;
font-size: 12px;
text-align: center;
line-height: 1.2;
user-select: none;
transition: transform 0.2s, box-shadow 0.2s;
`;
floatBtn.onmouseover = () => {
floatBtn.style.transform = 'scale(1.1)';
floatBtn.style.boxShadow = '0 6px 16px rgba(0, 0, 0, 0.4)';
};
floatBtn.onmouseout = () => {
floatBtn.style.transform = 'scale(1)';
floatBtn.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.3)';
};
floatBtn.onclick = () => {
modal.show();
};
document.body.appendChild(floatBtn);
}
function createDomainConfigModal() {
const overlay = document.createElement('div');
overlay.id = 'plan-mapping-domain-config-overlay';
overlay.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 100002;
display: none;
`;
const modal = document.createElement('div');
modal.id = 'plan-mapping-domain-config-modal';
modal.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
max-width: 600px;
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
z-index: 100003;
display: none;
flex-direction: column;
max-height: 80vh;
`;
const header = document.createElement('div');
header.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
border-bottom: 1px solid #ddd;
background-color: #f5f5f5;
`;
const title = document.createElement('h2');
title.textContent = '域名配置';
title.style.margin = '0';
title.style.fontSize = '18px';
header.appendChild(title);
const closeBtn = document.createElement('button');
closeBtn.textContent = '×';
closeBtn.style.cssText = `
background: none;
border: none;
font-size: 28px;
cursor: pointer;
color: #666;
padding: 0;
width: 30px;
height: 30px;
line-height: 30px;
text-align: center;
`;
closeBtn.onmouseover = () => closeBtn.style.color = '#000';
closeBtn.onmouseout = () => closeBtn.style.color = '#666';
closeBtn.onclick = () => {
overlay.style.display = 'none';
modal.style.display = 'none';
};
header.appendChild(closeBtn);
modal.appendChild(header);
const content = document.createElement('div');
content.style.cssText = `
padding: 20px;
overflow-y: auto;
flex: 1;
`;
const description = document.createElement('div');
description.style.cssText = `
margin-bottom: 15px;
padding: 10px;
background-color: #f0f7ff;
border-left: 3px solid #2196F3;
border-radius: 4px;
`;
description.innerHTML = `
配置说明:
默认匹配所有域名。如需限制脚本生效范围,可配置允许的域名列表,每行一个域名。
支持通配符,如 *.example.com 表示匹配 example.com 及其所有子域名。
留空则匹配所有域名。当前域名:${window.location.hostname}
`;
content.appendChild(description);
const label = document.createElement('label');
label.textContent = '允许的域名列表(每行一个):';
label.style.display = 'block';
label.style.marginBottom = '8px';
label.style.fontWeight = '500';
content.appendChild(label);
const textarea = document.createElement('textarea');
textarea.id = 'domain-config-textarea';
textarea.placeholder = 'example.com\n*.example.com\nsubdomain.example.com';
textarea.style.cssText = `
width: 100%;
min-height: 200px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: monospace;
font-size: 13px;
resize: vertical;
box-sizing: border-box;
`;
const currentDomains = getAllowedDomains();
textarea.value = currentDomains.join('\n');
content.appendChild(textarea);
const addCurrentBtn = document.createElement('button');
addCurrentBtn.textContent = '➕ 添加当前域名';
addCurrentBtn.style.cssText = `
margin-top: 10px;
padding: 6px 12px;
cursor: pointer;
background-color: #f5f5f5;
color: #333;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 13px;
`;
addCurrentBtn.onmouseover = () => addCurrentBtn.style.backgroundColor = '#e0e0e0';
addCurrentBtn.onmouseout = () => addCurrentBtn.style.backgroundColor = '#f5f5f5';
addCurrentBtn.onclick = () => {
const currentDomain = window.location.hostname;
const domains = textarea.value.split('\n').map(d => d.trim()).filter(d => d);
if (!domains.includes(currentDomain)) {
domains.push(currentDomain);
textarea.value = domains.join('\n');
}
};
content.appendChild(addCurrentBtn);
const errorMsg = document.createElement('div');
errorMsg.id = 'domain-config-error-msg';
errorMsg.style.cssText = `
margin-top: 10px;
padding: 10px;
background-color: #ffebee;
border-left: 3px solid #f44336;
border-radius: 4px;
color: #c62828;
font-size: 13px;
display: none;
`;
content.appendChild(errorMsg);
const successMsg = document.createElement('div');
successMsg.id = 'domain-config-success-msg';
successMsg.style.cssText = `
margin-top: 10px;
padding: 10px;
background-color: #e8f5e9;
border-left: 3px solid #4CAF50;
border-radius: 4px;
color: #2e7d32;
font-size: 13px;
display: none;
`;
content.appendChild(successMsg);
const btnContainer = document.createElement('div');
btnContainer.style.cssText = `
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
`;
const cancelBtn = document.createElement('button');
cancelBtn.textContent = '取消';
cancelBtn.style.cssText = `
padding: 8px 20px;
cursor: pointer;
background-color: #f5f5f5;
color: #333;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
`;
cancelBtn.onmouseover = () => cancelBtn.style.backgroundColor = '#e0e0e0';
cancelBtn.onmouseout = () => cancelBtn.style.backgroundColor = '#f5f5f5';
cancelBtn.onclick = () => {
overlay.style.display = 'none';
modal.style.display = 'none';
};
btnContainer.appendChild(cancelBtn);
const saveBtn = document.createElement('button');
saveBtn.textContent = '保存';
saveBtn.style.cssText = `
padding: 8px 20px;
cursor: pointer;
background-color: #2196F3;
color: white;
border: none;
border-radius: 4px;
font-size: 14px;
`;
saveBtn.onmouseover = () => saveBtn.style.backgroundColor = '#0b7dda';
saveBtn.onmouseout = () => saveBtn.style.backgroundColor = '#2196F3';
saveBtn.onclick = () => {
const inputText = textarea.value.trim();
const domains = inputText.split('\n')
.map(d => d.trim())
.filter(d => d && !d.startsWith('#')); // 过滤空行和注释
saveAllowedDomains(domains);
if (domains.length === 0) {
successMsg.textContent = '已设置为匹配所有域名,页面将自动刷新';
} else {
successMsg.textContent = `已保存 ${domains.length} 个域名配置,页面将自动刷新`;
}
successMsg.style.display = 'block';
errorMsg.style.display = 'none';
setTimeout(() => {
window.location.reload();
}, 1500);
};
btnContainer.appendChild(saveBtn);
content.appendChild(btnContainer);
modal.appendChild(content);
overlay.onclick = (e) => {
if (e.target === overlay) {
overlay.style.display = 'none';
modal.style.display = 'none';
}
};
document.body.appendChild(overlay);
document.body.appendChild(modal);
return {
show: () => {
overlay.style.display = 'block';
modal.style.display = 'flex';
const currentDomains = getAllowedDomains();
textarea.value = currentDomains.join('\n');
setTimeout(() => {
textarea.focus();
}, 100);
},
hide: () => {
overlay.style.display = 'none';
modal.style.display = 'none';
}
};
}
function init() {
function setup() {
const configModal = createDomainConfigModal();
const modal = createModal(configModal);
const floatBtn = createFloatingButton(modal, configModal);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', setup);
} else {
setup();
}
}
init();
})();