Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Broke some pages #4

Closed
xhacker opened this issue Nov 6, 2014 · 9 comments
Closed

Broke some pages #4

xhacker opened this issue Nov 6, 2014 · 9 comments
Labels

Comments

@xhacker
Copy link
Member

xhacker commented Nov 6, 2014

Like:

https://github.com/Nuclides/github-highlight-selected/blob/master/Update.plist
Don't click it directly, GitHub uses pjax. Open a new tab and enter the URL.

@xhacker xhacker added the bug label Nov 6, 2014
@xhacker
Copy link
Member Author

xhacker commented Nov 6, 2014

Another related issue is that when navigating though GitHub, the recent text node fix doesn't work. That's because document.ready is not triggered [1]. We should find a similar callback for pjax.

[1] http://stackoverflow.com/questions/8103449/document-ready-not-triggered-with-pjax

@PAEz
Copy link
Contributor

PAEz commented Dec 28, 2014

HI.
Thanks for this extension, really needed it.
I wanted it to work without page reloading coz I use OctoTree (because it rocks) and so had that problem.
Below is the code I used to get it to work.
Sorry its not in jquery style, but I only code JS.

$(function() {
    var codeArea = $(".js-file-line-container");
    var fileLineContainer='.js-file-line-container';
    var lastPage = location.href;
    //     function wrapTextNodes(el){
    //         console.log('wrapping',el)
    //         el.find(".js-file-line").contents().filter(function () {
    //             return this.nodeType == Node.TEXT_NODE;
    //         }).each(function () {
    //             $(this).replaceWith('<span>' + $(this).text() + '</span>');
    //         });}


    function wrapTextNodes(query) {
        // Why did all the gawd dam text selection stuff fail, urgggggghh....atleast this worked!
        function getTextNodesIn(elem, opt_fnFilter) {
            var textNodes = [];
            if (elem) {
                for (var nodes = elem.childNodes, i = nodes.length; i--;) {
                    var node = nodes[i],
                        nodeType = node.nodeType;
                    if (nodeType == 3) {
                        if (!opt_fnFilter || opt_fnFilter(node, elem)) {
                            textNodes.push(node);
                        }
                    } else if (nodeType == 1 || nodeType == 9 || nodeType == 11) {
                        textNodes = textNodes.concat(getTextNodesIn(node, opt_fnFilter));
                    }
                }
            }
            return textNodes;
        }
        var el = document.querySelector(query);
        if (!el) return;
        var results = getTextNodesIn(el);
        results.forEach(function(node) {
            var span = document.createElement('span');
            span.textContent = node.nodeValue;
            if (node.parentNode) node.parentNode.replaceChild(span, node);
        })
    }

    wrapTextNodes(fileLineContainer);

    // watch for page updating...
    var whatToObserve = {
        childList: true,
        attributes: false,
        subtree: true,
        attributeOldValue: false /*, attributeFilter: []*/
    };
    var mutationObserver = new MutationObserver(function(mutationRecords) {
        if (location.href != lastPage) {
            lastPage = location.href;
            wrapTextNodes(fileLineContainer);
        }
    });
    mutationObserver.observe(document.querySelector('.file-navigation .breadcrumb'), whatToObserve);

    function restore() {
        $(".ghs-highlight").removeClass("ghs-highlight");
        $(".ghs-partial-highlight").contents().unwrap();
    }

    function replaceAll(str, target, replacement) {
        return str.split(target).join(replacement);
    }

    $("body").mouseup(function(e) {
        restore();
        var codeArea = $(fileLineContainer); // think the page updating killed this, this fixs that
        var selection = $.trim(window.getSelection());

        if (selection) {
            codeArea.find("span:not(:has(*))").each(function() {
                if (this != e.target) {
                    if ($(this).text() == selection) {
                        $(this).addClass("ghs-highlight");
                    }
                    else if ($(this).text().indexOf(selection) > -1) {
                        $(this).html(function(_, html) {
                            return replaceAll(html, selection, '<span class="ghs-partial-highlight">' + selection + '</span>');
                        });
                    }
                }
            });
        }
    });
});

OH, and update the webstore :P

@PAEz
Copy link
Contributor

PAEz commented Dec 28, 2014

Look Mah!!! No JQuery!!!

