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

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 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);
    }

})();