diff --git a/repo/emoji-sets/client.mjs b/repo/emoji-sets/client.mjs index b67fb26..9093ec2 100644 --- a/repo/emoji-sets/client.mjs +++ b/repo/emoji-sets/client.mjs @@ -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(); diff --git a/repo/indentation-lines/client.css b/repo/indentation-lines/client.css index 8acd6b6..aaef3d0 100644 --- a/repo/indentation-lines/client.css +++ b/repo/indentation-lines/client.css @@ -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; -} diff --git a/repo/indentation-lines/client.mjs b/repo/indentation-lines/client.mjs index ad0300f..35137a9 100644 --- a/repo/indentation-lines/client.mjs +++ b/repo/indentation-lines/client.mjs @@ -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]}); + }`; } } diff --git a/repo/indentation-lines/mod.json b/repo/indentation-lines/mod.json index 67576ff..d73c754 100644 --- a/repo/indentation-lines/mod.json +++ b/repo/indentation-lines/mod.json @@ -54,6 +54,12 @@ "key": "table_of_contents", "label": "tables of contents", "value": true + }, + { + "type": "toggle", + "key": "outliner", + "label": "outliner (panel extension)", + "value": true } ] } diff --git a/repo/outliner/client.css b/repo/outliner/client.css new file mode 100644 index 0000000..f01768e --- /dev/null +++ b/repo/outliner/client.css @@ -0,0 +1,47 @@ +/* + * notion-enhancer: outliner + * (c) 2020 CloudHill (https://github.com/CloudHill) + * (c) 2021 dragonwocky (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); +} diff --git a/repo/outliner/client.mjs b/repo/outliner/client.mjs new file mode 100644 index 0000000..6f3899d --- /dev/null +++ b/repo/outliner/client.mjs @@ -0,0 +1,91 @@ +/* + * notion-enhancer: outliner + * (c) 2020 CloudHill (https://github.com/CloudHill) + * (c) 2021 dragonwocky (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`

${dbNoticeText}

`; + + const $headingList = web.html`
`; + + let viewFocused = false; + await components.addPanelView({ + id: '87e077cc-5402-451c-ac70-27cc4ae65546', + icon: web.html` + + + + + + + `, + title: 'Outliner', + $content: web.render(web.html`
`, $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`
`; + 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``, + $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(); +} diff --git a/repo/outliner/mod.json b/repo/outliner/mod.json new file mode 100644 index 0000000..12a83ba --- /dev/null +++ b/repo/outliner/mod.json @@ -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": [] +} diff --git a/repo/registry.json b/repo/registry.json index 96b6ef4..7365710 100644 --- a/repo/registry.json +++ b/repo/registry.json @@ -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" ] diff --git a/repo/word-counter/client.css b/repo/word-counter/client.css index 5bd4fb3..614e70b 100644 --- a/repo/word-counter/client.css +++ b/repo/word-counter/client.css @@ -7,6 +7,7 @@ #word-counter--notice { color: var(--theme--text_secondary); font-size: 14px; + margin-top: 0; } .word-counter--stat { diff --git a/repo/word-counter/client.mjs b/repo/word-counter/client.mjs index 44fe96d..1c15f95 100644 --- a/repo/word-counter/client.mjs +++ b/repo/word-counter/client.mjs @@ -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`${dbNoticeText}`; + $notice = web.html`

${dbNoticeText}

`; const $wordCount = web.html`12`, $characterCount = web.html`12`, @@ -47,7 +47,7 @@ export default async function ({ web, components }, db) { $readingTooltip = web.html`${await components.feather('info')}`, $speakingTime = web.html`18 secs`, $speakingTooltip = web.html`${await components.feather('info')}`, - $statContainer = web.render( + $statList = web.render( web.html`
`, web.render(web.html`

`, $wordCount, ' words'), web.render(web.html`

`, $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`
`, $notice, $statContainer), + $content: web.render(web.html`
`, $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'; } } };