// ==UserScript==
// @name Clip-to-Gist Quote Script (Lemur Compatible)
// @namespace http://tampermonkey.net/
// @version 2.3
// @description One-click clipboard quotes → GitHub Gist, with keyword highlighting, versioning & Lemur Browser compatibility
// @author Your Name
// @match *://*/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @connect api.github.com
// @run-at document-end
// ==/UserScript==
(function(){
'use strict';
// Fallback implementations
const setValue = typeof GM_setValue === 'function'
? GM_setValue
: (key, val) => localStorage.setItem(key, val);
const getValue = typeof GM_getValue === 'function'
? (key, def) => { const v = GM_getValue(key); return v == null ? def : v; }
: (key, def) => { const v = localStorage.getItem(key); return v == null ? def : v; };
const httpRequest = typeof GM_xmlhttpRequest === 'function'
? GM_xmlhttpRequest
: opts => {
const headers = opts.headers || {};
if (opts.method === 'GET') {
fetch(opts.url, { headers })
.then(res => res.text().then(text => opts.onload({ status: res.status, responseText: text })));
} else {
fetch(opts.url, { method: opts.method, headers, body: opts.data })
.then(res => res.text().then(text => opts.onload({ status: res.status, responseText: text })));
}
};
function addStyle(css) {
if (typeof GM_addStyle === 'function') {
GM_addStyle(css);
} else {
const s = document.createElement('style');
s.textContent = css;
document.head.appendChild(s);
}
}
// Version management
const VERSION_KEY = 'clip2gistVersion';
if (getValue(VERSION_KEY, null) == null) {
setValue(VERSION_KEY, 1);
}
// Global CSS
addStyle(`
#clip2gist-trigger { position:fixed!important;bottom:20px!important;right:20px!important;
width:40px;height:40px;line-height:40px;text-align:center;
background:#4CAF50;color:#fff;border-radius:50%;cursor:pointer;
z-index:2147483647!important;font-size:24px;box-shadow:0 2px 6px rgba(0,0,0,0.3);
}
.clip2gist-mask { position:fixed;inset:0;background:rgba(0,0,0,0.5);
display:flex;align-items:center;justify-content:center;z-index:2147483646;
}
.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:4px 0 12px;box-sizing:border-box;font-size:14px; }
.clip2gist-dialog button { margin-left:8px;padding:6px 12px;font-size:14px;cursor:pointer; }
.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;
}
`);
// If supported, register menu command
if (typeof GM_registerMenuCommand === 'function') {
GM_registerMenuCommand('Configure Gist', openConfigDialog);
}
// Insert floating trigger button
function insertTrigger() {
if (!document.body) {
return setTimeout(insertTrigger, 100);
}
const btn = document.createElement('div');
btn.id = 'clip2gist-trigger';
btn.textContent = '📝';
btn.addEventListener('click', mainFlow, false);
btn.addEventListener('dblclick', openConfigDialog, false);
document.body.appendChild(btn);
}
insertTrigger();
// Main flow: read clipboard, show editor
async function mainFlow() {
let text = '';
try {
text = await navigator.clipboard.readText();
} catch (e) {
return alert('Please use HTTPS and allow clipboard access');
}
if (!text.trim()) {
return alert('Clipboard is empty');
}
showEditor(text.trim());
}
// Editor dialog
function showEditor(rawText) {
const mask = document.createElement('div'); mask.className = 'clip2gist-mask';
const dlg = document.createElement('div'); dlg.className = 'clip2gist-dialog';
// Word-by-word spans
const container = document.createElement('div');
rawText.split(/\s+/).forEach(word => {
const span = document.createElement('span');
span.className = 'clip2gist-word';
span.textContent = word;
span.onclick = () => { span.classList.toggle('selected'); updatePreview(); };
container.appendChild(span);
});
dlg.appendChild(container);
// Preview area
const preview = document.createElement('div');
preview.id = 'clip2gist-preview';
dlg.appendChild(preview);
// Buttons row
const row = document.createElement('div');
['Cancel', 'Configure', 'Confirm'].forEach(label => {
const btn = document.createElement('button');
btn.textContent = label;
if (label === 'Cancel') btn.onclick = () => document.body.removeChild(mask);
if (label === 'Configure') btn.onclick = openConfigDialog;
if (label === 'Confirm') btn.onclick = confirmUpload;
row.appendChild(btn);
});
dlg.appendChild(row);
mask.appendChild(dlg);
document.body.appendChild(mask);
updatePreview();
// Build preview string
function updatePreview() {
const spans = Array.from(container.children);
const parts = [];
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++;
}
parts.push(`{${group.join(' ')}}`);
i = j;
} else {
parts.push(spans[i].textContent);
i++;
}
}
preview.textContent = parts.join(' ');
}
// Confirm upload
function confirmUpload() {
const gistId = getValue('gistId', '');
const token = getValue('githubToken', '');
if (!gistId || !token) {
return alert('Please configure Gist ID and GitHub Token first');
}
const ver = getValue(VERSION_KEY, 1);
const header = `Version ${ver}`;
const content = preview.textContent;
// GET existing gist
httpRequest({
method: 'GET',
url: `https://api.github.com/gists/${gistId}`,
headers: { Authorization: `token ${token}` },
onload(res1) {
if (res1.status !== 200) {
return alert('Failed to fetch Gist: ' + res1.status);
}
const data = JSON.parse(res1.responseText);
const file = Object.keys(data.files)[0];
const oldContent = data.files[file].content;
const updated = `\n\n----\n${header}\n${content}` + oldContent;
// PATCH update
httpRequest({
method: 'PATCH',
url: `https://api.github.com/gists/${gistId}`,
headers: {
Authorization: `token ${token}`,
'Content-Type': 'application/json'
},
data: JSON.stringify({ files: { [file]: { content: updated } } }),
onload(res2) {
if (res2.status === 200) {
alert(`Upload successful! Version ${ver}`);
setValue(VERSION_KEY, ver + 1);
document.body.removeChild(mask);
} else {
alert('Failed to update Gist: ' + res2.status);
}
}
});
}
});
}
}
// Configuration dialog
function openConfigDialog() {
const mask = document.createElement('div'); mask.className = 'clip2gist-mask';
const dlg = document.createElement('div'); dlg.className = 'clip2gist-dialog';
const label1 = document.createElement('label');
label1.textContent = 'Gist ID:';
const input1 = document.createElement('input');
input1.value = getValue('gistId', '');
const label2 = document.createElement('label');
label2.textContent = 'GitHub Token:';
const input2 = document.createElement('input');
input2.value = getValue('githubToken', '');
dlg.append(label1, input1, label2, input2);
const saveBtn = document.createElement('button');
saveBtn.textContent = 'Save';
saveBtn.onclick = () => {
setValue('gistId', input1.value.trim());
setValue('githubToken', input2.value.trim());
alert('Configuration saved');
document.body.removeChild(mask);
};
const cancelBtn = document.createElement('button');
cancelBtn.textContent = 'Cancel';
cancelBtn.onclick = () => document.body.removeChild(mask);
dlg.append(saveBtn, cancelBtn);
mask.appendChild(dlg);
document.body.appendChild(mask);
}
})();