mirror of
https://github.com/notion-enhancer/notion-enhancer.git
synced 2025-04-10 15:39:01 +00:00
feat: port word counter extension
This commit is contained in:
parent
3601680c24
commit
9a7a814ab8
@ -7,6 +7,22 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
function Heading({ indent, ...props }, ...children) {
|
||||||
|
const { html } = globalThis.__enhancerApi;
|
||||||
|
return html`<div
|
||||||
|
role="button"
|
||||||
|
class="notion-enhancer--outliner-heading
|
||||||
|
block cursor-pointer select-none text-[14px]
|
||||||
|
decoration-(2 [color:var(--theme--fg-border)])
|
||||||
|
hover:bg-[color:var(--theme--bg-hover)]
|
||||||
|
py-[6px] pr-[2px] pl-[${indent * 18}px]
|
||||||
|
underline-(& offset-4) last:mb-[24px]"
|
||||||
|
...${props}
|
||||||
|
>
|
||||||
|
${children}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
export default async (api, db) => {
|
export default async (api, db) => {
|
||||||
const { html, debounce, addMutationListener, addPanelView } = api,
|
const { html, debounce, addMutationListener, addPanelView } = api,
|
||||||
behavior = (await db.get("smoothScrolling")) ? "smooth" : "auto",
|
behavior = (await db.get("smoothScrolling")) ? "smooth" : "auto",
|
||||||
@ -35,7 +51,7 @@ export default async (api, db) => {
|
|||||||
</svg>`,
|
</svg>`,
|
||||||
$view: html`<section>
|
$view: html`<section>
|
||||||
<p
|
<p
|
||||||
class="py-[12px] pl-[18px]
|
class="py-[12px] px-[18px]
|
||||||
text-([color:var(--theme--fg-secondary)] [13px])"
|
text-([color:var(--theme--fg-secondary)] [13px])"
|
||||||
>
|
>
|
||||||
Click on a heading to jump to it.
|
Click on a heading to jump to it.
|
||||||
@ -44,21 +60,6 @@ export default async (api, db) => {
|
|||||||
</section>`,
|
</section>`,
|
||||||
});
|
});
|
||||||
|
|
||||||
function Heading({ indent, ...props }, ...children) {
|
|
||||||
return html`<div
|
|
||||||
role="button"
|
|
||||||
class="notion-enhancer--outliner-heading
|
|
||||||
block cursor-pointer select-none text-[14px]
|
|
||||||
decoration-(2 [color:var(--theme--fg-border)])
|
|
||||||
hover:bg-[color:var(--theme--bg-hover)]
|
|
||||||
py-[6px] pr-[2px] pl-[${indent * 18}px]
|
|
||||||
underline-(& offset-4) last:mb-[24px]"
|
|
||||||
...${props}
|
|
||||||
>
|
|
||||||
${children}
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
let $page;
|
let $page;
|
||||||
const updatePage = () => {
|
const updatePage = () => {
|
||||||
if (document.contains($page)) return;
|
if (document.contains($page)) return;
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
/**
|
|
||||||
* notion-enhancer: word counter
|
|
||||||
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (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);
|
|
||||||
}
|
|
@ -1,108 +1,84 @@
|
|||||||
/**
|
/**
|
||||||
* notion-enhancer: word counter
|
* notion-enhancer: word counter
|
||||||
* (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
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const humanTime = (mins) => {
|
const humanTime = (mins) => {
|
||||||
let readable = '';
|
let readable = "";
|
||||||
if (1 <= mins) {
|
if (1 <= mins || !mins) {
|
||||||
readable += `${Math.floor(mins)} min`;
|
readable += `${Math.floor(mins)} min`;
|
||||||
if (2 <= mins) readable += 's';
|
if (2 <= mins) readable += "s";
|
||||||
}
|
}
|
||||||
const secs = Math.round((mins % 1) * 60);
|
const secs = Math.round((mins % 1) * 60);
|
||||||
if (1 <= secs) {
|
if (1 <= secs) {
|
||||||
if (1 <= mins) readable += ' ';
|
if (1 <= mins) readable += " ";
|
||||||
readable += `${secs} sec`;
|
readable += `${secs} sec`;
|
||||||
if (2 <= secs) readable += 's';
|
if (2 <= secs) readable += "s";
|
||||||
}
|
}
|
||||||
return readable;
|
return readable;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function ({ web, components }, db) {
|
function Stat(props, ...children) {
|
||||||
const dbNoticeText = 'Open a page to see its word count.',
|
const { html } = globalThis.__enhancerApi,
|
||||||
pageNoticeText = 'Click a stat to copy it.',
|
$stat = html`<div
|
||||||
$notice = web.html`<p id="word-counter--notice">${dbNoticeText}</p>`;
|
role="button"
|
||||||
|
class="select-none cursor-pointer rounded-[3px]
|
||||||
const $wordCount = web.html`<b>12</b>`,
|
transition hover:bg-[color:var(--theme--bg-hover)]
|
||||||
$characterCount = web.html`<b>12</b>`,
|
text-[14px] my-[6px] mx-[12px] py-[2px] px-[10px]"
|
||||||
$sentenceCount = web.html`<b>12</b>`,
|
...${props}
|
||||||
$blockCount = web.html`<b>12</b>`,
|
>
|
||||||
$readingTime = web.html`<b>10 mins</b>`,
|
${children}
|
||||||
$readingTooltip = web.html`${await components.feather('info')}`,
|
</div>`;
|
||||||
$speakingTime = web.html`<b>18 secs</b>`,
|
$stat.addEventListener("click", () => {
|
||||||
$speakingTooltip = web.html`${await components.feather('info')}`,
|
navigator.clipboard.writeText($stat.innerText);
|
||||||
$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'),
|
|
||||||
web.render(web.html`<p class="word-counter--stat"></p>`, $sentenceCount, ' sentences'),
|
|
||||||
web.render(web.html`<p class="word-counter--stat"></p>`, $blockCount, ' blocks'),
|
|
||||||
web.render(
|
|
||||||
web.html`<p class="word-counter--stat"></p>`,
|
|
||||||
$readingTooltip,
|
|
||||||
$readingTime,
|
|
||||||
' reading time'
|
|
||||||
),
|
|
||||||
web.render(
|
|
||||||
web.html`<p class="word-counter--stat"></p>`,
|
|
||||||
$speakingTooltip,
|
|
||||||
$speakingTime,
|
|
||||||
' speaking time'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
$statList.querySelectorAll('.word-counter--stat').forEach(($stat) => {
|
|
||||||
$stat.addEventListener('click', () => web.copyToClipboard($stat.innerText));
|
|
||||||
});
|
});
|
||||||
components.addTooltip($readingTooltip, '**~ 275 wpm**', { offsetDirection: 'left' });
|
return $stat;
|
||||||
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`<div></div>`, $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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default async (api, db) => {
|
||||||
|
const { html, debounce, addMutationListener, addPanelView } = api,
|
||||||
|
readingSpeed = await db.get("readingSpeed"),
|
||||||
|
speakingSpeed = await db.get("speakingSpeed"),
|
||||||
|
$wordCount = html`<b>0</b>`,
|
||||||
|
$characterCount = html`<b>0</b>`,
|
||||||
|
$sentenceCount = html`<b>0</b>`,
|
||||||
|
$blockCount = html`<b>0</b>`,
|
||||||
|
$readingTime = html`<b>${humanTime(0)}</b>`,
|
||||||
|
$speakingTime = html`<b>${humanTime(0)}</b>`,
|
||||||
|
page = ".notion-page-content";
|
||||||
|
addPanelView({
|
||||||
|
title: "Word Counter",
|
||||||
|
$icon: "type",
|
||||||
|
$view: html`<section>
|
||||||
|
<p
|
||||||
|
class="py-[12px] px-[18px]
|
||||||
|
text-([color:var(--theme--fg-secondary)] [13px])"
|
||||||
|
>
|
||||||
|
Click on a stat to copy it.
|
||||||
|
</p>
|
||||||
|
<${Stat}>${$wordCount} words<//>
|
||||||
|
<${Stat}>${$characterCount} characters<//>
|
||||||
|
<${Stat}>${$sentenceCount} sentences<//>
|
||||||
|
<${Stat}>${$blockCount} blocks<//>
|
||||||
|
<${Stat}>${$readingTime} reading time<//>
|
||||||
|
<${Stat}>${$speakingTime} speaking time<//>
|
||||||
|
</section>`,
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
@ -25,5 +25,6 @@
|
|||||||
"description": "The average number of words spoken per minute, used in calculating a page's speaking time.",
|
"description": "The average number of words spoken per minute, used in calculating a page's speaking time.",
|
||||||
"value": 180
|
"value": 180
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"clientScripts": ["client.mjs"]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user