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

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

// ==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);
    }

})();