/* * notion-enhancer: icon sets * (c) 2019 jayhxmo (https://jaymo.io/) * (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, notion }, db) { const recentUploads = await db.get(['recent_uploads'], []), $triangleSvg = web.html` `; const mediaMenuSelector = '.notion-media-menu', mediaScrollerSelector = '.notion-media-menu > .notion-scroller', mediaFilterSelector = '.notion-media-menu > :first-child > :last-child', mediaLinkInputSelector = '.notion-focusable-within > input[type=url]', tabBtnSelector = (n) => `.notion-media-menu > :first-child > :first-child > :nth-child(${n})`; const renderSetTitle = async (id, title, $tooltip = undefined) => { const isCollapsed = await db.get(['collapsed', id], false), $title = web.html`

`; web.render( $title, $triangleSvg.cloneNode(true), web.html`${web.escape(title)}` ); $title.addEventListener('click', () => { const newState = $title.dataset.collapsed !== 'true'; db.set(['collapsed', id], newState); $title.dataset.collapsed = newState; }); if ($tooltip) { const $infoSvg = web.html`${await components.feather('info', { class: 'info' })}`; components.setTooltip($infoSvg, $tooltip); web.render($title, $infoSvg); } return $title; }; const $iconsTab = web.html`
Icons
`, // actions $iconsLinkInput = web.html``, $iconsLinkSubmit = web.html``, $iconsUploadFile = web.html``, $iconsUploadSubmit = web.render( web.html``, 'Upload an image', $iconsUploadFile ), // sets $setsList = web.html`
`, $recentUploadsTitle = await renderSetTitle( 'recent_uploads', 'Recent', web.html`

Click to reuse an icon
Shift-click to remove it

` ), $recentUploads = web.html`
`, // container $iconsScroller = web.render( web.html``, web.render( web.html`
`, $iconsLinkInput, $iconsLinkSubmit, $iconsUploadSubmit ), web.render($setsList, $recentUploadsTitle, $recentUploads) ); let $mediaMenu, $activeTabUnderline; const insertIconsTab = async (event) => { if (document.contains($mediaMenu)) return; // prevent injection into file upload menus $mediaMenu = document.querySelector(mediaMenuSelector); if (!$mediaMenu || !$mediaMenu.textContent.includes('Emoji')) return; const $emojiTab = document.querySelector(tabBtnSelector(1)), $emojiScroller = document.querySelector(mediaScrollerSelector), $emojiFilter = document.querySelector(mediaFilterSelector), $uploadTab = document.querySelector(tabBtnSelector(2)), $linkTab = document.querySelector(tabBtnSelector(3)); $uploadTab.style.display = 'none'; $linkTab.style.display = 'none'; if ($activeTabUnderline) $activeTabUnderline.remove(); $activeTabUnderline = $emojiTab.children[1] || $uploadTab.children[1] || $linkTab.children[1]; $emojiTab.after($iconsTab); $emojiScroller.after($iconsScroller); const renderRecentUploads = () => { web.empty($recentUploads); for (let i = recentUploads.length - 1; i >= 0; i--) { const { signed, url } = recentUploads[i], $icon = web.html` `; web.render($recentUploads, $icon); $icon.addEventListener('click', (event) => { if (event.shiftKey) { recentUploads.splice(i, 1); db.set(['recent_uploads'], recentUploads); $icon.remove(); } else setIcon(url); }); } $recentUploads.style.height = `${$recentUploads.scrollHeight}px`; }, renderSets = async () => {}; const displayIconsTab = (force = false) => { if ($iconsTab.contains($activeTabUnderline) && !force) return; web.render($iconsTab, $activeTabUnderline); $iconsScroller.style.display = ''; $emojiScroller.style.display = 'none'; $emojiFilter.style.display = 'none'; renderRecentUploads(); }, displayEmojiTab = (force = false) => { if ($emojiTab.contains($activeTabUnderline) && !force) return; web.render($emojiTab, $activeTabUnderline); $iconsScroller.style.display = 'none'; $emojiScroller.style.display = ''; $emojiFilter.style.display = ''; }; // use onclick instead of eventlistener to override prev $iconsTab.onclick = displayIconsTab; $emojiTab.onclick = displayEmojiTab; // otherwise both may be visible on reopen displayEmojiTab(true); async function setIcon(iconUrl) { // without this react gets upset displayEmojiTab(); $linkTab.firstChild.click(); await new Promise(requestAnimationFrame); // call native setter, imitate human input const $notionLinkInput = $mediaMenu.querySelector(mediaLinkInputSelector), proto = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value'); proto.set.call($notionLinkInput, iconUrl); const inputEvent = new Event('input', { bubbles: true }), enterKeydownEvent = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, keyCode: 13, }); $notionLinkInput.dispatchEvent(inputEvent); $notionLinkInput.dispatchEvent(enterKeydownEvent); } const submitLinkIcon = () => { const url = $iconsLinkInput.firstElementChild.value; setIcon(url); recentUploads.push({ signed: notion.sign(url, notion.getPageID()), url: url }); db.set(['recent_uploads'], recentUploads); }; $iconsLinkInput.onkeyup = (event) => { if (event.code === 13) submitLinkIcon(); }; $iconsLinkSubmit.onclick = submitLinkIcon; // upload file to aws, then submit link $iconsUploadSubmit.onclick = $iconsUploadFile.click; $iconsUploadFile.onchange = async (event) => { const file = event.target.files[0], url = await notion.upload(file); setIcon(url); recentUploads.push({ signed: notion.sign(url, notion.getPageID()), url: url }); db.set(['recent_uploads'], recentUploads); }; }; web.addDocumentObserver(insertIconsTab, [mediaMenuSelector]); }