From cf1eb203ca4ad1039a0d7d35bf4df7e9f2124646 Mon Sep 17 00:00:00 2001 From: Ryo Hilmawan <54142180+CloudHill@users.noreply.github.com> Date: Mon, 23 Nov 2020 14:00:10 +0700 Subject: [PATCH] upload side panel core --- mods/side-panel/app.css | 196 ++++++++++++++++++++++ mods/side-panel/mod.js | 361 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 557 insertions(+) create mode 100644 mods/side-panel/app.css create mode 100644 mods/side-panel/mod.js diff --git a/mods/side-panel/app.css b/mods/side-panel/app.css new file mode 100644 index 0000000..f9c116d --- /dev/null +++ b/mods/side-panel/app.css @@ -0,0 +1,196 @@ +/* + * side panel + * (c) 2020 dragonwocky (https://dragonwocky.me/) + * (c) 2020 CloudHill + * under the MIT license + */ + +.notion-frame { + transition: padding-right 300ms ease-in-out; +} +.enhancer-panel--container { + flex-grow: 0; + flex-shrink: 0; + position: absolute; + top: 0; + bottom: 0; + right: 0; + z-index: 99; + height: 100vh; + background: var(--theme--sidebar); + color: var(--theme--text_ui) !important; + font-weight: 500; + cursor: default; + transition: box-shadow 300ms ease-in, width 300ms ease-in-out; +} + +#enhancer-panel { + display: flex; + flex-direction: column; + position: relative; + pointer-events: auto; + background: var(--theme--sidebar); + cursor: auto; + max-height: 100%; + transition: transform 300ms ease-in-out, + opacity 300ms ease-in-out, + right 300ms ease-in-out; +} +#enhancer-panel[data-locked="false"] { + max-height: calc(100vh - 120px); + box-shadow: var(--theme--box-shadow_strong) !important; +} +#enhancer-panel[data-full-height="true"] { + height: 100%; +} +#enhancer-panel[data-locked="false"][data-full-height="true"] { + height: calc(100vh - 120px); +} + +.enhancer-panel--header { + flex-grow: 0; + flex-shrink: 0; + display: flex; + align-items: center; + height: 45px; + width: 100%; + color: var(--theme--text); + font-size: 14px; + padding: 2px 14px; + overflow: hidden; + user-select: none; + cursor: pointer; + transition: color 0.4s ease, background 0.4s ease, box-shadow 0.4s ease; +} +.enhancer-panel--header:hover { + background: var(--theme--interactive_hover); +} + +.enhancer-panel--icon { + flex-grow: 0; + flex-shrink: 0; + border-radius: 3px; + width: 22px; + height: 22px; + margin-right: 8px; + display: flex; + align-items: center; + justify-content: center; +} +.enhancer-panel--icon svg { + width: 100%; + height: 100%; +} + +.enhancer-panel--title { + margin-right: 6px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.enhancer-panel--switcher-icon { + width: 12px; + height: 12px; + fill: var(--theme--text_ui); +} +.enhancer-panel--switcher-icon svg { + width: 100%; + height: 100%; + display: block; +} + +.enhancer-panel--toggle { + flex-grow: 0; + flex-shrink: 0; + position: relative; + display: flex; + align-items: center; + justify-content: center; + margin-left: auto; + height: 24px; + width: 24px; + border-radius: 3px; + cursor: pointer; + opacity: 0; + transition: background 20ms ease-in, opacity 300ms ease-in; +} +#enhancer-panel:hover .enhancer-panel--toggle { + opacity: 1; +} +.enhancer-panel--toggle:hover { + background: var(--theme--interactive_hover); +} +.enhancer-panel--toggle svg { + width: 14px; + height: 14px; + fill: var(--theme--text_ui); + transition: transform 400ms ease-in; +} +#enhancer-panel[data-locked="false"] .enhancer-panel--toggle svg { + transform: rotateZ(-180deg); +} + +#enhancer-panel--content { + flex: 1; + width: 100%; + color: var(--theme--text); + font-size: var(--theme--font_body-size); + display: flex; + flex-direction: column; + position: relative; + min-height: 0; +} + +.enhancer-panel--resize { + position: absolute; + top: 0px; + left: 0px; + height: 100vh; + width: 0px; + z-index: 1; +} +#enhancer-panel[data-locked="false"] .enhancer-panel--resize { + height: 100%; +} +.enhancer-panel--resize div { + height: 100%; + width: 6px; +} + +.enhancer-panel--overlay-container { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 999; + overflow: hidden; +} + +.enhancer-panel--switcher { + max-width: calc(100vw - 24px); + position: relative; + right: 14px; + border-radius: 3px; + padding: 8px 0; + box-shadow: var(--theme--box-shadow_strong); + background: var(--theme--card); + overflow: hidden; +} + +.enhancer-panel--switcher-item { + display: flex; + align-items: center; + width: 100%; + padding: 8px 14px; + color: var(--theme--text_ui); + font-size: 14px; + user-select: none; + cursor: pointer; + overflow: hidden; + transition: background 0.4s ease; +} +.enhancer-panel--switcher-item:hover { + background: var(--theme--interactive_hover); +} \ No newline at end of file diff --git a/mods/side-panel/mod.js b/mods/side-panel/mod.js new file mode 100644 index 0000000..3dbb711 --- /dev/null +++ b/mods/side-panel/mod.js @@ -0,0 +1,361 @@ +/* + * side panel + * (c) 2020 dragonwocky (https://dragonwocky.me/) + * (c) 2020 CloudHill + * under the MIT license + */ + +'use strict'; + +const { createElement, getEnhancements } = require('../../pkg/helpers.js'), + path = require('path'), + fs = require('fs-extra'); + +module.exports = { + id: 'c8b1db83-ee37-45b4-bdb3-a7f3d36113db', + tags: ['extension', 'panel'], + name: 'side panel', + desc: 'adds a side panel to notion.', + version: '1.0.0', + author: 'CloudHill', + hacks: { + 'renderer/preload.js'(store, __exports) { + // Load panel mods + let panelMods = getEnhancements().loaded.filter(mod => + (mod.panel && (store('mods')[mod.id] || {}).enabled) + ); + + // Get panel info + panelMods.forEach(mod => initMod(mod)); + async function initMod(mod) { + try { + if (typeof mod.panel === 'object') { + // html + mod.panelHtml = await fs.readFile( + path.resolve(__dirname, `../${mod.dir}/${mod.panel.html}`) + ); + // name + if (!mod.panel.name) mod.panel.name = mod.name; + // icon + mod.panelIcon = mod.panel.name[0]; + if (mod.panel.icon) { + const iconPath = path.resolve(__dirname, `../${mod.dir}/${mod.panel.icon}`); + if (await fs.pathExists(iconPath)) + mod.panelIcon = await fs.readFile(iconPath); + } + // js + if (mod.panel.js) { + const jsPath = `../${mod.dir}/${mod.panel.js}`; + if (await fs.pathExists(path.resolve(__dirname, jsPath))) + mod.panelJs = jsPath; + } + // fullHeight + if (mod.panel.fullHeight) mod.panelFullHeight = mod.panel.fullHeight; + } else if (typeof mod.panel === 'string') { + mod.panelIcon = mod.name[0]; + mod.panelHtml = await fs.readFile( + path.resolve(__dirname, `../${mod.dir}/${mod.panel}`) + ); + } else throw Error; + } catch (err) { + console.log('invalid panel mod: ' + mod.name); + panelMods = panelMods.filter(panelMod => panelMod !== mod); + } + } + + document.addEventListener('readystatechange', (event) => { + if (document.readyState !== 'complete') return false; + if (panelMods.length < 1) return; + const attempt_interval = setInterval(enhance, 500); + function enhance() { + const frame = document.querySelector('.notion-frame'); + if (!frame) return; + clearInterval(attempt_interval); + + // Initialize panel + const container = createElement( + '
' + ) + const panel = createElement( + `
` + ) + + frame.after(container); + container.appendChild(panel); + + // Panel contents + const header = createElement(` +
+
+
+
+ `) + const toggle = createElement(` +
+ + + + +
+ `) + const content = createElement( + '
' + ) + const resize = createElement(` +
+
+
+ `) + + panel.append(header, content, resize); + + // Add switcher if there is more than one panel mods + if (panelMods.length > 1) { + header.addEventListener('click', renderSwitcher); + + const switcherIcon = createElement(` +
+ + + + +
+ `) + header.appendChild(switcherIcon); + } else header.addEventListener('click', togglePanel); + + header.appendChild(toggle); + toggle.addEventListener('click', togglePanel); + + // Restore lock state + if (store().locked === 'true') lockPanel(); + else unlockPanel(false); + + enableResize(); + + // Attempt to load last opened mod + let loaded = false; + if (store().last_open) { + panelMods.forEach(mod => { + if (mod.id === store().last_open) { + loadContent(mod); + loaded = true; + } + }) + } else if (!loaded) { + loadContent(panelMods[0]); + } + + function loadContent(mod) { + store().last_open = mod.id; + panel.querySelector('.enhancer-panel--title').innerText = mod.panel.name || mod.name; + panel.querySelector('.enhancer-panel--icon').innerHTML = mod.panelIcon; + document.getElementById('enhancer-panel--content').innerHTML = mod.panelHtml; + + if (mod.panelJs) + require(mod.panelJs)(store(mod.id))(); + + if (mod.panelFullHeight) { + panel.dataset.fullHeight = mod.panelFullHeight; + } else panel.dataset.fullHeight = ''; + } + + function unlockPanel(animate) { + panel.dataset.locked = 'false'; + setPanelWidth(store().width); + + if (animate) { + panel.animate( + [ + { opacity: 1, transform: 'none' }, + { opacity: 1, transform: 'translateY(60px)', offset: 0.4}, + { opacity: 0, transform: `translateX(${store().width - 30}px) translateY(60px)`}, + ], + { duration: 600, easing: 'ease-out' } + ).onfinish = () => { + panel.addEventListener('mouseover', showPanel); + panel.addEventListener('mouseleave', hidePanel); + } + } else { + panel.addEventListener('mouseover', showPanel); + panel.addEventListener('mouseleave', hidePanel); + } + + hidePanel(); + } + + function lockPanel() { + panel.dataset.locked = 'true'; + setPanelWidth(store().width); + + // Reset animation styles + panel.style.opacity = ''; + panel.style.transform = ''; + + // Hover event listeners + panel.removeEventListener('mouseover', showPanel); + panel.removeEventListener('mouseleave', hidePanel); + } + + function togglePanel(e) { + e.stopPropagation(); + if (isLocked()) unlockPanel(true); + else lockPanel(); + store().locked = panel.dataset.locked; + } + + function showPanel() { + if (!isLocked()) { + panel.style.opacity = 1; + panel.style.transform = 'translateY(60px)'; + } + } + + function hidePanel() { + if (!isLocked()) { + panel.style.opacity = 0; + panel.style.transform = `translateX(${store().width - 30}px) translateY(60px)`; + } + } + + function renderSwitcherItem(mod) { + if (mod.panel) { + const item = createElement( + `
+
${mod.panelIcon}
+
${mod.panel.name || mod.name}
+
` + ); + item.addEventListener('click', () => loadContent(mod)); + return item; + } + } + + function renderSwitcher() { + if (document.querySelector('.enhancer-panel--overlay-container')) return; + + // Layer to close switcher + const overlayContainer = createElement( + '
' + ); + overlayContainer.addEventListener('click', hideSwitcher) + document.querySelector('.notion-app-inner').appendChild(overlayContainer); + + // Position switcher below header + const rect = header.getBoundingClientRect(); + const div = createElement(` +
+
+
+ `); + + // Render switcher + const switcher = createElement( + '
' + ); + panelMods.forEach(mod => + switcher.append(renderSwitcherItem(mod)) + ); + + overlayContainer.appendChild(div); + div.firstElementChild.appendChild(switcher); + + // Fade in + switcher.animate( + [ {opacity: 0}, {opacity: 1} ], + { duration: 200 } + ); + + // Prevent panel from closing if unlocked + panel.removeEventListener('mouseleave', hidePanel); + } + + function hideSwitcher() { + const overlayContainer = document.querySelector('.enhancer-panel--overlay-container'); + overlayContainer.removeEventListener('click', hideSwitcher); + + // Fade out + document.querySelector('.enhancer-panel--switcher').animate( + [ {opacity: 1}, {opacity: 0} ], + { duration: 200 } + ).onfinish = () => overlayContainer.remove(); + + if (!isLocked()) panel.addEventListener('mouseleave', hidePanel); + } + + function setPanelWidth(width) { + store().width = width; + panel.style.width = width + 'px'; + + if (isLocked()) { + container.style.width = width + 'px'; + frame.style.paddingRight = width + 'px'; + panel.style.right = 0; + } else { + container.style.width = 0; + frame.style.paddingRight = 0; + panel.style.right = width + 'px'; + } + } + + function enableResize() { + const handle = resize.firstElementChild; + handle.addEventListener('mousedown', initDrag); + + let startX, startWidth; + const div = createElement( + '
' + ); + + function initDrag(e) { + startX = e.clientX; + startWidth = store().width; + + panel.appendChild(div); + + // Set transitions + container.style.transition = 'width 50ms ease-in'; + panel.style.transition = 'width 50ms ease-in, right 50ms ease-in'; + frame.style.transition = 'padding-right 50ms ease-in'; + + handle.style.cursor = ''; + // Prevent panel from closing if unlocked + panel.removeEventListener('mouseleave', hidePanel); + + document.body.addEventListener('mousemove', drag); + document.body.addEventListener('mouseup', stopDrag); + } + + function drag(e) { + e.preventDefault(); + let width = startWidth + (startX - e.clientX); + if (width < 190) width = 190; + if (width > 480) width = 480; + setPanelWidth(width); + } + + function stopDrag() { + handle.style.cursor = 'col-resize'; + panel.removeChild(div); + + // Reset transitions + container.style.transition = + panel.style.transition = + frame.style.transition = ''; + + if (!isLocked()) panel.addEventListener('mouseleave', hidePanel); + + document.body.removeEventListener('mousemove', drag); + document.body.removeEventListener('mouseup', stopDrag); + } + } + + function isLocked() { + if (panel.dataset.locked === 'true') return true; + else return false; + } + } + }); + }, + }, +};