/* * outliner * (c) 2020 dragonwocky (https://dragonwocky.me/) * (c) 2020 CloudHill * under the MIT license */ 'use strict'; const { createElement } = require("../../pkg/helpers"); module.exports = (store, __exports) => { let lastSearch; // 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 = ''; } } } }); // Observe for header changes const contentObserver = new MutationObserver((list, observer) => { list.forEach(m => { let header; if ( ( m.type === 'childList' && ( m.target.hasAttribute('placeholder') || m.target.className?.includes('header-block') ) && ( (header = getHeaderBlock(m.target)) || (header = getHeaderBlock(m.addedNodes[0])) ) ) || ( m.type === 'characterData' && (header = getHeaderBlock(m.target.parentElement)) ) ) updateOutlineHeader(header); else if ( m.type === 'childList' && m.removedNodes[0] && ( isHeaderElement(m.removedNodes[0]) || m.removedNodes[0].querySelector?.('[class*="header-block"]') ) ) findHeaders(); }) }); function startContentObserver() { findHeaders(); contentObserver.disconnect(); contentObserver.observe( document.querySelector('.notion-page-content'), { childList: true, subtree: true, characterData: true, } ); } function findHeaders() { // Add cooldown to prevent the function being run twice at the 'same' time if (lastSearch >= (Date.now() - 10)) return; lastSearch = Date.now(); const outline = document.querySelector('.outliner'); if (!outline) return; outline.textContent = ''; if (store().lined) outline.setAttribute('lined', ''); const pageContent = document.querySelector('.notion-page-content'); const headerBlocks = pageContent.querySelectorAll('[class*="header-block"]'); headerBlocks.forEach(header => { const blockId = header.dataset.blockId.replace(/-/g, ''); const headerEl = header.querySelector('[placeholder]'); const placeholder = headerEl.getAttribute('placeholder'); const outlineHeader = createElement(`
`); header.outline = outlineHeader; outlineHeader.firstElementChild.innerHTML = headerEl.innerHTML; outline.append(outlineHeader); }) } function updateOutlineHeader(header) { const headerEl = header.querySelector('[placeholder]') || header; if (!( headerEl && header.outline && header.outline.parentElement )) return findHeaders(); const outlineHeader = header.outline; outlineHeader.firstElementChild.innerHTML = headerEl.innerHTML; updateOutlineLevel(outlineHeader, headerEl.getAttribute('placeholder').slice(-1)); } function updateOutlineLevel(outlineHeader, level) { outlineHeader.setAttribute('header-level', level); outlineHeader.firstElementChild.setAttribute('outline-placeholder', `Header ${level}`) } function getHeaderBlock(el) { return el?.closest?.('[class*="header-block"]'); } function isHeaderElement(el) { let placeholder; if (el) { placeholder = el.getAttribute?.('placeholder') || el.querySelector?.('[placeholder]')?.getAttribute('placeholder'); } if (!placeholder) placeholder = ''; return placeholder.includes('Heading'); } return { onLoad() { // Find headers when switching panels if (document.querySelector('.notion-page-content')) { startContentObserver(); }; pageObserver.observe(document.body, { childList: true, subtree: true, }); }, onSwitch() { pageObserver.disconnect(); contentObserver.disconnect(); } } }