(function() {

    var fileLineContainer = '.js-file-line-container';
    var lastPage = location.href;
    var textNodes;

    function wrapTextNodes(query) {
        // Why did all the gawd dam text selection stuff fail, urgggggghh....atleast this worked!   http://cwestblog.com/2014/03/14/javascript-getting-all-text-nodes/
        function getTextNodesIn(elem, opt_fnFilter) {
            if (elem) {
                for (var nodes = elem.childNodes, i = nodes.length; i--;) {
                    var node = nodes[i],
                        nodeType = node.nodeType;
                    if (nodeType == 3) {
                        if (!opt_fnFilter || opt_fnFilter(node, elem)) {
                            if (node.nodeValue.trim() != '') textNodes.push(node);
                        }
                    } else if (nodeType == 1 || nodeType == 9 || nodeType == 11) {
                        getTextNodesIn(node, opt_fnFilter)
                    }
                }
            }
        }
        var el = document.querySelector(query);
        if (!el) return;
        textNodes = [];
        getTextNodesIn(el);
        textNodes.forEach(function(node, index) {
            var span = document.createElement('span');
            span.textContent = node.nodeValue;
            node.parentNode.replaceChild(span, node);
            textNodes[index] = span;
        });
    }

    wrapTextNodes(fileLineContainer);

    // watch for page updating...
    var whatToObserve = {
        childList: true,
        attributes: false,
        subtree: false,
        attributeOldValue: false /*, attributeFilter: []*/
    };
    var mutationObserver = new MutationObserver(function(mutationRecords) {
        if (location.href != lastPage) {
            lastPage = location.href;
            wrapTextNodes(fileLineContainer);
        }
    });
    mutationObserver.observe(document.querySelector('#js-repo-pjax-container'), whatToObserve);

    function restore() {
        [].forEach.call(document.querySelectorAll(".ghs-highlight"), function(el) {
            el.classList.remove("ghs-highlight");
        });
        [].forEach.call(document.querySelectorAll(".ghs-partial-highlight"), function(el) {
            el.parentNode.replaceChild(el.childNodes[0], el);
        });
    }

    function replaceAll(str, target, replacement) {
        return str.split(target).join(replacement);
    }

    document.body.addEventListener('mouseup', function(e) {
        restore();
        var selection = window.getSelection().toString().trim();

        if (selection) {
            textNodes.forEach(function(el) {
                if (el != e.target) {
                    if (el.textContent == selection) {
                        el.classList.add("ghs-highlight");
                    } else if (el.textContent.indexOf(selection) > -1) {
                        el.innerHTML = replaceAll(el.innerHTML, selection, '<span class="ghs-partial-highlight">' + selection + '</span>');
                    }
                }
            });
        }
    });
})();

hehe, couldnt resist, small things should be small....seems to work ;)

@PAEz
Copy link
Contributor

PAEz commented Dec 29, 2014

And here it is with the ability to highlight things in the same element and when stuff is highlighted you can use ctrl+Up and ctrl+Down to go through highlights......do what ever you want with it......let me guess, its gonna sit here and rot :P

