Medium: Editor For Programmers

Use `code` for inline code. Automatically fix quotes in code tags. Link to a section of text easily.

Ajankohdalta 9.7.2016. Katso uusin versio.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Medium: Editor For Programmers
// @namespace    https://github.com/Zren/
// @version      4
// @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
// ==/UserScript==

(function(){
    function wrapInlineCode() {
        var tagBefore = '<code class="markup--code"><strong class="markup--strong">';
        var tagAfter = '</strong></code>';

        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 <br>
                    
                    var secondPre = el.parentNode;
                    var firstPre = secondPre.previousSibling;
                    
                    firstPre.appendChild(document.createElement('br')); // <br> removed during split
                    firstPre.appendChild(document.createTextNode(''));
                    var newFocusLine = document.createElement('br');
                    firstPre.appendChild(newFocusLine); // The actual <br> we wanted to enter.
                    
                    // Move all elements back into the first pre.
                    while (secondPre.firstChild) {
                        firstPre.appendChild(secondPre.firstChild);
                    }
                    
                    // Delete the second <pre>
                    // 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 <pre> and <code> 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);
})();