Code Browser Bookmark

Add code bookmark in code-browser

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Code Browser Bookmark
// @namespace    http://tampermonkey.net/
// @version      230111.2238
// @description  Add code bookmark in code-browser
// @author       Bing
// @match        https://codebrowser.dev/*
// @icon         https://codebrowser.dev/img/favico.svg
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function () {
    'use strict';

    const marked_linenumber_style = 'background: dodgerblue !important;color: white !important;border-top-left-radius: 50% !important;border-bottom-left-radius: 50% !important;';
    const marked_bnt_style = 'border-radius: 50%;cursor: pointer;border: solid rgb(0, 0, 0, 1) 0.6ex;';
    const view_style = 'visibility: visible;user-select: none;position: fixed;top: 5px;left: ' + (window.innerWidth - 400) + 'px;z-index: 9999;background: rgb(239, 239, 239);border-radius: 5px;width: fit-content;text-overflow: ellipsis;white-space: nowrap;';
    const view_visible = function () {
        if (!view_box || view_box.style == '') {
            return view_style;
        }
        view_box.style.visibility = 'visible';
    };

    const unmarked_bnt_style = 'border-radius: 50%;cursor: pointer;border: solid rgb(0, 0, 0, 0.2) 0.6ex;'
    const unmarked_linenumber_style = '';
    const view_hidden = function () {
        if (!view_box || view_box.style == '') {
            return view_style;
        }
        view_box.style.visibility = 'hidden';
    };

    var host = 'https://codebrowser.dev';
    var file_name = window.location.pathname;
    var book_marks = {};
    let local_marks = window.localStorage.getItem('code-browser-bookmarks');
    if (local_marks) {
        try {
            book_marks = JSON.parse(local_marks);
        } catch {
            book_marks = {};
        }
    }

    if (!('status' in book_marks)) {
        book_marks.status = {};
    }
    if (!('style' in book_marks.status)) {
        book_marks.status.style = '';
    }
    if (!('top_bar_hide_btn_style' in book_marks.status)) {
        book_marks.status.top_bar_hide_btn_style = '';
    }
    if (!('content_style' in book_marks.status)) {
        book_marks.status.content_style = '';
    }
    if (!('content_scroll_top' in book_marks.status)) {
        book_marks.status.content_scroll_top = 0;
    }
    if (!('content_scroll_left' in book_marks.status)) {
        book_marks.status.content_scroll_left = 0;
    }


    var view_box = document.querySelector('#code-browser-view-box');
    update_view_box();


    function store_marks() {
        window.localStorage.setItem('code-browser-bookmarks', JSON.stringify(book_marks));
    }

    function get_code_lines() {
        let code_table = document.querySelector('#content > table.code');
        if (!code_table) {
            return [];
        }

        let lines = code_table.querySelectorAll('tbody > tr');
        return lines;
    }

    function gen_mark_bnt() {
        let mark_container = document.createElement('td');
        mark_container.className = 'code-browser-bookmark-container';
        mark_container.style = 'text-align: right;vertical-align: middle;margin: auto;width: 1ex;cursor: pointer;'
        mark_container.setAttribute('marked', false);

        let mark_container_bnt = document.createElement('div');
        mark_container_bnt.className = 'code-browser-bookmark-bnt';
        mark_container_bnt.style = unmarked_bnt_style;

        mark_container.appendChild(mark_container_bnt);
        mark_container.onclick = on_mark_bnt_click;

        return mark_container;
    }

    function on_mark_bnt_click() {
        let mark_container = this;

        function update_bnt() {
            let line_info = gen_line_info();
            if (!line_info) {
                return;
            }
            let mark_bnt = mark_container.querySelector('.code-browser-bookmark-bnt');
            let is_marked = mark_container.getAttribute('marked') == 'true';

            mark_container.setAttribute('marked', !is_marked);
            mark_bnt.style = is_marked ? unmarked_bnt_style : marked_bnt_style;
            mark_container.parentNode.querySelector('th').style = is_marked ? unmarked_linenumber_style : marked_linenumber_style;

            if (is_marked) {
                remove_mark(line_info);
            } else {
                add_mark(line_info);
            }
        }

        function gen_line_info() {
            let line = mark_container.parentNode;
            let line_number = line.querySelector('th').innerText;
            let line_content = line.querySelectorAll('td')[1].innerText.trim();
            if (!line.querySelector('th > a')) {
                return null;
            }
            let line_mark = line.querySelector('th > a').getAttribute('href');
            return {
                'number': line_number,
                'content': line_content,
                'mark': line_mark,
            };
        }

        function add_mark(line_info) {
            if (!(file_name in book_marks)) {
                book_marks[file_name] = {
                    'status': {},
                    'list': []
                }
            }
            book_marks[file_name].list.push(line_info);
            update_marks();
        }

        function remove_mark(line_info) {
            if (file_name in book_marks) {
                for (let index in book_marks[file_name].list) {
                    if (book_marks[file_name].list[index].number == line_info.number) {
                        book_marks[file_name].list[index] = book_marks[file_name].list[0];
                        book_marks[file_name].list.shift();
                        break;
                    }
                }
                if (book_marks[file_name].list.length == 0) {
                    delete book_marks[file_name];
                }
            }
            update_marks();
        }

        function update_marks() {
            if (file_name in book_marks) {
                book_marks[file_name].list = book_marks[file_name].list.sort(function (a, b) {
                    return parseInt(a.number) - parseInt(b.number);
                });
            }
            store_marks();
            update_view_box();
        }

        update_bnt();
    }

    function update_view_box() {
        function gen_mark_list_view_line(number, content, link) {
            let mark_line = document.createElement('span');
            let mark_line_del_bnt = document.createElement('span');
            let mark_line_jump = document.createElement('a');
            let mark_line_number = document.createElement('span');
            let mark_line_content = document.createElement('span');
            mark_line_del_bnt.innerText = '-';
            mark_line_jump.href = link;
            mark_line_number.innerText = number;
            mark_line_content.innerText = content;

            mark_line.style = 'display: block;text-decoration: none;border: none;line-height: 1.8rem;';
            mark_line_del_bnt.style = 'float: left; margin-right: 1rem; width: 1rem; text-align: right; cursor: pointer;';
            mark_line_number.style = 'display: inline-block;text-align: left;width: 5ex;color: blue;';
            mark_line_content.style = 'margin-left: 0.5rem;';

            mark_line_del_bnt.onclick = function () {
                let block = mark_line.parentNode.getAttribute('block');
                let is_current_block = block == file_name;
                if (is_current_block) {
                    document.getElementById(number).style = unmarked_linenumber_style;
                    document.getElementById(number).parentNode.querySelector('.code-browser-bookmark-bnt').style = unmarked_bnt_style;
                }

                let block_ele = mark_line.parentNode;
                mark_line.remove();
                book_marks[block].list = book_marks[block].list.filter(item => item.number != number);
                if (book_marks[block].list.length == 0) {
                    delete book_marks[block];
                    block_ele.remove();
                }
                store_marks();
            };

            mark_line.appendChild(mark_line_del_bnt);
            mark_line_jump.appendChild(mark_line_number);
            mark_line_jump.appendChild(mark_line_content);
            mark_line.appendChild(mark_line_jump);

            return mark_line;
        }

        function gen_mark_list_view_file(block, marks) {
            let file_block = document.createElement('div');
            let file_block_title = document.createElement('div');
            let file_block_del_bnt = document.createElement('span');
            let file_block_title_bnt = document.createElement('span');
            let unfold = !('unfold' in marks.status) || (marks.status.unfold == true);
            file_block.setAttribute('block', block);
            file_block_title_bnt.innerText = block.replace('.html', '').slice(1);
            file_block_del_bnt.innerText = '-';
            file_block_del_bnt.style = 'float: left; margin-right: 0.1rem; width: 1rem; text-align: left; cursor: pointer;';
            file_block_title.appendChild(file_block_del_bnt);
            file_block_title.appendChild(file_block_title_bnt);
            file_block_title.style = 'cursor: pointer;';
            file_block_title_bnt.onclick = on_block_fold_unfold;
            file_block_del_bnt.onclick = delete_block;
            file_block.appendChild(file_block_title);

            function draw_block_lines() {
                marks.list.forEach(function (line) {
                    let number = line.number;
                    let content = line.content;
                    let link = host + block + line.mark;
                    let view_line = gen_mark_list_view_line(number, content, link);
                    file_block.appendChild(view_line);
                });
            }
            function on_block_fold_unfold() {
                let unfold = (!('unfold' in book_marks[block].status)) || (book_marks[block].status.unfold == true);
                if (unfold) {
                    file_block.innerHTML = '';
                    file_block.appendChild(file_block_title);
                } else {
                    draw_block_lines();
                }
                book_marks[block].status.unfold = !unfold;
                store_marks();
            }
            function delete_block() {
                file_block.remove();
                delete book_marks[block];
                store_marks();
            }

            if (unfold) {
                draw_block_lines();
            }
            return file_block;
        }

        function enable_view_drag() {
            let top_bar = document.createElement('div');
            let top_bar_hide_btn = document.createElement('span');
            top_bar_hide_btn.innerText = "(●'◡'●)";
            top_bar_hide_btn.style = 'cursor: pointer; visibility: visible;';
            top_bar_hide_btn.ondblclick = hidden_view;
            top_bar.id = '#code-browser-view-boxbar';
            top_bar.style = 'width: 100%;text-align: center;margin-bottom: 1ex;padding: 1ex 0;cursor: move;background: darkgray;';

            if (book_marks.status.top_bar_hide_btn_style != '') {
                top_bar_hide_btn.style = book_marks.status.top_bar_hide_btn_style;
            }

            top_bar.appendChild(top_bar_hide_btn);
            view_box.appendChild(top_bar);
            dragElement(view_box, top_bar);

            function hidden_view() {
                let view = document.getElementById('#code-browser-view-box');
                let hidden = view.getAttribute('visibility') == 'hidden';
                if (hidden) {
                    view.setAttribute('visibility', 'visible');
                    top_bar_hide_btn.style = 'cursor: pointer; visibility: visible;';
                    view_visible();
                } else {
                    view.setAttribute('visibility', 'hidden');
                    top_bar_hide_btn.style = 'cursor: pointer;background: rgb(0, 0, 0, 0.2);padding: 5px 10px;border-radius: 10px;color: darkslateblue;visibility: visible;';
                    view_hidden();
                }

                book_marks.status.style = view_box.style.cssText;
                book_marks.status.top_bar_hide_btn_style = top_bar_hide_btn.style.cssText;
                store_marks();
            }

            function dragElement(elmnt, drag_bar) {
                var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
                drag_bar.onmousedown = dragMouseDown;

                function dragMouseDown(e) {
                    e = e || window.event;
                    // get the mouse cursor position at startup:
                    pos3 = e.clientX;
                    pos4 = e.clientY;
                    document.onmouseup = closeDragElement;
                    // call a function whenever the cursor moves:
                    document.onmousemove = elementDrag;
                }

                function elementDrag(e) {
                    e = e || window.event;
                    // calculate the new cursor position:
                    pos1 = pos3 - e.clientX;
                    pos2 = pos4 - e.clientY;
                    pos3 = e.clientX;
                    pos4 = e.clientY;
                    // set the element's new position:
                    elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
                    elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
                }

                function closeDragElement() {
                    /* stop moving when mouse button is released:*/
                    document.onmouseup = null;
                    document.onmousemove = null;

                    book_marks.status.style = view_box.style.cssText;
                    store_marks();
                }
            }
        }

        function enable_view_content() {
            let content = document.createElement('div');
            content.id = '#code-browser-view-boxcontent';
            content.style = 'overflow: scroll;padding-left: 20px;padding-right: 20px;width: 300px;height: 400px;resize: both;';
            content.scrollTop = 0;
            content.scrollLeft = 0;
            if (book_marks.status.content_style != '') {
                content.style = book_marks.status.content_style;
            }

            content.onmouseup = function () {
                book_marks.status.content_style = content.style.cssText;
                store_marks();
            }
            content.onscroll = function () {
                book_marks.status.content_scroll_top = content.scrollTop;
                book_marks.status.content_scroll_left = content.scrollLeft;
                store_marks();
            }
            view_box.appendChild(content);
        }

        function clear_view_content() {
            document.getElementById('#code-browser-view-boxcontent').innerHTML = "";
        }

        function update_view_content(ele) {
            document.getElementById('#code-browser-view-boxcontent').appendChild(ele);
        }

        if (!view_box) {
            view_box = document.createElement('div');
            view_box.id = '#code-browser-view-box';
            view_box.style = view_style;
            if (book_marks.status.style != '') {
                view_box.style = book_marks.status.style;
            }

            enable_view_drag();
            enable_view_content();
            document.body.appendChild(view_box);
        }
        clear_view_content();
        for (let block in book_marks) {
            if (block == 'status') {
                continue;
            }
            if (book_marks[block].list.length == 0) {
                delete book_marks[block];
                store_marks();
                continue;
            }

            let file_block = gen_mark_list_view_file(block, book_marks[block]);
            update_view_content(file_block);

            if (book_marks.status.content_scroll_top != '') {
                document.getElementById('#code-browser-view-boxcontent').scrollTop = parseFloat(book_marks.status.content_scroll_top);
            }
            if (book_marks.status.content_scroll_left != '') {
                document.getElementById('#code-browser-view-boxcontent').scrollLeft = parseFloat(book_marks.status.content_scroll_left);
            }
        }
    }

    function on_mark_bnt_hover() {
        let mark_container = this;
        let line = mark_container.parentNode;
        let is_hover = !(line.getAttribute('hover') == 'true');

        if (is_hover) {
            line.style = 'background: #000';
            line.setAttribute('hover', true);
        } else {
            line.style = 'background: #fff';
            line.setAttribute('hover', false);
        }
    }

    let lines = get_code_lines();
    lines.forEach(function (line) {
        let mark_bnt = gen_mark_bnt();
        line.insertBefore(mark_bnt, line.firstChild);
    });

    if (file_name in book_marks) {
        for (let i in book_marks[file_name].list) {
            let number = book_marks[file_name].list[i].number;
            let marked_line = document.querySelectorAll('th')[parseInt(number) - 1].parentNode;
            let marked_line_bnt = marked_line.querySelector('.code-browser-bookmark-bnt');
            marked_line_bnt.parentNode.setAttribute('marked', true);

            marked_line_bnt.style = marked_bnt_style;
            marked_line.querySelector('th').style = marked_linenumber_style;
        }
    }
})();