(function() {

    var fileLineContainer = '.js-file-line-container';
    var lastPage = location.href;
    var textNodes = [];
    var highlighted = [];
    var highlightedIndex;

    function wrapTextNodes(query) {
        // Why did all the gawd dam text selection stuff fail, urgggggghh....atleast this worked!
        function getTextNodesIn(elem, opt_fnFilter) {
            if (elem) {
                for (var nodes = elem.childNodes, i = nodes.length; i--;) {
                    var node = nodes[i],
                        nodeType = node.nodeType;
                    if (nodeType == 3) {
                        if (!opt_fnFilter || opt_fnFilter(node, elem)) {
                            if (node.nodeValue.trim() != '') textNodes.push(node);
                        }
                    } else if (nodeType == 1 || nodeType == 9 || nodeType == 11) {
                        getTextNodesIn(node, opt_fnFilter)
                    }
                }
            }
        }
        var el = document.querySelector(query);
        if (!el) return;
        textNodes = [];
        getTextNodesIn(el);
        textNodes.forEach(function(node, index) {
            var span = document.createElement('span');
            span.textContent = node.nodeValue;
            node.parentNode.replaceChild(span, node);
            textNodes[index] = span;
        });
        textNodes.reverse();
    }

    wrapTextNodes(fileLineContainer);

    // watch for page updating...
    var whatToObserve = {
        childList: true,
        attributes: false,
        subtree: false,
        attributeOldValue: false /*, attributeFilter: []*/
    };
    var mutationObserver = new MutationObserver(function(mutationRecords) {
        if (location.href != lastPage) {
            lastPage = location.href;
            wrapTextNodes(fileLineContainer);
        }
    });
    mutationObserver.observe(document.querySelector('#js-repo-pjax-container'), whatToObserve);

    function restore() {
        highlighted.forEach(function(el) {
            if (el.classList.contains('ghs-highlight')) el.classList.remove("ghs-highlight");
            else {
                var parent = el.parentNode;
                parent.replaceChild(el.childNodes[0], el);
                parent.normalize();
            }
        })
        highlighted = [];
    }

    function selectElement(el) {
        var range = document.createRange();
        range.selectNodeContents(el);
        var sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
    }

    function splitReplace(el, str, selectionIndex) {
        var strLength = str.length;
        var source = el.textContent;
        var pos = -1;
        var lastPos = 0;
        var result = document.createDocumentFragment();
        var selected;
        while ((pos = source.indexOf(str, pos + 1)) != -1) {
            if (pos > lastPos) result.appendChild(document.createTextNode(source.substring(lastPos, pos)));
            var highlight = document.createElement('span');
            highlight.textContent = source.substring(pos, pos + strLength);
            highlight.classList.add('ghs-partial-highlight');
            if (pos === selectionIndex) {
                selected = highlight;
                highlightedIndex = highlighted.length;
            }
            result.appendChild(highlight);
            highlighted.push(highlight);
            lastPos = pos + strLength;
        }
        result.appendChild(document.createTextNode(source.substring(lastPos)));
        el.replaceChild(result, el.childNodes[0]);
        if (selected) selectElement(selected);
    }

    document.body.addEventListener('mousedown', function(e) {
        if (e.which != 1 || highlighted.length == 0) return;
        restore();
    });

    document.body.addEventListener('mouseup', function(e) {
        if (e.which != 1) return;
        var selection = window.getSelection();
        var selected = selection.toString().trim();

        if (selected) {
            textNodes.forEach(function(el) {
                if (el.textContent == selected) {
                    el.classList.add("ghs-highlight");
                    if (el == e.target) highlightedIndex = highlighted.length;
                    highlighted.push(el);
                } else if (el.textContent.indexOf(selected) > -1) {
                    if (el != e.target) splitReplace(el, selected);
                    else splitReplace(el, selected, Math.min(selection.anchorOffset, selection.focusOffset));
                };

            });
        }
    });

    window.addEventListener('keydown', function(e) {

        if (!highlighted.length > 0 || !e.ctrlKey) return;

        if (e.keyCode == 38) { // down key
            highlightedIndex--;
            if (highlightedIndex < 0) highlightedIndex = highlighted.length - 1;

        } else if (e.keyCode == 40) { // up key
            highlightedIndex++;
            if (highlightedIndex >= highlighted.length) highlightedIndex = 0;

        } else return;

        selectElement(highlighted[highlightedIndex]);
        elShow(highlighted[highlightedIndex]);
        e.preventDefault()
        return false;
    });

    function elShow(el) {
        var rect = el.getBoundingClientRect();
        if (rect.bottom >= (window.innerHeight || document.documentElement.clientHeight)) el.scrollIntoView(false);
        else if (rect.top <= 0) el.scrollIntoView(true);
    }

    var css = document.createElement("style");
    css.type = "text/css";
    css.innerHTML = ".ghs-highlight, .ghs-partial-highlight { border: 1px solid rgba(255, 181, 21, .6); background-color: rgba(255, 181, 21, .3);}";
    document.body.appendChild(css);

})();

@PAEz
Copy link
Contributor

PAEz commented Dec 30, 2014

hehehe....I gotta stop and get back to the winamp thing, but.....
Here it is with the ability to show a bar on the right that displays where the occurrences are.
Perfect would be to replace the background of the scroll bar, but Ive had problems mucking with that in the past so decided to not even try. But I still think its rather dam cool anyways, yeah? I was actually surprised at how painless this was to implement.

