From b294964b262ba8725fa7ad2b3b19eecd22b0430f Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Wed, 20 Oct 2021 15:42:22 +1100 Subject: [PATCH] extension: code line numbers --- repo/code-line-numbers/app.css | 30 ++++++++ repo/code-line-numbers/client.css | 63 +++++++++++++++++ repo/code-line-numbers/client.mjs | 62 ++++++++++++++++ repo/code-line-numbers/mod.js | 113 ++++++++++++++++++++++++++++++ repo/code-line-numbers/mod.json | 35 +++++++++ repo/registry.json | 1 + 6 files changed, 304 insertions(+) create mode 100644 repo/code-line-numbers/app.css create mode 100644 repo/code-line-numbers/client.css create mode 100644 repo/code-line-numbers/client.mjs create mode 100644 repo/code-line-numbers/mod.js create mode 100644 repo/code-line-numbers/mod.json diff --git a/repo/code-line-numbers/app.css b/repo/code-line-numbers/app.css new file mode 100644 index 0000000..8d9eb8b --- /dev/null +++ b/repo/code-line-numbers/app.css @@ -0,0 +1,30 @@ +/* + * code line numbers + * (c) 2020 dragonwocky (https://dragonwocky.me/) + * (c) 2020 CloudHill + * under the MIT license + */ + +.notion-code-block.line-numbers { + position: relative; +} + +.code-numbered { + padding-left: 48px !important; +} +#code-line-numbers { + font-size: var(--theme--font_code-size); + font-family: var(--theme--font_code); + color: var(--theme--text_ui_info); + background: var(--theme--code-background); + text-align: right; + position: absolute; + left: 0; + right: calc(100% - 48px); + padding-right: 18px; + overflow: hidden; + pointer-events: none; +} +#code-line-numbers:empty { + display: none; +} diff --git a/repo/code-line-numbers/client.css b/repo/code-line-numbers/client.css new file mode 100644 index 0000000..8df4fc2 --- /dev/null +++ b/repo/code-line-numbers/client.css @@ -0,0 +1,63 @@ +/* + * notion-enhancer: code line numbers + * (c) 2020 CloudHill (https://github.com/CloudHill) + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +.notion-code-block.line-numbers { + position: relative; +} +.code_line_numbers--plain:not(:empty) + div, +.code_line_numbers--background:not(:empty) + div, +.code_line_numbers--border:not(:empty) + div { + padding-left: 64px !important; +} + +.code_line_numbers--plain, +.code_line_numbers--background, +.code_line_numbers--border { + position: absolute; + left: 0; + right: calc(100% - 64px); + top: 34px; + bottom: 32px; + padding-right: 27px; + + font-size: 85%; + font-family: var(--theme--font_code); + text-align: right; + line-height: 1.5; + opacity: 0.8; + color: var(--theme--text_secondary); + + overflow: hidden; + pointer-events: none; +} +.code_line_numbers--plain:empty, +.code_line_numbers--background:empty, +.code_line_numbers--border:empty { + display: none; +} +.code_line_numbers--background::before { + content: ''; + position: absolute; + top: 0; + left: 7.25px; + width: calc(100% - 27px); + height: 100%; + display: block; + background-color: var(--theme--bg); + border-radius: 4px; + z-index: -1; +} +.code_line_numbers--border::before { + content: ''; + position: absolute; + top: 0; + right: calc(100% - 52px); + width: 2px; + height: 100%; + display: block; + background-color: var(--theme--ui_divider); +} diff --git a/repo/code-line-numbers/client.mjs b/repo/code-line-numbers/client.mjs new file mode 100644 index 0000000..3a318ba --- /dev/null +++ b/repo/code-line-numbers/client.mjs @@ -0,0 +1,62 @@ +/* + * notion-enhancer: code line numbers + * (c) 2020 CloudHill (https://github.com/CloudHill) + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +export default async function ({ web }, db) { + const singleLined = await db.get(['single_lined']), + codeBlockSelector = '.notion-code-block.line-numbers', + numbersClass = `code_line_numbers--${await db.get(['style'])}`, + $temp = web.html``; + + const numberCodeBlock = ($codeBlock) => { + const $numbers = + $codeBlock.querySelector(`.${numbersClass}`) || + web.html`1`; + if (!$codeBlock.contains($numbers)) $codeBlock.prepend($numbers); + + const lines = $codeBlock.lastElementChild.innerText.split(/\r\n|\r|\n/), + wordWrap = $codeBlock.lastElementChild.style.wordBreak === 'break-all'; + if (lines.at(-1) === '') lines.pop(); + + let lineNumbers = ''; + for (let i = 1; i <= lines.length + 1; i++) { + lineNumbers += `${i}\n`; + if (wordWrap && lines[i - 1]) { + $temp.innerText = lines[i - 1]; + $codeBlock.lastElementChild.append($temp); + const height = parseFloat($temp.getBoundingClientRect().height); + $temp.remove(); + for (let j = 1; j < height / 20.4; j++) lineNumbers += '\n'; + } + } + + if (!singleLined && lines.length < 2) lineNumbers = ''; + if ($numbers.innerText !== lineNumbers) $numbers.innerText = lineNumbers; + }, + numberAllCodeBlocks = () => { + for (const $codeBlock of document.querySelectorAll(codeBlockSelector)) { + numberCodeBlock($codeBlock); + } + }, + observeCodeBlocks = (event) => { + const tempEvent = [...event.addedNodes, ...event.removedNodes].includes($temp), + numbersEvent = + event.target.classList.contains(numbersClass) || + [...event.addedNodes, ...event.removedNodes].some(($node) => + $node?.classList?.contains(numbersClass) + ), + codeEvent = event.target.matches(`${codeBlockSelector}, ${codeBlockSelector} *`); + if (tempEvent || numbersEvent || !codeEvent) return; + + let $codeBlock = event.target; + while (!$codeBlock.matches(codeBlockSelector)) $codeBlock = $codeBlock.parentElement; + numberCodeBlock($codeBlock); + }; + + await web.whenReady(); + numberAllCodeBlocks(); + web.addDocumentObserver(observeCodeBlocks, [codeBlockSelector]); +} diff --git a/repo/code-line-numbers/mod.js b/repo/code-line-numbers/mod.js new file mode 100644 index 0000000..2d1f94b --- /dev/null +++ b/repo/code-line-numbers/mod.js @@ -0,0 +1,113 @@ +/* + * code line numbers + * (c) 2020 dragonwocky (https://dragonwocky.me/) + * (c) 2020 CloudHill + * under the MIT license + */ + +'use strict'; + +const { createElement } = require('../../pkg/helpers.js'); + +module.exports = { + id: 'd61dc8a7-b195-465b-935f-53eea9efe74e', + tags: ['extension'], + name: 'code line numbers', + desc: 'adds line numbers to code blocks.', + version: '1.2.0', + author: 'CloudHill', + options: [ + { + key: 'single_lined', + label: 'show line numbers on single-lined code blocks', + type: 'toggle', + value: false, + }, + ], + hacks: { + 'renderer/preload.js'(store, __exports) { + document.addEventListener('readystatechange', (event) => { + if (document.readyState !== 'complete') return false; + let queue = []; + const observer = new MutationObserver((list, observer) => { + if (!queue.length) requestAnimationFrame(() => handle(queue)); + queue.push(...list); + }); + observer.observe(document.body, { + childList: true, + subtree: true, + }); + + const resizeObserver = new ResizeObserver((list, observer) => number(list[0].target)); + + function handle(list) { + queue = []; + for (let { addedNodes, target } of list) { + const block = + target.querySelector('.line-numbers.notion-code-block') || + (addedNodes[0]?.classList?.contains('.notion-code-block') && + addedNodes[0].querySelector('.line-numbers.notion-code-block')); + + if (block) { + if (block.dataset.numbered) return; + number(block); + block.dataset.numbered = true; + resizeObserver.observe(block); + } + } + } + + function number(block) { + let codeLineNumbers = ''; + + let numbers = block.querySelector('#code-line-numbers'); + if (!numbers) { + numbers = createElement(''); + + // set size + const blockStyle = window.getComputedStyle(block.children[0]); + numbers.style.top = blockStyle.paddingTop; + numbers.style.bottom = blockStyle.paddingBottom; + + block.append(numbers); + + // get lineHeight + const temp = createElement('A'); + block.firstChild.append(temp); + block.lineHeight = temp.getBoundingClientRect().height; + temp.remove(); + } + + const lines = block.firstChild.innerText.split(/\r\n|\r|\n/); + if (lines[lines.length - 1] === '') lines.pop(); + let lineCounter = 0; + const wordWrap = block.firstChild.style.wordBreak === 'break-all'; + + for (let i = 0; i < lines.length; i++) { + lineCounter++; + codeLineNumbers += `${lineCounter}\n`; + + if (wordWrap) { + const temp = document.createElement('span'); + temp.innerText = lines[i]; + block.firstChild.append(temp); + const lineHeight = temp.getBoundingClientRect().height; + temp.remove(); + + for (let j = 1; j < lineHeight / block.lineHeight - 1; j++) + codeLineNumbers += '\n'; + } + } + + if (store().single_lined || codeLineNumbers.length > 2) { + block.firstChild.classList.add('code-numbered'); + numbers.innerText = codeLineNumbers || 1; + } else { + block.firstChild.classList.remove('code-numbered'); + numbers.innerText = ''; + } + } + }); + }, + }, +}; diff --git a/repo/code-line-numbers/mod.json b/repo/code-line-numbers/mod.json new file mode 100644 index 0000000..492e70c --- /dev/null +++ b/repo/code-line-numbers/mod.json @@ -0,0 +1,35 @@ +{ + "name": "code line numbers", + "id": "d61dc8a7-b195-465b-935f-53eea9efe74e", + "version": "0.4.0", + "description": "adds line numbers to code blocks.", + "tags": ["extension", "usability"], + "authors": [ + { + "name": "CloudHill", + "email": "rh.cloudhill@gmail.com", + "homepage": "https://github.com/CloudHill", + "avatar": "https://avatars.githubusercontent.com/u/54142180" + } + ], + "js": { + "client": ["client.mjs"] + }, + "css": { + "client": ["client.css"] + }, + "options": [ + { + "type": "toggle", + "key": "single_lined", + "label": "number single-lined code blocks", + "value": false + }, + { + "type": "select", + "key": "style", + "label": "line number style", + "values": ["plain", "background", "border"] + } + ] +} diff --git a/repo/registry.json b/repo/registry.json index 360b067..58f7a69 100644 --- a/repo/registry.json +++ b/repo/registry.json @@ -26,6 +26,7 @@ "bypass-preview", "topbar-icons", "word-counter", + "code-line-numbers", "calendar-scroll", "weekly-view", "collapse-properties",