Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New extensions: side panel and outliner #284

Merged
merged 10 commits into from
Dec 2, 2020
51 changes: 51 additions & 0 deletions mods/outliner/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* outliner
* (c) 2020 dragonwocky <thedragonring.bod@gmail.com> (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;
}
8 changes: 8 additions & 0 deletions mods/outliner/icon.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions mods/outliner/mod.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* outliner
* (c) 2020 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
* (c) 2020 CloudHill
* under the MIT license
*/

'use strict';

const store = require("../../pkg/store");

module.exports = {
id: '87e077cc-5402-451c-ac70-27cc4ae65546',
tags: ['extension', 'panel'],
name: 'outliner',
desc: 'table of contents.',
version: '1.0.0',
author: 'CloudHill',
options: [
{
key: 'fullHeight',
label: 'full height',
type: 'toggle',
value: false
}
],
panel: {
html: "panel.html",
name: "Outline",
icon: "icon.svg",
js: "panel.js",
fullHeight: store('87e077cc-5402-451c-ac70-27cc4ae65546').fullHeight
}
};
1 change: 1 addition & 0 deletions mods/outliner/panel.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div class="outliner"></div>
117 changes: 117 additions & 0 deletions mods/outliner/panel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* outliner
* (c) 2020 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
* (c) 2020 CloudHill
* under the MIT license
*/

'use strict';

const { createElement } = require("../../pkg/helpers");

module.exports = (store) => {
// 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 => {
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) return;
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(`
<div class="outline-header ${block.classList[1]}">
<a href="${window.location.pathname}#${blockId}"
placeholder="${placeholder}">${block.innerText}</a>
</div>
`);

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 {
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();
}
}
}