diff --git a/repo/emoji-sets/client.mjs b/repo/emoji-sets/client.mjs new file mode 100644 index 0000000..588e339 --- /dev/null +++ b/repo/emoji-sets/client.mjs @@ -0,0 +1,75 @@ +/* + * notion-enhancer: emoji sets + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +export default async function ({ web, env }, db) { + const style = await db.get(['style']), + // real emojis are used on macos instead of the twitter set + nativeEmojiSelector = `[style*='"Apple Color Emoji", "Segoe UI Emoji", NotoColorEmoji, "Noto Color Emoji", "Segoe UI Symbol", "Android Emoji", EmojiSymbols']:not(data-emoji-sets-unsupported)`, + imgEmojiSelector = '.notion-emoji:not(data-emoji-sets-unsupported)', + imgEmojiOverlaySelector = `${imgEmojiSelector} + [src*="notion-emojis"]`; + + await web.whenReady(); + + const nativeEmojis = document.querySelectorAll(nativeEmojiSelector).length, + imgEmojis = document.querySelectorAll(imgEmojiSelector).length; + + const unsupportedEmojis = [], + emojiReqs = new Map(), + getEmoji = async (emoji) => { + if (unsupportedEmojis.includes(emoji)) return undefined; + try { + if (!emojiReqs.get(emoji)) { + emojiReqs.set(emoji, fetch(`https://emojicdn.elk.sh/${emoji}?style=${style}`)); + } + const res = await emojiReqs.get(emoji); + if (!res.ok) throw new Error(); + return `url("https://emojicdn.elk.sh/${emoji}?style=${style}") 100% 100% / 100%`; + } catch { + unsupportedEmojis.push(emoji); + return undefined; + } + }; + + if (nativeEmojis) { + 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; + $emoji.style.width = '1em'; + $emoji.style.height = '1em'; + $emoji.style.display = 'inline-block'; + $emoji.innerText = ''; + await new Promise((res, rej) => requestAnimationFrame(res)); + } else { + $emoji.dataset.emojiSetsUnsupported = true; + } + } + }; + web.addDocumentObserver(updateEmojis, [nativeEmojiSelector]); + } + + if (style !== 'twitter' && imgEmojis) { + 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; + $emoji.style.opacity = 1; + if ($emoji.nextElementSibling?.matches?.(imgEmojiOverlaySelector)) { + $emoji.nextElementSibling.style.opacity = 0; + } + } else { + $emoji.dataset.emojiSetsUnsupported = true; + } + } + }; + updateEmojis(); + web.addDocumentObserver(updateEmojis, [imgEmojiSelector, imgEmojiOverlaySelector]); + } +} diff --git a/repo/emoji-sets/mod.json b/repo/emoji-sets/mod.json new file mode 100644 index 0000000..a6ee09a --- /dev/null +++ b/repo/emoji-sets/mod.json @@ -0,0 +1,42 @@ +{ + "name": "emoji sets", + "id": "a2401ee1-93ba-4b8c-9781-7f570bf5d71e", + "version": "0.4.0", + "description": "pick from a variety of emoji styles to use.", + "tags": ["extension", "customisation"], + "authors": [ + { + "name": "dragonwocky", + "email": "thedragonring.bod@gmail.com", + "homepage": "https://dragonwocky.me/", + "avatar": "https://dragonwocky.me/avatar.jpg" + } + ], + "js": { + "client": ["client.mjs"] + }, + "css": {}, + "options": [ + { + "type": "select", + "key": "style", + "label": "emoji style", + "values": [ + "twitter", + "apple", + "google", + "microsoft", + "samsung", + "whatsapp", + "facebook", + "messenger", + "joypixels", + "openmoji", + "emojidex", + "lg", + "htc", + "mozilla" + ] + } + ] +} diff --git a/repo/registry.json b/repo/registry.json index a3b4c38..0c137df 100644 --- a/repo/registry.json +++ b/repo/registry.json @@ -18,11 +18,12 @@ "pinky-boom", "bypass-preview", - "calendar-scroll", "font-chooser", - "collapse-properties", "scroll-to-top", + "calendar-scroll", + "collapse-properties", "indentation-lines", "focus-mode", - "word-counter" + "word-counter", + "emoji-sets" ] diff --git a/repo/word-counter/app.css b/repo/word-counter/app.css deleted file mode 100644 index 46ce67c..0000000 --- a/repo/word-counter/app.css +++ /dev/null @@ -1,59 +0,0 @@ -/* - * word counter - * (c) 2020 dragonwocky (https://dragonwocky.me/) - * under the MIT license - */ - -#word-counter-details { - width: 100%; - margin-bottom: 2em; -} -#word-counter-details > div { - display: flex; - flex-wrap: wrap; - margin: -0.5em; -} -#word-counter-details > div > p { - margin: 0.5em; - cursor: pointer; - font-size: var(--theme--font_label-size); - color: var(--theme--text); - border-radius: 3px; - padding: 0.25rem 0.5rem; - background: var(--theme--interactive_hover); - border: 1px solid transparent; -} -#word-counter-details > div > p:hover { - background: transparent; - border: 1px solid var(--theme--interactive_hover); -} - -#word-counter-details > div > span { - max-width: 10em; - padding: 0.4rem 0.5rem 0.25rem 0.5rem; - font-size: calc(var(--theme--font_label-size) * 0.8); - color: var(--theme--text_ui_info); -} - -#word-counter-details > div > p > svg { - height: 1em; - width: 1em; - margin: 0 0 -2px 0.3em; - color: var(--theme--text_ui_info); -} - -#word-counter-details-tooltip { - pointer-events: none; - position: absolute; - padding: 0.25em 0.5em; - border-radius: 3px; - box-shadow: var(--theme--box-shadow_strong); - border-right-width: 1px; - font-size: calc(var(--theme--font_label-size) * 0.8); - background: var(--theme--interactive_hover); - opacity: 0; - transition: opacity 120ms ease-in; -} -#word-counter-details-tooltip.active { - opacity: 1; -} diff --git a/repo/word-counter/mod.js b/repo/word-counter/mod.js deleted file mode 100644 index fa2ccbc..0000000 --- a/repo/word-counter/mod.js +++ /dev/null @@ -1,166 +0,0 @@ -/* - * word counter - * (c) 2020 dragonwocky (https://dragonwocky.me/) - * (c) 2020 admiraldus (https://github.com/admiraldus) - * under the MIT license - */ - -'use strict'; - -const { createElement } = require('../../pkg/helpers.js'); - -module.exports = { - id: 'b99deb52-6955-43d2-a53b-a31540cd19a5', - tags: ['extension'], - name: 'word counter', - desc: - 'add page details: word/character/sentence/block count & speaking/reading times.', - version: '0.2.0', - author: 'dragonwocky', - options: [ - { - key: 'hide_page_details_text', - label: 'hide "page details" text', - type: 'toggle', - value: false, - }, - ], - hacks: { - 'renderer/preload.js'(store, __exports) { - const copyToClipboard = (str) => { - const el = document.createElement('textarea'); - el.value = str; - el.setAttribute('readonly', ''); - el.style.position = 'absolute'; - el.style.left = '-9999px'; - document.body.appendChild(el); - el.select(); - document.execCommand('copy'); - document.body.removeChild(el); - }, - humanTime = (mins) => { - let readable = ''; - if (1 <= mins) { - readable += `${Math.floor(mins)} min`; - if (2 <= mins) readable += 's'; - } - const secs = Math.round((mins % 1) * 60); - if (1 <= secs) { - if (1 <= mins) readable += ' '; - readable += `${secs} sec`; - if (2 <= secs) readable += 's'; - } - return readable; - }; - - document.addEventListener('readystatechange', (event) => { - if (document.readyState !== 'complete') return false; - let queue = [], - $page = document.getElementsByClassName('notion-page-content')[0]; - const DOCUMENT_OBSERVER = new MutationObserver((list, observer) => { - if (!queue.length) requestIdleCallback(() => handle(queue)); - queue.push(...list); - }), - PAGE_OBSERVER = new MutationObserver(showPageWordDetails); - DOCUMENT_OBSERVER.observe(document.body, { - childList: true, - subtree: true, - }); - function handle(list) { - queue = []; - for (let { addedNodes } of list) { - if ( - addedNodes[0] && - addedNodes[0].className === 'notion-page-content' - ) { - $page = addedNodes[0]; - showPageWordDetails(); - - PAGE_OBSERVER.disconnect(); - PAGE_OBSERVER.observe($page, { - childList: true, - subtree: true, - characterData: true, - }); - } - } - } - const $container = createElement( - `
` - ), - $tooltip = createElement( - `` - ); - function showPageWordDetails() { - const details = { - words: $page.innerText.replace(/\s+/g, ' ').split(' ').length, - characters: $page.innerText.length, - sentences: $page.innerText.split('.').length, - blocks: $page.querySelectorAll('[data-block-id]').length, - }; - details['reading time'] = [ - humanTime(details.words / 275), - '~275 wpm', - ]; - details['speaking time'] = [ - humanTime(details.words / 180), - '~180 wpm', - ]; - - $container.children[0].innerHTML = ` - ${store().hide_page_details_text ? '' : 'page details
(click to copy)
'} - ${Object.keys(details).reduce( - (prev, key) => - prev + - (Array.isArray(details[key]) - ? `

- ${details[key][0]} ${key} - - - - -

` - : `

${details[key]} ${key}

`), - '' - )}`; - $page.previousElementSibling.children[0].appendChild($container); - if (!$container.offsetParent) return; - $container.offsetParent.appendChild($tooltip); - $container - .querySelectorAll('p') - .forEach((p) => - p.addEventListener('click', (e) => - copyToClipboard(e.target.innerText) - ) - ); - $container.querySelectorAll('[data-tooltip]').forEach((el) => { - el.addEventListener('mouseenter', (e) => { - $tooltip.innerText = el.getAttribute('data-tooltip'); - $tooltip.style.top = el.parentElement.offsetTop + 2.5 + 'px'; - $tooltip.style.left = - el.parentElement.offsetLeft + - el.parentElement.offsetWidth - - 5 + - 'px'; - $tooltip.classList.add('active'); - }); - el.addEventListener('mouseleave', (e) => - $tooltip.classList.remove('active') - ); - }); - } - }); - }, - }, -};