One-click clipboard quote → GitHub Gist, with keyword highlighting
目前為
// ==UserScript==
// @name Clip-to-Gist
// @name:zh-CN Clip-to-Gist 金句剪贴脚本(v2)
// @namespace https://github.com/yourusername
// @version 2.0
// @description One-click clipboard quote → GitHub Gist, with keyword highlighting
// @description:zh-CN 一键剪贴板金句并上传至 GitHub Gist,支持关键词标注和高亮
// @author Your Name
// @match *://*/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// 注册菜单:配置 Gist 参数
GM_registerMenuCommand('配置 Gist 参数', openConfigModal);
// 注入右下角触发按钮
const trigger = document.createElement('div');
trigger.id = 'clip2gist-trigger';
trigger.textContent = '📝';
document.body.appendChild(trigger);
// 样式
GM_addStyle(`
#clip2gist-trigger {
position: fixed; bottom: 20px; right: 20px;
width: 40px; height: 40px; line-height: 40px;
background: #4CAF50; color: #fff; text-align: center;
border-radius: 50%; cursor: pointer; z-index: 9999;
font-size: 24px;
}
.clip2gist-mask {
position: fixed; inset: 0; background: rgba(0,0,0,0.5);
display: flex; align-items: center; justify-content: center;
z-index: 10000;
}
.clip2gist-dialog {
background: #fff; padding: 20px; border-radius: 8px;
max-width: 90%; max-height: 90%; overflow: auto;
box-shadow: 0 2px 10px rgba(0,0,0,0.3);
}
.clip2gist-dialog input {
width: 100%; padding: 6px; margin-top: 4px; margin-bottom: 12px;
box-sizing: border-box; font-size: 14px;
}
.clip2gist-dialog button {
margin-left: 8px; padding: 6px 12px; font-size: 14px;
}
.clip2gist-word {
display: inline-block; margin: 2px; padding: 4px 6px;
border: 1px solid #ccc; border-radius: 4px; cursor: pointer;
user-select: none;
}
.clip2gist-word.selected {
background: #ffeb3b; border-color: #f1c40f;
}
#clip2gist-preview {
margin-top: 12px; padding: 8px; border: 1px solid #ddd;
min-height: 40px; font-family: monospace;
}
`);
trigger.addEventListener('click', mainFlow);
async function mainFlow() {
let text = '';
try {
text = await navigator.clipboard.readText();
} catch (e) {
return alert('请在 HTTPS 环境并授权剪贴板访问');
}
if (!text.trim()) return alert('剪贴板内容为空');
showEditDialog(text.trim());
}
function showEditDialog(rawText) {
const mask = document.createElement('div');
mask.className = 'clip2gist-mask';
const dlg = document.createElement('div');
dlg.className = 'clip2gist-dialog';
// 词块化
const wordContainer = document.createElement('div');
rawText.split(/\s+/).forEach(w => {
const sp = document.createElement('span');
sp.className = 'clip2gist-word';
sp.textContent = w;
sp.addEventListener('click', () => {
sp.classList.toggle('selected');
updatePreview();
});
wordContainer.appendChild(sp);
});
dlg.appendChild(wordContainer);
// 预览区
const preview = document.createElement('div');
preview.id = 'clip2gist-preview';
dlg.appendChild(preview);
// 按钮行
const btnRow = document.createElement('div');
const cancelBtn = document.createElement('button');
cancelBtn.textContent = '取消';
cancelBtn.addEventListener('click', () => document.body.removeChild(mask));
const configBtn = document.createElement('button');
configBtn.textContent = '配置';
configBtn.addEventListener('click', openConfigModal);
const confirmBtn = document.createElement('button');
confirmBtn.textContent = '确认';
confirmBtn.addEventListener('click', onConfirm);
btnRow.append(cancelBtn, configBtn, confirmBtn);
dlg.appendChild(btnRow);
mask.appendChild(dlg);
document.body.appendChild(mask);
updatePreview();
function updatePreview() {
const spans = Array.from(wordContainer.children);
const final = [];
for (let i = 0; i < spans.length;) {
if (spans[i].classList.contains('selected')) {
const group = [spans[i].textContent];
let j = i + 1;
while (j < spans.length && spans[j].classList.contains('selected')) {
group.push(spans[j].textContent);
j++;
}
final.push(`{${group.join(' ')}}`);
i = j;
} else {
final.push(spans[i].textContent);
i++;
}
}
preview.textContent = final.join(' ');
}
async function onConfirm() {
const gistId = await GM_getValue('gistId');
const token = await GM_getValue('githubToken');
if (!gistId || !token) {
return alert('请先通过“配置 Gist 参数”填写 Gist ID 与 Token');
}
const content = preview.textContent;
GM_xmlhttpRequest({
method: 'GET',
url: `https://api.github.com/gists/${gistId}`,
headers: { Authorization: `token ${token}` },
onload(resp1) {
if (resp1.status !== 200) return alert('拉取 Gist 失败');
const data = JSON.parse(resp1.responseText);
const fname = Object.keys(data.files)[0];
const old = data.files[fname].content;
const updated = `\n\n----\n${content}` + old;
GM_xmlhttpRequest({
method: 'PATCH',
url: `https://api.github.com/gists/${gistId}`,
headers: {
Authorization: `token ${token}`,
'Content-Type': 'application/json'
},
data: JSON.stringify({ files: { [fname]: { content: updated } } }),
onload(resp2) {
if (resp2.status === 200) {
alert('上传成功 🎉');
document.body.removeChild(mask);
} else {
alert('上传失败:' + resp2.status);
}
}
});
}
});
}
}
function openConfigModal() {
const mask = document.createElement('div');
mask.className = 'clip2gist-mask';
const dlg = document.createElement('div');
dlg.className = 'clip2gist-dialog';
const idLabel = document.createElement('label');
idLabel.textContent = 'Gist ID:';
const idInput = document.createElement('input');
idInput.value = GM_getValue('gistId', '');
const tkLabel = document.createElement('label');
tkLabel.textContent = 'GitHub Token:';
const tkInput = document.createElement('input');
tkInput.value = GM_getValue('githubToken', '');
dlg.append(idLabel, idInput, tkLabel, tkInput);
const saveBtn = document.createElement('button');
saveBtn.textContent = '保存';
saveBtn.addEventListener('click', () => {
GM_setValue('gistId', idInput.value.trim());
GM_setValue('githubToken', tkInput.value.trim());
alert('配置已保存');
document.body.removeChild(mask);
});
const cancelBtn2 = document.createElement('button');
cancelBtn2.textContent = '取消';
cancelBtn2.addEventListener('click', () => document.body.removeChild(mask));
dlg.append(saveBtn, cancelBtn2);
mask.appendChild(dlg);
document.body.appendChild(mask);
}
})();