(function() {

    var fileLineContainer = '.js-file-line-container';
    var lastPage = location.href;
    var textNodes = [];
    var highlighted = [];
    var highlightedIndex;

    function wrapTextNodes(query) {
        // Why did all the gawd dam text selection stuff fail, urgggggghh....atleast this worked!   http://cwestblog.com/2014/03/14/javascript-getting-all-text-nodes/
        function getTextNodesIn(elem, opt_fnFilter) {
            if (elem) {
                for (var nodes = elem.childNodes, i = nodes.length; i--;) {
                    var node = nodes[i],
                        nodeType = node.nodeType;
                    if (nodeType == 3) {
                        if (!opt_fnFilter || opt_fnFilter(node, elem)) {
                            if (node.nodeValue.trim() != '') {
                                var span = document.createElement('span');
                                span.textContent = node.nodeValue;
                                node.parentNode.replaceChild(span, node);
                                textNodes.push(span);
                            }
                        }
                    } else if (nodeType == 1 || nodeType == 9 || nodeType == 11) {
                        getTextNodesIn(node, opt_fnFilter)
                    }
                }
            }
        }
        var el = document.querySelector(query);
        if (!el) return;
        textNodes = [];
        getTextNodesIn(el);
        textNodes.reverse();
    }

    wrapTextNodes(fileLineContainer);

    // watch for page updating...
    var whatToObserve = {
        childList: true,
        attributes: false,
        subtree: false,
        attributeOldValue: false /*, attributeFilter: []*/
    };
    var mutationObserver = new MutationObserver(function(mutationRecords) {
        if (location.href != lastPage) {
            lastPage = location.href;
            wrapTextNodes(fileLineContainer);
        }
    });
    mutationObserver.observe(document.querySelector('#js-repo-pjax-container'), whatToObserve);

    function restore() {
        highlighted.forEach(function(el) {
            if (el.classList.contains('ghs-highlight')) el.classList.remove("ghs-highlight");
            else {
                var parent = el.parentNode;
                parent.replaceChild(el.childNodes[0], el);
                parent.normalize();
            }
        })
        highlighted = [];
    }

    function selectElement(el) {
        var range = document.createRange();
        range.selectNodeContents(el);
        var sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
    }

    function splitReplace(el, str, selectionIndex) {
        var strLength = str.length;
        var source = el.textContent;
        var pos = -1;
        var lastPos = 0;
        var result = document.createDocumentFragment();
        var selected;
        while ((pos = source.indexOf(str, pos + 1)) != -1) {
            if (pos > lastPos) result.appendChild(document.createTextNode(source.substring(lastPos, pos)));
            var highlight = document.createElement('span');
            highlight.textContent = source.substring(pos, pos + strLength);
            highlight.classList.add('ghs-partial-highlight');
            if (pos === selectionIndex) {
                selected = highlight;
                highlightedIndex = highlighted.length;
            }
            result.appendChild(highlight);
            highlighted.push(highlight);
            lastPos = pos + strLength;
        }
        result.appendChild(document.createTextNode(source.substring(lastPos)));
        el.replaceChild(result, el.childNodes[0]);
        if (selected) selectElement(selected);
    }

    document.body.addEventListener('mousedown', function(e) {
        if (e.which != 1 || highlighted.length == 0) return;
        restore();
        canvas.style.display = 'none';
    });

    document.body.addEventListener('mouseup', function(e) {
        if (e.which != 1) return;
        var selection = window.getSelection();
        var selected = selection.toString().trim();

        if (selected) {
            textNodes.forEach(function(el) {
                if (el.textContent == selected) {
                    el.classList.add("ghs-highlight");
                    if (el == e.target) highlightedIndex = highlighted.length;
                    highlighted.push(el);
                } else if (el.textContent.indexOf(selected) > -1) {
                    if (el != e.target) splitReplace(el, selected);
                    else splitReplace(el, selected, Math.min(selection.anchorOffset, selection.focusOffset));
                };

            });
            updateHighlighter();
        }
    });

    window.addEventListener('keydown', function(e) {

        if (!highlighted.length > 0 || !e.ctrlKey) return;

        if (e.keyCode == 38) { // down key
            highlightedIndex--;
            if (highlightedIndex < 0) highlightedIndex = highlighted.length - 1;

        } else if (e.keyCode == 40) { // up key
            highlightedIndex++;
            if (highlightedIndex >= highlighted.length) highlightedIndex = 0;

        } else return;

        selectElement(highlighted[highlightedIndex]);
        elShow(highlighted[highlightedIndex]);
        updateHighlighter();
        e.preventDefault()
        return false;
    });

    function elShow(el) {
        var rect = el.getBoundingClientRect();
        if (rect.bottom >= (window.innerHeight || document.documentElement.clientHeight)) el.scrollIntoView(false);
        else if (rect.top <= 0) el.scrollIntoView(true);
    }

    // Do the Highlighter bar on the right
    var canvas = document.createElement("canvas");
    canvas.setAttribute('id', 'highlighternoconflict');
    var canvasUpdating = false;
    document.body.appendChild(canvas);
    var ctx = canvas.getContext('2d');

    function generateHighlighter() {
        canvas.style.display = 'block';

        canvas.height = document.documentElement.getBoundingClientRect().height;
        canvas.width = 20;

        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = "rgba(0,0,0,0.1)";
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        ctx.fillStyle = "rgba(255, 181, 21, .3)";
        highlighted.forEach(function(el, index) {
            if (index == highlightedIndex) ctx.fillStyle = "rgba(0, 0, 255, 0.8)";
            else ctx.fillStyle = "rgba(255, 181, 21, .3)";
            var box = el.getBoundingClientRect();
            ctx.fillRect(0, window.scrollY + box.top, canvas.width, box.height);
        })
        ctx.fillStyle = "rgba(0,0,0,0.0)";
        ctx.strokeStyle = "rgba(0,200,200,0.85)";
        ctx.strokeRect(0, window.scrollY, canvas.width, window.innerHeight);
        canvasUpdating = false;
    }

    function updateHighlighter() {
        if (highlighted.length && canvasUpdating == false) {
            canvasUpdating = true;
            window.requestAnimationFrame(generateHighlighter);
        }
    }

    window.addEventListener('scroll', updateHighlighter);
    window.addEventListener('resize', updateHighlighter);

    // Add the css...with a content script this would be seperate but I put it here for dev purposes..this was made in the Snippets section of the dev tools Sources panel
    var css = document.createElement("style");
    css.type = "text/css";
    css.innerHTML = (function() {
        /*
.ghs-highlight, .ghs-partial-highlight {
    border: 1px solid rgba(255, 181, 21, .6);
    background-color: rgba(255, 181, 21, .3);
}

#highlighternoconflict{
    width:20px;
    position:fixed;
    top:0px;
    bottom:0px;
    right:0;
    height:100%;
    display:none;
}
*/
    }).toString().split('\n').slice(2, -2).join('\n').trim();
    document.body.appendChild(css);

})();

