Clip-to-Gist Quote Script (ES5, Lemur Compatible)

One-click clipboard quotes → GitHub Gist, with keyword highlighting, versioning & Lemur Browser compatibility

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Clip-to-Gist Quote Script (ES5, Lemur Compatible)
// @namespace    http://tampermonkey.net/
// @version      2.3.1
// @description  One-click clipboard quotes → GitHub Gist, with keyword highlighting, versioning & Lemur Browser compatibility
// @author       Your Name
// @include      *://*/*
// @run-at       document-end
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @connect      api.github.com
// ==/UserScript==

(function(){
    'use strict';

    // --- Fallback wrappers ---
    var setValue = (typeof GM_setValue === 'function') ?
        GM_setValue :
        function(k, v){ localStorage.setItem(k, v); };

    var getValue = (typeof GM_getValue === 'function') ?
        function(k, def){ var v = GM_getValue(k); return (v===undefined||v===null)?def:v; } :
        function(k, def){ var v = localStorage.getItem(k); return (v===undefined||v===null)?def:v; };

    var httpRequest = (typeof GM_xmlhttpRequest === 'function') ?
        GM_xmlhttpRequest :
        function(opts){
            var headers = opts.headers || {};
            if(opts.method === 'GET'){
                fetch(opts.url, { headers: headers })
                  .then(function(res){ return res.text().then(function(txt){
                      opts.onload({ status: res.status, responseText: txt });
                  }); });
            } else {
                fetch(opts.url, { method: opts.method, headers: headers, body: opts.data })
                  .then(function(res){ return res.text().then(function(txt){
                      opts.onload({ status: res.status, responseText: txt });
                  }); });
            }
        };

    // --- Version key init ---
    var VERSION_KEY = 'clip2gistVersion';
    if(getValue(VERSION_KEY, null) === null){
        setValue(VERSION_KEY, 1);
    }

    // --- Inject global CSS via <style> ---
    function addGlobalStyle(css){
        var head = document.getElementsByTagName('head')[0];
        if(!head){ return; }
        var style = document.createElement('style');
        style.type = 'text/css';
        style.textContent = css;
        head.appendChild(style);
    }
    addGlobalStyle(
        '#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;top:0;left:0;right:0;bottom: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;' +
        '}'
    );

    // --- Insert floating trigger button ---
    function insertTrigger(){
        if(!document.body){
            return setTimeout(insertTrigger, 100);
        }
        var 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: read from clipboard and pop editor ---
    function mainFlow(){
        navigator.clipboard && navigator.clipboard.readText
          ? navigator.clipboard.readText().then(function(txt){
              if(!txt.trim()){
                  alert('Clipboard is empty');
              } else {
                  showEditor(txt.trim());
              }
          }, function(){
              alert('Please use HTTPS and allow clipboard access');
          })
          : alert('Clipboard API not supported');
    }

    // --- Show editor dialog ---
    function showEditor(rawText){
        var mask = document.createElement('div');
        mask.className = 'clip2gist-mask';
        var dlg = document.createElement('div');
        dlg.className = 'clip2gist-dialog';

        // word spans
        var wrap = document.createElement('div');
        var words = rawText.split(/\s+/);
        for(var i=0;i<words.length;i++){
            var sp = document.createElement('span');
            sp.className = 'clip2gist-word';
            sp.textContent = words[i];
            sp.onclick = (function(el){
                return function(){
                    el.classList.toggle('selected');
                    updatePreview();
                };
            })(sp);
            wrap.appendChild(sp);
        }
        dlg.appendChild(wrap);

        // preview
        var prev = document.createElement('div');
        prev.id = 'clip2gist-preview';
        dlg.appendChild(prev);

        // buttons
        var row = document.createElement('div');
        ['Cancel','Configure','Confirm'].forEach(function(label){
            var b = document.createElement('button');
            b.textContent = label;
            if(label==='Cancel'){
                b.onclick = function(){ document.body.removeChild(mask); };
            } else if(label==='Configure'){
                b.onclick = openConfigDialog;
            } else {
                b.onclick = confirmUpload;
            }
            row.appendChild(b);
        });
        dlg.appendChild(row);

        mask.appendChild(dlg);
        document.body.appendChild(mask);
        updatePreview();

        // update preview text
        function updatePreview(){
            var spans = wrap.children;
            var parts = [];
            for(var j=0;j<spans.length;){
                if(spans[j].classList.contains('selected')){
                    var grp = [spans[j].textContent], k = j+1;
                    while(k<spans.length && spans[k].classList.contains('selected')){
                        grp.push(spans[k].textContent);
                        k++;
                    }
                    parts.push('{' + grp.join(' ') + '}');
                    j = k;
                } else {
                    parts.push(spans[j].textContent);
                    j++;
                }
            }
            prev.textContent = parts.join(' ');
        }

        // upload to Gist
        function confirmUpload(){
            var gistId = getValue('gistId','');
            var token  = getValue('githubToken','');
            if(!gistId || !token){
                alert('Please configure Gist ID and GitHub Token first');
                return;
            }
            var ver = getValue(VERSION_KEY,1);
            var header = 'Version ' + ver;
            var content = prev.textContent;

            // GET existing
            httpRequest({
                method: 'GET',
                url: 'https://api.github.com/gists/' + gistId,
                headers: { 'Authorization': 'token ' + token },
                onload: function(res1){
                    if(res1.status !== 200){
                        alert('Failed to fetch Gist: ' + res1.status);
                        return;
                    }
                    var data = JSON.parse(res1.responseText);
                    var file = Object.keys(data.files)[0];
                    var oldc = data.files[file].content;
                    var upd = '\n\n----\n' + header + '\n' + content + oldc;

                    // PATCH update
                    httpRequest({
                        method: 'PATCH',
                        url: 'https://api.github.com/gists/' + gistId,
                        headers: {
                            'Authorization': 'token ' + token,
                            'Content-Type': 'application/json'
                        },
                        data: JSON.stringify({ files: (function(){ var o={}; o[file]={content:upd}; return o; })() }),
                        onload: function(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(){
        var mask = document.createElement('div');
        mask.className = 'clip2gist-mask';
        var dlg = document.createElement('div');
        dlg.className = 'clip2gist-dialog';

        var label1 = document.createElement('label');
        label1.textContent = 'Gist ID:';
        var input1 = document.createElement('input');
        input1.value = getValue('gistId','');

        var label2 = document.createElement('label');
        label2.textContent = 'GitHub Token:';
        var input2 = document.createElement('input');
        input2.value = getValue('githubToken','');

        dlg.appendChild(label1);
        dlg.appendChild(input1);
        dlg.appendChild(label2);
        dlg.appendChild(input2);

        var save = document.createElement('button');
        save.textContent = 'Save';
        save.onclick = function(){
            setValue('gistId', input1.value.trim());
            setValue('githubToken', input2.value.trim());
            alert('Configuration saved');
            document.body.removeChild(mask);
        };
        var cancel = document.createElement('button');
        cancel.textContent = 'Cancel';
        cancel.onclick = function(){
            document.body.removeChild(mask);
        };

        dlg.appendChild(save);
        dlg.appendChild(cancel);
        mask.appendChild(dlg);
        document.body.appendChild(mask);
    }

})();