Medium: Editor For Programmers

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

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name         Medium: Editor For Programmers
// @namespace    https://github.com/Zren/
// @version      5
// @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 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 tagBefore = '<code class="markup--code' + (sel.focusNode.parentNode.tagName === 'P' ? 'markup--p-code' : '') + '"><strong class="markup--strong markup--code-strong">';
            //var tagAfter = '</strong></code>';
            //var tagBefore = '<code class="markup--code' + (sel.focusNode.parentNode.tagName === 'P' ? 'markup--p-code' : '') + '">';
            var tagBefore = '<code>';
            var tagAfter = '</code>';
            
            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.indexOf('‘') >= 0 || tag.innerHTML.indexOf('’') >= 0) {
                    tag.innerHTML = tag.innerHTML.replace('“', '"').replace('”', '"').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);
})();