@xhacker
Copy link
Member Author

xhacker commented Dec 30, 2014

@PAEz: thanks for comments! We are currently traveling, will look into it some days later :)

@PAEz
Copy link
Contributor

PAEz commented Jan 1, 2015

Cool, would be interested to know what ya think.
I tried it on a really big page....
https://github.com/leetreveil/musicmetadata/blob/master/dist%2Fmusicmetadata.js
...and changed the way the occurrence bar is drawn, plus you have to use a load event as ready is too early for that page. Considering just how big that page is I thought the pauses when this thing did its work was very acceptable.
Also made it so you can now click somewhere on the occurrence bar and the page goes there and click and drag to scroll up and down.

window.addEventListener('load', function() {

    var fileLineContainer = '.js-file-line-container';
    var lastPage = location.href;
    var textNodes = [];
    var highlighted = [];
    var highlightedIndex;

    function wrapTextNodes(query) {
        // Why did all the gawd dam text selection stuff fail, urgggggghh....atleast this worked!   http://cwestblog.com/2014/03/14/javascript-getting-all-text-nodes/
        function getTextNodesIn(elem, opt_fnFilter) {
            if (elem) {
                for (var nodes = elem.childNodes, i = nodes.length; i--;) {
                    var node = nodes[i],
                        nodeType = node.nodeType;
                    if (nodeType == 3) {
                        if (!opt_fnFilter || opt_fnFilter(node, elem)) {
                            if (node.nodeValue.trim() != '') {
                                var span = document.createElement('span');
                                span.textContent = node.nodeValue;
                                node.parentNode.replaceChild(span, node);
                                textNodes.push(span);
                            }
                        }
                    } else if (nodeType == 1 || nodeType == 9 || nodeType == 11) {
                        getTextNodesIn(node, opt_fnFilter)
                    }
                }
            }
        }
        var el = document.querySelector(query);
        if (!el) return;
        textNodes = [];
        getTextNodesIn(el);
        textNodes.reverse();
    }

    wrapTextNodes(fileLineContainer);

    // watch for page updating...
    var whatToObserve = {
        childList: true,
        attributes: false,
        subtree: false,
        attributeOldValue: false /*, attributeFilter: []*/
    };
    var mutationObserver = new MutationObserver(function(mutationRecords) {
        if (location.href != lastPage) {
            lastPage = location.href;
            wrapTextNodes(fileLineContainer);
        }
    });
    mutationObserver.observe(document.querySelector('#js-repo-pjax-container'), whatToObserve);

    function restore() {
        highlighted.forEach(function(el) {
            if (el.classList.contains('ghs-highlight')) el.classList.remove("ghs-highlight");
            else {
                var parent = el.parentNode;
                parent.replaceChild(el.childNodes[0], el);
                parent.normalize();
            }
        })
        highlighted = [];
    }

    function selectElement(el) {
        var range = document.createRange();
        range.selectNodeContents(el);
        var sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
    }

    function splitReplace(el, str, selectionIndex) {
        var strLength = str.length;
        var source = el.textContent;
        var pos = -1;
        var lastPos = 0;
        var result = document.createDocumentFragment();
        var selected;
        while ((pos = source.indexOf(str, pos + 1)) != -1) {
            if (pos > lastPos) result.appendChild(document.createTextNode(source.substring(lastPos, pos)));
            var highlight = document.createElement('span');
            highlight.textContent = source.substring(pos, pos + strLength);
            highlight.classList.add('ghs-partial-highlight');
            if (pos === selectionIndex) {
                selected = highlight;
                highlightedIndex = highlighted.length;
            }
            result.appendChild(highlight);
            highlighted.push(highlight);
            lastPos = pos + strLength;
        }
        result.appendChild(document.createTextNode(source.substring(lastPos)));
        el.replaceChild(result, el.childNodes[0]);
        if (selected) selectElement(selected);
    }

    var canvasDraggin = false;

    document.body.addEventListener('mousedown', function(e) {
        if (e.target == canvas && e.which===1) {
            canvasDraggin = true;
            var box=document.documentElement.getBoundingClientRect();
            var heightRatio = box.height / window.innerHeight;
            var half= window.innerHeight/2 ;
            document.body.scrollTop = (e.y * heightRatio)-half;
            window.addEventListener('mousemove', canvasDragger);
            return;
        }
        if (e.which != 1 || highlighted.length == 0 || e.target == canvas) return;
        restore();
        canvas.style.display = 'none';
    });

    function canvasDragger(e) {
        if (!canvasDraggin) return;
        var heightRatio = document.documentElement.getBoundingClientRect().height / window.innerHeight;
        var half= window.innerHeight/2 ;
        document.body.scrollTop = (e.y * heightRatio)-half;
        e.preventDefault();
        return false;
    }

    document.body.addEventListener('mouseup', function(e) {
        if (e.which != 1) return;
        if(canvasDraggin){
            canvasDraggin = false;
            window.removeEventListener('mousemove', canvasDragger);
            return;
        }
        var selection = window.getSelection();
        var selected = selection.toString().trim();

        if (selected) {
            textNodes.forEach(function(el) {
                if (el.textContent == selected) {
                    el.classList.add("ghs-highlight");
                    if (el == e.target) highlightedIndex = highlighted.length;
                    highlighted.push(el);
                } else if (el.textContent.indexOf(selected) > -1) {
                    if (el != e.target) splitReplace(el, selected);
                    else splitReplace(el, selected, Math.min(selection.anchorOffset, selection.focusOffset));
                };

            });
            updateHighlighter();
        }
    });

    window.addEventListener('keydown', function(e) {

        if (!highlighted.length > 0 || !e.ctrlKey) return;

        if (e.keyCode == 38) { // down key
            highlightedIndex--;
            if (highlightedIndex < 0) highlightedIndex = highlighted.length - 1;

        } else if (e.keyCode == 40) { // up key
            highlightedIndex++;
            if (highlightedIndex >= highlighted.length) highlightedIndex = 0;

        } else return;

        selectElement(highlighted[highlightedIndex]);
        elShow(highlighted[highlightedIndex]);
        updateHighlighter();
        e.preventDefault();
        return false;
    });

    function elShow(el) {
        var rect = el.getBoundingClientRect();
        if (rect.bottom >= (window.innerHeight || document.documentElement.clientHeight)) el.scrollIntoView(false);
        else if (rect.top <= 0) el.scrollIntoView(true);
    }

    // Do the Highlighter bar on the right
    canvas = document.createElement("canvas");

    canvas.setAttribute('id', 'highlighternoconflict');
    var canvasUpdating = false;
    document.body.appendChild(canvas);
    var ctx = canvas.getContext('2d');

    function generateHighlighter() {
        var canvasHeight = window.innerHeight; // Height to make the bar
        var heightRatio = canvasHeight / document.documentElement.getBoundingClientRect().height;

        canvas.style.display = 'block';

        //         canvas.height = document.documentElement.getBoundingClientRect().height;
        canvas.height = canvasHeight;

        canvas.width = 20;

        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = "rgba(0,0,0,0.1)";
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        ctx.fillStyle = "rgba(255, 181, 21, .3)";
        var lastY = -1,
            y = 0;
        highlighted.forEach(function(el, index) {
            var box = el.getBoundingClientRect();
            y = (((window.scrollY + box.top) * heightRatio) + 0.5) | 0;
            if (y == lastY && index != highlightedIndex) return;

            if (index == highlightedIndex) ctx.fillStyle = "rgba(0, 0, 255, 1)";
            else ctx.fillStyle = "rgba(255, 181, 21, 1)";
            ctx.fillRect(0, y, canvas.width, (((box.height * heightRatio) + .5) | 0) || 1);
            lastY = y;
        })
        ctx.fillStyle = "rgba(0,0,0,0.0)";
        ctx.lineWidth = 1;
        ctx.strokeStyle = "rgba(0,200,200,0.85)";
        ctx.strokeRect(0, ((window.scrollY * heightRatio) + 0.5) | 0, canvas.width, ((window.innerHeight * heightRatio) + .5) | 0);
        canvasUpdating = false;
    }

    function updateHighlighter() {
        if (highlighted.length && canvasUpdating == false) {
            canvasUpdating = true;
            window.requestAnimationFrame(generateHighlighter);
        }
    }

    window.addEventListener('scroll', updateHighlighter);
    window.addEventListener('resize', updateHighlighter);

    // Add the css...with a content script this would be seperate but I put it here for dev purposes..this was made in the Snippets section of the dev tools Sources panel
    var css = document.createElement("style");
    css.type = "text/css";
    css.innerHTML = (function() {
        /*
.ghs-highlight, .ghs-partial-highlight {
    border: 1px solid rgba(255, 181, 21, .6);
    background-color: rgba(255, 181, 21, .3);
}

#highlighternoconflict{
    width:20px;
    position:fixed;
    top:0px;
    bottom:0px;
    right:0;
    height:100%;
    display:none;
}
*/
    }).toString().split('\n').slice(2, -2).join('\n').trim();
    document.body.appendChild(css);

}); //)();

