mirror of
https://github.com/notion-enhancer/notion-enhancer.git
synced 2025-04-06 21:49:03 +00:00
extension: outliner
This commit is contained in:
parent
a9427f4520
commit
382451156c
@ -19,6 +19,7 @@ export default async function ({ web, env }, db) {
|
||||
const unsupportedEmojis = [],
|
||||
emojiReqs = new Map(),
|
||||
getEmoji = async (emoji) => {
|
||||
emoji = encodeURIComponent(emoji);
|
||||
if (unsupportedEmojis.includes(emoji)) return undefined;
|
||||
try {
|
||||
if (!emojiReqs.get(emoji)) {
|
||||
@ -37,16 +38,14 @@ export default async function ({ web, env }, db) {
|
||||
const updateEmojis = async () => {
|
||||
const $emojis = document.querySelectorAll(nativeEmojiSelector);
|
||||
for (const $emoji of $emojis) {
|
||||
const emojiBg = await getEmoji($emoji.ariaLabel);
|
||||
if (emojiBg) {
|
||||
$emoji.style.background = emojiBg;
|
||||
const emojiSrc = await getEmoji($emoji.ariaLabel);
|
||||
if (emojiSrc) {
|
||||
$emoji.style.background = emojiSrc;
|
||||
$emoji.style.width = '1em';
|
||||
$emoji.style.height = '1em';
|
||||
$emoji.style.display = 'inline-block';
|
||||
$emoji.innerText = '';
|
||||
} else {
|
||||
$emoji.dataset.emojiSetsUnsupported = true;
|
||||
}
|
||||
} else $emoji.dataset.emojiSetsUnsupported = true;
|
||||
}
|
||||
};
|
||||
web.addDocumentObserver(updateEmojis, [nativeEmojiSelector]);
|
||||
@ -56,16 +55,14 @@ export default async function ({ web, env }, db) {
|
||||
const updateEmojis = async () => {
|
||||
const $emojis = document.querySelectorAll(imgEmojiSelector);
|
||||
for (const $emoji of $emojis) {
|
||||
const emojiBg = await getEmoji($emoji.ariaLabel);
|
||||
if (emojiBg) {
|
||||
$emoji.style.background = emojiBg;
|
||||
const emojiSrc = await getEmoji($emoji.ariaLabel);
|
||||
if (emojiSrc) {
|
||||
$emoji.style.background = emojiSrc;
|
||||
$emoji.style.opacity = 1;
|
||||
if ($emoji.nextElementSibling?.matches?.(imgEmojiOverlaySelector)) {
|
||||
$emoji.nextElementSibling.style.opacity = 0;
|
||||
}
|
||||
} else {
|
||||
$emoji.dataset.emojiSetsUnsupported = true;
|
||||
}
|
||||
} else $emoji.dataset.emojiSetsUnsupported = true;
|
||||
}
|
||||
};
|
||||
updateEmojis();
|
||||
|
@ -5,6 +5,41 @@
|
||||
* (https://notion-enhancer.github.io/) under the MIT license
|
||||
*/
|
||||
|
||||
.notion-page-content .notion-bulleted_list-block > div > div:last-child,
|
||||
.notion-page-content .notion-numbered_list-block > div > div:last-child,
|
||||
.notion-page-content .notion-to_do-block > div > div:last-child,
|
||||
.notion-page-content .notion-toggle-block > div > div:last-child,
|
||||
.notion-page-content .notion-table_of_contents-block > div > div > a > div > div {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.notion-page-content .notion-bulleted_list-block > div > div:last-child::before,
|
||||
.notion-page-content .notion-numbered_list-block > div > div:last-child::before,
|
||||
.notion-page-content .notion-to_do-block > div > div:last-child::before,
|
||||
.notion-page-content .notion-toggle-block > div > div:last-child::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: calc(100% - 2em);
|
||||
top: 2em;
|
||||
left: -14.48px;
|
||||
}
|
||||
|
||||
.notion-page-content
|
||||
.notion-table_of_contents-block
|
||||
> div
|
||||
> div
|
||||
> a
|
||||
> div
|
||||
> div:not([style*='margin-left: 0px'])
|
||||
> div::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: -14.48px;
|
||||
}
|
||||
|
||||
/* add background to block dragger */
|
||||
.notion-frame
|
||||
> [style*='position: absolute; top: 0px; left: 0px;']
|
||||
[style*='position: absolute; top: 3px; left: -20px; width: 18px; height: 24px; pointer-events: auto; cursor: -webkit-grab;']
|
||||
@ -57,37 +92,3 @@
|
||||
> .plus:hover {
|
||||
background: rgba(255, 255, 255, 0.1) !important;
|
||||
}
|
||||
|
||||
.notion-page-content .notion-bulleted_list-block > div > div:last-child,
|
||||
.notion-page-content .notion-numbered_list-block > div > div:last-child,
|
||||
.notion-page-content .notion-to_do-block > div > div:last-child,
|
||||
.notion-page-content .notion-toggle-block > div > div:last-child,
|
||||
.notion-page-content .notion-table_of_contents-block > div > div > a > div > div {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.notion-page-content .notion-bulleted_list-block > div > div:last-child::before,
|
||||
.notion-page-content .notion-numbered_list-block > div > div:last-child::before,
|
||||
.notion-page-content .notion-to_do-block > div > div:last-child::before,
|
||||
.notion-page-content .notion-toggle-block > div > div:last-child::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: calc(100% - 2em);
|
||||
top: 2em;
|
||||
left: -14.48px;
|
||||
}
|
||||
|
||||
.notion-page-content
|
||||
.notion-table_of_contents-block
|
||||
> div
|
||||
> div
|
||||
> a
|
||||
> div
|
||||
> div:not([style*='margin-left: 0px'])
|
||||
> div::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: -14.48px;
|
||||
}
|
||||
|
@ -58,13 +58,32 @@ export default async function ({ web }, db) {
|
||||
}`;
|
||||
|
||||
if (rainbow) {
|
||||
for (let i = 0; i < colors.length; i++) {
|
||||
css += `
|
||||
.notion-page-content ${`.notion-table_of_contents-block `.repeat(i + 1)}
|
||||
> div > div > a > div > div:not([style*='margin-left: 0px']) > div::before {
|
||||
--indentation_lines--color: var(--theme--text_${colors[i]});
|
||||
}`;
|
||||
}
|
||||
css += `
|
||||
.notion-page-content .notion-table_of_contents-block > div > div > a > div
|
||||
> div[style*='margin-left: 24px'] > div::before {
|
||||
--indentation_lines--color: var(--theme--text_${colors[0]});
|
||||
}
|
||||
.notion-page-content .notion-table_of_contents-block > div > div > a > div
|
||||
> div[style*='margin-left: 48px'] > div::before {
|
||||
--indentation_lines--color: var(--theme--text_${colors[1]});
|
||||
}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (db.get(['outliner'])) {
|
||||
css += `
|
||||
.outliner--header:not([style='--outliner--indent:0px;'])::before {
|
||||
border-left: 1px ${style} var(--indentation_lines--color, currentColor);
|
||||
opacity: ${opacity};
|
||||
}`;
|
||||
if (rainbow) {
|
||||
css += `
|
||||
.outliner--header[style='--outliner--indent:18px;']::before {
|
||||
--indentation_lines--color: var(--theme--text_${colors[0]});
|
||||
}
|
||||
.outliner--header[style='--outliner--indent:36px;']::before {
|
||||
--indentation_lines--color: var(--theme--text_${colors[1]});
|
||||
}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,6 +54,12 @@
|
||||
"key": "table_of_contents",
|
||||
"label": "tables of contents",
|
||||
"value": true
|
||||
},
|
||||
{
|
||||
"type": "toggle",
|
||||
"key": "outliner",
|
||||
"label": "outliner (panel extension)",
|
||||
"value": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
47
repo/outliner/client.css
Normal file
47
repo/outliner/client.css
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* notion-enhancer: outliner
|
||||
* (c) 2020 CloudHill <rl.cloudhill@gmail.com> (https://github.com/CloudHill)
|
||||
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||
* (https://notion-enhancer.github.io/) under the MIT license
|
||||
*/
|
||||
|
||||
#outliner--notice {
|
||||
color: var(--theme--text_secondary);
|
||||
font-size: 14px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.outliner--header {
|
||||
position: relative;
|
||||
margin: 0 -1rem;
|
||||
padding: 0 1rem;
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
line-height: 2.2;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
text-overflow: ellipsis;
|
||||
text-decoration: none;
|
||||
text-indent: var(--outliner--indent);
|
||||
color: inherit;
|
||||
cursor: pointer !important;
|
||||
transition: background 20ms ease-in;
|
||||
}
|
||||
.outliner--header:hover {
|
||||
background: var(--theme--ui_interactive-hover);
|
||||
}
|
||||
|
||||
.outliner--header:empty::after {
|
||||
color: var(--theme--text_secondary);
|
||||
content: attr(placeholder);
|
||||
}
|
||||
|
||||
/* indentation lines */
|
||||
.outliner--header:not([style='--outliner--indent:0px;'])::before {
|
||||
content: '';
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: calc((1rem + var(--outliner--indent)) - 11px);
|
||||
}
|
91
repo/outliner/client.mjs
Normal file
91
repo/outliner/client.mjs
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* notion-enhancer: outliner
|
||||
* (c) 2020 CloudHill <rl.cloudhill@gmail.com> (https://github.com/CloudHill)
|
||||
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (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`<p id="outliner--notice">${dbNoticeText}</p>`;
|
||||
|
||||
const $headingList = web.html`<div></div>`;
|
||||
|
||||
let viewFocused = false;
|
||||
await components.addPanelView({
|
||||
id: '87e077cc-5402-451c-ac70-27cc4ae65546',
|
||||
icon: web.html`<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" 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>`,
|
||||
title: 'Outliner',
|
||||
$content: web.render(web.html`<div></div>`, $notice, $headingList),
|
||||
onFocus: () => {
|
||||
viewFocused = true;
|
||||
},
|
||||
onBlur: () => {
|
||||
viewFocused = false;
|
||||
},
|
||||
});
|
||||
await web.whenReady();
|
||||
|
||||
let $page;
|
||||
const updateHeadings = () => {
|
||||
if (!$page) return;
|
||||
const $headerBlocks = $page.querySelectorAll(
|
||||
'[class^="notion-"][class*="header-block"]'
|
||||
),
|
||||
$fragment = web.html`<div></div>`;
|
||||
let depth = 0,
|
||||
indent = 0;
|
||||
for (const $header of $headerBlocks) {
|
||||
const id = $header.dataset.blockId.replace(/-/g, ''),
|
||||
placeholder = $header.querySelector('[placeholder]').getAttribute('placeholder'),
|
||||
headerDepth = +placeholder.at(-1);
|
||||
if (depth && depth < headerDepth) {
|
||||
indent += 18;
|
||||
} else if (depth > headerDepth) {
|
||||
indent = Math.max(indent - 18, 0);
|
||||
}
|
||||
depth = headerDepth;
|
||||
const $outlineHeader = web.render(
|
||||
web.html`<a href="#${id}" class="outliner--header"
|
||||
placeholder="${web.escape(placeholder)}"
|
||||
style="--outliner--indent:${indent}px;"></a>`,
|
||||
$header.innerText
|
||||
);
|
||||
$fragment.append($outlineHeader);
|
||||
}
|
||||
if ($fragment.innerHTML !== $headingList.innerHTML) {
|
||||
web.render(web.empty($headingList), ...$fragment.children);
|
||||
}
|
||||
},
|
||||
pageObserver = () => {
|
||||
if (!viewFocused) return;
|
||||
if (document.contains($page)) {
|
||||
updateHeadings();
|
||||
} else {
|
||||
$page = document.getElementsByClassName('notion-page-content')[0];
|
||||
if ($page) {
|
||||
$notice.innerText = pageNoticeText;
|
||||
$headingList.style.display = '';
|
||||
updateHeadings();
|
||||
} else {
|
||||
$notice.innerText = dbNoticeText;
|
||||
$headingList.style.display = 'none';
|
||||
}
|
||||
}
|
||||
};
|
||||
web.addDocumentObserver(pageObserver, [
|
||||
'.notion-header-block',
|
||||
'.notion-sub_header-block',
|
||||
'.notion-sub_sub_header-block',
|
||||
'.notion-collection_view_page-block',
|
||||
]);
|
||||
pageObserver();
|
||||
}
|
22
repo/outliner/mod.json
Normal file
22
repo/outliner/mod.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "outliner",
|
||||
"id": "87e077cc-5402-451c-ac70-27cc4ae65546",
|
||||
"version": "0.4.0",
|
||||
"description": "adds a table of contents to the side panel.",
|
||||
"tags": ["extension", "panel"],
|
||||
"authors": [
|
||||
{
|
||||
"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": []
|
||||
}
|
@ -17,14 +17,15 @@
|
||||
"playful-purple",
|
||||
"pinky-boom",
|
||||
|
||||
"bypass-preview",
|
||||
"font-chooser",
|
||||
"outliner",
|
||||
"scroll-to-top",
|
||||
"indentation-lines",
|
||||
"emoji-sets",
|
||||
"bypass-preview",
|
||||
"topbar-icons",
|
||||
"word-counter",
|
||||
"calendar-scroll",
|
||||
"collapse-properties",
|
||||
"indentation-lines",
|
||||
"focus-mode",
|
||||
"word-counter",
|
||||
"emoji-sets",
|
||||
"topbar-icons"
|
||||
"focus-mode"
|
||||
]
|
||||
|
@ -7,6 +7,7 @@
|
||||
#word-counter--notice {
|
||||
color: var(--theme--text_secondary);
|
||||
font-size: 14px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.word-counter--stat {
|
||||
|
@ -35,9 +35,9 @@ const copyToClipboard = async (str) => {
|
||||
};
|
||||
|
||||
export default async function ({ web, components }, db) {
|
||||
const dbNoticeText = 'Open a normal page to see word count.',
|
||||
const dbNoticeText = 'Open a page to see its word count.',
|
||||
pageNoticeText = 'Click a stat to copy it.',
|
||||
$notice = web.html`<span id="word-counter--notice">${dbNoticeText}</span>`;
|
||||
$notice = web.html`<p id="word-counter--notice">${dbNoticeText}</p>`;
|
||||
|
||||
const $wordCount = web.html`<b>12</b>`,
|
||||
$characterCount = web.html`<b>12</b>`,
|
||||
@ -47,7 +47,7 @@ export default async function ({ web, components }, db) {
|
||||
$readingTooltip = web.html`${await components.feather('info')}`,
|
||||
$speakingTime = web.html`<b>18 secs</b>`,
|
||||
$speakingTooltip = web.html`${await components.feather('info')}`,
|
||||
$statContainer = web.render(
|
||||
$statList = web.render(
|
||||
web.html`<div></div>`,
|
||||
web.render(web.html`<p class="word-counter--stat"></p>`, $wordCount, ' words'),
|
||||
web.render(web.html`<p class="word-counter--stat"></p>`, $characterCount, ' characters'),
|
||||
@ -66,15 +66,24 @@ export default async function ({ web, components }, db) {
|
||||
' speaking time'
|
||||
)
|
||||
);
|
||||
$statContainer.querySelectorAll('.word-counter--stat').forEach(($stat) => {
|
||||
$statList.querySelectorAll('.word-counter--stat').forEach(($stat) => {
|
||||
$stat.addEventListener('click', () => copyToClipboard($stat.innerText));
|
||||
});
|
||||
components.setTooltip($readingTooltip, '**~ 275 wpm**');
|
||||
components.setTooltip($speakingTooltip, '**~ 180 wpm**');
|
||||
|
||||
let viewFocused = false;
|
||||
await components.addPanelView({
|
||||
id: 'b99deb52-6955-43d2-a53b-a31540cd19a5',
|
||||
icon: await components.feather('type'),
|
||||
title: 'Word Counter',
|
||||
$content: web.render(web.html`<div></div>`, $notice, $statContainer),
|
||||
$content: web.render(web.html`<div></div>`, $notice, $statList),
|
||||
onFocus: () => {
|
||||
viewFocused = true;
|
||||
},
|
||||
onBlur: () => {
|
||||
viewFocused = false;
|
||||
},
|
||||
});
|
||||
|
||||
let $page;
|
||||
@ -89,17 +98,18 @@ export default async function ({ web, components }, db) {
|
||||
$speakingTime.innerText = humanTime(words / 180);
|
||||
},
|
||||
pageObserver = () => {
|
||||
if (!viewFocused) return;
|
||||
if (document.contains($page)) {
|
||||
updateStats();
|
||||
} else {
|
||||
$page = document.getElementsByClassName('notion-page-content')[0];
|
||||
if ($page) {
|
||||
$notice.innerText = pageNoticeText;
|
||||
$statContainer.style.display = '';
|
||||
$statList.style.display = '';
|
||||
updateStats();
|
||||
} else {
|
||||
$notice.innerText = dbNoticeText;
|
||||
$statContainer.style.display = 'none';
|
||||
$statList.style.display = 'none';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user