Add code bookmark in code-browser
// ==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;
}
}
})();