mirror of
				https://github.com/notion-enhancer/notion-enhancer.git
				synced 2025-11-04 16:18:08 +11: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]);
 | 
						|
}
 |