mirror of
https://github.com/notion-enhancer/notion-enhancer.git
synced 2025-04-10 15:39:01 +00:00
feat: render headings in outliner
This commit is contained in:
parent
ff1e5f7550
commit
6661c5559b
@ -63,19 +63,18 @@ const isEnabled = async (id) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const modDatabase = async (id) => {
|
const modDatabase = async (id) => {
|
||||||
const optionDefaults =
|
const optionDefaults = (await getMods())
|
||||||
(await getMods())
|
.find((mod) => mod.id === id)
|
||||||
.find((mod) => mod.id === id)
|
?.options?.map?.((opt) => {
|
||||||
?.options?.map?.((opt) => {
|
let value = opt.value;
|
||||||
let value = opt.value;
|
value ??= opt.values?.[0]?.value;
|
||||||
value ??= opt.values?.[0]?.value;
|
value ??= opt.values?.[0];
|
||||||
value ??= opt.values?.[0];
|
return [opt.key, value];
|
||||||
return [opt.key, value];
|
})
|
||||||
})
|
?.filter?.(([, value]) => typeof value !== "undefined");
|
||||||
?.filter?.(([, value]) => typeof value !== "undefined") ?? {};
|
|
||||||
return globalThis.__enhancerApi.initDatabase(
|
return globalThis.__enhancerApi.initDatabase(
|
||||||
[await getProfile(), id],
|
[await getProfile(), id],
|
||||||
Object.fromEntries(optionDefaults)
|
Object.fromEntries(optionDefaults ?? [])
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -58,11 +58,11 @@ const insertMenu = async (api, db) => {
|
|||||||
|
|
||||||
let _contentWindow;
|
let _contentWindow;
|
||||||
const updateMenuTheme = () => {
|
const updateMenuTheme = () => {
|
||||||
const darkMode = document.body.classList.contains("dark"),
|
const darkMode = document.body.classList.contains("dark"),
|
||||||
notionTheme = darkMode ? "dark" : "light";
|
notionTheme = darkMode ? "dark" : "light";
|
||||||
menuPing.theme = notionTheme;
|
menuPing.theme = notionTheme;
|
||||||
_contentWindow?.postMessage?.(menuPing, "*");
|
_contentWindow?.postMessage?.(menuPing, "*");
|
||||||
};
|
};
|
||||||
|
|
||||||
const $modal = html`<${Modal}>
|
const $modal = html`<${Modal}>
|
||||||
<${Frame}
|
<${Frame}
|
||||||
@ -122,14 +122,13 @@ const insertMenu = async (api, db) => {
|
|||||||
const insertPanel = async (api, db) => {
|
const insertPanel = async (api, db) => {
|
||||||
const notionFrame = ".notion-frame",
|
const notionFrame = ".notion-frame",
|
||||||
togglePanelHotkey = await db.get("togglePanelHotkey"),
|
togglePanelHotkey = await db.get("togglePanelHotkey"),
|
||||||
{ addMutationListener, removeMutationListener } = api,
|
{ html, setState, addMutationListener, removeMutationListener } = api;
|
||||||
{ html, setState, addPanelView } = api;
|
|
||||||
|
|
||||||
const $panel = html`<${Panel}
|
const $panel = html`<${Panel}
|
||||||
hotkey="${togglePanelHotkey}"
|
hotkey="${togglePanelHotkey}"
|
||||||
...${Object.assign(
|
...${Object.assign(
|
||||||
...["Width", "Open", "View"].map((key) => ({
|
...["Width", "Open", "View"].map((key) => ({
|
||||||
[`_get${key}`]: () => db.get(`sidePanel${key}`),
|
[`_get${key}`]: () => db.get(`panel${key}`),
|
||||||
[`_set${key}`]: async (value) => {
|
[`_set${key}`]: async (value) => {
|
||||||
await db.set(`panel${key}`, value);
|
await db.set(`panel${key}`, value);
|
||||||
setState({ rerender: true });
|
setState({ rerender: true });
|
||||||
@ -146,27 +145,6 @@ const insertPanel = async (api, db) => {
|
|||||||
};
|
};
|
||||||
addMutationListener(notionFrame, appendToDom);
|
addMutationListener(notionFrame, appendToDom);
|
||||||
appendToDom();
|
appendToDom();
|
||||||
|
|
||||||
const $helloThere = html`<div class="p-[16px]">hello there</div>`,
|
|
||||||
$generalKenobi = html`<div class="p-[16px]">general kenobi</div>`;
|
|
||||||
addPanelView({
|
|
||||||
title: "outliner",
|
|
||||||
// prettier-ignore
|
|
||||||
$icon: html`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24">
|
|
||||||
<circle cx="5" cy="7" r="2.8"/>
|
|
||||||
<circle cx="5" cy="17" r="2.79"/>
|
|
||||||
<path d="M21,5.95H11c-0.55,0-1-0.45-1-1v0c0-0.55,0.45-1,1-1h10c0.55,0,1,0.45,1,1v0C22,5.5,21.55,5.95,21,5.95z"/>
|
|
||||||
<path d="M17,10.05h-6c-0.55,0-1-0.45-1-1v0c0-0.55,0.45-1,1-1h6c0.55,0,1,0.45,1,1v0C18,9.6,17.55,10.05,17,10.05z"/>
|
|
||||||
<path d="M21,15.95H11c-0.55,0-1-0.45-1-1v0c0-0.55,0.45-1,1-1h10c0.55,0,1,0.45,1,1v0C22,15.5,21.55,15.95,21,15.95z" />
|
|
||||||
<path d="M17,20.05h-6c-0.55,0-1-0.45-1-1v0c0-0.55,0.45-1,1-1h6c0.55,0,1,0.45,1,1v0C18,19.6,17.55,20.05,17,20.05z"/>
|
|
||||||
</svg>`,
|
|
||||||
$view: $helloThere,
|
|
||||||
});
|
|
||||||
addPanelView({
|
|
||||||
title: "word counter",
|
|
||||||
$icon: "type",
|
|
||||||
$view: $generalKenobi,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async (api, db) =>
|
export default async (api, db) =>
|
||||||
|
@ -1,22 +1,26 @@
|
|||||||
/**
|
/**
|
||||||
* notion-enhancer: outliner
|
* notion-enhancer: outliner
|
||||||
* (c) 2020 CloudHill <rl.cloudhill@gmail.com> (https://github.com/CloudHill)
|
* (c) 2021 CloudHill <rl.cloudhill@gmail.com> (https://github.com/CloudHill)
|
||||||
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
* (c) 2024 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||||
* (https://notion-enhancer.github.io/) under the MIT license
|
* (https://notion-enhancer.github.io/) under the MIT license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default async function ({ web, components }, db) {
|
"use strict";
|
||||||
const dbNoticeText = 'Open a page to see its table of contents.',
|
|
||||||
pageNoticeText = 'Click on a heading to jump to it.',
|
|
||||||
$notice = web.html`<p id="outliner--notice">${dbNoticeText}</p>`;
|
|
||||||
|
|
||||||
const $headingList = web.html`<div></div>`;
|
export default async (api, db) => {
|
||||||
|
const { html, addMutationListener, addPanelView } = api,
|
||||||
let viewFocused = false,
|
frame = ".notion-sidebar-container + div",
|
||||||
$page;
|
page = ".notion-page-content",
|
||||||
await components.addPanelView({
|
headings = [
|
||||||
id: '87e077cc-5402-451c-ac70-27cc4ae65546',
|
".notion-header-block",
|
||||||
icon: web.html`<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
".notion-sub_header-block",
|
||||||
|
".notion-sub_sub_header-block",
|
||||||
|
],
|
||||||
|
$view = html`<div></div>`;
|
||||||
|
addPanelView({
|
||||||
|
title: "Outliner",
|
||||||
|
// prettier-ignore
|
||||||
|
$icon: html`<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24">
|
||||||
<circle cx="5" cy="7" r="2.8"/>
|
<circle cx="5" cy="7" r="2.8"/>
|
||||||
<circle cx="5" cy="17" r="2.79"/>
|
<circle cx="5" cy="17" r="2.79"/>
|
||||||
<path d="M21,5.95H11c-0.55,0-1-0.45-1-1v0c0-0.55,0.45-1,1-1h10c0.55,0,1,0.45,1,1v0C22,5.5,21.55,5.95,21,5.95z"/>
|
<path d="M21,5.95H11c-0.55,0-1-0.45-1-1v0c0-0.55,0.45-1,1-1h10c0.55,0,1,0.45,1,1v0C22,5.5,21.55,5.95,21,5.95z"/>
|
||||||
@ -24,61 +28,47 @@ export default async function ({ web, components }, db) {
|
|||||||
<path d="M21,15.95H11c-0.55,0-1-0.45-1-1v0c0-0.55,0.45-1,1-1h10c0.55,0,1,0.45,1,1v0C22,15.5,21.55,15.95,21,15.95z" />
|
<path d="M21,15.95H11c-0.55,0-1-0.45-1-1v0c0-0.55,0.45-1,1-1h10c0.55,0,1,0.45,1,1v0C22,15.5,21.55,15.95,21,15.95z" />
|
||||||
<path d="M17,20.05h-6c-0.55,0-1-0.45-1-1v0c0-0.55,0.45-1,1-1h6c0.55,0,1,0.45,1,1v0C18,19.6,17.55,20.05,17,20.05z"/>
|
<path d="M17,20.05h-6c-0.55,0-1-0.45-1-1v0c0-0.55,0.45-1,1-1h6c0.55,0,1,0.45,1,1v0C18,19.6,17.55,20.05,17,20.05z"/>
|
||||||
</svg>`,
|
</svg>`,
|
||||||
title: 'Outliner',
|
$view,
|
||||||
$content: web.render(web.html`<div></div>`, $notice, $headingList),
|
|
||||||
onFocus: () => {
|
|
||||||
viewFocused = true;
|
|
||||||
$page = document.getElementsByClassName('notion-page-content')[0];
|
|
||||||
updateHeadings();
|
|
||||||
},
|
|
||||||
onBlur: () => {
|
|
||||||
viewFocused = false;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
await web.whenReady();
|
|
||||||
|
|
||||||
function updateHeadings() {
|
function Heading({ indent, ...props }, ...children) {
|
||||||
if (!$page) return;
|
return html`<div
|
||||||
$notice.innerText = pageNoticeText;
|
role="button"
|
||||||
$headingList.style.display = '';
|
class="notion-enhancer--outliner-heading
|
||||||
const $headerBlocks = $page.querySelectorAll('[class^="notion-"][class*="header-block"]'),
|
block cursor-pointer select-none text-[14px]
|
||||||
$fragment = web.html`<div></div>`;
|
m-px py-[6px] pr-[2px] pl-[${indent * 18}px]
|
||||||
let indent = 0;
|
decoration-(2 [color:var(--theme--fg-border)])
|
||||||
for (const $header of $headerBlocks) {
|
hover:bg-[color:var(--theme--bg-hover)]
|
||||||
const id = $header.dataset.blockId.replace(/-/g, ''),
|
underline-(& offset-4)"
|
||||||
placeholder = $header.querySelector('[placeholder]').getAttribute('placeholder'),
|
...${props}
|
||||||
headerDepth = +[...placeholder].reverse()[0];
|
>
|
||||||
indent = (headerDepth-1) * 18;
|
${children}
|
||||||
const $outlineHeader = web.render(
|
</div>`;
|
||||||
web.html`<a href="#${id}" class="outliner--header"
|
|
||||||
placeholder="${web.escape(placeholder)}"
|
|
||||||
style="--outliner--indent:${indent}px;"></a>`,
|
|
||||||
$header.innerText
|
|
||||||
);
|
|
||||||
$outlineHeader.addEventListener('click', (event) => {
|
|
||||||
location.hash = '';
|
|
||||||
});
|
|
||||||
web.render($fragment, $outlineHeader);
|
|
||||||
}
|
|
||||||
if ($fragment.innerHTML !== $headingList.innerHTML) {
|
|
||||||
web.render(web.empty($headingList), ...$fragment.children);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const pageObserver = () => {
|
|
||||||
if (!viewFocused) return;
|
const getHeadings = () => {
|
||||||
if (document.contains($page)) {
|
return [...document.querySelectorAll(headings.join(", "))];
|
||||||
updateHeadings();
|
},
|
||||||
} else {
|
getHeadingLevel = ($heading) => {
|
||||||
$page = document.querySelector('.notion-page-content');
|
for (let i = 0; i < headings.length; i++)
|
||||||
if (!$page) {
|
if ($heading.matches(headings[i])) return i + 1;
|
||||||
$notice.innerText = dbNoticeText;
|
},
|
||||||
$headingList.style.display = 'none';
|
updateHeadings = () => {
|
||||||
} else updateHeadings();
|
$view.innerHTML = "";
|
||||||
}
|
for (const $heading of getHeadings()) {
|
||||||
};
|
const title = $heading.innerText,
|
||||||
web.addDocumentObserver(pageObserver, [
|
indent = getHeadingLevel($heading);
|
||||||
'.notion-page-content',
|
if (!title) continue;
|
||||||
'.notion-collection_view_page-block',
|
const $h = html`<${Heading} indent=${indent} onclick=${() => {
|
||||||
]);
|
// todo: scroll into view
|
||||||
pageObserver();
|
}}>${title}</p>`;
|
||||||
}
|
$view.append($h);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let $page;
|
||||||
|
addMutationListener(page, () => {
|
||||||
|
if (!document.contains($page)) $page = document.querySelector(page);
|
||||||
|
if ($page) updateHeadings();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
@ -1,23 +1,21 @@
|
|||||||
{
|
{
|
||||||
"name": "outliner",
|
"name": "Outliner",
|
||||||
|
"version": "0.5.0",
|
||||||
"id": "87e077cc-5402-451c-ac70-27cc4ae65546",
|
"id": "87e077cc-5402-451c-ac70-27cc4ae65546",
|
||||||
"version": "0.4.0",
|
"description": "Adds a table of contents to the side panel to overview and navigate the current page's headings and subheadings.",
|
||||||
"description": "adds a table of contents to the side panel.",
|
"thumbnail": "outliner.png",
|
||||||
"preview": "outliner.png",
|
"tags": ["panel"],
|
||||||
"tags": ["extension", "panel"],
|
|
||||||
"authors": [
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "dragonwocky",
|
||||||
|
"homepage": "https://dragonwocky.me/",
|
||||||
|
"avatar": "https://dragonwocky.me/avatar.jpg"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "CloudHill",
|
"name": "CloudHill",
|
||||||
"email": "rh.cloudhill@gmail.com",
|
|
||||||
"homepage": "https://github.com/CloudHill",
|
"homepage": "https://github.com/CloudHill",
|
||||||
"avatar": "https://avatars.githubusercontent.com/u/54142180"
|
"avatar": "https://avatars.githubusercontent.com/u/54142180"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"js": {
|
"clientScripts": ["client.mjs"]
|
||||||
"client": ["client.mjs"]
|
|
||||||
},
|
|
||||||
"css": {
|
|
||||||
"client": ["client.css"]
|
|
||||||
},
|
|
||||||
"options": []
|
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
"type": "number",
|
"type": "number",
|
||||||
"key": "distanceScrolledUntilShown",
|
"key": "distanceScrolledUntilShown",
|
||||||
"description": "How far down a page you must be scrolled for the scroll to top button to appear.",
|
"description": "How far down a page you must be scrolled for the scroll to top button to appear.",
|
||||||
"value": 50
|
"value": 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "select",
|
"type": "select",
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
"core",
|
"core",
|
||||||
"extensions/titlebar",
|
"extensions/titlebar",
|
||||||
"extensions/topbar",
|
"extensions/topbar",
|
||||||
|
"extensions/outliner",
|
||||||
"extensions/scroll-to-top",
|
"extensions/scroll-to-top",
|
||||||
"themes/classic-dark"
|
"themes/classic-dark"
|
||||||
]
|
]
|
||||||
|
Loading…
Reference in New Issue
Block a user