mirror of
https://github.com/notion-enhancer/notion-enhancer.git
synced 2025-04-07 05:59:02 +00:00
183 lines
7.1 KiB
JavaScript
183 lines
7.1 KiB
JavaScript
/*
|
|
* notion-enhancer: icon sets
|
|
* (c) 2019 jayhxmo (https://jaymo.io/)
|
|
* (c) 2020 CloudHill <rl.cloudhill@gmail.com> (https://github.com/CloudHill)
|
|
* (c) 2021 dragonwocky <thedragonring.bod@gmail.com> (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`<svg viewBox="0 0 100 100" class="triangle">
|
|
<polygon points="5.9,88.2 50,11.8 94.1,88.2" />
|
|
</svg>`;
|
|
|
|
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`<p class="icon_sets--title"
|
|
${isCollapsed ? 'data-collapsed="true"' : ''}></p>`;
|
|
web.render(
|
|
$title,
|
|
$triangleSvg.cloneNode(true),
|
|
web.html`<span>${web.escape(title)}</span>`
|
|
);
|
|
$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`<div class="icon_sets--tab_button">
|
|
<div class="notion-focusable" role="button" tabindex="0">Icons</div>
|
|
</div>`,
|
|
// actions
|
|
$iconsLinkInput = web.html`<div class="icon_sets--link_input">
|
|
<input placeholder="Paste an image link…" type="url">
|
|
</div>`,
|
|
$iconsLinkSubmit = web.html`<button class="icon_sets--link_submit">Submit</button>`,
|
|
$iconsUploadFile = web.html`<input type="file" accept="image/*" style="display:none">`,
|
|
$iconsUploadSubmit = web.render(
|
|
web.html`<button class="icon_sets--upload"></button>`,
|
|
'Upload an image',
|
|
$iconsUploadFile
|
|
),
|
|
// sets
|
|
$setsList = web.html`<div class="icon_sets--list"></div>`,
|
|
$recentUploadsTitle = await renderSetTitle(
|
|
'recent_uploads',
|
|
'Recent',
|
|
web.html`<p><b>Click</b> to reuse an icon <br><b>Shift-click</b> to remove it</p>`
|
|
),
|
|
$recentUploads = web.html`<div class="icon_sets--set"></div>`,
|
|
// container
|
|
$iconsScroller = web.render(
|
|
web.html`<div class="icon_sets--scroller" style="display:none"></div>`,
|
|
web.render(
|
|
web.html`<div class="icon_sets--actions"></div>`,
|
|
$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`<span class="icon_sets--icon">
|
|
<img src="${web.escape(signed)}">
|
|
</span>`;
|
|
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]);
|
|
}
|