diff --git a/mods/outliner/app.css b/mods/outliner/app.css new file mode 100644 index 0000000..700dcff --- /dev/null +++ b/mods/outliner/app.css @@ -0,0 +1,51 @@ +/* + * outliner + * (c) 2020 dragonwocky (https://dragonwocky.me/) + * (c) 2020 CloudHill + * under the MIT license + */ + +.outliner { + max-height: 100%; + overflow: hidden auto; +} + +.outline-header { + display: flex; + align-items: center; + height: 2.2em; + cursor: pointer; + user-select: none; + transition: background 20ms ease-in; +} +.outline-header:hover { + background: var(--theme--interactive_hover); +} + +.outline-header a { + width: 100%; + height: 100%; + padding: 0 14px; + line-height: 2.2; + color: inherit; + text-decoration: none; + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.outline-header a:empty:before { + color: var(--theme--text_ui_info); + content: attr(placeholder); + display: block; +} +.outline-header.notion-header-block a { + text-indent: 0; +} +.outline-header.notion-sub_header-block a { + text-indent: 18px; +} +.outline-header.notion-sub_sub_header-block a { + text-indent: 36px; +} \ No newline at end of file diff --git a/mods/outliner/icon.svg b/mods/outliner/icon.svg new file mode 100644 index 0000000..30f8901 --- /dev/null +++ b/mods/outliner/icon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/mods/outliner/mod.js b/mods/outliner/mod.js new file mode 100644 index 0000000..5fa2272 --- /dev/null +++ b/mods/outliner/mod.js @@ -0,0 +1,23 @@ +/* + * outliner + * (c) 2020 dragonwocky (https://dragonwocky.me/) + * (c) 2020 CloudHill + * under the MIT license + */ + +'use strict'; + +module.exports = { + id: '87e077cc-5402-451c-ac70-27cc4ae65546', + tags: ['extension', 'panel'], + name: 'outliner', + desc: 'table of contents.', + version: '1.0.0', + author: 'CloudHill', + panel: { + html: "panel.html", + name: "Outline", + icon: "icon.svg", + js: "panel.js", + } +}; diff --git a/mods/outliner/panel.html b/mods/outliner/panel.html new file mode 100644 index 0000000..fa82ef7 --- /dev/null +++ b/mods/outliner/panel.html @@ -0,0 +1,2 @@ +
+
\ No newline at end of file diff --git a/mods/outliner/panel.js b/mods/outliner/panel.js new file mode 100644 index 0000000..32ffa05 --- /dev/null +++ b/mods/outliner/panel.js @@ -0,0 +1,115 @@ +/* + * outliner + * (c) 2020 dragonwocky (https://dragonwocky.me/) + * (c) 2020 CloudHill + * under the MIT license + */ + +'use strict'; + +const { createElement } = require("../../pkg/helpers"); + +module.exports = (store) => { + function initOutliner() { + // Find headers when switching panels + if (document.querySelector('.notion-page-content')) { + startContentObserver(); + }; + + // Observe for page changes + const pageObserver = new MutationObserver((list, observer) => { + for ( let { addedNodes } of list) { + if (addedNodes[0]) { + if (addedNodes[0].className === 'notion-page-content') { + startContentObserver(); + } + // Clear outline on database pages + else if (addedNodes[0].className === 'notion-scroller') { + contentObserver.disconnect(); + const outline = document.querySelector('.outliner'); + if (outline) outline.textContent = ''; + } + } + } + }); + pageObserver.observe(document.body, { + childList: true, + subtree: true, + }); + + // Observe for header changes + const contentObserver = new MutationObserver((list, observer) => { + list.forEach(m => { + if ( + ( + m.type === 'childList' && + ( + isHeaderElement(m.target) || + isHeaderElement(m.addedNodes[0]) || + isHeaderElement(m.removedNodes[0]) + ) + ) || + ( + m.type === 'characterData' && + isHeaderElement(m.target.parentElement) + ) + ) findHeaders(); + }) + }); + function startContentObserver() { + findHeaders(); + contentObserver.disconnect(); + contentObserver.observe( + document.querySelector('.notion-page-content'), + { + childList: true, + subtree: true, + characterData: true, + } + ); + } + } + + function findHeaders() { + const outline = document.querySelector('.outliner'); + if (!outline) { + pageObserver.disconnect(); + observer.disconnect(); + } + outline.textContent = ''; + + const pageContent = document.querySelector('.notion-page-content'); + const headerBlocks = pageContent.querySelectorAll('[class*="header-block"]'); + + headerBlocks.forEach(block => { + const blockId = block.dataset.blockId.replace(/-/g, ''); + const placeholder = block.querySelector('[placeholder]').getAttribute('placeholder'); + const header = createElement(` + + `); + + outline.append(header); + }) + } + + function isHeaderElement(el) { + let placeholder; + if (el) { + if ( + el.querySelector && + el.querySelector('[placeholder]') + ) { + placeholder = el.querySelector('[placeholder]').getAttribute('placeholder') + } else if (el.getAttribute) { + placeholder = el.getAttribute('placeholder'); + } + } + if (!placeholder) placeholder = ''; + return placeholder.includes('Heading'); + } + + return initOutliner; +} \ No newline at end of file