From 9a7a814ab8591c9e710f62ac1fffe06dd99d61a8 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Mon, 5 Feb 2024 15:43:06 +1100 Subject: [PATCH] feat: port word counter extension --- src/extensions/outliner/client.mjs | 33 +++--- src/extensions/word-counter/client.css | 40 ------- src/extensions/word-counter/client.mjs | 158 +++++++++++-------------- src/extensions/word-counter/mod.json | 3 +- 4 files changed, 86 insertions(+), 148 deletions(-) delete mode 100644 src/extensions/word-counter/client.css diff --git a/src/extensions/outliner/client.mjs b/src/extensions/outliner/client.mjs index fe26044..e318094 100644 --- a/src/extensions/outliner/client.mjs +++ b/src/extensions/outliner/client.mjs @@ -7,6 +7,22 @@ "use strict"; +function Heading({ indent, ...props }, ...children) { + const { html } = globalThis.__enhancerApi; + return html`
+ ${children} +
`; +} + export default async (api, db) => { const { html, debounce, addMutationListener, addPanelView } = api, behavior = (await db.get("smoothScrolling")) ? "smooth" : "auto", @@ -35,7 +51,7 @@ export default async (api, db) => { `, $view: html`

Click on a heading to jump to it. @@ -44,21 +60,6 @@ export default async (api, db) => {

`, }); - function Heading({ indent, ...props }, ...children) { - return html`
- ${children} -
`; - } - let $page; const updatePage = () => { if (document.contains($page)) return; diff --git a/src/extensions/word-counter/client.css b/src/extensions/word-counter/client.css deleted file mode 100644 index bd44781..0000000 --- a/src/extensions/word-counter/client.css +++ /dev/null @@ -1,40 +0,0 @@ -/** - * notion-enhancer: word counter - * (c) 2021 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -#word-counter--notice { - color: var(--theme--text_secondary); - font-size: 14px; - margin-top: 0; -} - -.word-counter--stat { - display: block; - background: var(--theme--ui_interactive-hover); - color: var(--theme--text); - font-size: 14px; - line-height: 1.2; - border: 1px solid transparent; - border-radius: 3px; - padding: 6px 8px; - cursor: pointer; - user-select: none; -} -.word-counter--stat:focus, -.word-counter--stat:hover { - background: transparent; - border: 1px solid var(--theme--ui_interactive-hover); -} -.word-counter--stat:active { - background: var(--theme--ui_interactive-active); -} - -.word-counter--stat svg { - display: inline-block; - height: 1em; - width: 1em; - margin: 0 0.4em -2px 0; - color: var(--theme--icon_secondary); -} diff --git a/src/extensions/word-counter/client.mjs b/src/extensions/word-counter/client.mjs index 30722d3..42e1f25 100644 --- a/src/extensions/word-counter/client.mjs +++ b/src/extensions/word-counter/client.mjs @@ -1,108 +1,84 @@ /** * notion-enhancer: word counter - * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (c) 2024 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ const humanTime = (mins) => { - let readable = ''; - if (1 <= mins) { + let readable = ""; + if (1 <= mins || !mins) { readable += `${Math.floor(mins)} min`; - if (2 <= mins) readable += 's'; + if (2 <= mins) readable += "s"; } const secs = Math.round((mins % 1) * 60); if (1 <= secs) { - if (1 <= mins) readable += ' '; + if (1 <= mins) readable += " "; readable += `${secs} sec`; - if (2 <= secs) readable += 's'; + if (2 <= secs) readable += "s"; } return readable; }; -export default async function ({ web, components }, db) { - const dbNoticeText = 'Open a page to see its word count.', - pageNoticeText = 'Click a stat to copy it.', - $notice = web.html`

${dbNoticeText}

`; - - const $wordCount = web.html`12`, - $characterCount = web.html`12`, - $sentenceCount = web.html`12`, - $blockCount = web.html`12`, - $readingTime = web.html`10 mins`, - $readingTooltip = web.html`${await components.feather('info')}`, - $speakingTime = web.html`18 secs`, - $speakingTooltip = web.html`${await components.feather('info')}`, - $statList = web.render( - web.html`
`, - web.render(web.html`

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

`, $characterCount, ' characters'), - web.render(web.html`

`, $sentenceCount, ' sentences'), - web.render(web.html`

`, $blockCount, ' blocks'), - web.render( - web.html`

`, - $readingTooltip, - $readingTime, - ' reading time' - ), - web.render( - web.html`

`, - $speakingTooltip, - $speakingTime, - ' speaking time' - ) - ); - $statList.querySelectorAll('.word-counter--stat').forEach(($stat) => { - $stat.addEventListener('click', () => web.copyToClipboard($stat.innerText)); +function Stat(props, ...children) { + const { html } = globalThis.__enhancerApi, + $stat = html`
+ ${children} +
`; + $stat.addEventListener("click", () => { + navigator.clipboard.writeText($stat.innerText); }); - components.addTooltip($readingTooltip, '**~ 275 wpm**', { offsetDirection: 'left' }); - components.addTooltip($speakingTooltip, '**~ 180 wpm**', { offsetDirection: 'left' }); - - let viewFocused = false, - $page; - await components.addPanelView({ - id: 'b99deb52-6955-43d2-a53b-a31540cd19a5', - icon: await components.feather('type'), - title: 'Word Counter', - $content: web.render(web.html`
`, $notice, $statList), - onFocus: () => { - viewFocused = true; - $page = document.getElementsByClassName('notion-page-content')[0]; - updateStats(); - }, - onBlur: () => { - viewFocused = false; - }, - }); - - function updateStats() { - if (!$page) return; - const words = $page.innerText.split(/[^\w]+/).length; - $wordCount.innerText = words; - $characterCount.innerText = $page.innerText.length; - $sentenceCount.innerText = $page.innerText.split('.').length; - $blockCount.innerText = $page.querySelectorAll('[data-block-id]').length; - $readingTime.innerText = humanTime(words / 275); - $speakingTime.innerText = humanTime(words / 180); - } - const pageObserver = () => { - if (!viewFocused) return; - if (document.contains($page)) { - updateStats(); - } else { - $page = document.getElementsByClassName('notion-page-content')[0]; - if ($page) { - $notice.innerText = pageNoticeText; - $statList.style.display = ''; - updateStats(); - } else { - $notice.innerText = dbNoticeText; - $statList.style.display = 'none'; - } - } - }; - web.addDocumentObserver(pageObserver, [ - '.notion-page-content', - '.notion-collection_view_page-block', - ]); - pageObserver(); + return $stat; } + +export default async (api, db) => { + const { html, debounce, addMutationListener, addPanelView } = api, + readingSpeed = await db.get("readingSpeed"), + speakingSpeed = await db.get("speakingSpeed"), + $wordCount = html`0`, + $characterCount = html`0`, + $sentenceCount = html`0`, + $blockCount = html`0`, + $readingTime = html`${humanTime(0)}`, + $speakingTime = html`${humanTime(0)}`, + page = ".notion-page-content"; + addPanelView({ + title: "Word Counter", + $icon: "type", + $view: html`
+

+ Click on a stat to copy it. +

+ <${Stat}>${$wordCount} words + <${Stat}>${$characterCount} characters + <${Stat}>${$sentenceCount} sentences + <${Stat}>${$blockCount} blocks + <${Stat}>${$readingTime} reading time + <${Stat}>${$speakingTime} speaking time +
`, + }); + + let $page; + const updateStats = debounce(() => { + if (!document.contains($page)) $page = document.querySelector(page); + if (!$page) return; + const text = $page.innerText, + words = text.split(/[^\w]+/).length; + $wordCount.innerText = words; + $characterCount.innerText = text.length; + $sentenceCount.innerText = text.split(".").filter((s) => s.trim()).length; + $blockCount.innerText = $page.querySelectorAll("[data-block-id]").length; + $readingTime.innerText = humanTime(words / readingSpeed); + $speakingTime.innerText = humanTime(words / speakingSpeed); + }); + addMutationListener(page, updateStats); + updateStats(); +}; diff --git a/src/extensions/word-counter/mod.json b/src/extensions/word-counter/mod.json index 8b3c8a4..753a8bc 100644 --- a/src/extensions/word-counter/mod.json +++ b/src/extensions/word-counter/mod.json @@ -25,5 +25,6 @@ "description": "The average number of words spoken per minute, used in calculating a page's speaking time.", "value": 180 } - ] + ], + "clientScripts": ["client.mjs"] }