From 6661c5559b26b5484def7f3e03895d3d9e5694ab Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Wed, 31 Jan 2024 00:12:14 +1100 Subject: [PATCH] feat: render headings in outliner --- src/common/registry.js | 21 ++--- src/core/client.mjs | 36 ++------ src/extensions/outliner/client.mjs | 126 ++++++++++++-------------- src/extensions/outliner/mod.json | 24 +++-- src/extensions/scroll-to-top/mod.json | 2 +- src/registry.json | 1 + 6 files changed, 88 insertions(+), 122 deletions(-) diff --git a/src/common/registry.js b/src/common/registry.js index 1ceb5e8..eea05e1 100644 --- a/src/common/registry.js +++ b/src/common/registry.js @@ -63,19 +63,18 @@ const isEnabled = async (id) => { }; const modDatabase = async (id) => { - const optionDefaults = - (await getMods()) - .find((mod) => mod.id === id) - ?.options?.map?.((opt) => { - let value = opt.value; - value ??= opt.values?.[0]?.value; - value ??= opt.values?.[0]; - return [opt.key, value]; - }) - ?.filter?.(([, value]) => typeof value !== "undefined") ?? {}; + const optionDefaults = (await getMods()) + .find((mod) => mod.id === id) + ?.options?.map?.((opt) => { + let value = opt.value; + value ??= opt.values?.[0]?.value; + value ??= opt.values?.[0]; + return [opt.key, value]; + }) + ?.filter?.(([, value]) => typeof value !== "undefined"); return globalThis.__enhancerApi.initDatabase( [await getProfile(), id], - Object.fromEntries(optionDefaults) + Object.fromEntries(optionDefaults ?? []) ); }; diff --git a/src/core/client.mjs b/src/core/client.mjs index e4d2be3..8376611 100644 --- a/src/core/client.mjs +++ b/src/core/client.mjs @@ -58,11 +58,11 @@ const insertMenu = async (api, db) => { let _contentWindow; const updateMenuTheme = () => { - const darkMode = document.body.classList.contains("dark"), - notionTheme = darkMode ? "dark" : "light"; - menuPing.theme = notionTheme; - _contentWindow?.postMessage?.(menuPing, "*"); - }; + const darkMode = document.body.classList.contains("dark"), + notionTheme = darkMode ? "dark" : "light"; + menuPing.theme = notionTheme; + _contentWindow?.postMessage?.(menuPing, "*"); + }; const $modal = html`<${Modal}> <${Frame} @@ -122,14 +122,13 @@ const insertMenu = async (api, db) => { const insertPanel = async (api, db) => { const notionFrame = ".notion-frame", togglePanelHotkey = await db.get("togglePanelHotkey"), - { addMutationListener, removeMutationListener } = api, - { html, setState, addPanelView } = api; + { html, setState, addMutationListener, removeMutationListener } = api; const $panel = html`<${Panel} hotkey="${togglePanelHotkey}" ...${Object.assign( ...["Width", "Open", "View"].map((key) => ({ - [`_get${key}`]: () => db.get(`sidePanel${key}`), + [`_get${key}`]: () => db.get(`panel${key}`), [`_set${key}`]: async (value) => { await db.set(`panel${key}`, value); setState({ rerender: true }); @@ -146,27 +145,6 @@ const insertPanel = async (api, db) => { }; addMutationListener(notionFrame, appendToDom); appendToDom(); - - const $helloThere = html`
hello there
`, - $generalKenobi = html`
general kenobi
`; - addPanelView({ - title: "outliner", - // prettier-ignore - $icon: html` - - - - - - - `, - $view: $helloThere, - }); - addPanelView({ - title: "word counter", - $icon: "type", - $view: $generalKenobi, - }); }; export default async (api, db) => diff --git a/src/extensions/outliner/client.mjs b/src/extensions/outliner/client.mjs index b0c7550..6a7d51a 100644 --- a/src/extensions/outliner/client.mjs +++ b/src/extensions/outliner/client.mjs @@ -1,22 +1,26 @@ /** * notion-enhancer: outliner - * (c) 2020 CloudHill (https://github.com/CloudHill) - * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (c) 2021 CloudHill (https://github.com/CloudHill) + * (c) 2024 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ -export default async function ({ web, components }, db) { - const dbNoticeText = 'Open a page to see its table of contents.', - pageNoticeText = 'Click on a heading to jump to it.', - $notice = web.html`

${dbNoticeText}

`; +"use strict"; - const $headingList = web.html`
`; - - let viewFocused = false, - $page; - await components.addPanelView({ - id: '87e077cc-5402-451c-ac70-27cc4ae65546', - icon: web.html` +export default async (api, db) => { + const { html, addMutationListener, addPanelView } = api, + frame = ".notion-sidebar-container + div", + page = ".notion-page-content", + headings = [ + ".notion-header-block", + ".notion-sub_header-block", + ".notion-sub_sub_header-block", + ], + $view = html`
`; + addPanelView({ + title: "Outliner", + // prettier-ignore + $icon: html` @@ -24,61 +28,47 @@ export default async function ({ web, components }, db) { `, - title: 'Outliner', - $content: web.render(web.html`
`, $notice, $headingList), - onFocus: () => { - viewFocused = true; - $page = document.getElementsByClassName('notion-page-content')[0]; - updateHeadings(); - }, - onBlur: () => { - viewFocused = false; - }, + $view, }); - await web.whenReady(); - function updateHeadings() { - if (!$page) return; - $notice.innerText = pageNoticeText; - $headingList.style.display = ''; - const $headerBlocks = $page.querySelectorAll('[class^="notion-"][class*="header-block"]'), - $fragment = web.html`
`; - let indent = 0; - for (const $header of $headerBlocks) { - const id = $header.dataset.blockId.replace(/-/g, ''), - placeholder = $header.querySelector('[placeholder]').getAttribute('placeholder'), - headerDepth = +[...placeholder].reverse()[0]; - indent = (headerDepth-1) * 18; - const $outlineHeader = web.render( - web.html``, - $header.innerText - ); - $outlineHeader.addEventListener('click', (event) => { - location.hash = ''; - }); - web.render($fragment, $outlineHeader); - } - if ($fragment.innerHTML !== $headingList.innerHTML) { - web.render(web.empty($headingList), ...$fragment.children); - } + function Heading({ indent, ...props }, ...children) { + return html`
+ ${children} +
`; } - const pageObserver = () => { - if (!viewFocused) return; - if (document.contains($page)) { - updateHeadings(); - } else { - $page = document.querySelector('.notion-page-content'); - if (!$page) { - $notice.innerText = dbNoticeText; - $headingList.style.display = 'none'; - } else updateHeadings(); - } - }; - web.addDocumentObserver(pageObserver, [ - '.notion-page-content', - '.notion-collection_view_page-block', - ]); - pageObserver(); -} + + const getHeadings = () => { + return [...document.querySelectorAll(headings.join(", "))]; + }, + getHeadingLevel = ($heading) => { + for (let i = 0; i < headings.length; i++) + if ($heading.matches(headings[i])) return i + 1; + }, + updateHeadings = () => { + $view.innerHTML = ""; + for (const $heading of getHeadings()) { + const title = $heading.innerText, + indent = getHeadingLevel($heading); + if (!title) continue; + const $h = html`<${Heading} indent=${indent} onclick=${() => { + // todo: scroll into view + }}>${title}

`; + $view.append($h); + } + }; + + let $page; + addMutationListener(page, () => { + if (!document.contains($page)) $page = document.querySelector(page); + if ($page) updateHeadings(); + }); +}; diff --git a/src/extensions/outliner/mod.json b/src/extensions/outliner/mod.json index 2ebbb48..2b2de33 100644 --- a/src/extensions/outliner/mod.json +++ b/src/extensions/outliner/mod.json @@ -1,23 +1,21 @@ { - "name": "outliner", + "name": "Outliner", + "version": "0.5.0", "id": "87e077cc-5402-451c-ac70-27cc4ae65546", - "version": "0.4.0", - "description": "adds a table of contents to the side panel.", - "preview": "outliner.png", - "tags": ["extension", "panel"], + "description": "Adds a table of contents to the side panel to overview and navigate the current page's headings and subheadings.", + "thumbnail": "outliner.png", + "tags": ["panel"], "authors": [ + { + "name": "dragonwocky", + "homepage": "https://dragonwocky.me/", + "avatar": "https://dragonwocky.me/avatar.jpg" + }, { "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": [] + "clientScripts": ["client.mjs"] } diff --git a/src/extensions/scroll-to-top/mod.json b/src/extensions/scroll-to-top/mod.json index bfcdd8e..f279483 100644 --- a/src/extensions/scroll-to-top/mod.json +++ b/src/extensions/scroll-to-top/mod.json @@ -27,7 +27,7 @@ "type": "number", "key": "distanceScrolledUntilShown", "description": "How far down a page you must be scrolled for the scroll to top button to appear.", - "value": 50 + "value": 0 }, { "type": "select", diff --git a/src/registry.json b/src/registry.json index c7fe0ba..cdce986 100644 --- a/src/registry.json +++ b/src/registry.json @@ -2,6 +2,7 @@ "core", "extensions/titlebar", "extensions/topbar", + "extensions/outliner", "extensions/scroll-to-top", "themes/classic-dark" ]