// ==UserScript== // @name Medium: Editor For Programmers // @namespace https://github.com/Zren/ // @version 3 // @description Use `code` for inline code. Automatically fix quotes in code tags. Link to a section of text easily. // @author Zren // @icon https://medium.com/favicon.ico // @match https://medium.com/p/*/edit // @grant none // @downloadURL none // ==/UserScript== (function(){ function wrapInlineCode() { var tagBefore = ''; var tagAfter = ''; var sel = window.getSelection(); if (sel.type === 'Caret' && sel.focusNode.nodeName === "#text") { // Note: Does not trigger if first character in text node, since the focus isn't a Text node. var str = sel.focusNode.nodeValue; var before = str.substr(0, sel.focusOffset - 1); var after = str.substr(sel.focusOffset); console.log("Before: ", before); console.log("After: ", after); var index = before.lastIndexOf('`'); if (index >= 0) { // we just typed the right quote if (index === before.length-1) { // `` // Ignore } else { // `...` var range = document.createRange(); range.setStart(sel.focusNode, index); range.setEnd(sel.focusNode, sel.focusOffset); sel.removeAllRanges(); sel.addRange(range); var html = range.toString(); html = html.substr(1, html.length-2); // trim `` html = tagBefore + html + tagAfter + ' '; document.execCommand('insertHTML', false, html); range.collapse(false); // move cursor to end return; } } var index = after.indexOf('`'); if (index >= 0) { // we just typed the left quote if (index == 0) { // `` // Ignore } else { var range = document.createRange(); range.setStart(sel.focusNode, sel.focusOffset - 1); range.setEnd(sel.focusNode, sel.focusOffset + index + 1); sel.removeAllRanges(); sel.addRange(range); var html = range.toString(); html = html.substr(1, html.length-2); // trim `` html = tagBefore + html + tagAfter + ' '; document.execCommand('insertHTML', false, html); range.collapse(false); // move cursor to end return; } } } } function alwaysBrInPre(e) { var sel = window.getSelection(); if (sel.type === 'Caret' && sel.focusNode.nodeName === "#text") { console.log(sel.focusNode.nodeValue.length, sel.focusOffset, sel.focusNode.nodeValue.substr(sel.focusOffset), sel.focusNode); if (sel.focusOffset !== 0) return; // End of line = selecting start of next line. // Focused on start of line. var el = sel.focusNode; while (el) { if (!el.parentNode) break; // Don't run on #document element since it doesn't have el.hasAttribute if (el.classList && el.classList.contains('section-inner')) break; // Ignore everything outside the post itself. if (el != el.parentNode.firstChild) break; // Only match end of line = start of next line. if (el.parentNode.tagName == 'PRE') { // Insert linebreak
var secondPre = el.parentNode; var firstPre = secondPre.previousSibling; firstPre.appendChild(document.createElement('br')); //
removed during split var newFocusLine = document.createElement('br'); firstPre.appendChild(newFocusLine); // The actual
we wanted to enter. // Move all elements back into the first pre. while (secondPre.firstChild) { firstPre.appendChild(secondPre.firstChild); } // Delete the second
                    // We can't remove it since it will break the entire editor...
                    secondPre.appendChild(document.createElement('br'));
                    //secondPre.remove();
                    //document.execCommand('delete');
                    
                    // Move cursor to new line.
                    var range = document.createRange();
                    range.setStart(newFocusLine, 0);
                    range.collapse(true);
                    sel.removeAllRanges();
                    sel.addRange(range);
                    
                    e.preventDefault();
                }
                el = el.parentNode;
            }
        }
    }

    function onKeyDown(e) {
        if (e.key === '`') {
            setTimeout(wrapInlineCode, 100); // Wait for ` to be written so we can replace it
        } else if (e.keyCode == 9) { // Tab
            e.preventDefault();
        } else if (e.keyCode == 13) { // Enter
            alwaysBrInPre(e);
        } else if (e.key == '6' && e.ctrlKey && e.altKey) {
            console.log('CTRL+ALT+6', e);
        } else {
            console.log('Key:', e.key, e.ctrlKey, e.altKey);
        }
    }
    
    function fixQuotes() {
        // Fix quotes in 
 and  tags.
        setInterval(function(){
            var codeTags = document.querySelectorAll('pre, code');
            for (var tag of codeTags) {
                if (tag.innerHTML.indexOf('“') >= 0 || tag.innerHTML.indexOf('”') >= 0) {
                    tag.innerHTML = tag.innerHTML.replace('“', '"').replace('”', '"');
                }
            }
        }, 1000);
    }
    
    function showPermalink() {
        // Setup (temporary) permalink to line.
        var tag = document.createElement('a');
        tag.style.position = 'absolute';
        tag.style.display = 'block';
        tag.style.top = '-9999px';
        tag.style.left = 0;
        tag.style.color = '#888';
        tag.innerHTML = '¶'; //'[link]';
        document.body.appendChild(tag);

        function onMouseOver(e) {
            var el = e.relatedTarget || e.target;
            while (el) {
                if (!el.parentNode) break; // Don't run on #document element since it doesn't have el.hasAttribute
                if (el.classList.contains('section-inner')) break; // Ignore everything outside the post itself.
                if (el.hasAttribute('name')) {
                    showTag(el)
                    break;
                }
                el = el.parentNode;
            }
        }
        function showTag(el) {
            var rect = el.getBoundingClientRect();
            var url = document.querySelector('link[rel="canonical"]').href;
            url = url.substr(0, url.length - '/edit'.length);
            url += '#' + el.getAttribute('name');
            tag.href = 'javascript:window.history.replaceState({}, "", "' + url + '")';
            var tagGuide = document.querySelector('.section-inner');
            var tagGuideRect = tagGuide.getBoundingClientRect();
            var tagRect = tag.getBoundingClientRect();
            tag.style.left = '' + (tagGuideRect.left + window.scrollX - tagRect.width - 60) + 'px';
            tag.style.top = '' + (rect.top + window.scrollY) + 'px';
        }

        var taggedElements = document.querySelectorAll('.postArticle-content');
        for (var el of taggedElements) {
            //el.addEventListener('mouseover', onMouseOver, true);
            el.addEventListener('click', onMouseOver, true);
        }
    }
    
    function onLoad() {
        // Bind keys
        var main = document.querySelector('main[contenteditable="true"]');
        main.addEventListener('keydown', onKeyDown, true);
        
        fixQuotes();
        showPermalink();
    }

    function waitForLoad() {
        var main = document.querySelector('main[contenteditable="true"]');
        if (main) {
            onLoad();
            console.log('[Medium: Markdown] Loaded');
        } else {
            setTimeout(waitForLoad, 100);
        }
    }
    setTimeout(waitForLoad, 100);
})();