@PAEz
Copy link
Contributor

PAEz commented Jan 2, 2015

hehe...one last time....
Noticed the bar didnt work in FF right so fixed that.
I dont actually ever use FF but I like it and so thought Id try it in that. It was a bit broken but with a few changes it works great in both browsers now. I just used it as a Greasemonkey script, so you could add that to your releases.

window.addEventListener('load', function() {
// (function(){
    var fileLineContainer = '.js-file-line-container';
    var lastPage = location.href;
    var textNodes = [];
    var highlighted = [];
    var highlightedIndex;

    function wrapTextNodes(query) {
        // Why did all the gawd dam text selection stuff fail, urgggggghh....atleast this worked!   http://cwestblog.com/2014/03/14/javascript-getting-all-text-nodes/
        function getTextNodesIn(elem, opt_fnFilter) {
            if (elem) {
                for (var nodes = elem.childNodes, i = nodes.length; i--;) {
                    var node = nodes[i],
                        nodeType = node.nodeType;
                    if (nodeType == 3) {
                        if (!opt_fnFilter || opt_fnFilter(node, elem)) {
                            if (node.nodeValue.trim() != '') {
                                var span = document.createElement('span');
                                span.textContent = node.nodeValue;
                                node.parentNode.replaceChild(span, node);
                                textNodes.push(span);
                            }
                        }
                    } else if (nodeType == 1 || nodeType == 9 || nodeType == 11) {
                        getTextNodesIn(node, opt_fnFilter)
                    }
                }
            }
        }
        var el = document.querySelector(query);
        if (!el) return;
        textNodes = [];
        getTextNodesIn(el);
        textNodes.reverse();
    }

    wrapTextNodes(fileLineContainer);

    // watch for page updating...
    var whatToObserve = {
        childList: true,
        attributes: false,
        subtree: false,
        attributeOldValue: false /*, attributeFilter: []*/
    };
    var mutationObserver = new MutationObserver(function(mutationRecords) {
        if (location.href != lastPage) {
            lastPage = location.href;
            wrapTextNodes(fileLineContainer);
        }
    });
    mutationObserver.observe(document.querySelector('#js-repo-pjax-container'), whatToObserve);

    function restore() {
        highlighted.forEach(function(el) {
            if (el.classList.contains('ghs-highlight')) el.classList.remove("ghs-highlight");
            else {
                var parent = el.parentNode;
                parent.replaceChild(el.childNodes[0], el);
                parent.normalize();
            }
        })
        highlighted = [];
    }

    function selectElement(el) {
        var range = document.createRange();
        range.selectNodeContents(el);
        var sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
    }

    function splitReplace(el, str, selectionIndex) {
        var strLength = str.length;
        var source = el.textContent;
        var pos = -1;
        var lastPos = 0;
        var result = document.createDocumentFragment();
        var selected;
        while ((pos = source.indexOf(str, pos + 1)) != -1) {
            if (pos > lastPos) result.appendChild(document.createTextNode(source.substring(lastPos, pos)));
            var highlight = document.createElement('span');
            highlight.textContent = source.substring(pos, pos + strLength);
            highlight.classList.add('ghs-partial-highlight');
            if (pos === selectionIndex) {
                selected = highlight;
                highlightedIndex = highlighted.length;
            }
            result.appendChild(highlight);
            highlighted.push(highlight);
            lastPos = pos + strLength;
        }
        result.appendChild(document.createTextNode(source.substring(lastPos)));
        el.replaceChild(result, el.childNodes[0]);
        if (selected) selectElement(selected);
    }

    var canvasDraggin = false;

    function barScrollToY(y){
        var iHieght = document.documentElement.clientHeight;
        var box = document.documentElement.getBoundingClientRect();
            var heightRatio = box.height / iHieght;
            var half= iHieght/2 ;
            window.scrollTo(window.pageXOffset,(y * heightRatio)-half);
    }

    document.body.addEventListener('mousedown', function(e) {
        if (e.target == canvas && e.which===1) {
            canvasDraggin = true;
            barScrollToY(e.clientY);
            window.addEventListener('mousemove', canvasDragger);
            return;
        }
        if (e.which != 1 || highlighted.length == 0) return;
        restore();
        canvas.style.display = 'none';
    });

    function canvasDragger(e) {
        if (!canvasDraggin) return;
        barScrollToY(e.clientY);
        e.preventDefault();
        return false;
    }

    document.body.addEventListener('mouseup', function(e) {
        if (e.which != 1) return;
        if(canvasDraggin){
            canvasDraggin = false;
            window.removeEventListener('mousemove', canvasDragger);
            return;
        }
        var selection = window.getSelection();
        var selected = selection.toString().trim();

        if (selected) {
            textNodes.forEach(function(el) {
                if (el.textContent == selected) {
                    el.classList.add("ghs-highlight");
                    if (el == e.target) highlightedIndex = highlighted.length;
                    highlighted.push(el);
                } else if (el.textContent.indexOf(selected) > -1) {
                    if (el != e.target) splitReplace(el, selected);
                    else splitReplace(el, selected, Math.min(selection.anchorOffset, selection.focusOffset));
                };

            });
            updateHighlighter();
        }
    });

    window.addEventListener('keydown', function(e) {

        if (!highlighted.length > 0 || !e.ctrlKey) return;

        if (e.keyCode == 38) { // down key
            highlightedIndex--;
            if (highlightedIndex < 0) highlightedIndex = highlighted.length - 1;

        } else if (e.keyCode == 40) { // up key
            highlightedIndex++;
            if (highlightedIndex >= highlighted.length) highlightedIndex = 0;

        } else return;

        selectElement(highlighted[highlightedIndex]);
        showElement(highlighted[highlightedIndex]);
        updateHighlighter();
        e.preventDefault();
        return false;
    });

    function showElement(el) {
        var rect = el.getBoundingClientRect();
        if (rect.bottom >= document.documentElement.clientHeight) el.scrollIntoView(false);
        else if (rect.top <= 0) el.scrollIntoView(true);
    }

    // Do the Highlighter bar on the right
    canvas = document.createElement("canvas");

    canvas.setAttribute('id', 'highlighternoconflict');
    var canvasUpdating = false;
    document.body.appendChild(canvas);
    var ctx = canvas.getContext('2d');

    function generateHighlighter() {
        var canvasHeight = window.document.documentElement.clientHeight; // Height to make the bar
        var heightRatio = canvasHeight / document.documentElement.getBoundingClientRect().height;

        canvas.style.display = 'block';

        //         canvas.height = document.documentElement.getBoundingClientRect().height;
        canvas.height = canvasHeight;

        canvas.width = 20;

        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = "rgba(0,0,0,0.1)";
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        ctx.fillStyle = "rgba(255, 181, 21, .3)";
        var lastY = -1,
            y = 0;
        highlighted.forEach(function(el, index) {
            var box = el.getBoundingClientRect();
            y = (((window.scrollY + box.top) * heightRatio) + 0.5) | 0;
            if (y == lastY && index != highlightedIndex) return;

            if (index == highlightedIndex) ctx.fillStyle = "rgba(0, 0, 255, 1)";
            else ctx.fillStyle = "rgba(255, 181, 21, 1)";
            ctx.fillRect(0, y, canvas.width, (((box.height * heightRatio) + .5) | 0) || 1);
            lastY = y;
        })
        ctx.fillStyle = "rgba(0,0,0,0.0)";
        ctx.lineWidth = 1;
        ctx.strokeStyle = "rgba(0,200,200,0.85)";
        ctx.strokeRect(0, ((window.scrollY * heightRatio) + 0.5) | 0, canvas.width, ((canvasHeight * heightRatio) + .5) | 0);
        canvasUpdating = false;
    }

    function updateHighlighter() {
        if (highlighted.length && canvasUpdating == false) {
            canvasUpdating = true;
            window.requestAnimationFrame(generateHighlighter);
        }
    }

    window.addEventListener('scroll', updateHighlighter);
    window.addEventListener('resize', updateHighlighter);

    // Add the css...with a content script this would be seperate but I put it here for dev purposes..this was made in the Snippets section of the dev tools Sources panel
    var css = document.createElement("style");
    css.type = "text/css";
    css.innerHTML = (function() {
        /*
.ghs-highlight, .ghs-partial-highlight {
    border: 1px solid rgba(255, 181, 21, .6);
    background-color: rgba(255, 181, 21, .3);
}

#highlighternoconflict{
    width:20px;
    position:fixed;
    top:0px;
    bottom:0px;
    right:0;
    height:100%;
    display:none;
}
*/
    }).toString().split('\n').slice(2, -2).join('\n').trim();
    document.body.appendChild(css);

}); //)();
// })();

@xhacker
Copy link
Member Author

xhacker commented Jan 6, 2015

Finally get home. I love all the features you implemented, especially the bar! But I'm a little overwhelmed. Could you split it up and use pull request? One for fixing the bug, one for removing jQuery (I don’t think it’s necessary though—as a half-way js dev I kinda like jQuery), one for add the awesome bar, etc. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants