From d1fc35c608980853380a33f8cc0372c881f69b32 Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 3 Oct 2021 19:23:00 +1100 Subject: [PATCH 01/52] Initial commit --- api/README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 api/README.md diff --git a/api/README.md b/api/README.md new file mode 100644 index 0000000..caea1ac --- /dev/null +++ b/api/README.md @@ -0,0 +1,2 @@ +# api +the standard api available to and depending on by the enhancer's mods and core From cdf8537153a26bbcae8a6ed8a4ff7af4f5f9f302 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Sun, 3 Oct 2021 19:29:18 +1100 Subject: [PATCH 02/52] upload progress from extension repo --- api/README.md | 3 +- api/_.mjs | 25 ++++ api/components/_.mjs | 36 ++++++ api/components/feather.mjs | 36 ++++++ api/components/panel.css | 237 ++++++++++++++++++++++++++++++++++ api/components/panel.mjs | 245 +++++++++++++++++++++++++++++++++++ api/components/tooltip.css | 25 ++++ api/components/tooltip.mjs | 38 ++++++ api/env.mjs | 46 +++++++ api/fmt.mjs | 132 +++++++++++++++++++ api/fs.mjs | 48 +++++++ api/registry-validation.mjs | 248 ++++++++++++++++++++++++++++++++++++ api/registry.mjs | 164 ++++++++++++++++++++++++ api/storage.mjs | 67 ++++++++++ api/web.mjs | 238 ++++++++++++++++++++++++++++++++++ 15 files changed, 1587 insertions(+), 1 deletion(-) create mode 100644 api/_.mjs create mode 100644 api/components/_.mjs create mode 100644 api/components/feather.mjs create mode 100644 api/components/panel.css create mode 100644 api/components/panel.mjs create mode 100644 api/components/tooltip.css create mode 100644 api/components/tooltip.mjs create mode 100644 api/env.mjs create mode 100644 api/fmt.mjs create mode 100644 api/fs.mjs create mode 100644 api/registry-validation.mjs create mode 100644 api/registry.mjs create mode 100644 api/storage.mjs create mode 100644 api/web.mjs diff --git a/api/README.md b/api/README.md index caea1ac..9f4674f 100644 --- a/api/README.md +++ b/api/README.md @@ -1,2 +1,3 @@ # api -the standard api available to and depending on by the enhancer's mods and core + +the standard api used by the enhancer's mods and core diff --git a/api/_.mjs b/api/_.mjs new file mode 100644 index 0000000..334d08c --- /dev/null +++ b/api/_.mjs @@ -0,0 +1,25 @@ +/* + * notion-enhancer: api + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +'use strict'; + +/** @module notion-enhancer/api */ + +/** environment-specific methods and constants */ +export * as env from './env.mjs'; +/** environment-specific filesystem reading */ +export * as fs from './fs.mjs'; +/** environment-specific data persistence */ +export * as storage from './storage.mjs'; + +/** helpers for formatting, validating and parsing values */ +export * as fmt from './fmt.mjs'; +/** interactions with the enhancer's repository of mods */ +export * as registry from './registry.mjs'; +/** helpers for manipulation of a webpage */ +export * as web from './web.mjs'; +/** shared notion-style elements */ +export * as components from './components/_.mjs'; diff --git a/api/components/_.mjs b/api/components/_.mjs new file mode 100644 index 0000000..e1d98e6 --- /dev/null +++ b/api/components/_.mjs @@ -0,0 +1,36 @@ +/* + * notion-enhancer: api + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +'use strict'; + +/** + * shared notion-style elements + * @module notion-enhancer/api/components + */ + +/** + * add a tooltip to show extra information on hover + * @param {HTMLElement} $ref - the element that will trigger the tooltip when hovered + * @param {string} text - the markdown content of the tooltip + */ +export { tooltip } from './tooltip.mjs'; + +/** + * generate an icon from the feather icons set + * @param {string} name - the name/id of the icon + * @param {object} attrs - an object of attributes to apply to the icon e.g. classes + * @returns {string} an svg string + */ +export { feather } from './feather.mjs'; + +/** + * adds a view to the enhancer's side panel + * @param {string} param0.id - a uuid, used to restore it on reload if it was last open + * @param {string} param0.icon - an svg string + * @param {string} param0.title - the name of the view + * @param {Element} param0.$content - an element containing the content of the view + */ +export { addPanelView } from './panel.mjs'; diff --git a/api/components/feather.mjs b/api/components/feather.mjs new file mode 100644 index 0000000..3734bda --- /dev/null +++ b/api/components/feather.mjs @@ -0,0 +1,36 @@ +/* + * notion-enhancer: api + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +'use strict'; + +/** + * shared notion-style elements + * @module notion-enhancer/api/components/feather + */ + +import { fs, web } from '../_.mjs'; + +let _$iconSheet; + +/** + * generate an icon from the feather icons set + * @param {string} name - the name/id of the icon + * @param {object} attrs - an object of attributes to apply to the icon e.g. classes + * @returns {string} an svg string + */ +export const feather = async (name, attrs = {}) => { + if (!_$iconSheet) { + _$iconSheet = web.html`${await fs.getText('dep/feather-sprite.svg')}`; + } + attrs.style = ( + (attrs.style ? attrs.style + ';' : '') + + 'stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;fill:none;' + ).trim(); + attrs.viewBox = '0 0 24 24'; + return ` `${web.escape(key)}="${web.escape(val)}"`) + .join(' ')}>${_$iconSheet.getElementById(name)?.innerHTML}`; +}; diff --git a/api/components/panel.css b/api/components/panel.css new file mode 100644 index 0000000..1294867 --- /dev/null +++ b/api/components/panel.css @@ -0,0 +1,237 @@ +/* + * notion-enhancer core: components + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (c) 2021 CloudHill (https://github.com/CloudHill) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +:root { + --component--panel-width: 260px; +} + +#enhancer--panel-hover-trigger { + height: 100vh; + width: 2.5rem; + max-height: 100%; + z-index: 999; + position: absolute; + top: 0; + right: 0; + flex-grow: 0; + flex-shrink: 0; + transition: width 300ms ease-in-out; +} +#enhancer--panel-hover-trigger[data-enhancer-panel-pinned] { + /* taking up the physical space of the panel to move topbar buttons */ + position: relative; + width: var(--component--panel-width); +} + +.notion-cursor-listener > div[style*='flex-end'] { + transition: margin-right 300ms ease-in-out; +} +.notion-cursor-listener > div[style*='flex-end'][data-enhancer-panel-pinned] { + margin-right: var(--component--panel-width); +} +.notion-frame { + transition: padding-right 300ms ease-in-out; +} +.notion-frame[data-enhancer-panel-pinned] { + padding-right: var(--component--panel-width); +} + +#enhancer--panel { + z-index: 999; + position: absolute; + background: var(--theme--bg_secondary); + width: var(--component--panel-width); + right: calc(-1 * var(--component--panel-width)); + opacity: 0; + height: 100vh; + flex-grow: 0; + flex-shrink: 0; + display: flex; + flex-direction: column; + transition: 300ms ease-in; + + margin-top: 5rem; + max-height: calc(100vh - 10rem); +} +#enhancer--panel-hover-trigger:hover + #enhancer--panel:not([data-enhancer-panel-pinned]), +#enhancer--panel:not([data-enhancer-panel-pinned]):hover { + opacity: 1; + transform: translateX(calc(-1 * var(--component--panel-width))); + box-shadow: var(--theme--ui_shadow, rgba(15, 15, 15, 0.05)) 0px 0px 0px 1px, + var(--theme--ui_shadow, rgba(15, 15, 15, 0.1)) 0px 3px 6px, + var(--theme--ui_shadow, rgba(15, 15, 15, 0.2)) 0px 9px 24px !important; +} +#enhancer--panel[data-enhancer-panel-pinned] { + opacity: 1; + max-height: 100%; + margin-top: 0; + transform: translateX(calc(-1 * var(--component--panel-width))); +} + +.enhancer--panel-view-title { + margin: 0; + height: 1em; + display: flex; + align-items: center; + font-size: 1.1rem; + font-weight: 600; +} +.enhancer--panel-view-title svg, +.enhancer--panel-view-title img { + height: 1em; + width: 1em; +} +.enhancer--panel-view-title .enhancer--panel-view-title-text { + font-size: 0.9em; + margin: 0 0 0 0.75em; + padding-bottom: 0.3em; + white-space: nowrap; + overflow: hidden; +} + +#enhancer--panel-header { + font-size: 1.2rem; + font-weight: 600; + display: flex; + align-items: center; + cursor: pointer; + user-select: none; + padding: 0.75rem 0 0.75rem 1rem; + --scoped--bg: var(--theme--bg_secondary); + background: var(--scoped--bg); +} +#enhancer--panel-header-title { + max-width: calc(100% - 4.25rem); +} +#enhancer--panel-header-title .enhancer--panel-view-title { + font-size: 1.2rem; +} +#enhancer--panel-header-title .enhancer--panel-view-title-text { + max-width: calc(100% - 1.75em); + position: relative; +} +.enhancer--panel-view-title-fade-edge { + display: inline-block; + width: 0.75rem; + height: 1em; +} + +#enhancer--panel-switcher-overlay-container { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 999; + overflow: hidden; +} +#enhancer--panel-switcher { + max-width: 320px; + position: relative; + right: 14px; + border-radius: 3px; + padding: 8px 0; + background: var(--theme--bg_popup); + box-shadow: var(--theme--ui_shadow, rgba(15, 15, 15, 0.05)) 0px 0px 0px 1px, + var(--theme--ui_shadow, rgba(15, 15, 15, 0.1)) 0px 3px 6px, + var(--theme--ui_shadow, rgba(15, 15, 15, 0.2)) 0px 9px 24px !important; + overflow: hidden; +} +.enhancer--panel-switcher-item { + display: flex; + align-items: center; + width: 100%; + padding: 8px 14px; + user-select: none; + cursor: pointer; + overflow: hidden; + position: relative; + --scoped--bg: var(--theme--bg_popup); + background: var(--scoped--bg); +} +#enhancer--panel-header:hover, +#enhancer--panel-header:focus-within, +.enhancer--panel-switcher-item:hover, +.enhancer--panel-switcher-item:focus { + --scoped--bg: var(--theme--ui_interactive-hover); +} +#enhancer--panel-header:active, +.enhancer--panel-switcher-item:active { + background: var(--theme--ui_interactive-active); +} +.enhancer--panel-view-title-fade-edge:after { + content: ''; + height: 100%; + position: absolute; + right: 0; + top: 0; + width: 0.75rem; + background: linear-gradient( + to right, + transparent 0%, + var(--scoped--bg) 50%, + var(--scoped--bg) 100% + ); +} + +#enhancer--panel-content { + margin: 0.75rem 1rem; + font-size: 1rem; +} +#enhancer--panel-header-switcher { + padding: 4px; +} +#enhancer--panel-header-toggle { + margin-left: auto; + padding-right: 1rem; + height: 100%; + width: 2.5em; + opacity: 0; + display: flex; +} +#enhancer--panel-header-toggle > div { + margin: auto 0 auto auto; +} +#enhancer--panel-header-switcher, +#enhancer--panel-header-toggle > div { + color: var(--theme--icon_secondary); + height: 1em; + width: 1em; + cursor: pointer; + display: flex; + flex-direction: column; + transition: 300ms ease-in-out; +} +#enhancer--panel #enhancer--panel-header-toggle svg { + transition: 300ms ease-in-out; +} +#enhancer--panel:not([data-enhancer-panel-pinned]) #enhancer--panel-header-toggle svg { + transform: rotateZ(-180deg); +} +#enhancer--panel:hover #enhancer--panel-header-toggle { + opacity: 1; +} + +#enhancer--panel-resize { + position: absolute; + left: -5px; + height: 100%; + width: 10px; +} +#enhancer--panel[data-enhancer-panel-pinned] #enhancer--panel-resize { + cursor: col-resize; +} +#enhancer--panel-resize div { + transition: background 150ms ease-in-out; + background: transparent; + width: 2px; + margin-left: 4px; + height: 100%; +} +#enhancer--panel[data-enhancer-panel-pinned] #enhancer--panel-resize:hover div { + background: var(--theme--ui_divider); +} diff --git a/api/components/panel.mjs b/api/components/panel.mjs new file mode 100644 index 0000000..f4c743b --- /dev/null +++ b/api/components/panel.mjs @@ -0,0 +1,245 @@ +/* + * notion-enhancer: api + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +'use strict'; + +/** + * shared notion-style elements + * @module notion-enhancer/api/components/side-panel + */ + +import { web, components, registry } from '../_.mjs'; +const db = await registry.db('36a2ffc9-27ff-480e-84a7-c7700a7d232d'); + +const _views = [], + svgExpand = web.raw` + +`; + +// open + close +let $notionFrame, + $notionRightSidebar, + // resize + dragStartX, + dragStartWidth, + dragEventsFired, + panelWidth, + // render content + $notionApp; + +// open + close +const $panel = web.html`
`, + $pinnedToggle = web.html`
+ ${await components.feather('chevrons-right')} +
`, + $hoverTrigger = web.html`
`, + panelPinnedAttr = 'data-enhancer-panel-pinned', + isPinned = () => $panel.hasAttribute(panelPinnedAttr), + togglePanel = () => { + const $elems = [$notionRightSidebar, $notionFrame, $hoverTrigger, $panel]; + if (isPinned()) { + closeSwitcher(); + for (const $elem of $elems) $elem.removeAttribute(panelPinnedAttr); + } else { + for (const $elem of $elems) $elem.setAttribute(panelPinnedAttr, 'true'); + } + db.set(['panel.pinned'], isPinned()); + }, + // resize + $resizeHandle = web.html`
`, + updateWidth = async () => { + document.documentElement.style.setProperty('--component--panel-width', panelWidth + 'px'); + db.set(['panel.width'], panelWidth); + }, + resizeDrag = (event) => { + event.preventDefault(); + dragEventsFired = true; + panelWidth = dragStartWidth + (dragStartX - event.clientX); + if (panelWidth < 190) panelWidth = 190; + if (panelWidth > 480) panelWidth = 480; + $panel.style.width = panelWidth + 'px'; + $hoverTrigger.style.width = panelWidth + 'px'; + $notionFrame.style.paddingRight = panelWidth + 'px'; + $notionRightSidebar.style.right = panelWidth + 'px'; + }, + resizeEnd = (event) => { + $panel.style.width = ''; + $hoverTrigger.style.width = ''; + $notionFrame.style.paddingRight = ''; + $notionRightSidebar.style.right = ''; + updateWidth(); + $resizeHandle.style.cursor = ''; + document.body.removeEventListener('mousemove', resizeDrag); + document.body.removeEventListener('mouseup', resizeEnd); + }, + resizeStart = (event) => { + dragStartX = event.clientX; + dragStartWidth = panelWidth; + $resizeHandle.style.cursor = 'auto'; + document.body.addEventListener('mousemove', resizeDrag); + document.body.addEventListener('mouseup', resizeEnd); + }, + // render content + $panelTitle = web.html`
`, + $header = web.render(web.html`
`, $panelTitle), + $panelContent = web.html`
`, + $switcher = web.html`
`, + $switcherTrigger = web.html`
+ ${svgExpand} +
`, + $switcherOverlayContainer = web.html`
`, + isSwitcherOpen = () => document.body.contains($switcher), + openSwitcher = () => { + if (!isPinned()) return togglePanel(); + web.render($notionApp, $switcherOverlayContainer); + web.empty($switcher); + for (const view of _views) { + const open = $panelTitle.contains(view.$title), + $item = web.render( + web.html`
`, + web.render( + web.html``, + view.$icon.cloneNode(true), + view.$title.cloneNode(true) + ) + ); + $item.addEventListener('click', () => { + renderView(view); + db.set(['panel.open'], view.id); + }); + web.render($switcher, $item); + } + const rect = $header.getBoundingClientRect(); + web.render( + web.empty($switcherOverlayContainer), + web.render( + web.html`
`, + web.render( + web.html`
`, + $switcher + ) + ) + ); + $switcher.querySelector('[data-open]').focus(); + $switcher.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 200 }); + document.addEventListener('keydown', switcherKeyListeners); + }, + closeSwitcher = () => { + document.removeEventListener('keydown', switcherKeyListeners); + $switcher.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 200 }).onfinish = () => + $switcherOverlayContainer.remove(); + }, + switcherKeyListeners = (event) => { + if (isSwitcherOpen()) { + switch (event.key) { + case 'Escape': + closeSwitcher(); + event.stopPropagation(); + break; + case 'Enter': + document.activeElement.click(); + event.stopPropagation(); + break; + case 'ArrowUp': + const $prev = event.target.previousElementSibling; + ($prev || event.target.parentElement.lastElementChild).focus(); + event.stopPropagation(); + break; + case 'ArrowDown': + const $next = event.target.nextElementSibling; + ($next || event.target.parentElement.firstElementChild).focus(); + event.stopPropagation(); + break; + } + } + }, + renderView = (view) => { + web.render( + web.empty($panelTitle), + web.render( + web.html``, + view.$icon, + view.$title + ) + ); + web.render(web.empty($panelContent), view.$content); + }; + +async function createPanel() { + const notionRightSidebarSelector = '.notion-cursor-listener > div[style*="flex-end"]'; + await web.whenReady([notionRightSidebarSelector]); + $notionFrame = document.querySelector('.notion-frame'); + $notionRightSidebar = document.querySelector(notionRightSidebarSelector); + if (await db.get(['panel.pinned'])) togglePanel(); + web.addHotkeyListener(await db.get(['panel.hotkey']), togglePanel); + $pinnedToggle.addEventListener('click', (event) => { + event.stopPropagation(); + togglePanel(); + }); + web.render( + $panel, + web.render($header, $panelTitle, $switcherTrigger, $pinnedToggle), + $panelContent, + $resizeHandle + ); + + await enablePanelResize(); + await createViews(); + + $notionRightSidebar.after($hoverTrigger, $panel); +} + +async function enablePanelResize() { + panelWidth = await db.get(['panel.width'], 240); + updateWidth(); + $resizeHandle.addEventListener('mousedown', resizeStart); + $resizeHandle.addEventListener('click', () => { + if (dragEventsFired) { + dragEventsFired = false; + } else togglePanel(); + }); +} + +async function createViews() { + $notionApp = document.querySelector('.notion-app-inner'); + $header.addEventListener('click', openSwitcher); + $switcherTrigger.addEventListener('click', openSwitcher); + $switcherOverlayContainer.addEventListener('click', closeSwitcher); +} + +web.loadStylesheet('api/components/panel.css'); + +/** + * adds a view to the enhancer's side panel + * @param {string} param0.id - a uuid, used to restore the last open view on reload + * @param {string} param0.icon - an svg string + * @param {string} param0.title - the name of the view + * @param {Element} param0.$content - an element containing the content of the view + */ +export const addPanelView = async ({ id, icon, title, $content }) => { + const view = { + id, + $icon: web.html` + ${icon} + `, + $title: web.html` + ${web.escape(title)} + + `, + $content, + }; + _views.push(view); + if (_views.length === 1) await createPanel(); + if (_views.length === 1 || (await db.get(['panel.open'])) === id) renderView(view); +}; diff --git a/api/components/tooltip.css b/api/components/tooltip.css new file mode 100644 index 0000000..b919963 --- /dev/null +++ b/api/components/tooltip.css @@ -0,0 +1,25 @@ +/* + * notion-enhancer core: components + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +#enhancer--tooltip { + position: absolute; + background: var(--theme--ui_tooltip); + font-size: 11.5px; + padding: 0.15rem 0.4rem; + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important; + border-radius: 3px; + max-width: 20rem; + display: none; +} +#enhancer--tooltip p { + margin: 0.25rem 0; +} +#enhancer--tooltip p:first-child { + color: var(--theme--ui_tooltip-title); +} +#enhancer--tooltip p:not(:first-child) { + color: var(--theme--ui_tooltip-description); +} diff --git a/api/components/tooltip.mjs b/api/components/tooltip.mjs new file mode 100644 index 0000000..e52d3f0 --- /dev/null +++ b/api/components/tooltip.mjs @@ -0,0 +1,38 @@ +/* + * notion-enhancer: api + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +'use strict'; + +/** + * shared notion-style elements + * @module notion-enhancer/api/components/tooltip + */ + +import { fmt, web } from '../_.mjs'; + +const _$tooltip = web.html`
`; +web.loadStylesheet('api/components/tooltip.css'); + +/** + * add a tooltip to show extra information on hover + * @param {HTMLElement} $ref - the element that will trigger the tooltip when hovered + * @param {string} text - the markdown content of the tooltip + */ +export const tooltip = ($ref, text) => { + web.render(document.body, _$tooltip); + text = fmt.md.render(text); + $ref.addEventListener('mouseover', (event) => { + _$tooltip.innerHTML = text; + _$tooltip.style.display = 'block'; + }); + $ref.addEventListener('mousemove', (event) => { + _$tooltip.style.top = event.clientY - _$tooltip.clientHeight + 'px'; + _$tooltip.style.left = event.clientX - _$tooltip.clientWidth + 'px'; + }); + $ref.addEventListener('mouseout', (event) => { + _$tooltip.style.display = ''; + }); +}; diff --git a/api/env.mjs b/api/env.mjs new file mode 100644 index 0000000..b2bccdc --- /dev/null +++ b/api/env.mjs @@ -0,0 +1,46 @@ +/* + * notion-enhancer: api + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +'use strict'; + +/** + * environment-specific methods and constants + * @module notion-enhancer/api/env + */ + +import * as env from '../env/env.mjs'; + +/** + * the environment/platform name code is currently being executed in + * @constant + * @type {string} + */ +export const name = env.name; + +/** + * the current version of the enhancer + * @constant + * @type {string} + */ +export const version = env.version; + +/** + * open the enhancer's menu + * @type {function} + */ +export const focusMenu = env.focusMenu; + +/** + * focus an active notion tab + * @type {function} + */ +export const focusNotion = env.focusNotion; + +/** + * reload all notion and enhancer menu tabs to apply changes + * @type {function} + */ +export const reload = env.reload; diff --git a/api/fmt.mjs b/api/fmt.mjs new file mode 100644 index 0000000..f905c01 --- /dev/null +++ b/api/fmt.mjs @@ -0,0 +1,132 @@ +/* + * notion-enhancer: api + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +'use strict'; + +/** + * helpers for formatting or parsing text + * @module notion-enhancer/api/fmt + */ + +import { web, fs, components } from './_.mjs'; + +import '../dep/prism.min.js'; +/** syntax highlighting using https://prismjs.com/ */ +export const prism = Prism; +Prism.manual = true; +Prism.hooks.add('complete', async (event) => { + event.element.parentElement.removeAttribute('tabindex'); + event.element.parentElement.parentElement + .querySelector('.copy-to-clipboard-button') + .prepend(web.html`${await components.feather('clipboard')}`); +}); + +import '../dep/markdown-it.min.js'; +/** markdown -> html using https://github.com/markdown-it/markdown-it/ */ +export const md = new markdownit({ + linkify: true, + highlight: (str, lang) => + web.html`
${web.escape(
+      str
+    )}
`, +}); +md.renderer.rules.code_block = (tokens, idx, options, env, slf) => { + const attrIdx = tokens[idx].attrIndex('class'); + if (attrIdx === -1) { + tokens[idx].attrPush(['class', 'match-braces language-plaintext']); + } else tokens[idx].attrs[attrIdx][1] = 'match-braces language-plaintext'; + return web.html`${web.escape( + tokens[idx].content + )}\n`; +}; +md.core.ruler.push( + 'heading_ids', + function (md, state) { + const slugs = new Set(); + state.tokens.forEach(function (token, i) { + if (token.type === 'heading_open') { + const text = md.renderer.render(state.tokens[i + 1].children, md.options), + slug = slugger(text, slugs); + slugs.add(slug); + const attrIdx = token.attrIndex('id'); + if (attrIdx === -1) { + token.attrPush(['id', slug]); + } else token.attrs[attrIdx][1] = slug; + } + }); + }.bind(null, md) +); + +/** + * transform a heading into a slug (a lowercase alphanumeric string separated by dashes), + * e.g. for use as an anchor id + * @param {string} heading - the original heading to be slugified + * @param {Set} [slugs] - a list of pre-generated slugs to avoid duplicates + * @returns {string} the generated slug + */ +export const slugger = (heading, slugs = new Set()) => { + heading = heading + .replace(/\s/g, '-') + .replace(/[^A-Za-z0-9-_]/g, '') + .toLowerCase(); + let i = 0, + slug = heading; + while (slugs.has(slug)) { + i++; + slug = `${heading}-${i}`; + } + return slug; +}; + +const patterns = { + alphanumeric: /^[\w\.-]+$/, + uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, + semver: + /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i, + email: + /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i, + url: /^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i, + color: /^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i, +}; +function test(str, pattern) { + const match = str.match(pattern); + return !!(match && match.length); +} + +/** + * test the type of a value. unifies builtin, regex, and environment/api checks + * @param {*} value - the value to check + * @param {string|array} type - the type the value should be or a list of allowed values + * @returns {boolean} whether or not the value matches the type + */ +export const is = async (value, type, { extension = '' } = {}) => { + extension = !value || !value.endsWith || value.endsWith(extension); + if (Array.isArray(type)) { + return type.includes(value); + } + switch (type) { + case 'array': + return Array.isArray(value); + case 'object': + return value && typeof value === 'object' && !Array.isArray(value); + case 'undefined': + case 'boolean': + case 'number': + return typeof value === type && extension; + case 'string': + return typeof value === type && value.length && extension; + case 'alphanumeric': + case 'uuid': + case 'semver': + case 'email': + case 'url': + case 'color': + return typeof value === 'string' && test(value, patterns[type]) && extension; + case 'file': + return typeof value === 'string' && value && (await fs.isFile(value)) && extension; + } + return false; +}; diff --git a/api/fs.mjs b/api/fs.mjs new file mode 100644 index 0000000..ef05d90 --- /dev/null +++ b/api/fs.mjs @@ -0,0 +1,48 @@ +/* + * notion-enhancer: api + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +'use strict'; + +/** + * environment-specific filesystem reading + * @module notion-enhancer/api/fs + */ + +import * as fs from '../env/fs.mjs'; + +/** + * transform a path relative to the enhancer root directory into an absolute path + * @type {function} + * @param {string} path - a url or within-the-enhancer filepath + * @returns {string} an absolute filepath + */ +export const localPath = fs.localPath; + +/** + * fetch and parse a json file's contents + * @type {function} + * @param {string} path - a url or within-the-enhancer filepath + * @param {object} [opts] - the second argument of a fetch() request + * @returns {object} the json value of the requested file as a js object + */ +export const getJSON = fs.getJSON; + +/** + * fetch a text file's contents + * @type {function} + * @param {string} path - a url or within-the-enhancer filepath + * @param {object} [opts] - the second argument of a fetch() request + * @returns {string} the text content of the requested file + */ +export const getText = fs.getText; + +/** + * check if a file exists + * @type {function} + * @param {string} path - a url or within-the-enhancer filepath + * @returns {boolean} whether or not the file exists + */ +export const isFile = fs.isFile; diff --git a/api/registry-validation.mjs b/api/registry-validation.mjs new file mode 100644 index 0000000..afe7819 --- /dev/null +++ b/api/registry-validation.mjs @@ -0,0 +1,248 @@ +/* + * notion-enhancer: api + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +'use strict'; + +import { fmt, registry } from './_.mjs'; + +const check = async ( + mod, + key, + value, + types, + { + extension = '', + error = `invalid ${key} (${extension ? `${extension} ` : ''}${types}): ${JSON.stringify( + value + )}`, + optional = false, + } = {} + ) => { + let test; + for (const type of Array.isArray(types) ? [types] : types.split('|')) { + if (type === 'file') { + test = + value && !value.startsWith('http') + ? await fmt.is(`repo/${mod._dir}/${value}`, type, { extension }) + : false; + } else test = await fmt.is(value, type, { extension }); + if (test) break; + } + if (!test) { + if (optional && (await fmt.is(value, 'undefined'))) return true; + if (error) registry._errors.push({ source: mod._dir, message: error }); + return false; + } + return true; + }, + validateEnvironments = (mod) => { + return check(mod, 'environments', mod.environments, 'array', { optional: true }).then( + (passed) => { + if (!passed) return false; + if (!mod.environments) { + mod.environments = registry.supportedEnvs; + return true; + } + return mod.environments.map((tag) => + check(mod, 'environments.env', tag, registry.supportedEnvs) + ); + } + ); + }, + validateTags = (mod) => { + return check(mod, 'tags', mod.tags, 'array').then((passed) => { + if (!passed) return false; + const containsCategory = mod.tags.filter((tag) => + ['core', 'extension', 'theme'].includes(tag) + ).length; + if (!containsCategory) { + registry._errors.push({ + source: mod._dir, + message: `invalid tags (must contain at least one of 'core', 'extension', or 'theme'): ${JSON.stringify( + mod.tags + )}`, + }); + return false; + } + if ( + (mod.tags.includes('theme') && + !(mod.tags.includes('light') || mod.tags.includes('dark'))) || + (mod.tags.includes('light') && mod.tags.includes('dark')) + ) { + registry._errors.push({ + source: mod._dir, + message: `invalid tags (themes must be either 'light' or 'dark', not neither or both): ${JSON.stringify( + mod.tags + )}`, + }); + return false; + } + return mod.tags.map((tag) => check(mod, 'tags.tag', tag, 'string')); + }); + }, + validateAuthors = (mod) => { + return check(mod, 'authors', mod.authors, 'array').then((passed) => { + if (!passed) return false; + return mod.authors.map((author) => [ + check(mod, 'authors.author.name', author.name, 'string'), + check(mod, 'authors.author.email', author.email, 'email'), + check(mod, 'authors.author.homepage', author.homepage, 'url'), + check(mod, 'authors.author.avatar', author.avatar, 'url'), + ]); + }); + }, + validateCSS = (mod) => { + return check(mod, 'css', mod.css, 'object').then((passed) => { + if (!passed) return false; + const tests = []; + for (let dest of ['frame', 'client', 'menu']) { + if (!mod.css[dest]) continue; + let test = check(mod, `css.${dest}`, mod.css[dest], 'array'); + test = test.then((passed) => { + if (!passed) return false; + return mod.css[dest].map((file) => + check(mod, `css.${dest}.file`, file, 'file', { extension: '.css' }) + ); + }); + tests.push(test); + } + return tests; + }); + }, + validateJS = (mod) => { + return check(mod, 'js', mod.js, 'object').then((passed) => { + if (!passed) return false; + const tests = []; + if (mod.js.client) { + let test = check(mod, 'js.client', mod.js.client, 'array'); + test = test.then((passed) => { + if (!passed) return false; + return mod.js.client.map((file) => + check(mod, 'js.client.file', file, 'file', { extension: '.mjs' }) + ); + }); + tests.push(test); + } + if (mod.js.electron) { + let test = check(mod, 'js.electron', mod.js.electron, 'array'); + test = test.then((passed) => { + if (!passed) return false; + return mod.js.electron.map((file) => + check(mod, 'js.electron.file', file, 'object').then((passed) => { + if (!passed) return false; + return [ + check(mod, 'js.electron.file.source', file.source, 'file', { + extension: '.mjs', + }), + // referencing the file within the electron app + // existence can't be validated, so only format is + check(mod, 'js.electron.file.target', file.target, 'string', { + extension: '.js', + }), + ]; + }) + ); + }); + tests.push(test); + } + return tests; + }); + }, + validateOptions = (mod) => { + return check(mod, 'options', mod.options, 'array').then((passed) => { + if (!passed) return false; + return mod.options.map((option) => + check(mod, 'options.option.type', option.type, registry.optionTypes).then((passed) => { + if (!passed) return false; + const tests = [ + check(mod, 'options.option.key', option.key, 'alphanumeric'), + check(mod, 'options.option.label', option.label, 'string'), + check(mod, 'options.option.tooltip', option.tooltip, 'string', { + optional: true, + }), + check(mod, 'options.option.environments', option.environments, 'array', { + optional: true, + }).then((passed) => { + if (!passed) return false; + if (!option.environments) { + option.environments = registry.supportedEnvs; + return true; + } + return option.environments.map((environment) => + check( + mod, + 'options.option.environments.env', + environment, + registry.supportedEnvs + ) + ); + }), + ]; + switch (option.type) { + case 'toggle': + tests.push(check(mod, 'options.option.value', option.value, 'boolean')); + break; + case 'select': + tests.push( + check(mod, 'options.option.values', option.values, 'array').then((passed) => { + if (!passed) return false; + return option.values.map((value) => + check(mod, 'options.option.values.value', value, 'string') + ); + }) + ); + break; + case 'text': + case 'hotkey': + tests.push(check(mod, 'options.option.value', option.value, 'string')); + break; + case 'number': + case 'color': + tests.push(check(mod, 'options.option.value', option.value, option.type)); + break; + case 'file': + tests.push( + check(mod, 'options.option.extensions', option.extensions, 'array').then( + (passed) => { + if (!passed) return false; + return option.extensions.map((value) => + check(mod, 'options.option.extensions.extension', value, 'string') + ); + } + ) + ); + } + return tests; + }) + ); + }); + }; + +/** + * internally used to validate mod.json files and provide helpful errors + * @private + * @param {object} mod - a mod's mod.json in object form + * @returns {boolean} whether or not the mod has passed validation + */ +export async function validate(mod) { + let conditions = [ + check(mod, 'name', mod.name, 'string'), + check(mod, 'id', mod.id, 'uuid'), + check(mod, 'version', mod.version, 'semver'), + validateEnvironments(mod), + check(mod, 'description', mod.description, 'string'), + check(mod, 'preview', mod.preview, 'file|url', { optional: true }), + validateTags(mod), + validateAuthors(mod), + validateCSS(mod), + validateJS(mod), + validateOptions(mod), + ]; + do { + conditions = await Promise.all(conditions.flat(Infinity)); + } while (conditions.some((condition) => Array.isArray(condition))); + return conditions.every((passed) => passed); +} diff --git a/api/registry.mjs b/api/registry.mjs new file mode 100644 index 0000000..54ee095 --- /dev/null +++ b/api/registry.mjs @@ -0,0 +1,164 @@ +/* + * notion-enhancer: api + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +'use strict'; + +/** + * interactions with the enhancer's repository of mods + * @module notion-enhancer/api/registry + */ + +import { env, fs, storage } from './_.mjs'; +import { validate } from './registry-validation.mjs'; + +export const _cache = [], + _errors = []; + +/** + * mod ids whitelisted as part of the enhancer's core, permanently enabled + * @constant + * @type {array} + */ +export const core = [ + 'a6621988-551d-495a-97d8-3c568bca2e9e', + '0f0bf8b6-eae6-4273-b307-8fc43f2ee082', + '36a2ffc9-27ff-480e-84a7-c7700a7d232d', +]; + +/** + * all environments/platforms currently supported by the enhancer + * @constant + * @type {array} + */ +export const supportedEnvs = ['linux', 'win32', 'darwin', 'extension']; + +/** + * all available configuration types + * @constant + * @type {array} + */ +export const optionTypes = ['toggle', 'select', 'text', 'number', 'color', 'file', 'hotkey']; + +/** + * the name of the active configuration profile + * @returns {string} + */ +export const profileName = async () => storage.get(['currentprofile'], 'default'); + +/** + * the root database for the current profile + * @returns {object} the get/set functions for the profile's storage + */ +export const profileDB = async () => storage.db(['profiles', await profileName()]); + +/** a notification displayed when the menu is opened for the first time */ +export const welcomeNotification = { + id: '84e2d49b-c3dc-44b4-a154-cf589676bfa0', + color: 'purple', + icon: 'message-circle', + message: 'Welcome! Come chat with us on Discord.', + link: 'https://discord.gg/sFWPXtA', + version: env.version, +}; + +/** + * list all available mods in the repo + * @param {function} filter - a function to filter out mods + * @returns {array} a validated list of mod.json objects + */ +export const list = async (filter = (mod) => true) => { + if (!_cache.length) { + for (const dir of await fs.getJSON('repo/registry.json')) { + try { + const mod = await fs.getJSON(`repo/${dir}/mod.json`); + mod._dir = dir; + if (await validate(mod)) _cache.push(mod); + } catch (e) { + console.log(e); + _errors.push({ source: dir, message: 'invalid mod.json' }); + } + } + } + const list = []; + for (const mod of _cache) if (await filter(mod)) list.push(mod); + return list; +}; + +/** + * list validation errors encountered when loading the repo + * @returns {array} error objects with an error message and a source directory + */ +export const errors = async () => { + if (!_errors.length) await list(); + return _errors; +}; + +/** + * get a single mod from the repo + * @param {string} id - the uuid of the mod + * @returns {object} the mod's mod.json + */ +export const get = async (id) => { + if (!_cache.length) await list(); + return _cache.find((mod) => mod.id === id); +}; + +/** + * checks if a mod is enabled: affected by the core whitelist, + * environment and menu configuration + * @param {string} id - the uuid of the mod + * @returns {boolean} whether or not the mod is enabled + */ +export const enabled = async (id) => { + const mod = await get(id); + if (!mod.environments.includes(env.name)) return false; + if (core.includes(id)) return true; + return (await profileDB()).get(['_mods', id], false); +}; + +/** + * get a default value of a mod's option according to its mod.json + * @param {string} id - the uuid of the mod + * @param {string} key - the key of the option + * @returns {string|number|boolean|undefined} the option's default value + */ +export const optionDefault = async (id, key) => { + const mod = await get(id), + opt = mod.options.find((opt) => opt.key === key); + if (!opt) return undefined; + switch (opt.type) { + case 'toggle': + case 'text': + case 'number': + case 'color': + case 'hotkey': + return opt.value; + case 'select': + return opt.values[0]; + case 'file': + return undefined; + } +}; + +/** + * access the storage partition of a mod in the current profile + * @param {string} id - the uuid of the mod + * @returns {object} an object with the wrapped get/set functions + */ +export const db = async (id) => { + const db = await profileDB(); + return storage.db( + [id], + async (path, fallback = undefined) => { + if (path.length === 2) { + // profiles -> profile -> mod -> option + fallback = (await optionDefault(id, path[1])) ?? fallback; + } + return db.get(path, fallback); + }, + db.set + ); +}; diff --git a/api/storage.mjs b/api/storage.mjs new file mode 100644 index 0000000..4a3e63b --- /dev/null +++ b/api/storage.mjs @@ -0,0 +1,67 @@ +/* + * notion-enhancer: api + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +'use strict'; + +/** + * environment-specific data persistence + * @module notion-enhancer/api/storage + */ + +import * as storage from '../env/storage.mjs'; + +/** + * get persisted data + * @type {function} + * @param {array} path - the path of keys to the value being fetched + * @param {*} [fallback] - a default value if the path is not matched + * @returns {Promise} value ?? fallback + */ +export const get = storage.get; + +/** + * persist data + * @type {function} + * @param {array} path - the path of keys to the value being set + * @param {*} value - the data to save + * @returns {Promise} resolves when data has been saved + */ +export const set = storage.set; + +/** + * create a wrapper for accessing a partition of the storage + * @type {function} + * @param {array} namespace - the path of keys to prefix all storage requests with + * @param {function} [get] - the storage get function to be wrapped + * @param {function} [set] - the storage set function to be wrapped + * @returns {object} an object with the wrapped get/set functions + */ +export const db = storage.db; + +/** + * add an event listener for changes in storage + * @type {function} + * @param {onStorageChangeCallback} callback - called whenever a change in + * storage is initiated from the current process + */ +export const addChangeListener = storage.addChangeListener; + +/** + * remove a listener added with storage.addChangeListener + * @type {function} + * @param {onStorageChangeCallback} callback + */ +export const removeChangeListener = storage.removeChangeListener; + +/** + * @callback onStorageChangeCallback + * @param {object} event + * @param {string} event.type - 'set' or 'reset' + * @param {string} event.namespace- the name of the store, e.g. a mod id + * @param {string} [event.key] - the key associated with the changed value + * @param {string} [event.new] - the new value being persisted to the store + * @param {string} [event.old] - the previous value associated with the key + */ diff --git a/api/web.mjs b/api/web.mjs new file mode 100644 index 0000000..d7ae5d1 --- /dev/null +++ b/api/web.mjs @@ -0,0 +1,238 @@ +/* + * notion-enhancer: api + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +'use strict'; + +/** + * helpers for manipulation of a webpage + * @module notion-enhancer/api/web + */ + +import { fs } from './_.mjs'; + +let _hotkeyEventListeners = [], + _documentObserver, + _documentObserverListeners = [], + _documentObserverEvents = []; + +import '../dep/jscolor.min.js'; +/** color picker with alpha channel using https://jscolor.com/ */ +export const jscolor = JSColor; + +/** + * wait until a page is loaded and ready for modification + * @param {array} [selectors=[]] - wait for the existence of elements that match these css selectors + * @returns {Promise} a promise that will resolve when the page is ready + */ +export const whenReady = (selectors = []) => { + return new Promise((res, rej) => { + function onLoad() { + let isReadyInt; + isReadyInt = setInterval(isReadyTest, 100); + function isReadyTest() { + if (selectors.every((selector) => document.querySelector(selector))) { + clearInterval(isReadyInt); + res(true); + } + } + isReadyTest(); + } + if (document.readyState !== 'complete') { + document.addEventListener('readystatechange', (event) => { + if (document.readyState === 'complete') onLoad(); + }); + } else onLoad(); + }); +}; + +/** + * parse the current location search params into a usable form + * @returns {map} a map of the url search params + */ +export const queryParams = () => new URLSearchParams(window.location.search); + +/** + * replace special html characters with escaped versions + * @param {string} str + * @returns {string} escaped string + */ +export const escape = (str) => + str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/'/g, ''') + .replace(/"/g, '"') + .replace(/\\/g, '\'); + +/** + * a tagged template processor for raw html: + * stringifies, minifies, and syntax highlights + * @example web.raw`

hello

` + * @returns {string} the processed html + */ +export const raw = (str, ...templates) => { + const html = str + .map( + (chunk) => + chunk + + (['string', 'number'].includes(typeof templates[0]) + ? templates.shift() + : escape(JSON.stringify(templates.shift(), null, 2) ?? '')) + ) + .join(''); + return html.includes(' line.trim()) + .filter((line) => line.length) + .join(' '); +}; + +/** + * create a single html element inc. attributes and children from a string + * @example web.html`

hello

` + * @returns {Element} the constructed html element + */ +export const html = (str, ...templates) => { + const $fragment = document.createRange().createContextualFragment(raw(str, ...templates)); + return $fragment.children.length === 1 ? $fragment.children[0] : $fragment.children; +}; + +/** + * appends a list of html elements to a parent + * @param $container - the parent element + * @param $elems - the elements to be appended + * @returns {Element} the updated $container + */ +export const render = ($container, ...$elems) => { + $elems = $elems + .map(($elem) => ($elem instanceof HTMLCollection ? [...$elem] : $elem)) + .flat(Infinity) + .filter(($elem) => $elem); + $container.append(...$elems); + return $container; +}; + +/** + * removes all children from an element without deleting them/their behaviours + * @param $container - the parent element + * @returns {Element} the updated $container + */ +export const empty = ($container) => { + while ($container.firstChild && $container.removeChild($container.firstChild)); + return $container; +}; + +/** + * loads/applies a css stylesheet to the page + * @param {string} path - a url or within-the-enhancer filepath + */ +export const loadStylesheet = (path) => { + render( + document.head, + html`` + ); + return true; +}; + +document.addEventListener('keyup', (event) => { + if (document.activeElement.nodeName === 'INPUT') return; + for (const hotkey of _hotkeyEventListeners) { + const pressed = hotkey.keys.every((key) => { + key = key.toLowerCase(); + const modifiers = { + metaKey: ['meta', 'os', 'win', 'cmd', 'command'], + ctrlKey: ['ctrl', 'control'], + shiftKey: ['shift'], + altKey: ['alt'], + }; + for (const modifier in modifiers) { + const pressed = modifiers[modifier].includes(key) && event[modifier]; + if (pressed) return true; + } + if (key === event.key.toLowerCase()) return true; + }); + if (pressed) hotkey.callback(event); + } +}); + +/** + * register a hotkey listener to the page + * @param {array} keys - the combination of keys that will trigger the hotkey. + * key codes can be tested at http://keycode.info/ and are case-insensitive. + * available modifiers are 'alt', 'ctrl', 'meta', and 'shift'. + * @param {function} callback - called whenever the keys are pressed + */ +export const addHotkeyListener = (keys, callback) => { + if (typeof keys === 'string') keys = keys.split('+'); + _hotkeyEventListeners.push({ keys, callback }); +}; +/** + * remove a listener added with web.addHotkeyListener + * @param {function} callback + */ +export const removeHotkeyListener = (callback) => { + _hotkeyEventListeners = _hotkeyEventListeners.filter( + (listener) => listener.callback !== callback + ); +}; + +/** + * add a listener to watch for changes to the dom + * @param {onDocumentObservedCallback} callback + * @param {array} [selectors] + */ +export const addDocumentObserver = (callback, selectors = []) => { + if (!_documentObserver) { + const handle = (queue) => { + while (queue.length) { + const event = queue.shift(); + for (const listener of _documentObserverListeners) { + if ( + !listener.selectors.length || + listener.selectors.some( + (selector) => + event.target.matches(selector) || event.target.matches(`${selector} *`) + ) + ) { + listener.callback(event); + } + } + } + }; + _documentObserver = new MutationObserver((list, observer) => { + if (!_documentObserverEvents.length) + requestIdleCallback(() => handle(_documentObserverEvents)); + _documentObserverEvents.push(...list); + }); + _documentObserver.observe(document.body, { + childList: true, + subtree: true, + attributes: true, + }); + } + _documentObserverListeners.push({ callback, selectors }); +}; + +/** + * remove a listener added with web.addDocumentObserver + * @param {onDocumentObservedCallback} callback + */ +export const removeDocumentObserver = (callback) => { + _documentObserverListeners = _documentObserverListeners.filter( + (listener) => listener.callback !== callback + ); +}; + +/** + * @callback onDocumentObservedCallback + * @param {MutationRecord} event - the observed dom mutation event + */ From cce4788aba851ff780540dfd9dadf4d1892dce8f Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Sun, 3 Oct 2021 22:59:48 +1100 Subject: [PATCH 03/52] workflow: update parent repo --- api/.github/workflows/update-parents.yml | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 api/.github/workflows/update-parents.yml diff --git a/api/.github/workflows/update-parents.yml b/api/.github/workflows/update-parents.yml new file mode 100644 index 0000000..dcd1dff --- /dev/null +++ b/api/.github/workflows/update-parents.yml @@ -0,0 +1,26 @@ +name: 'update parent repositories' + +on: + push: + branches: + - dev + +jobs: + sync: + name: 'update extension' + runs-on: ubuntu-latest + steps: + - name: checkout repo + uses: actions/checkout@v2 + with: + token: ${{ secrets.CI_TOKEN }} + submodules: true + repository: notion-enhancer/extension + - name: pull updates + run: | + git pull --recurse-submodules + git submodule update --remote --recursive + - name: commit changes + uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: 'update submodule: ${{ github.event.repository.name }}' From a86b363ea212413a4b3ae72ab438dde785db3854 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Sun, 3 Oct 2021 23:07:42 +1100 Subject: [PATCH 04/52] workflow: add loop --- api/.github/workflows/update-parents.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/api/.github/workflows/update-parents.yml b/api/.github/workflows/update-parents.yml index dcd1dff..dc57648 100644 --- a/api/.github/workflows/update-parents.yml +++ b/api/.github/workflows/update-parents.yml @@ -7,15 +7,18 @@ on: jobs: sync: - name: 'update extension' + name: update parent runs-on: ubuntu-latest + strategy: + matrix: + repo: ['notion-enhancer/extension'] steps: - name: checkout repo uses: actions/checkout@v2 with: token: ${{ secrets.CI_TOKEN }} submodules: true - repository: notion-enhancer/extension + repository: ${{ matrix.repo }} - name: pull updates run: | git pull --recurse-submodules From 9bb779149b35d2b7448b2395e9e93ae439af1fe6 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Mon, 4 Oct 2021 18:32:03 +1100 Subject: [PATCH 05/52] add unofficial notion api wrapper (capable of getting, searching, creating and editing all content) --- api/_.mjs | 2 ++ api/components/_.mjs | 9 +++++---- api/components/panel.mjs | 9 +++++---- api/fmt.mjs | 12 ++++++++++++ api/web.mjs | 2 +- 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/api/_.mjs b/api/_.mjs index 334d08c..8c879ca 100644 --- a/api/_.mjs +++ b/api/_.mjs @@ -15,6 +15,8 @@ export * as fs from './fs.mjs'; /** environment-specific data persistence */ export * as storage from './storage.mjs'; +/** a basic wrapper around notion's unofficial api */ +export * as notion from './notion.mjs'; /** helpers for formatting, validating and parsing values */ export * as fmt from './fmt.mjs'; /** interactions with the enhancer's repository of mods */ diff --git a/api/components/_.mjs b/api/components/_.mjs index e1d98e6..a12c864 100644 --- a/api/components/_.mjs +++ b/api/components/_.mjs @@ -28,9 +28,10 @@ export { feather } from './feather.mjs'; /** * adds a view to the enhancer's side panel - * @param {string} param0.id - a uuid, used to restore it on reload if it was last open - * @param {string} param0.icon - an svg string - * @param {string} param0.title - the name of the view - * @param {Element} param0.$content - an element containing the content of the view + * @param {object} panel- information used to construct and render the panel + * @param {string} panel.id - a uuid, used to restore the last open view on reload + * @param {string} panel.icon - an svg string + * @param {string} panel.title - the name of the view + * @param {Element} panel.$content - an element containing the content of the view */ export { addPanelView } from './panel.mjs'; diff --git a/api/components/panel.mjs b/api/components/panel.mjs index f4c743b..6b9b745 100644 --- a/api/components/panel.mjs +++ b/api/components/panel.mjs @@ -222,10 +222,11 @@ web.loadStylesheet('api/components/panel.css'); /** * adds a view to the enhancer's side panel - * @param {string} param0.id - a uuid, used to restore the last open view on reload - * @param {string} param0.icon - an svg string - * @param {string} param0.title - the name of the view - * @param {Element} param0.$content - an element containing the content of the view + * @param {object} panel- information used to construct and render the panel + * @param {string} panel.id - a uuid, used to restore the last open view on reload + * @param {string} panel.icon - an svg string + * @param {string} panel.title - the name of the view + * @param {Element} panel.$content - an element containing the content of the view */ export const addPanelView = async ({ id, icon, title, $content }) => { const view = { diff --git a/api/fmt.mjs b/api/fmt.mjs index f905c01..1d6204a 100644 --- a/api/fmt.mjs +++ b/api/fmt.mjs @@ -81,6 +81,18 @@ export const slugger = (heading, slugs = new Set()) => { return slug; }; +/** + * generate a reasonably random uuidv4 string. uses crypto implementation if available + * (from https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid) + * @returns {string} a uuidv4 + */ +export const uuidv4 = () => + crypto?.randomUUID + ? crypto.randomUUID() + : ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => + (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16) + ); + const patterns = { alphanumeric: /^[\w\.-]+$/, uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, diff --git a/api/web.mjs b/api/web.mjs index d7ae5d1..656c447 100644 --- a/api/web.mjs +++ b/api/web.mjs @@ -24,7 +24,7 @@ export const jscolor = JSColor; /** * wait until a page is loaded and ready for modification - * @param {array} [selectors=[]] - wait for the existence of elements that match these css selectors + * @param {array} [selectors] - wait for the existence of elements that match these css selectors * @returns {Promise} a promise that will resolve when the page is ready */ export const whenReady = (selectors = []) => { From fd2b7292283cf39bcc93d1579f7d7cd623e3e1a4 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Mon, 4 Oct 2021 18:35:22 +1100 Subject: [PATCH 06/52] fix typos, rename notion.add to notion.create --- api/components/_.mjs | 2 +- api/components/panel.mjs | 2 +- api/fmt.mjs | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/api/components/_.mjs b/api/components/_.mjs index a12c864..33cccb5 100644 --- a/api/components/_.mjs +++ b/api/components/_.mjs @@ -28,7 +28,7 @@ export { feather } from './feather.mjs'; /** * adds a view to the enhancer's side panel - * @param {object} panel- information used to construct and render the panel + * @param {object} panel - information used to construct and render the panel * @param {string} panel.id - a uuid, used to restore the last open view on reload * @param {string} panel.icon - an svg string * @param {string} panel.title - the name of the view diff --git a/api/components/panel.mjs b/api/components/panel.mjs index 6b9b745..e90c5ab 100644 --- a/api/components/panel.mjs +++ b/api/components/panel.mjs @@ -222,7 +222,7 @@ web.loadStylesheet('api/components/panel.css'); /** * adds a view to the enhancer's side panel - * @param {object} panel- information used to construct and render the panel + * @param {object} panel - information used to construct and render the panel * @param {string} panel.id - a uuid, used to restore the last open view on reload * @param {string} panel.icon - an svg string * @param {string} panel.title - the name of the view diff --git a/api/fmt.mjs b/api/fmt.mjs index 1d6204a..8d8b193 100644 --- a/api/fmt.mjs +++ b/api/fmt.mjs @@ -86,12 +86,12 @@ export const slugger = (heading, slugs = new Set()) => { * (from https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid) * @returns {string} a uuidv4 */ -export const uuidv4 = () => - crypto?.randomUUID - ? crypto.randomUUID() - : ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => - (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16) - ); +export const uuidv4 = () => { + if (crypto?.randomUUID) return crypto.randomUUID(); + return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => + (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16) + ); +}; const patterns = { alphanumeric: /^[\w\.-]+$/, From ae400099eb96d2df5d91041594be806cba0e9606 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Mon, 4 Oct 2021 18:39:56 +1100 Subject: [PATCH 07/52] attempt #2: notion unofficial api wrapper (capable of getting, searching, creating and editing all content) --- api/notion.mjs | 353 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 353 insertions(+) create mode 100644 api/notion.mjs diff --git a/api/notion.mjs b/api/notion.mjs new file mode 100644 index 0000000..7cc49e5 --- /dev/null +++ b/api/notion.mjs @@ -0,0 +1,353 @@ +/* + * notion-enhancer: api + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +'use strict'; + +/** + * a basic wrapper around notion's content apis + * @module notion-enhancer/api/notion + */ + +import { web, fs, fmt } from './_.mjs'; + +const standardiseUUID = (uuid) => { + if (uuid?.length === 32 && !uuid.includes('-')) { + uuid = uuid.replace( + /([\d\w]{8})([\d\w]{4})([\d\w]{4})([\d\w]{4})([\d\w]{12})/, + '$1-$2-$3-$4-$5' + ); + } + return uuid; +}; + +/** + * get the id of the current user (requires user to be signed in) + * @returns {string} uuidv4 user id + */ +export const getUserID = () => + JSON.parse(localStorage['LRU:KeyValueStore2:current-user-id'] || {}).value; + +/** + * get the id of the currently open page + * @returns {string} uuidv4 page id + */ +export const getPageID = () => + standardiseUUID( + web.queryParams().get('p') || location.pathname.split(/(-|\/)/g).reverse()[0] + ); + +let _spaceID; +/** + * get the id of the currently open workspace (requires user to be signed in) + * @returns {string} uuidv4 space id + */ +export const getSpaceID = async () => { + if (!_spaceID) _spaceID = (await get(getPageID())).space_id; + return _spaceID; +}; + +/** + * unofficial content api: get a block by id + * (requires user to be signed in or content to be public). + * why not use the official api? + * 1. cors blocking prevents use on the client + * 2. the majority of blocks are still 'unsupported' + * @param {string} id - uuidv4 record id + * @param {string} [table] - record type (default: 'block'). + * may also be 'collection', 'collection_view', 'space', 'notion_user', 'discussion', or 'comment' + * @returns {Promise} record data. type definitions can be found here: + * https://github.com/NotionX/react-notion-x/tree/master/packages/notion-types/src + */ +export const get = async (id, table = 'block') => { + id = standardiseUUID(id); + const json = await fs.getJSON('https://www.notion.so/api/v3/getRecordValues', { + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ requests: [{ table, id }] }), + method: 'POST', + }); + return json.results[0].value; +}; + +/** + * unofficial content api: search all blocks in a space + * (requires user to be signed in or content to be public). + * why not use the official api? + * 1. cors blocking prevents use on the client + * 2. the majority of blocks are still 'unsupported' + * @param {string} [query] - query to search blocks in the space for + * @param {number} [limit] - the max number of results to return (default: 20) + * @param {string} [spaceID] - uuidv4 workspace id + * @returns {object} the number of total results, the list of matches, and related record values. + * type definitions can be found here: https://github.com/NotionX/react-notion-x/blob/master/packages/notion-types/src/api.ts + */ +export const search = async (query = '', limit = 20, spaceID = getSpaceID()) => { + spaceID = standardiseUUID(await spaceID); + const json = await fs.getJSON('https://www.notion.so/api/v3/search', { + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + type: 'BlocksInSpace', + query, + spaceId: spaceID, + limit, + filters: { + isDeletedOnly: false, + excludeTemplates: false, + isNavigableOnly: false, + requireEditPermissions: false, + ancestors: [], + createdBy: [], + editedBy: [], + lastEditedTime: {}, + createdTime: {}, + }, + sort: 'Relevance', + source: 'quick_find', + }), + method: 'POST', + }); + return json; +}; + +/** + * unofficial content api: update a property/the content of an existing record + * (requires user to be signed in or content to be public). + * TEST THIS THOROUGHLY. misuse can corrupt a record, leading the notion client + * to be unable to parse and render content properly and throw errors. + * why not use the official api? + * 1. cors blocking prevents use on the client + * 2. the majority of blocks are still 'unsupported' + * @param {object} pointer - the record being updated + * @param {object} recordValue - the new raw data values to set to the record. + * for examples, use notion.get to fetch an existing block record. + * to use this to update content, set pointer.path to ['properties', 'title] + * and recordValue to an array of rich text segments. a segment is an array + * where the first value is the displayed text and the second value + * is an array of decorations. a decoration is an array where the first value + * is a modifier and the second value specifies it. e.g. + * [ + * ['bold text', [['b']]], + * [' '], + * ['an italicised link', [['i'], ['a', 'https://github.com']]], + * [' '], + * ['highlighted text', [['h', 'pink_background']]], + * ] + * more examples can be creating a block with the desired content/formatting, + * then find the value of blockRecord.properties.title using notion.get. + * type definitions can be found here: https://github.com/NotionX/react-notion-x/blob/master/packages/notion-types/src/core.ts + * @param {string} pointer.recordID - uuidv4 record id + * @param {string} [pointer.recordTable] - record type (default: 'block'). + * may also be 'collection', 'collection_view', 'space', 'notion_user', 'discussion', or 'comment' + * @param {string} [pointer.property] - the record property to update. + * for record content, it will be the default: 'title'. + * for page properties, it will be the property id (the key used in pageRecord.properties). + * other possible values are unknown/untested + * @param {string} [pointer.spaceID] - uuidv4 workspace id + * @param {string} [pointer.path] - the path to the key to be set within the record + * (default: [], the root of the record's values) + * @returns {boolean|object} true if success, else an error object + */ +export const set = async ( + { recordID, recordTable = 'block', spaceID = getSpaceID(), path = [] }, + recordValue = {} +) => { + spaceID = standardiseUUID(await spaceID); + recordID = standardiseUUID(recordID); + const json = await fs.getJSON('https://www.notion.so/api/v3/saveTransactions', { + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + requestId: fmt.uuidv4(), + transactions: [ + { + id: fmt.uuidv4(), + spaceId: spaceID, + operations: [ + { + pointer: { + table: recordTable, + id: recordID, + spaceId: spaceID, + }, + path, + command: path.length ? 'set' : 'update', + args: recordValue, + }, + ], + }, + ], + }), + method: 'POST', + }); + return json.errorId ? json : true; +}; + +/** + * unofficial content api: create and add a new block to a page + * (requires user to be signed in or content to be public). + * TEST THIS THOROUGHLY. misuse can corrupt a record, leading the notion client + * to be unable to parse and render content properly and throw errors. + * why not use the official api? + * 1. cors blocking prevents use on the client + * 2. the majority of blocks are still 'unsupported' + * @param {object} insert - the new record. + * @param {object} pointer - where to insert the new block + * for examples, use notion.get to fetch an existing block record. + * type definitions can be found here: https://github.com/NotionX/react-notion-x/blob/master/packages/notion-types/src/block.ts + * may also be 'collection', 'collection_view', 'space', 'notion_user', 'discussion', or 'comment' + * @param {object} [insert.recordValue] - the new raw data values to set to the record. + * @param {object} [insert.recordTable] - record type (default: 'block'). + * may also be 'collection', 'collection_view', 'space', 'notion_user', 'discussion', or 'comment' + * @param {string} [pointer.prepend] - insert before pointer.siblingID. if false, will be appended after + * @param {string} [pointer.siblingID] - uuidv4 sibling id. if unset, the record will be + * inserted at the end of the page start (or the start if pointer.prepend is true) + * @param {string} [pointer.parentID] - uuidv4 parent id + * @param {string} [pointer.parentTable] - parent record type (default: 'block'). + * @param {string} [pointer.spaceID] - uuidv4 space id + * @param {string} [pointer.userID] - uuidv4 user id + * instead of the end + * @returns {string|object} error object or uuidv4 of the new record + */ +export const create = async ( + { recordValue = {}, recordTable = 'block' } = {}, + { + prepend = false, + siblingID = undefined, + parentID = getPageID(), + parentTable = 'block', + spaceID = getSpaceID(), + userID = getUserID(), + } = {} +) => { + spaceID = standardiseUUID(await spaceID); + parentID = standardiseUUID(parentID); + siblingID = standardiseUUID(siblingID); + const recordID = standardiseUUID(recordValue?.id ?? fmt.uuidv4()), + path = [], + args = { + type: 'text', + id: recordID, + version: 0, + created_time: new Date().getTime(), + last_edited_time: new Date().getTime(), + parent_id: parentID, + parent_table: parentTable, + alive: true, + created_by_table: 'notion_user', + created_by_id: userID, + last_edited_by_table: 'notion_user', + last_edited_by_id: userID, + space_id: spaceID, + permissions: [{ type: 'user_permission', role: 'editor', user_id: userID }], + }; + if (parentTable === 'space') { + parentID = spaceID; + args.parent_id = spaceID; + path.push('pages'); + args.type = 'page'; + } else if (parentTable === 'collection_view') { + path.push('page_sort'); + args.type = 'page'; + } else { + path.push('content'); + } + console.log( + { + pointer: { + table: parentTable, + id: parentID, + spaceId: spaceID, + }, + path, + command: prepend ? 'listBefore' : 'listAfter', + args: { + ...(siblingID ? { after: siblingID } : {}), + id: recordID, + }, + }, + { + pointer: { + table: recordTable, + id: recordID, + spaceId: spaceID, + }, + path: [], + command: 'set', + args: { + ...args, + ...recordValue, + }, + } + ); + const json = await fs.getJSON('https://www.notion.so/api/v3/saveTransactions', { + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + requestId: fmt.uuidv4(), + transactions: [ + { + id: fmt.uuidv4(), + spaceId: spaceID, + operations: [ + { + pointer: { + table: parentTable, + id: parentID, + spaceId: spaceID, + }, + path, + command: prepend ? 'listBefore' : 'listAfter', + args: { + ...(siblingID ? { after: siblingID } : {}), + id: recordID, + }, + }, + { + pointer: { + table: recordTable, + id: recordID, + spaceId: spaceID, + }, + path: [], + command: 'set', + args: { + ...args, + ...recordValue, + }, + }, + ], + }, + ], + }), + method: 'POST', + }); + return json.errorId ? json : recordID; +}; + +/** + * redirect through notion to a resource's signed aws url for display outside of notion + * (requires user to be signed in or content to be public) + * @param src source url for file + * @param {string} recordID uuidv4 record/block/file id + * @param {string} [recordTable] record type (default: 'block'). + * may also be 'collection', 'collection_view', 'space', 'notion_user', 'discussion', or 'comment' + * @returns {string} url signed if necessary, else string as-is + */ +export const sign = (src, recordID, recordTable = 'block') => { + if (src.startsWith('/')) src = `https://notion.so${src}`; + if (src.includes('secure.notion-static.com')) { + src = new URL(src); + src = `https://www.notion.so/signed/${encodeURIComponent( + src.origin + src.pathname + )}?table=${recordTable}&id=${recordID}`; + } + return src; +}; From a5ecd020689afd3f60e34449139557dadd9a17e2 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Tue, 5 Oct 2021 00:04:43 +1100 Subject: [PATCH 08/52] improved tooltip styling --- api/components/tooltip.css | 21 ++++++++++++--------- api/components/tooltip.mjs | 4 ++-- api/registry.mjs | 1 + 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/api/components/tooltip.css b/api/components/tooltip.css index b919963..cb52358 100644 --- a/api/components/tooltip.css +++ b/api/components/tooltip.css @@ -5,21 +5,24 @@ */ #enhancer--tooltip { - position: absolute; background: var(--theme--ui_tooltip); - font-size: 11.5px; - padding: 0.15rem 0.4rem; - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important; border-radius: 3px; - max-width: 20rem; + box-shadow: var(--theme--ui_shadow) 0px 1px 4px; + color: var(--theme--ui_tooltip-description); display: none; + font-size: 12px; + font-weight: 500; + line-height: 1.4; + max-width: 20rem; + overflow: hidden; + padding: 4px 8px; + position: absolute; } #enhancer--tooltip p { margin: 0.25rem 0; } -#enhancer--tooltip p:first-child { +#enhancer--tooltip b, +#enhancer--tooltip strong { + font-weight: 500; color: var(--theme--ui_tooltip-title); } -#enhancer--tooltip p:not(:first-child) { - color: var(--theme--ui_tooltip-description); -} diff --git a/api/components/tooltip.mjs b/api/components/tooltip.mjs index e52d3f0..9dffd4b 100644 --- a/api/components/tooltip.mjs +++ b/api/components/tooltip.mjs @@ -23,9 +23,9 @@ web.loadStylesheet('api/components/tooltip.css'); */ export const tooltip = ($ref, text) => { web.render(document.body, _$tooltip); - text = fmt.md.render(text); + text = web.html`${fmt.md.render(text)}`; $ref.addEventListener('mouseover', (event) => { - _$tooltip.innerHTML = text; + web.render(web.empty(_$tooltip), text); _$tooltip.style.display = 'block'; }); $ref.addEventListener('mousemove', (event) => { diff --git a/api/registry.mjs b/api/registry.mjs index 54ee095..462f3b3 100644 --- a/api/registry.mjs +++ b/api/registry.mjs @@ -153,6 +153,7 @@ export const db = async (id) => { return storage.db( [id], async (path, fallback = undefined) => { + if (typeof path === 'string') path = [path]; if (path.length === 2) { // profiles -> profile -> mod -> option fallback = (await optionDefault(id, path[1])) ?? fallback; From 1f88c043dc7614a6c6f57fd4b154f6e7f873ea49 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Thu, 7 Oct 2021 23:07:09 +1100 Subject: [PATCH 09/52] make author email optional --- api/registry-validation.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/registry-validation.mjs b/api/registry-validation.mjs index afe7819..61e07b9 100644 --- a/api/registry-validation.mjs +++ b/api/registry-validation.mjs @@ -88,7 +88,7 @@ const check = async ( if (!passed) return false; return mod.authors.map((author) => [ check(mod, 'authors.author.name', author.name, 'string'), - check(mod, 'authors.author.email', author.email, 'email'), + check(mod, 'authors.author.email', author.email, 'email', { optional: true }), check(mod, 'authors.author.homepage', author.homepage, 'url'), check(mod, 'authors.author.avatar', author.avatar, 'url'), ]); From e78d6c29656468463f482301091b15e612719e37 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Fri, 8 Oct 2021 15:31:42 +1100 Subject: [PATCH 10/52] rename bg_popup to bg_card for side panel --- api/components/panel.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/components/panel.css b/api/components/panel.css index 1294867..ecd2715 100644 --- a/api/components/panel.css +++ b/api/components/panel.css @@ -135,7 +135,7 @@ right: 14px; border-radius: 3px; padding: 8px 0; - background: var(--theme--bg_popup); + background: var(--theme--bg_card); box-shadow: var(--theme--ui_shadow, rgba(15, 15, 15, 0.05)) 0px 0px 0px 1px, var(--theme--ui_shadow, rgba(15, 15, 15, 0.1)) 0px 3px 6px, var(--theme--ui_shadow, rgba(15, 15, 15, 0.2)) 0px 9px 24px !important; @@ -150,7 +150,7 @@ cursor: pointer; overflow: hidden; position: relative; - --scoped--bg: var(--theme--bg_popup); + --scoped--bg: var(--theme--bg_card); background: var(--scoped--bg); } #enhancer--panel-header:hover, From 579050e349a8855700e7ebc210cc5c748361eb36 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Fri, 8 Oct 2021 15:45:48 +1100 Subject: [PATCH 11/52] include commit msg in submodule update --- api/.github/workflows/update-parents.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/.github/workflows/update-parents.yml b/api/.github/workflows/update-parents.yml index dc57648..974cc03 100644 --- a/api/.github/workflows/update-parents.yml +++ b/api/.github/workflows/update-parents.yml @@ -26,4 +26,4 @@ jobs: - name: commit changes uses: stefanzweifel/git-auto-commit-action@v4 with: - commit_message: 'update submodule: ${{ github.event.repository.name }}' + commit_message: '[${{ github.event.repository.name }}] ${{ github.event.head_commit.message }}' From 9b4b21c2977e8f1b40c377ba8efb818486080ba5 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Sun, 10 Oct 2021 00:00:59 +1100 Subject: [PATCH 12/52] prevent double-reading of registry, allow menu/frame scripts --- api/notion.mjs | 28 --- api/registry-validation.mjs | 390 +++++++++++++++++------------------- api/registry.mjs | 42 ++-- 3 files changed, 206 insertions(+), 254 deletions(-) diff --git a/api/notion.mjs b/api/notion.mjs index 7cc49e5..29c8b4c 100644 --- a/api/notion.mjs +++ b/api/notion.mjs @@ -258,34 +258,6 @@ export const create = async ( } else { path.push('content'); } - console.log( - { - pointer: { - table: parentTable, - id: parentID, - spaceId: spaceID, - }, - path, - command: prepend ? 'listBefore' : 'listAfter', - args: { - ...(siblingID ? { after: siblingID } : {}), - id: recordID, - }, - }, - { - pointer: { - table: recordTable, - id: recordID, - spaceId: spaceID, - }, - path: [], - command: 'set', - args: { - ...args, - ...recordValue, - }, - } - ); const json = await fs.getJSON('https://www.notion.so/api/v3/saveTransactions', { headers: { 'Content-Type': 'application/json', diff --git a/api/registry-validation.mjs b/api/registry-validation.mjs index 61e07b9..5fb9165 100644 --- a/api/registry-validation.mjs +++ b/api/registry-validation.mjs @@ -9,216 +9,192 @@ import { fmt, registry } from './_.mjs'; const check = async ( - mod, - key, - value, - types, - { - extension = '', - error = `invalid ${key} (${extension ? `${extension} ` : ''}${types}): ${JSON.stringify( - value - )}`, - optional = false, - } = {} - ) => { - let test; - for (const type of Array.isArray(types) ? [types] : types.split('|')) { - if (type === 'file') { - test = - value && !value.startsWith('http') - ? await fmt.is(`repo/${mod._dir}/${value}`, type, { extension }) - : false; - } else test = await fmt.is(value, type, { extension }); - if (test) break; - } - if (!test) { - if (optional && (await fmt.is(value, 'undefined'))) return true; - if (error) registry._errors.push({ source: mod._dir, message: error }); - return false; - } - return true; - }, - validateEnvironments = (mod) => { - return check(mod, 'environments', mod.environments, 'array', { optional: true }).then( - (passed) => { - if (!passed) return false; - if (!mod.environments) { - mod.environments = registry.supportedEnvs; - return true; - } - return mod.environments.map((tag) => - check(mod, 'environments.env', tag, registry.supportedEnvs) - ); - } + mod, + key, + value, + types, + { + extension = '', + error = `invalid ${key} (${extension ? `${extension} ` : ''}${types}): ${JSON.stringify( + value + )}`, + optional = false, + } = {} +) => { + let test; + for (const type of Array.isArray(types) ? [types] : types.split('|')) { + if (type === 'file') { + test = + value && !value.startsWith('http') + ? await fmt.is(`repo/${mod._dir}/${value}`, type, { extension }) + : false; + } else test = await fmt.is(value, type, { extension }); + if (test) break; + } + if (!test) { + if (optional && (await fmt.is(value, 'undefined'))) return true; + if (error) mod._err(error); + return false; + } + return true; +}; + +const validateEnvironments = async (mod) => { + mod.environments = mod.environments ?? registry.supportedEnvs; + const isArray = await check(mod, 'environments', mod.environments, 'array'); + if (!isArray) return false; + return mod.environments.map((tag) => + check(mod, 'environments.env', tag, registry.supportedEnvs) ); }, - validateTags = (mod) => { - return check(mod, 'tags', mod.tags, 'array').then((passed) => { - if (!passed) return false; - const containsCategory = mod.tags.filter((tag) => - ['core', 'extension', 'theme'].includes(tag) - ).length; - if (!containsCategory) { - registry._errors.push({ - source: mod._dir, - message: `invalid tags (must contain at least one of 'core', 'extension', or 'theme'): ${JSON.stringify( - mod.tags - )}`, - }); - return false; - } - if ( - (mod.tags.includes('theme') && - !(mod.tags.includes('light') || mod.tags.includes('dark'))) || - (mod.tags.includes('light') && mod.tags.includes('dark')) - ) { - registry._errors.push({ - source: mod._dir, - message: `invalid tags (themes must be either 'light' or 'dark', not neither or both): ${JSON.stringify( - mod.tags - )}`, - }); - return false; - } - return mod.tags.map((tag) => check(mod, 'tags.tag', tag, 'string')); - }); - }, - validateAuthors = (mod) => { - return check(mod, 'authors', mod.authors, 'array').then((passed) => { - if (!passed) return false; - return mod.authors.map((author) => [ - check(mod, 'authors.author.name', author.name, 'string'), - check(mod, 'authors.author.email', author.email, 'email', { optional: true }), - check(mod, 'authors.author.homepage', author.homepage, 'url'), - check(mod, 'authors.author.avatar', author.avatar, 'url'), - ]); - }); - }, - validateCSS = (mod) => { - return check(mod, 'css', mod.css, 'object').then((passed) => { - if (!passed) return false; - const tests = []; - for (let dest of ['frame', 'client', 'menu']) { - if (!mod.css[dest]) continue; - let test = check(mod, `css.${dest}`, mod.css[dest], 'array'); - test = test.then((passed) => { - if (!passed) return false; - return mod.css[dest].map((file) => - check(mod, `css.${dest}.file`, file, 'file', { extension: '.css' }) - ); - }); - tests.push(test); - } - return tests; - }); - }, - validateJS = (mod) => { - return check(mod, 'js', mod.js, 'object').then((passed) => { - if (!passed) return false; - const tests = []; - if (mod.js.client) { - let test = check(mod, 'js.client', mod.js.client, 'array'); - test = test.then((passed) => { - if (!passed) return false; - return mod.js.client.map((file) => - check(mod, 'js.client.file', file, 'file', { extension: '.mjs' }) - ); - }); - tests.push(test); - } - if (mod.js.electron) { - let test = check(mod, 'js.electron', mod.js.electron, 'array'); - test = test.then((passed) => { - if (!passed) return false; - return mod.js.electron.map((file) => - check(mod, 'js.electron.file', file, 'object').then((passed) => { - if (!passed) return false; - return [ - check(mod, 'js.electron.file.source', file.source, 'file', { - extension: '.mjs', - }), - // referencing the file within the electron app - // existence can't be validated, so only format is - check(mod, 'js.electron.file.target', file.target, 'string', { - extension: '.js', - }), - ]; - }) - ); - }); - tests.push(test); - } - return tests; - }); - }, - validateOptions = (mod) => { - return check(mod, 'options', mod.options, 'array').then((passed) => { - if (!passed) return false; - return mod.options.map((option) => - check(mod, 'options.option.type', option.type, registry.optionTypes).then((passed) => { - if (!passed) return false; - const tests = [ - check(mod, 'options.option.key', option.key, 'alphanumeric'), - check(mod, 'options.option.label', option.label, 'string'), - check(mod, 'options.option.tooltip', option.tooltip, 'string', { - optional: true, - }), - check(mod, 'options.option.environments', option.environments, 'array', { - optional: true, - }).then((passed) => { - if (!passed) return false; - if (!option.environments) { - option.environments = registry.supportedEnvs; - return true; - } - return option.environments.map((environment) => - check( - mod, - 'options.option.environments.env', - environment, - registry.supportedEnvs - ) - ); - }), - ]; - switch (option.type) { - case 'toggle': - tests.push(check(mod, 'options.option.value', option.value, 'boolean')); - break; - case 'select': - tests.push( - check(mod, 'options.option.values', option.values, 'array').then((passed) => { - if (!passed) return false; - return option.values.map((value) => - check(mod, 'options.option.values.value', value, 'string') - ); - }) - ); - break; - case 'text': - case 'hotkey': - tests.push(check(mod, 'options.option.value', option.value, 'string')); - break; - case 'number': - case 'color': - tests.push(check(mod, 'options.option.value', option.value, option.type)); - break; - case 'file': - tests.push( - check(mod, 'options.option.extensions', option.extensions, 'array').then( - (passed) => { - if (!passed) return false; - return option.extensions.map((value) => - check(mod, 'options.option.extensions.extension', value, 'string') - ); - } - ) - ); - } - return tests; - }) + validateTags = async (mod) => { + const isArray = await check(mod, 'tags', mod.tags, 'array'); + if (!isArray) return false; + const categoryTags = ['core', 'extension', 'theme'], + containsCategory = mod.tags.filter((tag) => categoryTags.includes(tag)).length; + if (!containsCategory) { + mod._err( + `invalid tags (must contain at least one of 'core', 'extension', or 'theme'): + ${JSON.stringify(mod.tags)}` ); - }); + return false; + } + const isTheme = mod.tags.includes('theme'), + hasThemeMode = mod.tags.includes('light') || mod.tags.includes('dark'), + isBothThemeModes = mod.tags.includes('light') && mod.tags.includes('dark'); + if (isTheme && (!hasThemeMode || isBothThemeModes)) { + mod._err( + `invalid tags (themes must be either 'light' or 'dark', not neither or both): + ${JSON.stringify(mod.tags)}` + ); + return false; + } + return mod.tags.map((tag) => check(mod, 'tags.tag', tag, 'string')); + }, + validateAuthors = async (mod) => { + const isArray = await check(mod, 'authors', mod.authors, 'array'); + if (!isArray) return false; + return mod.authors.map((author) => [ + check(mod, 'authors.author.name', author.name, 'string'), + check(mod, 'authors.author.email', author.email, 'email', { optional: true }), + check(mod, 'authors.author.homepage', author.homepage, 'url'), + check(mod, 'authors.author.avatar', author.avatar, 'url'), + ]); + }, + validateCSS = async (mod) => { + const isArray = await check(mod, 'css', mod.css, 'object'); + if (!isArray) return false; + const tests = []; + for (let dest of ['frame', 'client', 'menu']) { + if (!mod.css[dest]) continue; + let test = await check(mod, `css.${dest}`, mod.css[dest], 'array'); + if (test) { + test = mod.css[dest].map((file) => + check(mod, `css.${dest}.file`, file, 'file', { extension: '.css' }) + ); + } + tests.push(test); + } + return tests; + }, + validateJS = async (mod) => { + const isArray = await check(mod, 'js', mod.js, 'object'); + if (!isArray) return false; + const tests = []; + for (let dest of ['frame', 'client', 'menu']) { + if (!mod.js[dest]) continue; + let test = await check(mod, `js.${dest}`, mod.js[dest], 'array'); + if (test) { + test = mod.js[dest].map((file) => + check(mod, `js.${dest}.file`, file, 'file', { extension: '.mjs' }) + ); + } + tests.push(test); + } + if (mod.js.electron) { + const isArray = await check(mod, 'js.electron', mod.js.electron, 'array'); + if (isArray) { + for (const file of mod.js.electron) { + const isObject = await check(mod, 'js.electron.file', file, 'object'); + if (!isObject) { + tests.push(false); + continue; + } + tests.push([ + check(mod, 'js.electron.file.source', file.source, 'file', { + extension: '.mjs', + }), + // referencing the file within the electron app + // existence can't be validated, so only format is + check(mod, 'js.electron.file.target', file.target, 'string', { + extension: '.js', + }), + ]); + } + } else tests.push(false); + } + return tests; + }, + validateOptions = async (mod) => { + const isArray = await check(mod, 'options', mod.options, 'array'); + if (!isArray) return false; + const tests = []; + for (const option of mod.options) { + const key = 'options.option', + optTypeValid = await check(mod, `${key}.type`, option.type, registry.optionTypes); + if (!optTypeValid) { + tests.push(false); + continue; + } + option.environments = option.environments ?? registry.supportedEnvs; + tests.push([ + check(mod, `${key}.key`, option.key, 'alphanumeric'), + check(mod, `${key}.label`, option.label, 'string'), + check(mod, `${key}.tooltip`, option.tooltip, 'string', { + optional: true, + }), + check(mod, `${key}.environments`, option.environments, 'array').then((isArray) => { + if (!isArray) return false; + return option.environments.map((environment) => + check(mod, `${key}.environments.env`, environment, registry.supportedEnvs) + ); + }), + ]); + switch (option.type) { + case 'toggle': + tests.push(check(mod, `${key}.value`, option.value, 'boolean')); + break; + case 'select': { + let test = await check(mod, `${key}.values`, option.values, 'array'); + if (test) { + test = option.values.map((value) => + check(mod, `${key}.values.value`, value, 'string') + ); + } + tests.push(test); + break; + } + case 'text': + case 'hotkey': + tests.push(check(mod, `${key}.value`, option.value, 'string')); + break; + case 'number': + case 'color': + tests.push(check(mod, `${key}.value`, option.value, option.type)); + break; + case 'file': { + let test = await check(mod, `${key}.extensions`, option.extensions, 'array'); + if (test) { + test = option.extensions.map((ext) => + check(mod, `${key}.extensions.extension`, ext, 'string') + ); + } + tests.push(test); + break; + } + } + } + return tests; }; /** diff --git a/api/registry.mjs b/api/registry.mjs index 462f3b3..1f62526 100644 --- a/api/registry.mjs +++ b/api/registry.mjs @@ -14,9 +14,6 @@ import { env, fs, storage } from './_.mjs'; import { validate } from './registry-validation.mjs'; -export const _cache = [], - _errors = []; - /** * mod ids whitelisted as part of the enhancer's core, permanently enabled * @constant @@ -64,27 +61,35 @@ export const welcomeNotification = { version: env.version, }; +let _list, + _errors = []; /** * list all available mods in the repo * @param {function} filter - a function to filter out mods * @returns {array} a validated list of mod.json objects */ export const list = async (filter = (mod) => true) => { - if (!_cache.length) { - for (const dir of await fs.getJSON('repo/registry.json')) { - try { - const mod = await fs.getJSON(`repo/${dir}/mod.json`); - mod._dir = dir; - if (await validate(mod)) _cache.push(mod); - } catch (e) { - console.log(e); - _errors.push({ source: dir, message: 'invalid mod.json' }); + if (!_list) { + _list = new Promise(async (res, rej) => { + const passed = []; + for (const dir of await fs.getJSON('repo/registry.json')) { + try { + const mod = { + ...(await fs.getJSON(`repo/${dir}/mod.json`)), + _dir: dir, + _err: (message) => _errors.push({ source: dir, message }), + }; + if (await validate(mod)) passed.push(mod); + } catch { + _errors.push({ source: dir, message: 'invalid mod.json' }); + } } - } + res(passed); + }); } - const list = []; - for (const mod of _cache) if (await filter(mod)) list.push(mod); - return list; + const filtered = []; + for (const mod of await _list) if (await filter(mod)) filtered.push(mod); + return filtered; }; /** @@ -92,7 +97,7 @@ export const list = async (filter = (mod) => true) => { * @returns {array} error objects with an error message and a source directory */ export const errors = async () => { - if (!_errors.length) await list(); + await list(); return _errors; }; @@ -102,8 +107,7 @@ export const errors = async () => { * @returns {object} the mod's mod.json */ export const get = async (id) => { - if (!_cache.length) await list(); - return _cache.find((mod) => mod.id === id); + return (await list((mod) => mod.id === id))[0]; }; /** From 824da94ffbd69a48681625ace51b4220da7af1a4 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Sun, 10 Oct 2021 00:59:55 +1100 Subject: [PATCH 13/52] shade/contrast rgb helpers --- api/fmt.mjs | 41 +++++++++++++++++++++++++++++++++++++++++ api/registry.mjs | 3 +++ 2 files changed, 44 insertions(+) diff --git a/api/fmt.mjs b/api/fmt.mjs index 8d8b193..4222c86 100644 --- a/api/fmt.mjs +++ b/api/fmt.mjs @@ -93,6 +93,47 @@ export const uuidv4 = () => { ); }; +/** + * log-based shading of an rgb color, from + * https://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors + * @param {number} p - a decimal amount to shade the color. + * 1 = white, 0 = the original color, -1 = black + * @param {string} c - the rgb color + * @returns {string} the shaded color + */ +export const rgbLogShade = (p, c) => { + var i = parseInt, + r = Math.round, + [a, b, c, d] = c.split(','), + P = p < 0, + t = P ? 0 : p * 255 ** 2, + P = P ? 1 + p : 1 - p; + return ( + 'rgb' + + (d ? 'a(' : '(') + + r((P * i(a[3] == 'a' ? a.slice(5) : a.slice(4)) ** 2 + t) ** 0.5) + + ',' + + r((P * i(b) ** 2 + t) ** 0.5) + + ',' + + r((P * i(c) ** 2 + t) ** 0.5) + + (d ? ',' + d : ')') + ); +}; + +/** + * pick a contrasting color e.g. for text on a variable color background + * using the hsp (perceived brightness) constants from http://alienryderflex.com/hsp.html + * @param {number} r - red (0-255) + * @param {number} g - green (0-255) + * @param {number} b - blue (0-255) + * @returns {string} the contrasting rgb color, white or black + */ +export const rgbContrast = (r, g, b) => { + return Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b)) > 127.5 + ? 'rgb(0,0,0)' + : 'rgb(255,255,255)'; +}; + const patterns = { alphanumeric: /^[\w\.-]+$/, uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, diff --git a/api/registry.mjs b/api/registry.mjs index 1f62526..b50feeb 100644 --- a/api/registry.mjs +++ b/api/registry.mjs @@ -61,6 +61,9 @@ export const welcomeNotification = { version: env.version, }; +/** the url of a json file online containing notifications pushed out to users */ +export const notificationsURL = 'https://notion-enhancer.github.io/notifications.json'; + let _list, _errors = []; /** From 5f52f520f4127da2fe571eb9a815d81ff7f27a9d Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Sun, 10 Oct 2021 12:27:42 +1100 Subject: [PATCH 14/52] adjust rgb contrast constant --- api/fmt.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/fmt.mjs b/api/fmt.mjs index 4222c86..cb3ded8 100644 --- a/api/fmt.mjs +++ b/api/fmt.mjs @@ -129,7 +129,7 @@ export const rgbLogShade = (p, c) => { * @returns {string} the contrasting rgb color, white or black */ export const rgbContrast = (r, g, b) => { - return Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b)) > 127.5 + return Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b)) > 165.75 ? 'rgb(0,0,0)' : 'rgb(255,255,255)'; }; From cc1418ee5d1b12b7399b4cdbafec6db8f657b411 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Tue, 12 Oct 2021 18:39:04 +1100 Subject: [PATCH 15/52] allow empty strings as option values --- api/fmt.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/fmt.mjs b/api/fmt.mjs index cb3ded8..314a813 100644 --- a/api/fmt.mjs +++ b/api/fmt.mjs @@ -170,7 +170,7 @@ export const is = async (value, type, { extension = '' } = {}) => { case 'number': return typeof value === type && extension; case 'string': - return typeof value === type && value.length && extension; + return typeof value === type && extension; case 'alphanumeric': case 'uuid': case 'semver': From a03168d980093890f4f3be7024f79dbc5ac6157b Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Wed, 13 Oct 2021 14:05:22 +1100 Subject: [PATCH 16/52] component: corner action --- api/_.mjs | 2 +- api/components/_.mjs | 12 ++++++-- api/components/corner-action.css | 53 ++++++++++++++++++++++++++++++++ api/components/corner-action.mjs | 43 ++++++++++++++++++++++++++ api/components/feather.mjs | 2 +- api/components/panel.css | 2 +- api/components/panel.mjs | 7 +++-- api/components/tooltip.css | 2 +- api/components/tooltip.mjs | 4 +-- api/env.mjs | 2 +- api/fmt.mjs | 2 +- api/fs.mjs | 2 +- api/notion.mjs | 2 +- api/registry-validation.mjs | 2 +- api/registry.mjs | 2 +- api/storage.mjs | 2 +- api/web.mjs | 2 +- 17 files changed, 124 insertions(+), 19 deletions(-) create mode 100644 api/components/corner-action.css create mode 100644 api/components/corner-action.mjs diff --git a/api/_.mjs b/api/_.mjs index 8c879ca..d970444 100644 --- a/api/_.mjs +++ b/api/_.mjs @@ -1,5 +1,5 @@ /* - * notion-enhancer: api + * notion-enhancer core: api * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ diff --git a/api/components/_.mjs b/api/components/_.mjs index 33cccb5..0446dfe 100644 --- a/api/components/_.mjs +++ b/api/components/_.mjs @@ -1,5 +1,5 @@ /* - * notion-enhancer: api + * notion-enhancer core: api * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ @@ -16,7 +16,7 @@ * @param {HTMLElement} $ref - the element that will trigger the tooltip when hovered * @param {string} text - the markdown content of the tooltip */ -export { tooltip } from './tooltip.mjs'; +export { setTooltip } from './tooltip.mjs'; /** * generate an icon from the feather icons set @@ -35,3 +35,11 @@ export { feather } from './feather.mjs'; * @param {Element} panel.$content - an element containing the content of the view */ export { addPanelView } from './panel.mjs'; + +/** + * adds a button to notion's bottom right corner + * @param {string} icon - an svg string + * @param {function} listener - the function to call when the button is clicked + * @returns {Element} the appended corner action element + */ +export { addCornerAction } from './corner-action.mjs'; diff --git a/api/components/corner-action.css b/api/components/corner-action.css new file mode 100644 index 0000000..5198f32 --- /dev/null +++ b/api/components/corner-action.css @@ -0,0 +1,53 @@ +/* + * notion-enhancer core: api + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (c) 2021 CloudHill (https://github.com/CloudHill) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +#enhancer--corner-actions { + position: absolute; + bottom: 26px; + right: 26px; + z-index: 101; + cursor: default; + pointer-events: none; + display: flex; + flex-direction: row-reverse; +} + +#enhancer--corner-actions > div { + position: static !important; + width: 36px; + height: 36px; + margin-left: 12px; + pointer-events: auto; + border-radius: 100%; + font-size: 20px; + + display: flex; + align-items: center; + justify-content: center; + background: var(--theme--bg_card); + box-shadow: var(--theme--ui_shadow, rgba(15, 15, 15, 0.15)) 0px 0px 0px 1px, + var(--theme--ui_shadow, rgba(15, 15, 15, 0.15)) 0px 2px 4px !important; + + user-select: none; + cursor: pointer; +} +#enhancer--corner-actions > div:hover { + background: var(--theme--ui_interactive-hover); +} +#enhancer--corner-actions > div:active { + background: var(--theme--ui_interactive-active); +} +#enhancer--corner-actions > div.hidden { + display: none; +} + +#enhancer--corner-actions > div > svg { + width: 22px; + height: 22px; + color: var(--theme--icon); + fill: var(--theme--icon); +} diff --git a/api/components/corner-action.mjs b/api/components/corner-action.mjs new file mode 100644 index 0000000..a2fb276 --- /dev/null +++ b/api/components/corner-action.mjs @@ -0,0 +1,43 @@ +/* + * notion-enhancer core: api + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (c) 2021 CloudHill (https://github.com/CloudHill) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +'use strict'; + +/** + * shared notion-style elements + * @module notion-enhancer/api/components/corner-action + */ + +import { web } from '../_.mjs'; + +web.loadStylesheet('api/components/corner-action.css'); + +const $cornerButtonsContainer = web.html`
`; + +/** + * adds a button to notion's bottom right corner + * @param {string} icon - an svg string + * @param {function} listener - the function to call when the button is clicked + * @returns {Element} the appended corner action element + */ +export const addCornerAction = async (icon, listener) => { + await web.whenReady(['.notion-help-button']); + const $helpButton = document.querySelector('.notion-help-button'), + $onboardingButton = document.querySelector('.onboarding-checklist-button'); + if ($onboardingButton) $cornerButtonsContainer.prepend($onboardingButton); + $cornerButtonsContainer.prepend($helpButton); + document + .querySelector('.notion-app-inner > .notion-cursor-listener') + .append($cornerButtonsContainer); + + const $actionButton = web.html`
${icon}
`; + $actionButton.addEventListener('click', listener); + + $cornerButtonsContainer.append($actionButton); + + return $actionButton; +}; diff --git a/api/components/feather.mjs b/api/components/feather.mjs index 3734bda..087b3da 100644 --- a/api/components/feather.mjs +++ b/api/components/feather.mjs @@ -1,5 +1,5 @@ /* - * notion-enhancer: api + * notion-enhancer core: api * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ diff --git a/api/components/panel.css b/api/components/panel.css index ecd2715..c0fe792 100644 --- a/api/components/panel.css +++ b/api/components/panel.css @@ -1,5 +1,5 @@ /* - * notion-enhancer core: components + * notion-enhancer core: api * (c) 2021 dragonwocky (https://dragonwocky.me/) * (c) 2021 CloudHill (https://github.com/CloudHill) * (https://notion-enhancer.github.io/) under the MIT license diff --git a/api/components/panel.mjs b/api/components/panel.mjs index e90c5ab..32c5d03 100644 --- a/api/components/panel.mjs +++ b/api/components/panel.mjs @@ -1,6 +1,7 @@ /* - * notion-enhancer: api + * notion-enhancer core: api * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (c) 2021 CloudHill (https://github.com/CloudHill) * (https://notion-enhancer.github.io/) under the MIT license */ @@ -14,6 +15,8 @@ import { web, components, registry } from '../_.mjs'; const db = await registry.db('36a2ffc9-27ff-480e-84a7-c7700a7d232d'); +web.loadStylesheet('api/components/panel.css'); + const _views = [], svgExpand = web.raw` `, panelPinnedAttr = 'data-enhancer-panel-pinned', isPinned = () => $panel.hasAttribute(panelPinnedAttr), togglePanel = () => { - const $elems = [$notionRightSidebar, $notionFrame, $hoverTrigger, $panel]; + const $elems = [$notionFrame, $notionRightSidebar, $hoverTrigger, $panel].filter( + ($el) => $el + ); if (isPinned()) { closeSwitcher(); for (const $elem of $elems) $elem.removeAttribute(panelPinnedAttr); @@ -180,10 +182,20 @@ const $panel = web.html`
`, }; async function createPanel() { - const notionRightSidebarSelector = '.notion-cursor-listener > div[style*="flex-end"]'; - await web.whenReady([notionRightSidebarSelector]); $notionFrame = document.querySelector('.notion-frame'); + + const notionRightSidebarSelector = '.notion-cursor-listener > div[style*="flex-end"]', + detectRightSidebar = () => { + if (!document.contains($notionRightSidebar)) { + $notionRightSidebar = document.querySelector(notionRightSidebarSelector); + if (isPinned() && $notionRightSidebar) { + $notionRightSidebar.setAttribute(panelPinnedAttr, 'true'); + } + } + }; $notionRightSidebar = document.querySelector(notionRightSidebarSelector); + web.addDocumentObserver(detectRightSidebar, [notionRightSidebarSelector]); + if (await db.get(['panel.pinned'])) togglePanel(); web.addHotkeyListener(await db.get(['panel.hotkey']), togglePanel); $pinnedToggle.addEventListener('click', (event) => { @@ -200,7 +212,10 @@ async function createPanel() { await enablePanelResize(); await createViews(); - $notionRightSidebar.after($hoverTrigger, $panel); + const cursorListenerSelector = + '.notion-cursor-listener > :last-child[style^="position: absolute"]'; + await web.whenReady([cursorListenerSelector]); + document.querySelector(cursorListenerSelector).before($hoverTrigger, $panel); } async function enablePanelResize() { @@ -224,12 +239,12 @@ async function createViews() { /** * adds a view to the enhancer's side panel * @param {object} panel - information used to construct and render the panel - * @param {string} panel.id - a uuid, used to restore the last open view on reload + * @param {string} [panel.id] - a uuid, used to restore the last open view on reload * @param {string} panel.icon - an svg string * @param {string} panel.title - the name of the view * @param {Element} panel.$content - an element containing the content of the view */ -export const addPanelView = async ({ id, icon, title, $content }) => { +export const addPanelView = async ({ id = fmt.uuidv4(), icon, title, $content }) => { const view = { id, $icon: web.html` diff --git a/api/components/tooltip.css b/api/components/tooltip.css index bcbbcb6..b807357 100644 --- a/api/components/tooltip.css +++ b/api/components/tooltip.css @@ -5,6 +5,7 @@ */ #enhancer--tooltip { + font-family: var(--theme--font_sans); background: var(--theme--ui_tooltip); border-radius: 3px; box-shadow: var(--theme--ui_shadow) 0px 1px 4px; @@ -15,8 +16,9 @@ line-height: 1.4; max-width: 20rem; overflow: hidden; - padding: 4px 8px; + padding: 2px 8px 4px 8px; position: absolute; + z-index: 999999999999999999; } #enhancer--tooltip p { margin: 0.25rem 0; From 860a9434378c9c47e6aac230a379e8fe019eb490 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Mon, 18 Oct 2021 20:33:45 +1100 Subject: [PATCH 18/52] observe child changes better --- api/web.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/web.mjs b/api/web.mjs index 884d276..dfa80e7 100644 --- a/api/web.mjs +++ b/api/web.mjs @@ -200,7 +200,9 @@ export const addDocumentObserver = (callback, selectors = []) => { !listener.selectors.length || listener.selectors.some( (selector) => - event.target.matches(selector) || event.target.matches(`${selector} *`) + event.target.matches(selector) || + event.target.matches(`${selector} *`) || + event.target.querySelector(selector) ) ) { listener.callback(event); From fe42600ab8b49f2fa7f689b83261bea7975e0140 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Tue, 19 Oct 2021 16:38:48 +1100 Subject: [PATCH 19/52] focus/blur listeners on side panel --- api/components/_.mjs | 4 +++- api/components/panel.mjs | 35 ++++++++++++++++++++++++++--------- api/web.mjs | 21 +++++++++++---------- 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/api/components/_.mjs b/api/components/_.mjs index cd0cc0d..dcc4a9e 100644 --- a/api/components/_.mjs +++ b/api/components/_.mjs @@ -29,10 +29,12 @@ export { feather } from './feather.mjs'; /** * adds a view to the enhancer's side panel * @param {object} panel - information used to construct and render the panel - * @param {string} [panel.id] - a uuid, used to restore the last open view on reload + * @param {string} panel.id - a uuid, used to restore the last open view on reload * @param {string} panel.icon - an svg string * @param {string} panel.title - the name of the view * @param {Element} panel.$content - an element containing the content of the view + * @param {function} panel.onBlur - runs when the view is selected/focused + * @param {function} panel.onFocus - runs when the view is unfocused/closed */ export { addPanelView } from './panel.mjs'; diff --git a/api/components/panel.mjs b/api/components/panel.mjs index 44a6735..e3f27eb 100644 --- a/api/components/panel.mjs +++ b/api/components/panel.mjs @@ -170,6 +170,7 @@ const $panel = web.html`
`, } }, renderView = (view) => { + const prevView = _views.find(({ $content }) => document.contains($content)); web.render( web.empty($panelTitle), web.render( @@ -178,10 +179,13 @@ const $panel = web.html`
`, view.$title ) ); + view.onFocus(); web.render(web.empty($panelContent), view.$content); + if (prevView) prevView.onBlur(); }; async function createPanel() { + await web.whenReady(['.notion-frame']); $notionFrame = document.querySelector('.notion-frame'); const notionRightSidebarSelector = '.notion-cursor-listener > div[style*="flex-end"]', @@ -239,22 +243,35 @@ async function createViews() { /** * adds a view to the enhancer's side panel * @param {object} panel - information used to construct and render the panel - * @param {string} [panel.id] - a uuid, used to restore the last open view on reload + * @param {string} panel.id - a uuid, used to restore the last open view on reload * @param {string} panel.icon - an svg string * @param {string} panel.title - the name of the view * @param {Element} panel.$content - an element containing the content of the view + * @param {function} panel.onBlur - runs when the view is selected/focused + * @param {function} panel.onFocus - runs when the view is unfocused/closed */ -export const addPanelView = async ({ id = fmt.uuidv4(), icon, title, $content }) => { +export const addPanelView = async ({ + id, + icon, + title, + $content, + onFocus = () => {}, + onBlur = () => {}, +}) => { const view = { id, - $icon: web.html` - ${icon} - `, - $title: web.html` - ${web.escape(title)} - - `, + $icon: web.render( + web.html``, + icon instanceof Element ? icon : web.html`${icon}` + ), + $title: web.render( + web.html``, + title, + web.html`` + ), $content, + onFocus, + onBlur, }; _views.push(view); if (_views.length === 1) await createPanel(); diff --git a/api/web.mjs b/api/web.mjs index dfa80e7..0de9929 100644 --- a/api/web.mjs +++ b/api/web.mjs @@ -194,17 +194,18 @@ export const addDocumentObserver = (callback, selectors = []) => { if (!_documentObserver) { const handle = (queue) => { while (queue.length) { - const event = queue.shift(); + const event = queue.shift(), + matchesAddedNode = ($node, selector) => + $node instanceof Element && + ($node.matches(selector) || + $node.matches(`${selector} *`) || + $node.querySelector(selector)), + matchesTarget = (selector) => + event.target.matches(selector) || + event.target.matches(`${selector} *`) || + [...event.addedNodes].some(($node) => matchesAddedNode($node, selector)); for (const listener of _documentObserverListeners) { - if ( - !listener.selectors.length || - listener.selectors.some( - (selector) => - event.target.matches(selector) || - event.target.matches(`${selector} *`) || - event.target.querySelector(selector) - ) - ) { + if (!listener.selectors.length || listener.selectors.some(matchesTarget)) { listener.callback(event); } } From 690b677dc9be2bf90ee42e138a00cdfbbf77aed0 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Tue, 19 Oct 2021 21:28:01 +1100 Subject: [PATCH 20/52] adjust url regex --- api/fmt.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/fmt.mjs b/api/fmt.mjs index a4b9ff1..53881f7 100644 --- a/api/fmt.mjs +++ b/api/fmt.mjs @@ -141,7 +141,7 @@ const patterns = { /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i, email: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i, - url: /^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i, + url: /^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,64}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i, color: /^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i, }; function test(str, pattern) { From db5ec9a934a67af259ce67bd7bd8704b0ee50332 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Wed, 20 Oct 2021 21:03:20 +1100 Subject: [PATCH 21/52] smoother tooltips, delay arg --- api/components/_.mjs | 4 +++- api/components/tooltip.css | 1 + api/components/tooltip.mjs | 30 +++++++++++++++++++++++------- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/api/components/_.mjs b/api/components/_.mjs index dcc4a9e..2119cd9 100644 --- a/api/components/_.mjs +++ b/api/components/_.mjs @@ -14,7 +14,9 @@ /** * add a tooltip to show extra information on hover * @param {HTMLElement} $ref - the element that will trigger the tooltip when hovered - * @param {string} text - the markdown content of the tooltip + * @param {string|HTMLElement} $text - the markdown content of the tooltip + * @param {number} [delay] - the amount of time the element needs to be hovered over + * for the tooltip to be shown */ export { setTooltip } from './tooltip.mjs'; diff --git a/api/components/tooltip.css b/api/components/tooltip.css index b807357..9f2bbcf 100644 --- a/api/components/tooltip.css +++ b/api/components/tooltip.css @@ -19,6 +19,7 @@ padding: 2px 8px 4px 8px; position: absolute; z-index: 999999999999999999; + pointer-events: none; } #enhancer--tooltip p { margin: 0.25rem 0; diff --git a/api/components/tooltip.mjs b/api/components/tooltip.mjs index e1eb8ec..83cfd0d 100644 --- a/api/components/tooltip.mjs +++ b/api/components/tooltip.mjs @@ -19,20 +19,36 @@ web.loadStylesheet('api/components/tooltip.css'); /** * add a tooltip to show extra information on hover * @param {HTMLElement} $ref - the element that will trigger the tooltip when hovered - * @param {string} text - the markdown content of the tooltip + * @param {string|HTMLElement} $text - the markdown content of the tooltip + * @param {number} [delay] - the amount of time the element needs to be hovered over + * for the tooltip to be shown */ -export const setTooltip = ($ref, text) => { +export const setTooltip = ($ref, $text, delay = 100) => { web.render(document.body, _$tooltip); - text = web.html`${fmt.md.render(text)}`; + if (!($text instanceof Element)) $text = web.html`${fmt.md.render($text)}`; + let displayDelay; $ref.addEventListener('mouseover', (event) => { - web.render(web.empty(_$tooltip), text); - _$tooltip.style.display = 'block'; + web.render(web.empty(_$tooltip), $text); + if (!displayDelay) { + displayDelay = setTimeout(async () => { + if ($ref.matches(':hover')) { + _$tooltip.style.display = 'block'; + _$tooltip.style.top = event.clientY - _$tooltip.clientHeight + 'px'; + _$tooltip.style.left = event.clientX - _$tooltip.clientWidth + 'px'; + await _$tooltip.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 65 }).finished; + } + displayDelay = undefined; + }, delay); + } }); $ref.addEventListener('mousemove', (event) => { _$tooltip.style.top = event.clientY - _$tooltip.clientHeight + 'px'; _$tooltip.style.left = event.clientX - _$tooltip.clientWidth + 'px'; }); - $ref.addEventListener('mouseout', (event) => { - _$tooltip.style.display = ''; + $ref.addEventListener('mouseout', async (event) => { + if (!$ref.matches(':hover')) { + await _$tooltip.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 65 }).finished; + _$tooltip.style.display = ''; + } }); }; From 7da5f65c85a0a5cd18957382c0a01dd6be730e76 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Wed, 20 Oct 2021 23:02:26 +1100 Subject: [PATCH 22/52] clipboard helpers --- api/web.mjs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/api/web.mjs b/api/web.mjs index 0de9929..e5f6f5f 100644 --- a/api/web.mjs +++ b/api/web.mjs @@ -143,6 +143,35 @@ export const loadStylesheet = (path) => { return true; }; +/** + * copy text to the clipboard + * @param {string} str - the string to copy + * @returns {Promise} + */ +export const copyToClipboard = async (str) => { + try { + await navigator.clipboard.writeText(str); + } catch { + 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); + } +}; + +/** + * read text from the clipboard + * @returns {Promise} + */ +export const readFromClipboard = () => { + return navigator.clipboard.readText(); +}; + document.addEventListener('keyup', (event) => { if (document.activeElement.nodeName === 'INPUT') return; for (const hotkey of _hotkeyEventListeners) { From e72115fd5156a6750331b253dd2afa4336d22505 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Thu, 21 Oct 2021 23:27:19 +1100 Subject: [PATCH 23/52] update panel, hotkey comment --- api/components/panel.css | 27 +++------------------------ api/components/panel.mjs | 10 +++------- api/web.mjs | 3 ++- 3 files changed, 8 insertions(+), 32 deletions(-) diff --git a/api/components/panel.css b/api/components/panel.css index 532926d..61acbd4 100644 --- a/api/components/panel.css +++ b/api/components/panel.css @@ -106,8 +106,7 @@ cursor: pointer; user-select: none; padding: 0.75rem 0 0.75rem 1rem; - --scoped--bg: var(--theme--bg_secondary); - background: var(--scoped--bg); + background: var(--theme--bg_secondary); } #enhancer--panel-header-title { max-width: calc(100% - 4.25rem); @@ -119,11 +118,6 @@ max-width: calc(100% - 1.75em); position: relative; } -.enhancer--panel-view-title-fade-edge { - display: inline-block; - width: 0.75rem; - height: 1em; -} #enhancer--panel-switcher-overlay-container { position: fixed; @@ -155,33 +149,18 @@ cursor: pointer; overflow: hidden; position: relative; - --scoped--bg: var(--theme--bg_card); - background: var(--scoped--bg); + background: var(--theme--bg_card); } #enhancer--panel-header:hover, #enhancer--panel-header:focus-within, .enhancer--panel-switcher-item:hover, .enhancer--panel-switcher-item:focus { - --scoped--bg: var(--theme--ui_interactive-hover); + background: var(--theme--ui_interactive-hover); } #enhancer--panel-header:active, .enhancer--panel-switcher-item:active { background: var(--theme--ui_interactive-active); } -.enhancer--panel-view-title-fade-edge:after { - content: ''; - height: 100%; - position: absolute; - right: 0; - top: 0; - width: 0.75rem; - background: linear-gradient( - to right, - transparent 0%, - var(--scoped--bg) 50%, - var(--scoped--bg) 100% - ); -} #enhancer--panel-content { margin: 0.75rem 1rem; diff --git a/api/components/panel.mjs b/api/components/panel.mjs index e3f27eb..5d44fc8 100644 --- a/api/components/panel.mjs +++ b/api/components/panel.mjs @@ -73,13 +73,13 @@ const $panel = web.html`
`, $panel.style.width = panelWidth + 'px'; $hoverTrigger.style.width = panelWidth + 'px'; $notionFrame.style.paddingRight = panelWidth + 'px'; - $notionRightSidebar.style.right = panelWidth + 'px'; + if ($notionRightSidebar) $notionRightSidebar.style.right = panelWidth + 'px'; }, resizeEnd = (event) => { $panel.style.width = ''; $hoverTrigger.style.width = ''; $notionFrame.style.paddingRight = ''; - $notionRightSidebar.style.right = ''; + if ($notionRightSidebar) $notionRightSidebar.style.right = ''; updateWidth(); $resizeHandle.style.cursor = ''; document.body.removeEventListener('mousemove', resizeDrag); @@ -264,11 +264,7 @@ export const addPanelView = async ({ web.html``, icon instanceof Element ? icon : web.html`${icon}` ), - $title: web.render( - web.html``, - title, - web.html`` - ), + $title: web.render(web.html``, title), $content, onFocus, onBlur, diff --git a/api/web.mjs b/api/web.mjs index e5f6f5f..12d43d0 100644 --- a/api/web.mjs +++ b/api/web.mjs @@ -195,9 +195,10 @@ document.addEventListener('keyup', (event) => { /** * register a hotkey listener to the page - * @param {array} keys - the combination of keys that will trigger the hotkey. + * @param {array|string} keys - the combination of keys that will trigger the hotkey. * key codes can be tested at http://keycode.info/ and are case-insensitive. * available modifiers are 'alt', 'ctrl', 'meta', and 'shift'. + * can be provided as a + separated string. * @param {function} callback - called whenever the keys are pressed */ export const addHotkeyListener = (keys, callback) => { From 10a58ca18d7ff436c0c785d6a0b9dcae1a592550 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Sat, 23 Oct 2021 23:12:30 +1100 Subject: [PATCH 24/52] add keydown & in-input hotkey opts --- api/components/corner-action.mjs | 10 +++--- api/web.mjs | 56 +++++++++++++++++++++----------- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/api/components/corner-action.mjs b/api/components/corner-action.mjs index a2fb276..3b21cf0 100644 --- a/api/components/corner-action.mjs +++ b/api/components/corner-action.mjs @@ -30,14 +30,14 @@ export const addCornerAction = async (icon, listener) => { $onboardingButton = document.querySelector('.onboarding-checklist-button'); if ($onboardingButton) $cornerButtonsContainer.prepend($onboardingButton); $cornerButtonsContainer.prepend($helpButton); - document - .querySelector('.notion-app-inner > .notion-cursor-listener') - .append($cornerButtonsContainer); + web.render( + document.querySelector('.notion-app-inner > .notion-cursor-listener'), + $cornerButtonsContainer + ); const $actionButton = web.html`
${icon}
`; $actionButton.addEventListener('click', listener); - - $cornerButtonsContainer.append($actionButton); + web.render($cornerButtonsContainer, $actionButton); return $actionButton; }; diff --git a/api/web.mjs b/api/web.mjs index 12d43d0..4ee4182 100644 --- a/api/web.mjs +++ b/api/web.mjs @@ -172,24 +172,33 @@ export const readFromClipboard = () => { return navigator.clipboard.readText(); }; +const triggerHotkeyListener = (event, hotkey) => { + const inInput = document.activeElement.nodeName === 'INPUT' && !hotkey.listenInInput; + if (inInput) return; + const pressed = hotkey.keys.every((key) => { + key = key.toLowerCase(); + const modifiers = { + metaKey: ['meta', 'os', 'win', 'cmd', 'command'], + ctrlKey: ['ctrl', 'control'], + shiftKey: ['shift'], + altKey: ['alt'], + }; + for (const modifier in modifiers) { + const pressed = modifiers[modifier].includes(key) && event[modifier]; + if (pressed) return true; + } + if (key === event.key.toLowerCase()) return true; + }); + if (pressed) hotkey.callback(event); +}; document.addEventListener('keyup', (event) => { - if (document.activeElement.nodeName === 'INPUT') return; - for (const hotkey of _hotkeyEventListeners) { - const pressed = hotkey.keys.every((key) => { - key = key.toLowerCase(); - const modifiers = { - metaKey: ['meta', 'os', 'win', 'cmd', 'command'], - ctrlKey: ['ctrl', 'control'], - shiftKey: ['shift'], - altKey: ['alt'], - }; - for (const modifier in modifiers) { - const pressed = modifiers[modifier].includes(key) && event[modifier]; - if (pressed) return true; - } - if (key === event.key.toLowerCase()) return true; - }); - if (pressed) hotkey.callback(event); + for (const hotkey of _hotkeyEventListeners.filter(({ keydown }) => !keydown)) { + triggerHotkeyListener(event, hotkey); + } +}); +document.addEventListener('keydown', (event) => { + for (const hotkey of _hotkeyEventListeners.filter(({ keydown }) => keydown)) { + triggerHotkeyListener(event, hotkey); } }); @@ -200,10 +209,19 @@ document.addEventListener('keyup', (event) => { * available modifiers are 'alt', 'ctrl', 'meta', and 'shift'. * can be provided as a + separated string. * @param {function} callback - called whenever the keys are pressed + * @param {object} [opts] - fine-tuned control over when the hotkey should be triggered + * @param {boolean} [opts.listenInInput] - whether the hotkey callback should be triggered + * when an input is focused + * @param {boolean} [opts.keydown] - whether to listen for the hotkey on keydown. + * by default, hotkeys are triggered by the keyup event. */ -export const addHotkeyListener = (keys, callback) => { +export const addHotkeyListener = ( + keys, + callback, + { listenInInput = false, keydown = false } = {} +) => { if (typeof keys === 'string') keys = keys.split('+'); - _hotkeyEventListeners.push({ keys, callback }); + _hotkeyEventListeners.push({ keys, callback, listenInInput, keydown }); }; /** * remove a listener added with web.addHotkeyListener From a497618ea4fd17e670c760d9bb5b43926ddba094 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Sat, 30 Oct 2021 19:13:27 +1100 Subject: [PATCH 25/52] notion api upload file, registry integration tag --- api/notion.mjs | 42 +++++++++++++++++++++++++++++++++++++ api/registry-validation.mjs | 4 ++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/api/notion.mjs b/api/notion.mjs index fbcf8e7..cd9a565 100644 --- a/api/notion.mjs +++ b/api/notion.mjs @@ -304,6 +304,48 @@ export const create = async ( return json.errorId ? json : recordID; }; +/** + * unofficial content api: upload a file to notion's aws servers + * (requires user to be signed in or content to be public). + * TEST THIS THOROUGHLY. misuse can corrupt a record, leading the notion client + * to be unable to parse and render content properly and throw errors. + * why not use the official api? + * 1. cors blocking prevents use on the client + * 2. the majority of blocks are still 'unsupported' + * @param {File} file - the file to upload + * @param {object} [pointer] - where the file should be accessible from + * @param {string} [pointer.pageID] - uuidv4 page id + * @param {string} [pointer.spaceID] - uuidv4 space id + * @returns {string|object} error object or the url of the uploaded file + */ +export const upload = async (file, { pageID = getPageID(), spaceID = getSpaceID() } = {}) => { + spaceID = standardiseUUID(await spaceID); + pageID = standardiseUUID(pageID); + const json = await fs.getJSON('https://www.notion.so/api/v3/getUploadFileUrl', { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify({ + bucket: 'secure', + name: file.name, + contentType: file.type, + record: { + table: 'block', + id: pageID, + spaceId: spaceID, + }, + }), + }); + if (json.errorId) return json; + fetch(json.signedPutUrl, { + method: 'PUT', + headers: { 'content-type': file.type }, + body: file, + }); + return json.url; +}; + /** * redirect through notion to a resource's signed aws url for display outside of notion * (requires user to be signed in or content to be public) diff --git a/api/registry-validation.mjs b/api/registry-validation.mjs index 76f6450..47a8d3f 100644 --- a/api/registry-validation.mjs +++ b/api/registry-validation.mjs @@ -50,11 +50,11 @@ const validateEnvironments = async (mod) => { validateTags = async (mod) => { const isArray = await check(mod, 'tags', mod.tags, 'array'); if (!isArray) return false; - const categoryTags = ['core', 'extension', 'theme'], + const categoryTags = ['core', 'extension', 'theme', 'integration'], containsCategory = mod.tags.filter((tag) => categoryTags.includes(tag)).length; if (!containsCategory) { mod._err( - `invalid tags (must contain at least one of 'core', 'extension', or 'theme'): + `invalid tags (must contain at least one of 'core', 'extension', 'theme' or 'integration'): ${JSON.stringify(mod.tags)}` ); return false; From 21c56590a5c3fbab3d0fd9f96f02d0cc08019790 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Sun, 31 Oct 2021 12:21:02 +1100 Subject: [PATCH 26/52] handle space and plus in hotkeys --- api/components/corner-action.css | 2 +- api/web.mjs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/api/components/corner-action.css b/api/components/corner-action.css index 5198f32..b04e466 100644 --- a/api/components/corner-action.css +++ b/api/components/corner-action.css @@ -28,7 +28,7 @@ display: flex; align-items: center; justify-content: center; - background: var(--theme--bg_card); + background: var(--theme--tag_default) !important; box-shadow: var(--theme--ui_shadow, rgba(15, 15, 15, 0.15)) 0px 0px 0px 1px, var(--theme--ui_shadow, rgba(15, 15, 15, 0.15)) 0px 2px 4px !important; diff --git a/api/web.mjs b/api/web.mjs index 4ee4182..baa101c 100644 --- a/api/web.mjs +++ b/api/web.mjs @@ -187,6 +187,8 @@ const triggerHotkeyListener = (event, hotkey) => { const pressed = modifiers[modifier].includes(key) && event[modifier]; if (pressed) return true; } + if (key === 'space') key = ' '; + if (key === 'plus') key = '+'; if (key === event.key.toLowerCase()) return true; }); if (pressed) hotkey.callback(event); From 76df1e76769fe41e0b7f76233317dec40353f5a1 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Sun, 31 Oct 2021 18:25:01 +1100 Subject: [PATCH 27/52] removed welcome notification from registry --- api/registry.mjs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/api/registry.mjs b/api/registry.mjs index 89e0bd9..6fa99a1 100644 --- a/api/registry.mjs +++ b/api/registry.mjs @@ -51,19 +51,6 @@ export const profileName = async () => storage.get(['currentprofile'], 'default' */ export const profileDB = async () => storage.db(['profiles', await profileName()]); -/** a notification displayed when the menu is opened for the first time */ -export const welcomeNotification = { - id: '84e2d49b-c3dc-44b4-a154-cf589676bfa0', - color: 'purple', - icon: 'message-circle', - message: 'Welcome! Come chat with us on Discord.', - link: 'https://discord.gg/sFWPXtA', - version: env.version, -}; - -/** the url of a json file online containing notifications pushed out to users */ -export const notificationsURL = 'https://notion-enhancer.github.io/notifications.json'; - let _list, _errors = []; /** From e3b598dc0034aa89d48acc5da00ccbad582a1a18 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Wed, 3 Nov 2021 21:53:21 +1100 Subject: [PATCH 28/52] corner button cross-theme readability --- api/components/corner-action.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/components/corner-action.css b/api/components/corner-action.css index b04e466..9b66eb9 100644 --- a/api/components/corner-action.css +++ b/api/components/corner-action.css @@ -48,6 +48,6 @@ #enhancer--corner-actions > div > svg { width: 22px; height: 22px; - color: var(--theme--icon); - fill: var(--theme--icon); + color: var(--theme--tag_default-text); + fill: var(--theme--tag_default-text); } From 671266fff0e6c29c5f8b1ae49194906a4228d3f5 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Sat, 6 Nov 2021 16:07:01 +1100 Subject: [PATCH 29/52] link to notion-enhancer/desktop repo --- api/.github/workflows/update-parents.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/.github/workflows/update-parents.yml b/api/.github/workflows/update-parents.yml index 974cc03..ac428e4 100644 --- a/api/.github/workflows/update-parents.yml +++ b/api/.github/workflows/update-parents.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - repo: ['notion-enhancer/extension'] + repo: ['notion-enhancer/extension', 'notion-enhancer/desktop'] steps: - name: checkout repo uses: actions/checkout@v2 From 0aabc67774fdda57d0703b958b29c46cd2006566 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Sun, 7 Nov 2021 16:08:22 +1100 Subject: [PATCH 30/52] port to cjs --- api/_.cjs | 22 ++++ api/_.mjs | 2 +- api/components/panel.mjs | 19 +-- api/env.cjs | 47 ++++++++ api/fmt.cjs | 139 ++++++++++++++++++++++ api/fs.cjs | 49 ++++++++ api/fs.mjs | 2 +- api/registry-validation.cjs | 224 ++++++++++++++++++++++++++++++++++++ api/registry.cjs | 159 +++++++++++++++++++++++++ api/storage.cjs | 68 +++++++++++ 10 files changed, 722 insertions(+), 9 deletions(-) create mode 100644 api/_.cjs create mode 100644 api/env.cjs create mode 100644 api/fmt.cjs create mode 100644 api/fs.cjs create mode 100644 api/registry-validation.cjs create mode 100644 api/registry.cjs create mode 100644 api/storage.cjs diff --git a/api/_.cjs b/api/_.cjs new file mode 100644 index 0000000..c1424d7 --- /dev/null +++ b/api/_.cjs @@ -0,0 +1,22 @@ +/* + * notion-enhancer core: api + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +'use strict'; + +/** @module notion-enhancer/api */ + +module.exports = { + /** environment-specific methods and constants */ + env: require('notion-enhancer/api/env.cjs'), + /** environment-specific file reading */ + fs: require('notion-enhancer/api/fs.cjs'), + /** environment-specific data persistence */ + storage: require('notion-enhancer/api/storage.cjs'), + /** helpers for formatting, validating and parsing values */ + fmt: require('notion-enhancer/api/fmt.cjs'), + /** interactions with the enhancer's repository of mods */ + registry: require('notion-enhancer/api/registry.cjs'), +}; diff --git a/api/_.mjs b/api/_.mjs index d970444..a81a82a 100644 --- a/api/_.mjs +++ b/api/_.mjs @@ -10,7 +10,7 @@ /** environment-specific methods and constants */ export * as env from './env.mjs'; -/** environment-specific filesystem reading */ +/** environment-specific file reading */ export * as fs from './fs.mjs'; /** environment-specific data persistence */ export * as storage from './storage.mjs'; diff --git a/api/components/panel.mjs b/api/components/panel.mjs index 5d44fc8..82ce3e3 100644 --- a/api/components/panel.mjs +++ b/api/components/panel.mjs @@ -13,7 +13,6 @@ */ import { fmt, web, components, registry } from '../_.mjs'; -const db = await registry.db('36a2ffc9-27ff-480e-84a7-c7700a7d232d'); web.loadStylesheet('api/components/panel.css'); @@ -27,8 +26,9 @@ const _views = [], 5.43056L 3.01191 8.43056L 3.98809 9.56944Z"> `; -// open + close -let $notionFrame, +let db, + // open + close + $notionFrame, $notionRightSidebar, // resize dragStartX, @@ -36,13 +36,11 @@ let $notionFrame, dragEventsFired, panelWidth, // render content - $notionApp; + $notionApp, + $pinnedToggle; // open + close const $panel = web.html`
`, - $pinnedToggle = web.html`
- ${await components.feather('chevrons-right')} -
`, $hoverTrigger = web.html`
`, panelPinnedAttr = 'data-enhancer-panel-pinned', isPinned = () => $panel.hasAttribute(panelPinnedAttr), @@ -258,6 +256,13 @@ export const addPanelView = async ({ onFocus = () => {}, onBlur = () => {}, }) => { + if (!db) db = await registry.db('36a2ffc9-27ff-480e-84a7-c7700a7d232d'); + if (!$pinnedToggle) { + $pinnedToggle = web.html`
+ ${await components.feather('chevrons-right')} +
`; + } + const view = { id, $icon: web.render( diff --git a/api/env.cjs b/api/env.cjs new file mode 100644 index 0000000..c328f19 --- /dev/null +++ b/api/env.cjs @@ -0,0 +1,47 @@ +/* + * notion-enhancer core: api + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +'use strict'; + +/** + * environment-specific methods and constants + * @module notion-enhancer/api/env + */ + +const env = require('../env/env.cjs'); +module.exports = {}; + +/** + * the environment/platform name code is currently being executed in + * @constant + * @type {string} + */ +module.exports.name = env.name; + +/** + * the current version of the enhancer + * @constant + * @type {string} + */ +module.exports.version = env.version; + +/** + * open the enhancer's menu + * @type {function} + */ +module.exports.focusMenu = env.focusMenu; + +/** + * focus an active notion tab + * @type {function} + */ +module.exports.focusNotion = env.focusNotion; + +/** + * reload all notion and enhancer menu tabs to apply changes + * @type {function} + */ +module.exports.reload = env.reload; diff --git a/api/fmt.cjs b/api/fmt.cjs new file mode 100644 index 0000000..72b125d --- /dev/null +++ b/api/fmt.cjs @@ -0,0 +1,139 @@ +/* + * notion-enhancer core: api + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +'use strict'; + +/** + * helpers for formatting or parsing text + * @module notion-enhancer/api/fmt + */ + +const fs = require('notion-enhancer/api/fs.cjs'); +module.exports = {}; + +/** + * transform a heading into a slug (a lowercase alphanumeric string separated by dashes), + * e.g. for use as an anchor id + * @param {string} heading - the original heading to be slugified + * @param {Set} [slugs] - a list of pre-generated slugs to avoid duplicates + * @returns {string} the generated slug + */ +module.exports.slugger = (heading, slugs = new Set()) => { + heading = heading + .replace(/\s/g, '-') + .replace(/[^A-Za-z0-9-_]/g, '') + .toLowerCase(); + let i = 0, + slug = heading; + while (slugs.has(slug)) { + i++; + slug = `${heading}-${i}`; + } + return slug; +}; + +/** + * generate a reasonably random uuidv4 string. uses crypto implementation if available + * (from https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid) + * @returns {string} a uuidv4 + */ +module.exports.uuidv4 = () => { + if (crypto?.randomUUID) return crypto.randomUUID(); + return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => + (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16) + ); +}; + +/** + * log-based shading of an rgb color, from + * https://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors + * @param {number} p - a decimal amount to shade the color. + * 1 = white, 0 = the original color, -1 = black + * @param {string} c - the rgb color + * @returns {string} the shaded color + */ +module.exports.rgbLogShade = (p, c) => { + var i = parseInt, + r = Math.round, + [a, b, c, d] = c.split(','), + P = p < 0, + t = P ? 0 : p * 255 ** 2, + P = P ? 1 + p : 1 - p; + return ( + 'rgb' + + (d ? 'a(' : '(') + + r((P * i(a[3] == 'a' ? a.slice(5) : a.slice(4)) ** 2 + t) ** 0.5) + + ',' + + r((P * i(b) ** 2 + t) ** 0.5) + + ',' + + r((P * i(c) ** 2 + t) ** 0.5) + + (d ? ',' + d : ')') + ); +}; + +/** + * pick a contrasting color e.g. for text on a variable color background + * using the hsp (perceived brightness) constants from http://alienryderflex.com/hsp.html + * @param {number} r - red (0-255) + * @param {number} g - green (0-255) + * @param {number} b - blue (0-255) + * @returns {string} the contrasting rgb color, white or black + */ +module.exports.rgbContrast = (r, g, b) => { + return Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b)) > 165.75 + ? 'rgb(0,0,0)' + : 'rgb(255,255,255)'; +}; + +const patterns = { + alphanumeric: /^[\w\.-]+$/, + uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, + semver: + /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i, + email: + /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i, + url: /^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,64}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i, + color: /^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i, +}; +function test(str, pattern) { + const match = str.match(pattern); + return !!(match && match.length); +} + +/** + * test the type of a value. unifies builtin, regex, and environment/api checks + * @param {*} value - the value to check + * @param {string|array} type - the type the value should be or a list of allowed values + * @returns {boolean} whether or not the value matches the type + */ +module.exports.is = async (value, type, { extension = '' } = {}) => { + extension = !value || !value.endsWith || value.endsWith(extension); + if (Array.isArray(type)) { + return type.includes(value); + } + switch (type) { + case 'array': + return Array.isArray(value); + case 'object': + return value && typeof value === 'object' && !Array.isArray(value); + case 'undefined': + case 'boolean': + case 'number': + return typeof value === type && extension; + case 'string': + return typeof value === type && extension; + case 'alphanumeric': + case 'uuid': + case 'semver': + case 'email': + case 'url': + case 'color': + return typeof value === 'string' && test(value, patterns[type]) && extension; + case 'file': + return typeof value === 'string' && value && (await fs.isFile(value)) && extension; + } + return false; +}; diff --git a/api/fs.cjs b/api/fs.cjs new file mode 100644 index 0000000..376d6a4 --- /dev/null +++ b/api/fs.cjs @@ -0,0 +1,49 @@ +/* + * notion-enhancer core: api + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +'use strict'; + +/** + * environment-specific file reading + * @module notion-enhancer/api/fs + */ + +const fs = require('../env/fs.cjs'); +module.exports = {}; + +/** + * transform a path relative to the enhancer root directory into an absolute path + * @type {function} + * @param {string} path - a url or within-the-enhancer filepath + * @returns {string} an absolute filepath + */ +module.exports.localPath = fs.localPath; + +/** + * fetch and parse a json file's contents + * @type {function} + * @param {string} path - a url or within-the-enhancer filepath + * @param {object} [opts] - the second argument of a fetch() request + * @returns {object} the json value of the requested file as a js object + */ +module.exports.getJSON = fs.getJSON; + +/** + * fetch a text file's contents + * @type {function} + * @param {string} path - a url or within-the-enhancer filepath + * @param {object} [opts] - the second argument of a fetch() request + * @returns {string} the text content of the requested file + */ +module.exports.getText = fs.getText; + +/** + * check if a file exists + * @type {function} + * @param {string} path - a url or within-the-enhancer filepath + * @returns {boolean} whether or not the file exists + */ +module.exports.isFile = fs.isFile; diff --git a/api/fs.mjs b/api/fs.mjs index eeb02df..e821d87 100644 --- a/api/fs.mjs +++ b/api/fs.mjs @@ -7,7 +7,7 @@ 'use strict'; /** - * environment-specific filesystem reading + * environment-specific file reading * @module notion-enhancer/api/fs */ diff --git a/api/registry-validation.cjs b/api/registry-validation.cjs new file mode 100644 index 0000000..6c522e9 --- /dev/null +++ b/api/registry-validation.cjs @@ -0,0 +1,224 @@ +/* + * notion-enhancer core: api + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +'use strict'; + +const { fmt, registry } = require('notion-enhancer/api/_.cjs'); + +const check = async ( + mod, + key, + value, + types, + { + extension = '', + error = `invalid ${key} (${extension ? `${extension} ` : ''}${types}): ${JSON.stringify( + value + )}`, + optional = false, + } = {} +) => { + let test; + for (const type of Array.isArray(types) ? [types] : types.split('|')) { + if (type === 'file') { + test = + value && !value.startsWith('http') + ? await fmt.is(`repo/${mod._dir}/${value}`, type, { extension }) + : false; + } else test = await fmt.is(value, type, { extension }); + if (test) break; + } + if (!test) { + if (optional && (await fmt.is(value, 'undefined'))) return true; + if (error) mod._err(error); + return false; + } + return true; +}; + +const validateEnvironments = async (mod) => { + mod.environments = mod.environments ?? registry.supportedEnvs; + const isArray = await check(mod, 'environments', mod.environments, 'array'); + if (!isArray) return false; + return mod.environments.map((tag) => + check(mod, 'environments.env', tag, registry.supportedEnvs) + ); + }, + validateTags = async (mod) => { + const isArray = await check(mod, 'tags', mod.tags, 'array'); + if (!isArray) return false; + const categoryTags = ['core', 'extension', 'theme', 'integration'], + containsCategory = mod.tags.filter((tag) => categoryTags.includes(tag)).length; + if (!containsCategory) { + mod._err( + `invalid tags (must contain at least one of 'core', 'extension', 'theme' or 'integration'): + ${JSON.stringify(mod.tags)}` + ); + return false; + } + const isTheme = mod.tags.includes('theme'), + hasThemeMode = mod.tags.includes('light') || mod.tags.includes('dark'), + isBothThemeModes = mod.tags.includes('light') && mod.tags.includes('dark'); + if (isTheme && (!hasThemeMode || isBothThemeModes)) { + mod._err( + `invalid tags (themes must be either 'light' or 'dark', not neither or both): + ${JSON.stringify(mod.tags)}` + ); + return false; + } + return mod.tags.map((tag) => check(mod, 'tags.tag', tag, 'string')); + }, + validateAuthors = async (mod) => { + const isArray = await check(mod, 'authors', mod.authors, 'array'); + if (!isArray) return false; + return mod.authors.map((author) => [ + check(mod, 'authors.author.name', author.name, 'string'), + check(mod, 'authors.author.email', author.email, 'email', { optional: true }), + check(mod, 'authors.author.homepage', author.homepage, 'url'), + check(mod, 'authors.author.avatar', author.avatar, 'url'), + ]); + }, + validateCSS = async (mod) => { + const isArray = await check(mod, 'css', mod.css, 'object'); + if (!isArray) return false; + const tests = []; + for (let dest of ['frame', 'client', 'menu']) { + if (!mod.css[dest]) continue; + let test = await check(mod, `css.${dest}`, mod.css[dest], 'array'); + if (test) { + test = mod.css[dest].map((file) => + check(mod, `css.${dest}.file`, file, 'file', { extension: '.css' }) + ); + } + tests.push(test); + } + return tests; + }, + validateJS = async (mod) => { + const isArray = await check(mod, 'js', mod.js, 'object'); + if (!isArray) return false; + const tests = []; + for (let dest of ['frame', 'client', 'menu']) { + if (!mod.js[dest]) continue; + let test = await check(mod, `js.${dest}`, mod.js[dest], 'array'); + if (test) { + test = mod.js[dest].map((file) => + check(mod, `js.${dest}.file`, file, 'file', { extension: '.mjs' }) + ); + } + tests.push(test); + } + if (mod.js.electron) { + const isArray = await check(mod, 'js.electron', mod.js.electron, 'array'); + if (isArray) { + for (const file of mod.js.electron) { + const isObject = await check(mod, 'js.electron.file', file, 'object'); + if (!isObject) { + tests.push(false); + continue; + } + tests.push([ + check(mod, 'js.electron.file.source', file.source, 'file', { + extension: '.mjs', + }), + // referencing the file within the electron app + // existence can't be validated, so only format is + check(mod, 'js.electron.file.target', file.target, 'string', { + extension: '.js', + }), + ]); + } + } else tests.push(false); + } + return tests; + }, + validateOptions = async (mod) => { + const isArray = await check(mod, 'options', mod.options, 'array'); + if (!isArray) return false; + const tests = []; + for (const option of mod.options) { + const key = 'options.option', + optTypeValid = await check(mod, `${key}.type`, option.type, registry.optionTypes); + if (!optTypeValid) { + tests.push(false); + continue; + } + option.environments = option.environments ?? registry.supportedEnvs; + tests.push([ + check(mod, `${key}.key`, option.key, 'alphanumeric'), + check(mod, `${key}.label`, option.label, 'string'), + check(mod, `${key}.tooltip`, option.tooltip, 'string', { + optional: true, + }), + check(mod, `${key}.environments`, option.environments, 'array').then((isArray) => { + if (!isArray) return false; + return option.environments.map((environment) => + check(mod, `${key}.environments.env`, environment, registry.supportedEnvs) + ); + }), + ]); + switch (option.type) { + case 'toggle': + tests.push(check(mod, `${key}.value`, option.value, 'boolean')); + break; + case 'select': { + let test = await check(mod, `${key}.values`, option.values, 'array'); + if (test) { + test = option.values.map((value) => + check(mod, `${key}.values.value`, value, 'string') + ); + } + tests.push(test); + break; + } + case 'text': + case 'hotkey': + tests.push(check(mod, `${key}.value`, option.value, 'string')); + break; + case 'number': + case 'color': + tests.push(check(mod, `${key}.value`, option.value, option.type)); + break; + case 'file': { + let test = await check(mod, `${key}.extensions`, option.extensions, 'array'); + if (test) { + test = option.extensions.map((ext) => + check(mod, `${key}.extensions.extension`, ext, 'string') + ); + } + tests.push(test); + break; + } + } + } + return tests; + }; + +/** + * internally used to validate mod.json files and provide helpful errors + * @private + * @param {object} mod - a mod's mod.json in object form + * @returns {boolean} whether or not the mod has passed validation + */ +module.exports.validate = async function (mod) { + let conditions = [ + check(mod, 'name', mod.name, 'string'), + check(mod, 'id', mod.id, 'uuid'), + check(mod, 'version', mod.version, 'semver'), + validateEnvironments(mod), + check(mod, 'description', mod.description, 'string'), + check(mod, 'preview', mod.preview, 'file|url', { optional: true }), + validateTags(mod), + validateAuthors(mod), + validateCSS(mod), + validateJS(mod), + validateOptions(mod), + ]; + do { + conditions = await Promise.all(conditions.flat(Infinity)); + } while (conditions.some((condition) => Array.isArray(condition))); + return conditions.every((passed) => passed); +}; diff --git a/api/registry.cjs b/api/registry.cjs new file mode 100644 index 0000000..47359c8 --- /dev/null +++ b/api/registry.cjs @@ -0,0 +1,159 @@ +/* + * notion-enhancer core: api + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +'use strict'; + +/** + * interactions with the enhancer's repository of mods + * @module notion-enhancer/api/registry + */ + +const { env, fs, storage } = require('notion-enhancer/api/_.cjs'), + { validate } = require('notion-enhancer/api/registry-validation.cjs'); + +/** + * mod ids whitelisted as part of the enhancer's core, permanently enabled + * @constant + * @type {array} + */ +module.exports.core = [ + 'a6621988-551d-495a-97d8-3c568bca2e9e', + '0f0bf8b6-eae6-4273-b307-8fc43f2ee082', + '36a2ffc9-27ff-480e-84a7-c7700a7d232d', +]; + +/** + * all environments/platforms currently supported by the enhancer + * @constant + * @type {array} + */ +module.exports.supportedEnvs = ['linux', 'win32', 'darwin', 'extension']; + +/** + * all available configuration types + * @constant + * @type {array} + */ +module.exports.optionTypes = ['toggle', 'select', 'text', 'number', 'color', 'file', 'hotkey']; + +/** + * the name of the active configuration profile + * @returns {string} + */ +module.exports.profileName = async () => storage.get(['currentprofile'], 'default'); + +/** + * the root database for the current profile + * @returns {object} the get/set functions for the profile's storage + */ +module.exports.profileDB = async () => storage.db(['profiles', await profileName()]); + +let _list, + _errors = []; +/** + * list all available mods in the repo + * @param {function} filter - a function to filter out mods + * @returns {array} a validated list of mod.json objects + */ +module.exports.list = async (filter = (mod) => true) => { + if (!_list) { + _list = new Promise(async (res, rej) => { + const passed = []; + for (const dir of await fs.getJSON('repo/registry.json')) { + try { + const mod = { + ...(await fs.getJSON(`repo/${dir}/mod.json`)), + _dir: dir, + _err: (message) => _errors.push({ source: dir, message }), + }; + if (await validate(mod)) passed.push(mod); + } catch { + _errors.push({ source: dir, message: 'invalid mod.json' }); + } + } + res(passed); + }); + } + const filtered = []; + for (const mod of await _list) if (await filter(mod)) filtered.push(mod); + return filtered; +}; + +/** + * list validation errors encountered when loading the repo + * @returns {array} error objects with an error message and a source directory + */ +module.exports.errors = async () => { + await list(); + return _errors; +}; + +/** + * get a single mod from the repo + * @param {string} id - the uuid of the mod + * @returns {object} the mod's mod.json + */ +module.exports.get = async (id) => { + return (await list((mod) => mod.id === id))[0]; +}; + +/** + * checks if a mod is enabled: affected by the core whitelist, + * environment and menu configuration + * @param {string} id - the uuid of the mod + * @returns {boolean} whether or not the mod is enabled + */ +module.exports.enabled = async (id) => { + const mod = await get(id); + if (!mod.environments.includes(env.name)) return false; + if (core.includes(id)) return true; + return (await profileDB()).get(['_mods', id], false); +}; + +/** + * get a default value of a mod's option according to its mod.json + * @param {string} id - the uuid of the mod + * @param {string} key - the key of the option + * @returns {string|number|boolean|undefined} the option's default value + */ +module.exports.optionDefault = async (id, key) => { + const mod = await get(id), + opt = mod.options.find((opt) => opt.key === key); + if (!opt) return undefined; + switch (opt.type) { + case 'toggle': + case 'text': + case 'number': + case 'color': + case 'hotkey': + return opt.value; + case 'select': + return opt.values[0]; + case 'file': + return undefined; + } +}; + +/** + * access the storage partition of a mod in the current profile + * @param {string} id - the uuid of the mod + * @returns {object} an object with the wrapped get/set functions + */ +module.exports.db = async (id) => { + const db = await profileDB(); + return storage.db( + [id], + async (path, fallback = undefined) => { + if (typeof path === 'string') path = [path]; + if (path.length === 2) { + // profiles -> profile -> mod -> option + fallback = (await optionDefault(id, path[1])) ?? fallback; + } + return db.get(path, fallback); + }, + db.set + ); +}; diff --git a/api/storage.cjs b/api/storage.cjs new file mode 100644 index 0000000..4556584 --- /dev/null +++ b/api/storage.cjs @@ -0,0 +1,68 @@ +/* + * notion-enhancer core: api + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +'use strict'; + +/** + * environment-specific data persistence + * @module notion-enhancer/api/storage + */ + +const storage = require('../env/storage.cjs'); +module.exports = {}; + +/** + * get persisted data + * @type {function} + * @param {array} path - the path of keys to the value being fetched + * @param {*} [fallback] - a default value if the path is not matched + * @returns {Promise} value ?? fallback + */ +module.exports.get = storage.get; + +/** + * persist data + * @type {function} + * @param {array} path - the path of keys to the value being set + * @param {*} value - the data to save + * @returns {Promise} resolves when data has been saved + */ +module.exports.set = storage.set; + +/** + * create a wrapper for accessing a partition of the storage + * @type {function} + * @param {array} namespace - the path of keys to prefix all storage requests with + * @param {function} [get] - the storage get function to be wrapped + * @param {function} [set] - the storage set function to be wrapped + * @returns {object} an object with the wrapped get/set functions + */ +module.exports.db = storage.db; + +/** + * add an event listener for changes in storage + * @type {function} + * @param {onStorageChangeCallback} callback - called whenever a change in + * storage is initiated from the current process + */ +module.exports.addChangeListener = storage.addChangeListener; + +/** + * remove a listener added with storage.addChangeListener + * @type {function} + * @param {onStorageChangeCallback} callback + */ +module.exports.removeChangeListener = storage.removeChangeListener; + +/** + * @callback onStorageChangeCallback + * @param {object} event + * @param {string} event.type - 'set' or 'reset' + * @param {string} event.namespace- the name of the store, e.g. a mod id + * @param {string} [event.key] - the key associated with the changed value + * @param {string} [event.new] - the new value being persisted to the store + * @param {string} [event.old] - the previous value associated with the key + */ From bf3948f9c51c3c464e0389127abcafa4343d0a79 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Sun, 7 Nov 2021 18:48:38 +1100 Subject: [PATCH 31/52] env.notionRequire --- api/env.cjs | 8 +++++++- api/fmt.cjs | 2 +- api/fs.cjs | 2 +- api/storage.cjs | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/api/env.cjs b/api/env.cjs index c328f19..27a6a49 100644 --- a/api/env.cjs +++ b/api/env.cjs @@ -5,6 +5,7 @@ */ 'use strict'; +module.exports = {}; /** * environment-specific methods and constants @@ -12,7 +13,6 @@ */ const env = require('../env/env.cjs'); -module.exports = {}; /** * the environment/platform name code is currently being executed in @@ -45,3 +45,9 @@ module.exports.focusNotion = env.focusNotion; * @type {function} */ module.exports.reload = env.reload; + +/** + * require() notion app files + * @param {string} path - path from the root of notion/resources/app/ e.g. main/createWindow.js + */ +module.exports.notionRequire = env.notionRequire; diff --git a/api/fmt.cjs b/api/fmt.cjs index 72b125d..dc1dd12 100644 --- a/api/fmt.cjs +++ b/api/fmt.cjs @@ -5,6 +5,7 @@ */ 'use strict'; +module.exports = {}; /** * helpers for formatting or parsing text @@ -12,7 +13,6 @@ */ const fs = require('notion-enhancer/api/fs.cjs'); -module.exports = {}; /** * transform a heading into a slug (a lowercase alphanumeric string separated by dashes), diff --git a/api/fs.cjs b/api/fs.cjs index 376d6a4..ed352ab 100644 --- a/api/fs.cjs +++ b/api/fs.cjs @@ -5,6 +5,7 @@ */ 'use strict'; +module.exports = {}; /** * environment-specific file reading @@ -12,7 +13,6 @@ */ const fs = require('../env/fs.cjs'); -module.exports = {}; /** * transform a path relative to the enhancer root directory into an absolute path diff --git a/api/storage.cjs b/api/storage.cjs index 4556584..47f01eb 100644 --- a/api/storage.cjs +++ b/api/storage.cjs @@ -5,6 +5,7 @@ */ 'use strict'; +module.exports = {}; /** * environment-specific data persistence @@ -12,7 +13,6 @@ */ const storage = require('../env/storage.cjs'); -module.exports = {}; /** * get persisted data From 5cab38d07c73f9552f2780bbfd8eef7b7782c347 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Sun, 7 Nov 2021 22:57:21 +1100 Subject: [PATCH 32/52] require() just before use --- api/fmt.cjs | 8 ++++---- api/registry-validation.cjs | 27 +++++++++++++-------------- api/registry-validation.mjs | 2 +- api/registry.cjs | 35 +++++++++++++++++++++-------------- 4 files changed, 39 insertions(+), 33 deletions(-) diff --git a/api/fmt.cjs b/api/fmt.cjs index dc1dd12..a429b19 100644 --- a/api/fmt.cjs +++ b/api/fmt.cjs @@ -12,8 +12,6 @@ module.exports = {}; * @module notion-enhancer/api/fmt */ -const fs = require('notion-enhancer/api/fs.cjs'); - /** * transform a heading into a slug (a lowercase alphanumeric string separated by dashes), * e.g. for use as an anchor id @@ -132,8 +130,10 @@ module.exports.is = async (value, type, { extension = '' } = {}) => { case 'url': case 'color': return typeof value === 'string' && test(value, patterns[type]) && extension; - case 'file': - return typeof value === 'string' && value && (await fs.isFile(value)) && extension; + case 'file': { + const { isFile } = require('notion-enhancer/api/fs.cjs'); + return typeof value === 'string' && value && (await isFile(value)) && extension; + } } return false; }; diff --git a/api/registry-validation.cjs b/api/registry-validation.cjs index 6c522e9..a0833ad 100644 --- a/api/registry-validation.cjs +++ b/api/registry-validation.cjs @@ -6,8 +6,6 @@ 'use strict'; -const { fmt, registry } = require('notion-enhancer/api/_.cjs'); - const check = async ( mod, key, @@ -21,18 +19,19 @@ const check = async ( optional = false, } = {} ) => { + const { is } = require('notion-enhancer/api/fmt.cjs'); let test; for (const type of Array.isArray(types) ? [types] : types.split('|')) { if (type === 'file') { test = value && !value.startsWith('http') - ? await fmt.is(`repo/${mod._dir}/${value}`, type, { extension }) + ? await is(`repo/${mod._dir}/${value}`, type, { extension }) : false; - } else test = await fmt.is(value, type, { extension }); + } else test = await is(value, type, { extension }); if (test) break; } if (!test) { - if (optional && (await fmt.is(value, 'undefined'))) return true; + if (optional && (await is(value, 'undefined'))) return true; if (error) mod._err(error); return false; } @@ -40,12 +39,11 @@ const check = async ( }; const validateEnvironments = async (mod) => { - mod.environments = mod.environments ?? registry.supportedEnvs; + const { supportedEnvs } = require('notion-enhancer/api/registry.cjs'); + mod.environments = mod.environments ?? supportedEnvs; const isArray = await check(mod, 'environments', mod.environments, 'array'); if (!isArray) return false; - return mod.environments.map((tag) => - check(mod, 'environments.env', tag, registry.supportedEnvs) - ); + return mod.environments.map((tag) => check(mod, 'environments.env', tag, supportedEnvs)); }, validateTags = async (mod) => { const isArray = await check(mod, 'tags', mod.tags, 'array'); @@ -122,7 +120,7 @@ const validateEnvironments = async (mod) => { } tests.push([ check(mod, 'js.electron.file.source', file.source, 'file', { - extension: '.mjs', + extension: '.cjs', }), // referencing the file within the electron app // existence can't be validated, so only format is @@ -136,17 +134,18 @@ const validateEnvironments = async (mod) => { return tests; }, validateOptions = async (mod) => { - const isArray = await check(mod, 'options', mod.options, 'array'); + const { supportedEnvs, optionTypes } = require('notion-enhancer/api/registry.cjs'), + isArray = await check(mod, 'options', mod.options, 'array'); if (!isArray) return false; const tests = []; for (const option of mod.options) { const key = 'options.option', - optTypeValid = await check(mod, `${key}.type`, option.type, registry.optionTypes); + optTypeValid = await check(mod, `${key}.type`, option.type, optionTypes); if (!optTypeValid) { tests.push(false); continue; } - option.environments = option.environments ?? registry.supportedEnvs; + option.environments = option.environments ?? supportedEnvs; tests.push([ check(mod, `${key}.key`, option.key, 'alphanumeric'), check(mod, `${key}.label`, option.label, 'string'), @@ -156,7 +155,7 @@ const validateEnvironments = async (mod) => { check(mod, `${key}.environments`, option.environments, 'array').then((isArray) => { if (!isArray) return false; return option.environments.map((environment) => - check(mod, `${key}.environments.env`, environment, registry.supportedEnvs) + check(mod, `${key}.environments.env`, environment, supportedEnvs) ); }), ]); diff --git a/api/registry-validation.mjs b/api/registry-validation.mjs index 47a8d3f..12b13fb 100644 --- a/api/registry-validation.mjs +++ b/api/registry-validation.mjs @@ -122,7 +122,7 @@ const validateEnvironments = async (mod) => { } tests.push([ check(mod, 'js.electron.file.source', file.source, 'file', { - extension: '.mjs', + extension: '.cjs', }), // referencing the file within the electron app // existence can't be validated, so only format is diff --git a/api/registry.cjs b/api/registry.cjs index 47359c8..ed88ba9 100644 --- a/api/registry.cjs +++ b/api/registry.cjs @@ -11,9 +11,6 @@ * @module notion-enhancer/api/registry */ -const { env, fs, storage } = require('notion-enhancer/api/_.cjs'), - { validate } = require('notion-enhancer/api/registry-validation.cjs'); - /** * mod ids whitelisted as part of the enhancer's core, permanently enabled * @constant @@ -43,13 +40,19 @@ module.exports.optionTypes = ['toggle', 'select', 'text', 'number', 'color', 'fi * the name of the active configuration profile * @returns {string} */ -module.exports.profileName = async () => storage.get(['currentprofile'], 'default'); +module.exports.profileName = async () => { + const storage = require('notion-enhancer/api/storage.cjs'); + return storage.get(['currentprofile'], 'default'); +}; /** * the root database for the current profile * @returns {object} the get/set functions for the profile's storage */ -module.exports.profileDB = async () => storage.db(['profiles', await profileName()]); +module.exports.profileDB = async () => { + const storage = require('notion-enhancer/api/storage.cjs'); + return storage.db(['profiles', await module.exports.profileName()]); +}; let _list, _errors = []; @@ -60,12 +63,14 @@ let _list, */ module.exports.list = async (filter = (mod) => true) => { if (!_list) { + const { validate } = require('notion-enhancer/api/registry-validation.cjs'), + { getJSON } = require('notion-enhancer/api/fs.cjs'); _list = new Promise(async (res, rej) => { const passed = []; - for (const dir of await fs.getJSON('repo/registry.json')) { + for (const dir of await getJSON('repo/registry.json')) { try { const mod = { - ...(await fs.getJSON(`repo/${dir}/mod.json`)), + ...(await getJSON(`repo/${dir}/mod.json`)), _dir: dir, _err: (message) => _errors.push({ source: dir, message }), }; @@ -87,7 +92,7 @@ module.exports.list = async (filter = (mod) => true) => { * @returns {array} error objects with an error message and a source directory */ module.exports.errors = async () => { - await list(); + await module.exports.list(); return _errors; }; @@ -97,7 +102,7 @@ module.exports.errors = async () => { * @returns {object} the mod's mod.json */ module.exports.get = async (id) => { - return (await list((mod) => mod.id === id))[0]; + return (await module.exports.list((mod) => mod.id === id))[0]; }; /** @@ -107,10 +112,11 @@ module.exports.get = async (id) => { * @returns {boolean} whether or not the mod is enabled */ module.exports.enabled = async (id) => { - const mod = await get(id); + const env = require('notion-enhancer/api/env.cjs'), + mod = await module.exports.get(id); if (!mod.environments.includes(env.name)) return false; - if (core.includes(id)) return true; - return (await profileDB()).get(['_mods', id], false); + if (module.exports.core.includes(id)) return true; + return (await module.exports.profileDB()).get(['_mods', id], false); }; /** @@ -143,14 +149,15 @@ module.exports.optionDefault = async (id, key) => { * @returns {object} an object with the wrapped get/set functions */ module.exports.db = async (id) => { - const db = await profileDB(); + const storage = require('notion-enhancer/api/storage.cjs'), + db = await module.exports.profileDB(); return storage.db( [id], async (path, fallback = undefined) => { if (typeof path === 'string') path = [path]; if (path.length === 2) { // profiles -> profile -> mod -> option - fallback = (await optionDefault(id, path[1])) ?? fallback; + fallback = (await module.exports.optionDefault(id, path[1])) ?? fallback; } return db.get(path, fallback); }, From 600862381255e6fb2d317b8d433def74f0935ffb Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Mon, 8 Nov 2021 22:15:35 +1100 Subject: [PATCH 33/52] remove conflict with corner buttons --- api/components/panel.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/components/panel.mjs b/api/components/panel.mjs index 82ce3e3..3613372 100644 --- a/api/components/panel.mjs +++ b/api/components/panel.mjs @@ -215,7 +215,7 @@ async function createPanel() { await createViews(); const cursorListenerSelector = - '.notion-cursor-listener > :last-child[style^="position: absolute"]'; + '.notion-cursor-listener > .notion-sidebar-container ~ [style^="position: absolute"]'; await web.whenReady([cursorListenerSelector]); document.querySelector(cursorListenerSelector).before($hoverTrigger, $panel); } From b6b997ca239511286f94d748f044600a278c5e0c Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Tue, 30 Nov 2021 17:59:48 +1100 Subject: [PATCH 34/52] cleanup folder structure, fix storage callback doc --- api/{ => client}/components/corner-action.css | 0 api/{ => client}/components/corner-action.mjs | 2 +- api/{ => client}/components/feather.mjs | 2 +- .../_.mjs => client/components/index.mjs} | 0 api/{ => client}/components/panel.css | 0 api/{ => client}/components/panel.mjs | 2 +- api/{ => client}/components/tooltip.css | 0 api/{ => client}/components/tooltip.mjs | 2 +- api/{ => client}/env.mjs | 2 +- api/{ => client}/fmt.mjs | 6 +++--- api/{ => client}/fs.mjs | 2 +- api/{ => client}/notion.mjs | 2 +- api/{ => client}/registry-validation.mjs | 2 +- api/{ => client}/registry.mjs | 2 +- api/{ => client}/storage.mjs | 6 ++---- api/{ => client}/web.mjs | 4 ++-- api/{_.cjs => index.cjs} | 10 +++++----- api/{_.mjs => index.mjs} | 16 ++++++++-------- api/{ => node}/env.cjs | 2 +- api/{ => node}/fmt.cjs | 2 +- api/{ => node}/fs.cjs | 2 +- api/{ => node}/registry-validation.cjs | 6 +++--- api/{ => node}/registry.cjs | 12 ++++++------ api/{ => node}/storage.cjs | 6 ++---- 24 files changed, 43 insertions(+), 47 deletions(-) rename api/{ => client}/components/corner-action.css (100%) rename api/{ => client}/components/corner-action.mjs (97%) rename api/{ => client}/components/feather.mjs (96%) rename api/{components/_.mjs => client/components/index.mjs} (100%) rename api/{ => client}/components/panel.css (100%) rename api/{ => client}/components/panel.mjs (99%) rename api/{ => client}/components/tooltip.css (100%) rename api/{ => client}/components/tooltip.mjs (97%) rename api/{ => client}/env.mjs (95%) rename api/{ => client}/fmt.mjs (98%) rename api/{ => client}/fs.mjs (97%) rename api/{ => client}/notion.mjs (99%) rename api/{ => client}/registry-validation.mjs (99%) rename api/{ => client}/registry.mjs (98%) rename api/{ => client}/storage.mjs (88%) rename api/{ => client}/web.mjs (99%) rename api/{_.cjs => index.cjs} (65%) rename api/{_.mjs => index.mjs} (62%) rename api/{ => node}/env.cjs (95%) rename api/{ => node}/fmt.cjs (98%) rename api/{ => node}/fs.cjs (96%) rename api/{ => node}/registry-validation.cjs (97%) rename api/{ => node}/registry.cjs (91%) rename api/{ => node}/storage.cjs (88%) diff --git a/api/components/corner-action.css b/api/client/components/corner-action.css similarity index 100% rename from api/components/corner-action.css rename to api/client/components/corner-action.css diff --git a/api/components/corner-action.mjs b/api/client/components/corner-action.mjs similarity index 97% rename from api/components/corner-action.mjs rename to api/client/components/corner-action.mjs index 3b21cf0..85dbe05 100644 --- a/api/components/corner-action.mjs +++ b/api/client/components/corner-action.mjs @@ -12,7 +12,7 @@ * @module notion-enhancer/api/components/corner-action */ -import { web } from '../_.mjs'; +import { web } from '../../index.mjs'; web.loadStylesheet('api/components/corner-action.css'); diff --git a/api/components/feather.mjs b/api/client/components/feather.mjs similarity index 96% rename from api/components/feather.mjs rename to api/client/components/feather.mjs index 087b3da..61108a7 100644 --- a/api/components/feather.mjs +++ b/api/client/components/feather.mjs @@ -11,7 +11,7 @@ * @module notion-enhancer/api/components/feather */ -import { fs, web } from '../_.mjs'; +import { fs, web } from '../../index.mjs'; let _$iconSheet; diff --git a/api/components/_.mjs b/api/client/components/index.mjs similarity index 100% rename from api/components/_.mjs rename to api/client/components/index.mjs diff --git a/api/components/panel.css b/api/client/components/panel.css similarity index 100% rename from api/components/panel.css rename to api/client/components/panel.css diff --git a/api/components/panel.mjs b/api/client/components/panel.mjs similarity index 99% rename from api/components/panel.mjs rename to api/client/components/panel.mjs index 3613372..d7e9e5f 100644 --- a/api/components/panel.mjs +++ b/api/client/components/panel.mjs @@ -12,7 +12,7 @@ * @module notion-enhancer/api/components/side-panel */ -import { fmt, web, components, registry } from '../_.mjs'; +import { fmt, web, components, registry } from '../../index.mjs'; web.loadStylesheet('api/components/panel.css'); diff --git a/api/components/tooltip.css b/api/client/components/tooltip.css similarity index 100% rename from api/components/tooltip.css rename to api/client/components/tooltip.css diff --git a/api/components/tooltip.mjs b/api/client/components/tooltip.mjs similarity index 97% rename from api/components/tooltip.mjs rename to api/client/components/tooltip.mjs index 83cfd0d..7e55473 100644 --- a/api/components/tooltip.mjs +++ b/api/client/components/tooltip.mjs @@ -11,7 +11,7 @@ * @module notion-enhancer/api/components/tooltip */ -import { fmt, web } from '../_.mjs'; +import { fmt, web } from '../../index.mjs'; const _$tooltip = web.html`
`; web.loadStylesheet('api/components/tooltip.css'); diff --git a/api/env.mjs b/api/client/env.mjs similarity index 95% rename from api/env.mjs rename to api/client/env.mjs index 9cb89ff..91142cf 100644 --- a/api/env.mjs +++ b/api/client/env.mjs @@ -11,7 +11,7 @@ * @module notion-enhancer/api/env */ -import * as env from '../env/env.mjs'; +import * as env from '../../env/env.mjs'; /** * the environment/platform name code is currently being executed in diff --git a/api/fmt.mjs b/api/client/fmt.mjs similarity index 98% rename from api/fmt.mjs rename to api/client/fmt.mjs index 53881f7..a1f94d7 100644 --- a/api/fmt.mjs +++ b/api/client/fmt.mjs @@ -11,9 +11,10 @@ * @module notion-enhancer/api/fmt */ -import { web, fs, components } from './_.mjs'; +import { web, fs, components } from '../index.mjs'; +import '../../dep/prism.min.js'; +import '../../dep/markdown-it.min.js'; -import '../dep/prism.min.js'; /** syntax highlighting using https://prismjs.com/ */ export const prism = Prism; Prism.manual = true; @@ -24,7 +25,6 @@ Prism.hooks.add('complete', async (event) => { .prepend(web.html`${await components.feather('clipboard')}`); }); -import '../dep/markdown-it.min.js'; /** markdown -> html using https://github.com/markdown-it/markdown-it/ */ export const md = new markdownit({ linkify: true, diff --git a/api/fs.mjs b/api/client/fs.mjs similarity index 97% rename from api/fs.mjs rename to api/client/fs.mjs index e821d87..4484085 100644 --- a/api/fs.mjs +++ b/api/client/fs.mjs @@ -11,7 +11,7 @@ * @module notion-enhancer/api/fs */ -import * as fs from '../env/fs.mjs'; +import * as fs from '../../env/fs.mjs'; /** * transform a path relative to the enhancer root directory into an absolute path diff --git a/api/notion.mjs b/api/client/notion.mjs similarity index 99% rename from api/notion.mjs rename to api/client/notion.mjs index cd9a565..9834f90 100644 --- a/api/notion.mjs +++ b/api/client/notion.mjs @@ -11,7 +11,7 @@ * @module notion-enhancer/api/notion */ -import { web, fs, fmt } from './_.mjs'; +import { web, fs, fmt } from '../index.mjs'; const standardiseUUID = (uuid) => { if (uuid?.length === 32 && !uuid.includes('-')) { diff --git a/api/registry-validation.mjs b/api/client/registry-validation.mjs similarity index 99% rename from api/registry-validation.mjs rename to api/client/registry-validation.mjs index 12b13fb..e75e34e 100644 --- a/api/registry-validation.mjs +++ b/api/client/registry-validation.mjs @@ -6,7 +6,7 @@ 'use strict'; -import { fmt, registry } from './_.mjs'; +import { fmt, registry } from '../index.mjs'; const check = async ( mod, diff --git a/api/registry.mjs b/api/client/registry.mjs similarity index 98% rename from api/registry.mjs rename to api/client/registry.mjs index 6fa99a1..eafd41c 100644 --- a/api/registry.mjs +++ b/api/client/registry.mjs @@ -11,7 +11,7 @@ * @module notion-enhancer/api/registry */ -import { env, fs, storage } from './_.mjs'; +import { env, fs, storage } from '../index.mjs'; import { validate } from './registry-validation.mjs'; /** diff --git a/api/storage.mjs b/api/client/storage.mjs similarity index 88% rename from api/storage.mjs rename to api/client/storage.mjs index 17d1ab4..a1d9c66 100644 --- a/api/storage.mjs +++ b/api/client/storage.mjs @@ -11,7 +11,7 @@ * @module notion-enhancer/api/storage */ -import * as storage from '../env/storage.mjs'; +import * as storage from '../../env/storage.mjs'; /** * get persisted data @@ -59,9 +59,7 @@ export const removeChangeListener = storage.removeChangeListener; /** * @callback onStorageChangeCallback * @param {object} event - * @param {string} event.type - 'set' or 'reset' - * @param {string} event.namespace- the name of the store, e.g. a mod id - * @param {string} [event.key] - the key associated with the changed value + * @param {string} event.path- the path of keys to the changed value * @param {string} [event.new] - the new value being persisted to the store * @param {string} [event.old] - the previous value associated with the key */ diff --git a/api/web.mjs b/api/client/web.mjs similarity index 99% rename from api/web.mjs rename to api/client/web.mjs index baa101c..342824c 100644 --- a/api/web.mjs +++ b/api/client/web.mjs @@ -11,14 +11,14 @@ * @module notion-enhancer/api/web */ -import { fs } from './_.mjs'; +import { fs } from '../index.mjs'; +import '../../dep/jscolor.min.js'; let _hotkeyEventListeners = [], _documentObserver, _documentObserverListeners = [], _documentObserverEvents = []; -import '../dep/jscolor.min.js'; /** color picker with alpha channel using https://jscolor.com/ */ export const jscolor = JSColor; diff --git a/api/_.cjs b/api/index.cjs similarity index 65% rename from api/_.cjs rename to api/index.cjs index c1424d7..92d255d 100644 --- a/api/_.cjs +++ b/api/index.cjs @@ -10,13 +10,13 @@ module.exports = { /** environment-specific methods and constants */ - env: require('notion-enhancer/api/env.cjs'), + env: require('notion-enhancer/api/node/env.cjs'), /** environment-specific file reading */ - fs: require('notion-enhancer/api/fs.cjs'), + fs: require('notion-enhancer/api/node/fs.cjs'), /** environment-specific data persistence */ - storage: require('notion-enhancer/api/storage.cjs'), + storage: require('notion-enhancer/api/node/storage.cjs'), /** helpers for formatting, validating and parsing values */ - fmt: require('notion-enhancer/api/fmt.cjs'), + fmt: require('notion-enhancer/api/node/fmt.cjs'), /** interactions with the enhancer's repository of mods */ - registry: require('notion-enhancer/api/registry.cjs'), + registry: require('notion-enhancer/api/node/registry.cjs'), }; diff --git a/api/_.mjs b/api/index.mjs similarity index 62% rename from api/_.mjs rename to api/index.mjs index a81a82a..def92b8 100644 --- a/api/_.mjs +++ b/api/index.mjs @@ -9,19 +9,19 @@ /** @module notion-enhancer/api */ /** environment-specific methods and constants */ -export * as env from './env.mjs'; +export * as env from './client/env.mjs'; /** environment-specific file reading */ -export * as fs from './fs.mjs'; +export * as fs from './client/fs.mjs'; /** environment-specific data persistence */ -export * as storage from './storage.mjs'; +export * as storage from './client/storage.mjs'; /** a basic wrapper around notion's unofficial api */ -export * as notion from './notion.mjs'; +export * as notion from './client/notion.mjs'; /** helpers for formatting, validating and parsing values */ -export * as fmt from './fmt.mjs'; +export * as fmt from './client/fmt.mjs'; /** interactions with the enhancer's repository of mods */ -export * as registry from './registry.mjs'; +export * as registry from './client/registry.mjs'; /** helpers for manipulation of a webpage */ -export * as web from './web.mjs'; +export * as web from './client/web.mjs'; /** shared notion-style elements */ -export * as components from './components/_.mjs'; +export * as components from './client/components/index.mjs'; diff --git a/api/env.cjs b/api/node/env.cjs similarity index 95% rename from api/env.cjs rename to api/node/env.cjs index 27a6a49..536e442 100644 --- a/api/env.cjs +++ b/api/node/env.cjs @@ -12,7 +12,7 @@ module.exports = {}; * @module notion-enhancer/api/env */ -const env = require('../env/env.cjs'); +const env = require('notion-enhancer/env/env.cjs'); /** * the environment/platform name code is currently being executed in diff --git a/api/fmt.cjs b/api/node/fmt.cjs similarity index 98% rename from api/fmt.cjs rename to api/node/fmt.cjs index a429b19..e82716e 100644 --- a/api/fmt.cjs +++ b/api/node/fmt.cjs @@ -131,7 +131,7 @@ module.exports.is = async (value, type, { extension = '' } = {}) => { case 'color': return typeof value === 'string' && test(value, patterns[type]) && extension; case 'file': { - const { isFile } = require('notion-enhancer/api/fs.cjs'); + const { isFile } = require('notion-enhancer/api/node/fs.cjs'); return typeof value === 'string' && value && (await isFile(value)) && extension; } } diff --git a/api/fs.cjs b/api/node/fs.cjs similarity index 96% rename from api/fs.cjs rename to api/node/fs.cjs index ed352ab..d189dcd 100644 --- a/api/fs.cjs +++ b/api/node/fs.cjs @@ -12,7 +12,7 @@ module.exports = {}; * @module notion-enhancer/api/fs */ -const fs = require('../env/fs.cjs'); +const fs = require('notion-enhancer/env/fs.cjs'); /** * transform a path relative to the enhancer root directory into an absolute path diff --git a/api/registry-validation.cjs b/api/node/registry-validation.cjs similarity index 97% rename from api/registry-validation.cjs rename to api/node/registry-validation.cjs index a0833ad..9e385c4 100644 --- a/api/registry-validation.cjs +++ b/api/node/registry-validation.cjs @@ -19,7 +19,7 @@ const check = async ( optional = false, } = {} ) => { - const { is } = require('notion-enhancer/api/fmt.cjs'); + const { is } = require('notion-enhancer/api/node/fmt.cjs'); let test; for (const type of Array.isArray(types) ? [types] : types.split('|')) { if (type === 'file') { @@ -39,7 +39,7 @@ const check = async ( }; const validateEnvironments = async (mod) => { - const { supportedEnvs } = require('notion-enhancer/api/registry.cjs'); + const { supportedEnvs } = require('notion-enhancer/api/node/registry.cjs'); mod.environments = mod.environments ?? supportedEnvs; const isArray = await check(mod, 'environments', mod.environments, 'array'); if (!isArray) return false; @@ -134,7 +134,7 @@ const validateEnvironments = async (mod) => { return tests; }, validateOptions = async (mod) => { - const { supportedEnvs, optionTypes } = require('notion-enhancer/api/registry.cjs'), + const { supportedEnvs, optionTypes } = require('notion-enhancer/api/node/registry.cjs'), isArray = await check(mod, 'options', mod.options, 'array'); if (!isArray) return false; const tests = []; diff --git a/api/registry.cjs b/api/node/registry.cjs similarity index 91% rename from api/registry.cjs rename to api/node/registry.cjs index ed88ba9..9aca6bc 100644 --- a/api/registry.cjs +++ b/api/node/registry.cjs @@ -41,7 +41,7 @@ module.exports.optionTypes = ['toggle', 'select', 'text', 'number', 'color', 'fi * @returns {string} */ module.exports.profileName = async () => { - const storage = require('notion-enhancer/api/storage.cjs'); + const storage = require('notion-enhancer/api/node/storage.cjs'); return storage.get(['currentprofile'], 'default'); }; @@ -50,7 +50,7 @@ module.exports.profileName = async () => { * @returns {object} the get/set functions for the profile's storage */ module.exports.profileDB = async () => { - const storage = require('notion-enhancer/api/storage.cjs'); + const storage = require('notion-enhancer/api/node/storage.cjs'); return storage.db(['profiles', await module.exports.profileName()]); }; @@ -63,8 +63,8 @@ let _list, */ module.exports.list = async (filter = (mod) => true) => { if (!_list) { - const { validate } = require('notion-enhancer/api/registry-validation.cjs'), - { getJSON } = require('notion-enhancer/api/fs.cjs'); + const { validate } = require('notion-enhancer/api/node/registry-validation.cjs'), + { getJSON } = require('notion-enhancer/api/node/fs.cjs'); _list = new Promise(async (res, rej) => { const passed = []; for (const dir of await getJSON('repo/registry.json')) { @@ -112,7 +112,7 @@ module.exports.get = async (id) => { * @returns {boolean} whether or not the mod is enabled */ module.exports.enabled = async (id) => { - const env = require('notion-enhancer/api/env.cjs'), + const env = require('notion-enhancer/api/node/env.cjs'), mod = await module.exports.get(id); if (!mod.environments.includes(env.name)) return false; if (module.exports.core.includes(id)) return true; @@ -149,7 +149,7 @@ module.exports.optionDefault = async (id, key) => { * @returns {object} an object with the wrapped get/set functions */ module.exports.db = async (id) => { - const storage = require('notion-enhancer/api/storage.cjs'), + const storage = require('notion-enhancer/api/node/storage.cjs'), db = await module.exports.profileDB(); return storage.db( [id], diff --git a/api/storage.cjs b/api/node/storage.cjs similarity index 88% rename from api/storage.cjs rename to api/node/storage.cjs index 47f01eb..db30e53 100644 --- a/api/storage.cjs +++ b/api/node/storage.cjs @@ -12,7 +12,7 @@ module.exports = {}; * @module notion-enhancer/api/storage */ -const storage = require('../env/storage.cjs'); +const storage = require('notion-enhancer/env/storage.cjs'); /** * get persisted data @@ -60,9 +60,7 @@ module.exports.removeChangeListener = storage.removeChangeListener; /** * @callback onStorageChangeCallback * @param {object} event - * @param {string} event.type - 'set' or 'reset' - * @param {string} event.namespace- the name of the store, e.g. a mod id - * @param {string} [event.key] - the key associated with the changed value + * @param {string} event.path- the path of keys to the changed value * @param {string} [event.new] - the new value being persisted to the store * @param {string} [event.old] - the previous value associated with the key */ From 8afc70da95574c94942b91e90815bd9c5d2e469b Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Tue, 30 Nov 2021 18:13:39 +1100 Subject: [PATCH 35/52] update component stylesheet paths --- api/client/components/corner-action.mjs | 2 +- api/client/components/panel.mjs | 2 +- api/client/components/tooltip.mjs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/client/components/corner-action.mjs b/api/client/components/corner-action.mjs index 85dbe05..26abc63 100644 --- a/api/client/components/corner-action.mjs +++ b/api/client/components/corner-action.mjs @@ -14,7 +14,7 @@ import { web } from '../../index.mjs'; -web.loadStylesheet('api/components/corner-action.css'); +web.loadStylesheet('api/client/components/corner-action.css'); const $cornerButtonsContainer = web.html`
`; diff --git a/api/client/components/panel.mjs b/api/client/components/panel.mjs index d7e9e5f..d08bb6d 100644 --- a/api/client/components/panel.mjs +++ b/api/client/components/panel.mjs @@ -14,7 +14,7 @@ import { fmt, web, components, registry } from '../../index.mjs'; -web.loadStylesheet('api/components/panel.css'); +web.loadStylesheet('api/client/components/panel.css'); const _views = [], svgExpand = web.raw` diff --git a/api/client/components/tooltip.mjs b/api/client/components/tooltip.mjs index 7e55473..7abf709 100644 --- a/api/client/components/tooltip.mjs +++ b/api/client/components/tooltip.mjs @@ -14,7 +14,7 @@ import { fmt, web } from '../../index.mjs'; const _$tooltip = web.html`
`; -web.loadStylesheet('api/components/tooltip.css'); +web.loadStylesheet('api/client/components/tooltip.css'); /** * add a tooltip to show extra information on hover From 461533fa07c276a3a98a1b7182bc15fda8969ddd Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Tue, 30 Nov 2021 20:44:04 +1100 Subject: [PATCH 36/52] push mit license, corner action color vars --- api/LICENSE | 21 +++++++++++++++++++++ api/client/components/corner-action.css | 14 ++++++++------ api/client/components/corner-action.mjs | 2 +- api/client/components/feather.mjs | 2 +- api/client/components/index.mjs | 2 +- api/client/components/panel.css | 2 +- api/client/components/panel.mjs | 2 +- api/client/components/tooltip.css | 2 +- api/client/components/tooltip.mjs | 2 +- 9 files changed, 36 insertions(+), 13 deletions(-) create mode 100644 api/LICENSE diff --git a/api/LICENSE b/api/LICENSE new file mode 100644 index 0000000..b503961 --- /dev/null +++ b/api/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 dragonwocky (https://dragonwocky.me/) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/api/client/components/corner-action.css b/api/client/components/corner-action.css index 9b66eb9..5479dbc 100644 --- a/api/client/components/corner-action.css +++ b/api/client/components/corner-action.css @@ -1,5 +1,5 @@ /* - * notion-enhancer core: api + * notion-enhancer core: components * (c) 2021 dragonwocky (https://dragonwocky.me/) * (c) 2021 CloudHill (https://github.com/CloudHill) * (https://notion-enhancer.github.io/) under the MIT license @@ -28,7 +28,9 @@ display: flex; align-items: center; justify-content: center; - background: var(--theme--tag_default) !important; + color: var(--theme--icon); + fill: var(--theme--icon); + background: var(--theme--ui_corner_action) !important; box-shadow: var(--theme--ui_shadow, rgba(15, 15, 15, 0.15)) 0px 0px 0px 1px, var(--theme--ui_shadow, rgba(15, 15, 15, 0.15)) 0px 2px 4px !important; @@ -36,10 +38,10 @@ cursor: pointer; } #enhancer--corner-actions > div:hover { - background: var(--theme--ui_interactive-hover); + background: var(--theme--ui_corner_action-hover) !important; } #enhancer--corner-actions > div:active { - background: var(--theme--ui_interactive-active); + background: var(--theme--ui_corner_action-active) !important; } #enhancer--corner-actions > div.hidden { display: none; @@ -48,6 +50,6 @@ #enhancer--corner-actions > div > svg { width: 22px; height: 22px; - color: var(--theme--tag_default-text); - fill: var(--theme--tag_default-text); + color: var(--theme--icon); + fill: var(--theme--icon); } diff --git a/api/client/components/corner-action.mjs b/api/client/components/corner-action.mjs index 26abc63..9f05c39 100644 --- a/api/client/components/corner-action.mjs +++ b/api/client/components/corner-action.mjs @@ -1,5 +1,5 @@ /* - * notion-enhancer core: api + * notion-enhancer core: components * (c) 2021 dragonwocky (https://dragonwocky.me/) * (c) 2021 CloudHill (https://github.com/CloudHill) * (https://notion-enhancer.github.io/) under the MIT license diff --git a/api/client/components/feather.mjs b/api/client/components/feather.mjs index 61108a7..97381f5 100644 --- a/api/client/components/feather.mjs +++ b/api/client/components/feather.mjs @@ -1,5 +1,5 @@ /* - * notion-enhancer core: api + * notion-enhancer core: components * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ diff --git a/api/client/components/index.mjs b/api/client/components/index.mjs index 2119cd9..43c85e4 100644 --- a/api/client/components/index.mjs +++ b/api/client/components/index.mjs @@ -1,5 +1,5 @@ /* - * notion-enhancer core: api + * notion-enhancer core: components * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ diff --git a/api/client/components/panel.css b/api/client/components/panel.css index 61acbd4..ef72b8d 100644 --- a/api/client/components/panel.css +++ b/api/client/components/panel.css @@ -1,5 +1,5 @@ /* - * notion-enhancer core: api + * notion-enhancer core: components * (c) 2021 dragonwocky (https://dragonwocky.me/) * (c) 2021 CloudHill (https://github.com/CloudHill) * (https://notion-enhancer.github.io/) under the MIT license diff --git a/api/client/components/panel.mjs b/api/client/components/panel.mjs index d08bb6d..d9b5914 100644 --- a/api/client/components/panel.mjs +++ b/api/client/components/panel.mjs @@ -1,5 +1,5 @@ /* - * notion-enhancer core: api + * notion-enhancer core: components * (c) 2021 dragonwocky (https://dragonwocky.me/) * (c) 2021 CloudHill (https://github.com/CloudHill) * (https://notion-enhancer.github.io/) under the MIT license diff --git a/api/client/components/tooltip.css b/api/client/components/tooltip.css index 9f2bbcf..588b960 100644 --- a/api/client/components/tooltip.css +++ b/api/client/components/tooltip.css @@ -1,5 +1,5 @@ /* - * notion-enhancer core: api + * notion-enhancer core: components * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ diff --git a/api/client/components/tooltip.mjs b/api/client/components/tooltip.mjs index 7abf709..d80f34b 100644 --- a/api/client/components/tooltip.mjs +++ b/api/client/components/tooltip.mjs @@ -1,5 +1,5 @@ /* - * notion-enhancer core: api + * notion-enhancer core: components * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ From 39f66c5bb8b468d77ac44e9bcdd58f7fc62d1d44 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Thu, 2 Dec 2021 17:57:37 +1100 Subject: [PATCH 37/52] fix cjs internal reference to registry.get --- api/node/registry.cjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/node/registry.cjs b/api/node/registry.cjs index 9aca6bc..532705a 100644 --- a/api/node/registry.cjs +++ b/api/node/registry.cjs @@ -126,7 +126,7 @@ module.exports.enabled = async (id) => { * @returns {string|number|boolean|undefined} the option's default value */ module.exports.optionDefault = async (id, key) => { - const mod = await get(id), + const mod = await module.exports.get(id), opt = mod.options.find((opt) => opt.key === key); if (!opt) return undefined; switch (opt.type) { From 7abbb779f575dac0d9b25908337f5600a2370610 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Sat, 4 Dec 2021 17:36:08 +1100 Subject: [PATCH 38/52] add api.electron to access __enhancerElectronApi --- api/client/electron.mjs | 48 +++++++++++++++++++++++++++++++++++++++++ api/index.mjs | 3 +++ 2 files changed, 51 insertions(+) create mode 100644 api/client/electron.mjs diff --git a/api/client/electron.mjs b/api/client/electron.mjs new file mode 100644 index 0000000..bff73ce --- /dev/null +++ b/api/client/electron.mjs @@ -0,0 +1,48 @@ +/* + * notion-enhancer core: api + * (c) 2021 dragonwocky (https://dragonwocky.me/) + * (https://notion-enhancer.github.io/) under the MIT license + */ + +'use strict'; + +/** + * access to electron renderer apis + * @module notion-enhancer/api/env + */ + +/** + * access to the electron BrowserWindow instance for the current window + * see https://www.electronjs.org/docs/latest/api/browser-window + * @type {BrowserWindow} + */ +export const browser = window.__enhancerElectronApi?.browser; + +/** + * access to the electron webFrame instance for the current page + * see https://www.electronjs.org/docs/latest/api/web-frame + * @type {webFrame} + */ +export const webFrame = window.__enhancerElectronApi?.webFrame; + +/** + * send a message to the main electron process + * @param {string} channel - the message identifier + * @param {any} data - the data to pass along with the message + */ +export const sendMessage = window.__enhancerElectronApi?.sendMessage; + +/** + * send a message to the webview's parent renderer process + * @param {string} channel - the message identifier + * @param {any} data - the data to pass along with the message + */ +export const sendMessageToHost = window.__enhancerElectronApi?.sendMessageToHost; + +/** + * receive a message from either the main process or + * the webview's parent renderer process + * @param {string} channel - the message identifier to listen for + * @param {function} listener - the message handler, passed the args (event, data) + */ +export const onMessage = window.__enhancerElectronApi?.onMessage; diff --git a/api/index.mjs b/api/index.mjs index def92b8..a53a104 100644 --- a/api/index.mjs +++ b/api/index.mjs @@ -15,6 +15,9 @@ export * as fs from './client/fs.mjs'; /** environment-specific data persistence */ export * as storage from './client/storage.mjs'; +/** access to electron renderer apis */ +export * as electron from './client/electron.mjs'; + /** a basic wrapper around notion's unofficial api */ export * as notion from './client/notion.mjs'; /** helpers for formatting, validating and parsing values */ From 24bc8bda6611d7da3b706b795e68936bfa80aa46 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Sun, 5 Dec 2021 17:52:50 +1100 Subject: [PATCH 39/52] overhauled tooltips: directional positioning, wrapping to max lines --- api/client/components/index.mjs | 13 +++-- api/client/components/tooltip.css | 5 +- api/client/components/tooltip.mjs | 92 +++++++++++++++++++++++++------ 3 files changed, 87 insertions(+), 23 deletions(-) diff --git a/api/client/components/index.mjs b/api/client/components/index.mjs index 43c85e4..203a359 100644 --- a/api/client/components/index.mjs +++ b/api/client/components/index.mjs @@ -14,11 +14,16 @@ /** * add a tooltip to show extra information on hover * @param {HTMLElement} $ref - the element that will trigger the tooltip when hovered - * @param {string|HTMLElement} $text - the markdown content of the tooltip - * @param {number} [delay] - the amount of time the element needs to be hovered over - * for the tooltip to be shown + * @param {string|HTMLElement} $content - markdown or element content of the tooltip + * @param {object=} [options] - configuration of how the tooltip should be displayed + * @param {number} [options.delay] - the amount of time in ms the element needs to be hovered over + * for the tooltip to be shown (default: 100) + * @param {string} [options.offsetDirection] - which side of the element the tooltip + * should be shown on: 'top', 'bottom', 'left' or 'right' (default: 'bottom') + * @param {number} [options.maxLines] - the max number of lines that the content may be wrapped + * to, used to position and size the tooltip correctly (default: 1) */ -export { setTooltip } from './tooltip.mjs'; +export { tooltip } from './tooltip.mjs'; /** * generate an icon from the feather icons set diff --git a/api/client/components/tooltip.css b/api/client/components/tooltip.css index 588b960..c05c1d2 100644 --- a/api/client/components/tooltip.css +++ b/api/client/components/tooltip.css @@ -16,13 +16,14 @@ line-height: 1.4; max-width: 20rem; overflow: hidden; - padding: 2px 8px 4px 8px; + padding: 4px 8px; position: absolute; z-index: 999999999999999999; pointer-events: none; + text-align: center; } #enhancer--tooltip p { - margin: 0.25rem 0; + margin: 0; } #enhancer--tooltip b, #enhancer--tooltip strong { diff --git a/api/client/components/tooltip.mjs b/api/client/components/tooltip.mjs index d80f34b..643ffb8 100644 --- a/api/client/components/tooltip.mjs +++ b/api/client/components/tooltip.mjs @@ -15,38 +15,96 @@ import { fmt, web } from '../../index.mjs'; const _$tooltip = web.html`
`; web.loadStylesheet('api/client/components/tooltip.css'); +web.render(document.body, _$tooltip); + +const countLines = ($el) => + [...$el.getClientRects()].reduce( + (prev, val) => (prev.some((p) => p.y === val.y) ? prev : [...prev, val]), + [] + ).length, + position = async ($ref, offsetDirection, maxLines) => { + _$tooltip.style.top = `0px`; + _$tooltip.style.left = `0px`; + const rect = $ref.getBoundingClientRect(), + { offsetWidth, offsetHeight } = _$tooltip, + pad = 6; + let x = rect.x, + y = Math.floor(rect.y); + + if (['top', 'bottom'].includes(offsetDirection)) { + if (offsetDirection === 'top') y -= offsetHeight + pad; + if (offsetDirection === 'bottom') y += rect.height + pad; + x -= offsetWidth / 2 - rect.width / 2; + _$tooltip.style.left = `${x}px`; + _$tooltip.style.top = `${y}px`; + const testLines = () => countLines(_$tooltip.firstElementChild) > maxLines, + padEdgesX = testLines(); + while (testLines()) { + _$tooltip.style.left = `${window.innerWidth - x > x ? x++ : x--}px`; + } + if (padEdgesX) { + x += window.innerWidth - x > x ? pad : -pad; + _$tooltip.style.left = `${x}px`; + } + } + + if (['left', 'right'].includes(offsetDirection)) { + y -= offsetHeight / 2 - rect.height / 2; + if (offsetDirection === 'left') x -= offsetWidth + pad; + if (offsetDirection === 'right') x += rect.width + pad; + _$tooltip.style.left = `${x}px`; + _$tooltip.style.top = `${y}px`; + } + + return true; + }; /** * add a tooltip to show extra information on hover * @param {HTMLElement} $ref - the element that will trigger the tooltip when hovered - * @param {string|HTMLElement} $text - the markdown content of the tooltip - * @param {number} [delay] - the amount of time the element needs to be hovered over - * for the tooltip to be shown + * @param {string|HTMLElement} $content - markdown or element content of the tooltip + * @param {object=} [options] - configuration of how the tooltip should be displayed + * @param {number} [options.delay] - the amount of time in ms the element needs to be hovered over + * for the tooltip to be shown (default: 100) + * @param {string} [options.offsetDirection] - which side of the element the tooltip + * should be shown on: 'top', 'bottom', 'left' or 'right' (default: 'bottom') + * @param {number} [options.maxLines] - the max number of lines that the content may be wrapped + * to, used to position and size the tooltip correctly (default: 1) */ -export const setTooltip = ($ref, $text, delay = 100) => { - web.render(document.body, _$tooltip); - if (!($text instanceof Element)) $text = web.html`${fmt.md.render($text)}`; +export const tooltip = ( + $ref, + $content, + { delay = 100, offsetDirection = 'bottom', maxLines = 1 } = {} +) => { + if (!($content instanceof Element)) + $content = web.html`
+ ${$content + .split('\n') + .map((text) => fmt.md.renderInline(text)) + .join('
')} +
`; + let displayDelay; - $ref.addEventListener('mouseover', (event) => { - web.render(web.empty(_$tooltip), $text); + $ref.addEventListener('mouseover', async (event) => { if (!displayDelay) { displayDelay = setTimeout(async () => { if ($ref.matches(':hover')) { - _$tooltip.style.display = 'block'; - _$tooltip.style.top = event.clientY - _$tooltip.clientHeight + 'px'; - _$tooltip.style.left = event.clientX - _$tooltip.clientWidth + 'px'; - await _$tooltip.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 65 }).finished; + if (_$tooltip.style.display !== 'block') { + _$tooltip.style.display = 'block'; + web.render(web.empty(_$tooltip), $content); + position($ref, offsetDirection, maxLines); + await _$tooltip.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 65 }) + .finished; + } } displayDelay = undefined; }, delay); } }); - $ref.addEventListener('mousemove', (event) => { - _$tooltip.style.top = event.clientY - _$tooltip.clientHeight + 'px'; - _$tooltip.style.left = event.clientX - _$tooltip.clientWidth + 'px'; - }); + $ref.addEventListener('mouseout', async (event) => { - if (!$ref.matches(':hover')) { + displayDelay = undefined; + if (_$tooltip.style.display === 'block' && !$ref.matches(':hover')) { await _$tooltip.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 65 }).finished; _$tooltip.style.display = ''; } From b770eb64f0079aeacae7bc411f76bc0b30e94e5d Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Tue, 7 Dec 2021 22:12:46 +1100 Subject: [PATCH 40/52] use esbuild to compile .mjs to .cjs --- api/client/fmt.mjs | 185 --- api/{client => }/components/corner-action.css | 4 +- api/{client => }/components/corner-action.mjs | 15 +- api/{client => }/components/feather.mjs | 6 +- api/{client => }/components/index.mjs | 6 +- api/{client => }/components/panel.css | 4 +- api/{client => }/components/panel.mjs | 65 +- api/{client => }/components/tooltip.css | 4 +- api/{client => }/components/tooltip.mjs | 23 +- api/{client => }/electron.mjs | 45 +- api/{client => }/env.mjs | 17 +- api/{node/fmt.cjs => fmt.mjs} | 23 +- api/{client => }/fs.mjs | 6 +- api/index.cjs | 1062 ++++++++++++++++- api/index.mjs | 25 +- api/node/env.cjs | 53 - api/node/fs.cjs | 49 - api/node/registry-validation.cjs | 223 ---- api/node/registry.cjs | 166 --- api/node/storage.cjs | 66 - api/{client => }/notion.mjs | 6 +- api/{client => }/registry-validation.mjs | 6 +- api/{client => }/registry.mjs | 6 +- api/{client => }/storage.mjs | 6 +- api/{client => }/web.mjs | 51 +- 25 files changed, 1233 insertions(+), 889 deletions(-) delete mode 100644 api/client/fmt.mjs rename api/{client => }/components/corner-action.css (97%) rename api/{client => }/components/corner-action.mjs (81%) rename api/{client => }/components/feather.mjs (92%) rename api/{client => }/components/index.mjs (96%) rename api/{client => }/components/panel.css (99%) rename api/{client => }/components/panel.mjs (88%) rename api/{client => }/components/tooltip.css (95%) rename api/{client => }/components/tooltip.mjs (87%) rename api/{client => }/electron.mjs (58%) rename api/{client => }/env.mjs (73%) rename api/{node/fmt.cjs => fmt.mjs} (89%) rename api/{client => }/fs.mjs (94%) delete mode 100644 api/node/env.cjs delete mode 100644 api/node/fs.cjs delete mode 100644 api/node/registry-validation.cjs delete mode 100644 api/node/registry.cjs delete mode 100644 api/node/storage.cjs rename api/{client => }/notion.mjs (99%) rename api/{client => }/registry-validation.mjs (98%) rename api/{client => }/registry.mjs (98%) rename api/{client => }/storage.mjs (95%) rename api/{client => }/web.mjs (91%) diff --git a/api/client/fmt.mjs b/api/client/fmt.mjs deleted file mode 100644 index a1f94d7..0000000 --- a/api/client/fmt.mjs +++ /dev/null @@ -1,185 +0,0 @@ -/* - * notion-enhancer core: api - * (c) 2021 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -'use strict'; - -/** - * helpers for formatting or parsing text - * @module notion-enhancer/api/fmt - */ - -import { web, fs, components } from '../index.mjs'; -import '../../dep/prism.min.js'; -import '../../dep/markdown-it.min.js'; - -/** syntax highlighting using https://prismjs.com/ */ -export const prism = Prism; -Prism.manual = true; -Prism.hooks.add('complete', async (event) => { - event.element.parentElement.removeAttribute('tabindex'); - event.element.parentElement.parentElement - .querySelector('.copy-to-clipboard-button') - .prepend(web.html`${await components.feather('clipboard')}`); -}); - -/** markdown -> html using https://github.com/markdown-it/markdown-it/ */ -export const md = new markdownit({ - linkify: true, - highlight: (str, lang) => - web.html`
${web.escape(
-      str
-    )}
`, -}); -md.renderer.rules.code_block = (tokens, idx, options, env, slf) => { - const attrIdx = tokens[idx].attrIndex('class'); - if (attrIdx === -1) { - tokens[idx].attrPush(['class', 'match-braces language-plaintext']); - } else tokens[idx].attrs[attrIdx][1] = 'match-braces language-plaintext'; - return web.html`${web.escape( - tokens[idx].content - )}\n`; -}; -md.core.ruler.push( - 'heading_ids', - function (md, state) { - const slugs = new Set(); - state.tokens.forEach(function (token, i) { - if (token.type === 'heading_open') { - const text = md.renderer.render(state.tokens[i + 1].children, md.options), - slug = slugger(text, slugs); - slugs.add(slug); - const attrIdx = token.attrIndex('id'); - if (attrIdx === -1) { - token.attrPush(['id', slug]); - } else token.attrs[attrIdx][1] = slug; - } - }); - }.bind(null, md) -); - -/** - * transform a heading into a slug (a lowercase alphanumeric string separated by dashes), - * e.g. for use as an anchor id - * @param {string} heading - the original heading to be slugified - * @param {Set} [slugs] - a list of pre-generated slugs to avoid duplicates - * @returns {string} the generated slug - */ -export const slugger = (heading, slugs = new Set()) => { - heading = heading - .replace(/\s/g, '-') - .replace(/[^A-Za-z0-9-_]/g, '') - .toLowerCase(); - let i = 0, - slug = heading; - while (slugs.has(slug)) { - i++; - slug = `${heading}-${i}`; - } - return slug; -}; - -/** - * generate a reasonably random uuidv4 string. uses crypto implementation if available - * (from https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid) - * @returns {string} a uuidv4 - */ -export const uuidv4 = () => { - if (crypto?.randomUUID) return crypto.randomUUID(); - return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => - (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16) - ); -}; - -/** - * log-based shading of an rgb color, from - * https://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors - * @param {number} p - a decimal amount to shade the color. - * 1 = white, 0 = the original color, -1 = black - * @param {string} c - the rgb color - * @returns {string} the shaded color - */ -export const rgbLogShade = (p, c) => { - var i = parseInt, - r = Math.round, - [a, b, c, d] = c.split(','), - P = p < 0, - t = P ? 0 : p * 255 ** 2, - P = P ? 1 + p : 1 - p; - return ( - 'rgb' + - (d ? 'a(' : '(') + - r((P * i(a[3] == 'a' ? a.slice(5) : a.slice(4)) ** 2 + t) ** 0.5) + - ',' + - r((P * i(b) ** 2 + t) ** 0.5) + - ',' + - r((P * i(c) ** 2 + t) ** 0.5) + - (d ? ',' + d : ')') - ); -}; - -/** - * pick a contrasting color e.g. for text on a variable color background - * using the hsp (perceived brightness) constants from http://alienryderflex.com/hsp.html - * @param {number} r - red (0-255) - * @param {number} g - green (0-255) - * @param {number} b - blue (0-255) - * @returns {string} the contrasting rgb color, white or black - */ -export const rgbContrast = (r, g, b) => { - return Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b)) > 165.75 - ? 'rgb(0,0,0)' - : 'rgb(255,255,255)'; -}; - -const patterns = { - alphanumeric: /^[\w\.-]+$/, - uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, - semver: - /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i, - email: - /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i, - url: /^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,64}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i, - color: /^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i, -}; -function test(str, pattern) { - const match = str.match(pattern); - return !!(match && match.length); -} - -/** - * test the type of a value. unifies builtin, regex, and environment/api checks - * @param {*} value - the value to check - * @param {string|array} type - the type the value should be or a list of allowed values - * @returns {boolean} whether or not the value matches the type - */ -export const is = async (value, type, { extension = '' } = {}) => { - extension = !value || !value.endsWith || value.endsWith(extension); - if (Array.isArray(type)) { - return type.includes(value); - } - switch (type) { - case 'array': - return Array.isArray(value); - case 'object': - return value && typeof value === 'object' && !Array.isArray(value); - case 'undefined': - case 'boolean': - case 'number': - return typeof value === type && extension; - case 'string': - return typeof value === type && extension; - case 'alphanumeric': - case 'uuid': - case 'semver': - case 'email': - case 'url': - case 'color': - return typeof value === 'string' && test(value, patterns[type]) && extension; - case 'file': - return typeof value === 'string' && value && (await fs.isFile(value)) && extension; - } - return false; -}; diff --git a/api/client/components/corner-action.css b/api/components/corner-action.css similarity index 97% rename from api/client/components/corner-action.css rename to api/components/corner-action.css index 5479dbc..fdff4ac 100644 --- a/api/client/components/corner-action.css +++ b/api/components/corner-action.css @@ -1,5 +1,5 @@ -/* - * notion-enhancer core: components +/** + * notion-enhancer: components * (c) 2021 dragonwocky (https://dragonwocky.me/) * (c) 2021 CloudHill (https://github.com/CloudHill) * (https://notion-enhancer.github.io/) under the MIT license diff --git a/api/client/components/corner-action.mjs b/api/components/corner-action.mjs similarity index 81% rename from api/client/components/corner-action.mjs rename to api/components/corner-action.mjs index 9f05c39..2ebf808 100644 --- a/api/client/components/corner-action.mjs +++ b/api/components/corner-action.mjs @@ -1,5 +1,5 @@ -/* - * notion-enhancer core: components +/** + * notion-enhancer: components * (c) 2021 dragonwocky (https://dragonwocky.me/) * (c) 2021 CloudHill (https://github.com/CloudHill) * (https://notion-enhancer.github.io/) under the MIT license @@ -12,11 +12,9 @@ * @module notion-enhancer/api/components/corner-action */ -import { web } from '../../index.mjs'; +import { web } from '../index.mjs'; -web.loadStylesheet('api/client/components/corner-action.css'); - -const $cornerButtonsContainer = web.html`
`; +let $stylesheet, $cornerButtonsContainer; /** * adds a button to notion's bottom right corner @@ -25,6 +23,11 @@ const $cornerButtonsContainer = web.html`
{ + if (!$stylesheet) { + $stylesheet = web.loadStylesheet('api/components/corner-action.css'); + $cornerButtonsContainer = web.html`
`; + } + await web.whenReady(['.notion-help-button']); const $helpButton = document.querySelector('.notion-help-button'), $onboardingButton = document.querySelector('.onboarding-checklist-button'); diff --git a/api/client/components/feather.mjs b/api/components/feather.mjs similarity index 92% rename from api/client/components/feather.mjs rename to api/components/feather.mjs index 97381f5..6e2350f 100644 --- a/api/client/components/feather.mjs +++ b/api/components/feather.mjs @@ -1,5 +1,5 @@ -/* - * notion-enhancer core: components +/** + * notion-enhancer: components * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ @@ -11,7 +11,7 @@ * @module notion-enhancer/api/components/feather */ -import { fs, web } from '../../index.mjs'; +import { fs, web } from '../index.mjs'; let _$iconSheet; diff --git a/api/client/components/index.mjs b/api/components/index.mjs similarity index 96% rename from api/client/components/index.mjs rename to api/components/index.mjs index 203a359..ab690bd 100644 --- a/api/client/components/index.mjs +++ b/api/components/index.mjs @@ -1,5 +1,5 @@ -/* - * notion-enhancer core: components +/** + * notion-enhancer: components * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ @@ -23,7 +23,7 @@ * @param {number} [options.maxLines] - the max number of lines that the content may be wrapped * to, used to position and size the tooltip correctly (default: 1) */ -export { tooltip } from './tooltip.mjs'; +export { addTooltip } from './tooltip.mjs'; /** * generate an icon from the feather icons set diff --git a/api/client/components/panel.css b/api/components/panel.css similarity index 99% rename from api/client/components/panel.css rename to api/components/panel.css index ef72b8d..547d484 100644 --- a/api/client/components/panel.css +++ b/api/components/panel.css @@ -1,5 +1,5 @@ -/* - * notion-enhancer core: components +/** + * notion-enhancer: components * (c) 2021 dragonwocky (https://dragonwocky.me/) * (c) 2021 CloudHill (https://github.com/CloudHill) * (https://notion-enhancer.github.io/) under the MIT license diff --git a/api/client/components/panel.mjs b/api/components/panel.mjs similarity index 88% rename from api/client/components/panel.mjs rename to api/components/panel.mjs index d9b5914..73225d6 100644 --- a/api/client/components/panel.mjs +++ b/api/components/panel.mjs @@ -1,5 +1,5 @@ -/* - * notion-enhancer core: components +/** + * notion-enhancer: components * (c) 2021 dragonwocky (https://dragonwocky.me/) * (c) 2021 CloudHill (https://github.com/CloudHill) * (https://notion-enhancer.github.io/) under the MIT license @@ -12,37 +12,43 @@ * @module notion-enhancer/api/components/side-panel */ -import { fmt, web, components, registry } from '../../index.mjs'; - -web.loadStylesheet('api/client/components/panel.css'); +import { web, components, registry } from '../index.mjs'; const _views = [], svgExpand = web.raw` - -`; + + `; -let db, +let $stylesheet, + db, // open + close $notionFrame, $notionRightSidebar, + $panel, + $hoverTrigger, // resize + $resizeHandle, dragStartX, dragStartWidth, dragEventsFired, panelWidth, // render content $notionApp, - $pinnedToggle; + $pinnedToggle, + $panelTitle, + $header, + $panelContent, + $switcher, + $switcherTrigger, + $switcherOverlayContainer; // open + close -const $panel = web.html`
`, - $hoverTrigger = web.html`
`, - panelPinnedAttr = 'data-enhancer-panel-pinned', +const panelPinnedAttr = 'data-enhancer-panel-pinned', isPinned = () => $panel.hasAttribute(panelPinnedAttr), togglePanel = () => { const $elems = [$notionFrame, $notionRightSidebar, $hoverTrigger, $panel].filter( @@ -57,7 +63,6 @@ const $panel = web.html`
`, db.set(['panel.pinned'], isPinned()); }, // resize - $resizeHandle = web.html`
`, updateWidth = async () => { document.documentElement.style.setProperty('--component--panel-width', panelWidth + 'px'); db.set(['panel.width'], panelWidth); @@ -91,14 +96,6 @@ const $panel = web.html`
`, document.body.addEventListener('mouseup', resizeEnd); }, // render content - $panelTitle = web.html`
`, - $header = web.render(web.html`
`, $panelTitle), - $panelContent = web.html`
`, - $switcher = web.html`
`, - $switcherTrigger = web.html`
- ${svgExpand} -
`, - $switcherOverlayContainer = web.html`
`, isSwitcherOpen = () => document.body.contains($switcher), openSwitcher = () => { if (!isPinned()) return togglePanel(); @@ -186,6 +183,18 @@ async function createPanel() { await web.whenReady(['.notion-frame']); $notionFrame = document.querySelector('.notion-frame'); + $panel = web.html`
`; + $hoverTrigger = web.html`
`; + $resizeHandle = web.html`
`; + $panelTitle = web.html`
`; + $header = web.render(web.html`
`, $panelTitle); + $panelContent = web.html`
`; + $switcher = web.html`
`; + $switcherTrigger = web.html`
+ ${svgExpand} +
`; + $switcherOverlayContainer = web.html`
`; + const notionRightSidebarSelector = '.notion-cursor-listener > div[style*="flex-end"]', detectRightSidebar = () => { if (!document.contains($notionRightSidebar)) { @@ -256,6 +265,10 @@ export const addPanelView = async ({ onFocus = () => {}, onBlur = () => {}, }) => { + if (!$stylesheet) { + $stylesheet = web.loadStylesheet('api/components/panel.css'); + } + if (!db) db = await registry.db('36a2ffc9-27ff-480e-84a7-c7700a7d232d'); if (!$pinnedToggle) { $pinnedToggle = web.html`
diff --git a/api/client/components/tooltip.css b/api/components/tooltip.css similarity index 95% rename from api/client/components/tooltip.css rename to api/components/tooltip.css index c05c1d2..35c3f62 100644 --- a/api/client/components/tooltip.css +++ b/api/components/tooltip.css @@ -1,5 +1,5 @@ -/* - * notion-enhancer core: components +/** + * notion-enhancer: components * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ diff --git a/api/client/components/tooltip.mjs b/api/components/tooltip.mjs similarity index 87% rename from api/client/components/tooltip.mjs rename to api/components/tooltip.mjs index 643ffb8..8e996b2 100644 --- a/api/client/components/tooltip.mjs +++ b/api/components/tooltip.mjs @@ -1,5 +1,5 @@ -/* - * notion-enhancer core: components +/** + * notion-enhancer: components * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ @@ -11,11 +11,9 @@ * @module notion-enhancer/api/components/tooltip */ -import { fmt, web } from '../../index.mjs'; +import { fs, web } from '../index.mjs'; -const _$tooltip = web.html`
`; -web.loadStylesheet('api/client/components/tooltip.css'); -web.render(document.body, _$tooltip); +let $stylesheet, _$tooltip; const countLines = ($el) => [...$el.getClientRects()].reduce( @@ -71,16 +69,25 @@ const countLines = ($el) => * @param {number} [options.maxLines] - the max number of lines that the content may be wrapped * to, used to position and size the tooltip correctly (default: 1) */ -export const tooltip = ( +export const addTooltip = async ( $ref, $content, { delay = 100, offsetDirection = 'bottom', maxLines = 1 } = {} ) => { + if (!$stylesheet) { + $stylesheet = web.loadStylesheet('api/components/tooltip.css'); + _$tooltip = web.html`
`; + web.render(document.body, _$tooltip); + } + + if (!globalThis.markdownit) await import(fs.localPath('dep/markdown-it.min.js')); + const md = markdownit({ linkify: true }); + if (!($content instanceof Element)) $content = web.html`
${$content .split('\n') - .map((text) => fmt.md.renderInline(text)) + .map((text) => md.renderInline(text)) .join('
')}
`; diff --git a/api/client/electron.mjs b/api/electron.mjs similarity index 58% rename from api/client/electron.mjs rename to api/electron.mjs index bff73ce..9fa95df 100644 --- a/api/client/electron.mjs +++ b/api/electron.mjs @@ -1,5 +1,5 @@ -/* - * notion-enhancer core: api +/** + * notion-enhancer: api * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ @@ -15,34 +15,65 @@ * access to the electron BrowserWindow instance for the current window * see https://www.electronjs.org/docs/latest/api/browser-window * @type {BrowserWindow} + * + * @env win32 + * @env linux + * @env darwin + * @runtime client + * @runtime menu */ -export const browser = window.__enhancerElectronApi?.browser; +export const browser = globalThis.__enhancerElectronApi?.browser; /** * access to the electron webFrame instance for the current page * see https://www.electronjs.org/docs/latest/api/web-frame * @type {webFrame} + * + * @env win32 + * @env linux + * @env darwin + * @runtime client + * @runtime menu */ -export const webFrame = window.__enhancerElectronApi?.webFrame; +export const webFrame = globalThis.__enhancerElectronApi?.webFrame; /** * send a message to the main electron process * @param {string} channel - the message identifier * @param {any} data - the data to pass along with the message + * + * @env win32 + * @env linux + * @env darwin + * @runtime client + * @runtime menu */ -export const sendMessage = window.__enhancerElectronApi?.sendMessage; +export const sendMessage = globalThis.__enhancerElectronApi?.ipcRenderer?.sendMessage; /** * send a message to the webview's parent renderer process * @param {string} channel - the message identifier * @param {any} data - the data to pass along with the message + * + * @env win32 + * @env linux + * @env darwin + * @runtime client + * @runtime menu */ -export const sendMessageToHost = window.__enhancerElectronApi?.sendMessageToHost; +export const sendMessageToHost = + globalThis.__enhancerElectronApi?.ipcRenderer?.sendMessageToHost; /** * receive a message from either the main process or * the webview's parent renderer process * @param {string} channel - the message identifier to listen for * @param {function} listener - the message handler, passed the args (event, data) + * + * @env win32 + * @env linux + * @env darwin + * @runtime client + * @runtime menu */ -export const onMessage = window.__enhancerElectronApi?.onMessage; +export const onMessage = globalThis.__enhancerElectronApi?.ipcRenderer?.onMessage; diff --git a/api/client/env.mjs b/api/env.mjs similarity index 73% rename from api/client/env.mjs rename to api/env.mjs index 91142cf..d8a1c06 100644 --- a/api/client/env.mjs +++ b/api/env.mjs @@ -1,5 +1,5 @@ -/* - * notion-enhancer core: api +/** + * notion-enhancer: api * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ @@ -11,7 +11,7 @@ * @module notion-enhancer/api/env */ -import * as env from '../../env/env.mjs'; +import * as env from '../env/env.mjs'; /** * the environment/platform name code is currently being executed in @@ -44,3 +44,14 @@ export const focusNotion = env.focusNotion; * @type {function} */ export const reload = env.reload; + +/** + * require() notion app files + * @param {string} path - within notion/resources/app/ e.g. main/createWindow.js + * + * @env win32 + * @env linux + * @env darwin + * @runtime electron + */ +export const notionRequire = env.notionRequire; diff --git a/api/node/fmt.cjs b/api/fmt.mjs similarity index 89% rename from api/node/fmt.cjs rename to api/fmt.mjs index e82716e..1f10578 100644 --- a/api/node/fmt.cjs +++ b/api/fmt.mjs @@ -1,17 +1,18 @@ -/* - * notion-enhancer core: api +/** + * notion-enhancer: api * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ 'use strict'; -module.exports = {}; /** * helpers for formatting or parsing text * @module notion-enhancer/api/fmt */ +import { fs } from './index.mjs'; + /** * transform a heading into a slug (a lowercase alphanumeric string separated by dashes), * e.g. for use as an anchor id @@ -19,7 +20,7 @@ module.exports = {}; * @param {Set} [slugs] - a list of pre-generated slugs to avoid duplicates * @returns {string} the generated slug */ -module.exports.slugger = (heading, slugs = new Set()) => { +export const slugger = (heading, slugs = new Set()) => { heading = heading .replace(/\s/g, '-') .replace(/[^A-Za-z0-9-_]/g, '') @@ -38,7 +39,7 @@ module.exports.slugger = (heading, slugs = new Set()) => { * (from https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid) * @returns {string} a uuidv4 */ -module.exports.uuidv4 = () => { +export const uuidv4 = () => { if (crypto?.randomUUID) return crypto.randomUUID(); return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16) @@ -53,7 +54,7 @@ module.exports.uuidv4 = () => { * @param {string} c - the rgb color * @returns {string} the shaded color */ -module.exports.rgbLogShade = (p, c) => { +export const rgbLogShade = (p, c) => { var i = parseInt, r = Math.round, [a, b, c, d] = c.split(','), @@ -80,7 +81,7 @@ module.exports.rgbLogShade = (p, c) => { * @param {number} b - blue (0-255) * @returns {string} the contrasting rgb color, white or black */ -module.exports.rgbContrast = (r, g, b) => { +export const rgbContrast = (r, g, b) => { return Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b)) > 165.75 ? 'rgb(0,0,0)' : 'rgb(255,255,255)'; @@ -107,7 +108,7 @@ function test(str, pattern) { * @param {string|array} type - the type the value should be or a list of allowed values * @returns {boolean} whether or not the value matches the type */ -module.exports.is = async (value, type, { extension = '' } = {}) => { +export const is = async (value, type, { extension = '' } = {}) => { extension = !value || !value.endsWith || value.endsWith(extension); if (Array.isArray(type)) { return type.includes(value); @@ -130,10 +131,8 @@ module.exports.is = async (value, type, { extension = '' } = {}) => { case 'url': case 'color': return typeof value === 'string' && test(value, patterns[type]) && extension; - case 'file': { - const { isFile } = require('notion-enhancer/api/node/fs.cjs'); - return typeof value === 'string' && value && (await isFile(value)) && extension; - } + case 'file': + return typeof value === 'string' && value && (await fs.isFile(value)) && extension; } return false; }; diff --git a/api/client/fs.mjs b/api/fs.mjs similarity index 94% rename from api/client/fs.mjs rename to api/fs.mjs index 4484085..659517f 100644 --- a/api/client/fs.mjs +++ b/api/fs.mjs @@ -1,5 +1,5 @@ -/* - * notion-enhancer core: api +/** + * notion-enhancer: api * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ @@ -11,7 +11,7 @@ * @module notion-enhancer/api/fs */ -import * as fs from '../../env/fs.mjs'; +import * as fs from '../env/fs.mjs'; /** * transform a path relative to the enhancer root directory into an absolute path diff --git a/api/index.cjs b/api/index.cjs index 92d255d..f3a32ce 100644 --- a/api/index.cjs +++ b/api/index.cjs @@ -1,22 +1,1042 @@ -/* - * notion-enhancer core: api - * (c) 2021 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -'use strict'; - -/** @module notion-enhancer/api */ - -module.exports = { - /** environment-specific methods and constants */ - env: require('notion-enhancer/api/node/env.cjs'), - /** environment-specific file reading */ - fs: require('notion-enhancer/api/node/fs.cjs'), - /** environment-specific data persistence */ - storage: require('notion-enhancer/api/node/storage.cjs'), - /** helpers for formatting, validating and parsing values */ - fmt: require('notion-enhancer/api/node/fmt.cjs'), - /** interactions with the enhancer's repository of mods */ - registry: require('notion-enhancer/api/node/registry.cjs'), +var __defProp = Object.defineProperty; +var __markAsModule = (target) => __defProp(target, "__esModule", { value: true }); +var __export = (target, all) => { + __markAsModule(target); + for (var name3 in all) + __defProp(target, name3, { get: all[name3], enumerable: true }); }; + +// insert/api/index.mjs +__export(exports, { + components: () => components_exports, + electron: () => electron_exports, + env: () => env_exports, + fmt: () => fmt_exports, + fs: () => fs_exports, + registry: () => registry_exports, + storage: () => storage_exports, + web: () => web_exports +}); + +// insert/api/env.mjs +var env_exports = {}; +__export(env_exports, { + focusMenu: () => focusMenu2, + focusNotion: () => focusNotion2, + name: () => name2, + notionRequire: () => notionRequire2, + reload: () => reload2, + version: () => version2 +}); + +// insert/env/env.mjs +"use strict"; +var name = globalThis.__enhancerElectronApi.platform; +var version = globalThis.__enhancerElectronApi.version; +var focusMenu = globalThis.__enhancerElectronApi.focusMenu; +var focusNotion = globalThis.__enhancerElectronApi.focusNotion; +var reload = globalThis.__enhancerElectronApi.reload; +var notionRequire = globalThis.__enhancerElectronApi.notionRequire; + +// insert/api/env.mjs +"use strict"; +var name2 = name; +var version2 = version; +var focusMenu2 = focusMenu; +var focusNotion2 = focusNotion; +var reload2 = reload; +var notionRequire2 = notionRequire; + +// insert/api/fs.mjs +var fs_exports = {}; +__export(fs_exports, { + getJSON: () => getJSON2, + getText: () => getText2, + isFile: () => isFile2, + localPath: () => localPath2 +}); + +// insert/env/fs.mjs +"use strict"; +var localPath = (path) => `notion://www.notion.so/__notion-enhancer/${path}`; +var getJSON = (path, opts = {}) => { + if (path.startsWith("http")) + return fetch(path, opts).then((res) => res.json()); + try { + return globalThis.__enhancerElectronApi.nodeRequire(`notion-enhancer/${path}`); + } catch (err) { + return fetch(localPath(path), opts).then((res) => res.json()); + } +}; +var getText = (path, opts = {}) => { + if (path.startsWith("http")) + return fetch(path, opts).then((res) => res.text()); + try { + const fs2 = globalThis.__enhancerElectronApi.nodeRequire("fs"), { resolve: resolvePath } = globalThis.__enhancerElectronApi.nodeRequire("path"); + return fs2.readFileSync(resolvePath(`${__dirname}/../../${path}`)); + } catch (err) { + return fetch(localPath(path), opts).then((res) => res.text()); + } +}; +var isFile = async (path) => { + try { + const fs2 = globalThis.__enhancerElectronApi.nodeRequire("fs"), { resolve: resolvePath } = globalThis.__enhancerElectronApi.nodeRequire("path"); + if (path.startsWith("http")) { + await fetch(path); + } else { + try { + fs2.existsSync(resolvePath(`${__dirname}/../../${path}`)); + } catch (err) { + await fetch(localPath(path)); + } + } + return true; + } catch { + return false; + } +}; + +// insert/api/fs.mjs +"use strict"; +var localPath2 = localPath; +var getJSON2 = getJSON; +var getText2 = getText; +var isFile2 = isFile; + +// insert/api/storage.mjs +var storage_exports = {}; +__export(storage_exports, { + addChangeListener: () => addChangeListener2, + db: () => db2, + get: () => get2, + removeChangeListener: () => removeChangeListener2, + set: () => set2 +}); + +// insert/env/storage.mjs +"use strict"; +var get = (path, fallback = void 0) => { + return globalThis.__enhancerElectronApi.db.get(path, fallback); +}; +var set = (path, value) => { + return globalThis.__enhancerElectronApi.db.set(path, value); +}; +var db = (namespace, getFunc = get, setFunc = set) => { + if (typeof namespace === "string") + namespace = [namespace]; + return { + get: (path = [], fallback = void 0) => getFunc([...namespace, ...path], fallback), + set: (path, value) => setFunc([...namespace, ...path], value) + }; +}; +var addChangeListener = (callback) => { + return globalThis.__enhancerElectronApi.db.addChangeListener(callback); +}; +var removeChangeListener = (callback) => { + return globalThis.__enhancerElectronApi.db.removeChangeListener(callback); +}; + +// insert/api/storage.mjs +"use strict"; +var get2 = get; +var set2 = set; +var db2 = db; +var addChangeListener2 = addChangeListener; +var removeChangeListener2 = removeChangeListener; + +// insert/api/electron.mjs +var electron_exports = {}; +__export(electron_exports, { + browser: () => browser, + onMessage: () => onMessage, + sendMessage: () => sendMessage, + sendMessageToHost: () => sendMessageToHost, + webFrame: () => webFrame +}); +"use strict"; +var browser = globalThis.__enhancerElectronApi?.browser; +var webFrame = globalThis.__enhancerElectronApi?.webFrame; +var sendMessage = globalThis.__enhancerElectronApi?.ipcRenderer?.sendMessage; +var sendMessageToHost = globalThis.__enhancerElectronApi?.ipcRenderer?.sendMessageToHost; +var onMessage = globalThis.__enhancerElectronApi?.ipcRenderer?.onMessage; + +// insert/api/fmt.mjs +var fmt_exports = {}; +__export(fmt_exports, { + is: () => is, + rgbContrast: () => rgbContrast, + rgbLogShade: () => rgbLogShade, + slugger: () => slugger, + uuidv4: () => uuidv4 +}); +"use strict"; +var slugger = (heading, slugs = new Set()) => { + heading = heading.replace(/\s/g, "-").replace(/[^A-Za-z0-9-_]/g, "").toLowerCase(); + let i = 0, slug = heading; + while (slugs.has(slug)) { + i++; + slug = `${heading}-${i}`; + } + return slug; +}; +var uuidv4 = () => { + if (crypto?.randomUUID) + return crypto.randomUUID(); + return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)); +}; +var rgbLogShade = (p, c) => { + var i = parseInt, r = Math.round, [a, b, c, d] = c.split(","), P = p < 0, t = P ? 0 : p * 255 ** 2, P = P ? 1 + p : 1 - p; + return "rgb" + (d ? "a(" : "(") + r((P * i(a[3] == "a" ? a.slice(5) : a.slice(4)) ** 2 + t) ** 0.5) + "," + r((P * i(b) ** 2 + t) ** 0.5) + "," + r((P * i(c) ** 2 + t) ** 0.5) + (d ? "," + d : ")"); +}; +var rgbContrast = (r, g, b) => { + return Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b)) > 165.75 ? "rgb(0,0,0)" : "rgb(255,255,255)"; +}; +var patterns = { + alphanumeric: /^[\w\.-]+$/, + uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, + semver: /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i, + email: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i, + url: /^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,64}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i, + color: /^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i +}; +function test(str, pattern) { + const match = str.match(pattern); + return !!(match && match.length); +} +var is = async (value, type, { extension = "" } = {}) => { + extension = !value || !value.endsWith || value.endsWith(extension); + if (Array.isArray(type)) { + return type.includes(value); + } + switch (type) { + case "array": + return Array.isArray(value); + case "object": + return value && typeof value === "object" && !Array.isArray(value); + case "undefined": + case "boolean": + case "number": + return typeof value === type && extension; + case "string": + return typeof value === type && extension; + case "alphanumeric": + case "uuid": + case "semver": + case "email": + case "url": + case "color": + return typeof value === "string" && test(value, patterns[type]) && extension; + case "file": + return typeof value === "string" && value && await fs_exports.isFile(value) && extension; + } + return false; +}; + +// insert/api/registry.mjs +var registry_exports = {}; +__export(registry_exports, { + core: () => core, + db: () => db3, + enabled: () => enabled, + errors: () => errors, + get: () => get3, + list: () => list, + optionDefault: () => optionDefault, + optionTypes: () => optionTypes, + profileDB: () => profileDB, + profileName: () => profileName, + supportedEnvs: () => supportedEnvs +}); + +// insert/api/registry-validation.mjs +"use strict"; +var check = async (mod, key, value, types, { + extension = "", + error = `invalid ${key} (${extension ? `${extension} ` : ""}${types}): ${JSON.stringify(value)}`, + optional = false +} = {}) => { + let test2; + for (const type of Array.isArray(types) ? [types] : types.split("|")) { + if (type === "file") { + test2 = value && !value.startsWith("http") ? await fmt_exports.is(`repo/${mod._dir}/${value}`, type, { extension }) : false; + } else + test2 = await fmt_exports.is(value, type, { extension }); + if (test2) + break; + } + if (!test2) { + if (optional && await fmt_exports.is(value, "undefined")) + return true; + if (error) + mod._err(error); + return false; + } + return true; +}; +var validateEnvironments = async (mod) => { + mod.environments = mod.environments ?? registry_exports.supportedEnvs; + const isArray = await check(mod, "environments", mod.environments, "array"); + if (!isArray) + return false; + return mod.environments.map((tag) => check(mod, "environments.env", tag, registry_exports.supportedEnvs)); +}; +var validateTags = async (mod) => { + const isArray = await check(mod, "tags", mod.tags, "array"); + if (!isArray) + return false; + const categoryTags = ["core", "extension", "theme", "integration"], containsCategory = mod.tags.filter((tag) => categoryTags.includes(tag)).length; + if (!containsCategory) { + mod._err(`invalid tags (must contain at least one of 'core', 'extension', 'theme' or 'integration'): + ${JSON.stringify(mod.tags)}`); + return false; + } + const isTheme = mod.tags.includes("theme"), hasThemeMode = mod.tags.includes("light") || mod.tags.includes("dark"), isBothThemeModes = mod.tags.includes("light") && mod.tags.includes("dark"); + if (isTheme && (!hasThemeMode || isBothThemeModes)) { + mod._err(`invalid tags (themes must be either 'light' or 'dark', not neither or both): + ${JSON.stringify(mod.tags)}`); + return false; + } + return mod.tags.map((tag) => check(mod, "tags.tag", tag, "string")); +}; +var validateAuthors = async (mod) => { + const isArray = await check(mod, "authors", mod.authors, "array"); + if (!isArray) + return false; + return mod.authors.map((author) => [ + check(mod, "authors.author.name", author.name, "string"), + check(mod, "authors.author.email", author.email, "email", { optional: true }), + check(mod, "authors.author.homepage", author.homepage, "url"), + check(mod, "authors.author.avatar", author.avatar, "url") + ]); +}; +var validateCSS = async (mod) => { + const isArray = await check(mod, "css", mod.css, "object"); + if (!isArray) + return false; + const tests = []; + for (let dest of ["frame", "client", "menu"]) { + if (!mod.css[dest]) + continue; + let test2 = await check(mod, `css.${dest}`, mod.css[dest], "array"); + if (test2) { + test2 = mod.css[dest].map((file) => check(mod, `css.${dest}.file`, file, "file", { extension: ".css" })); + } + tests.push(test2); + } + return tests; +}; +var validateJS = async (mod) => { + const isArray = await check(mod, "js", mod.js, "object"); + if (!isArray) + return false; + const tests = []; + for (let dest of ["frame", "client", "menu"]) { + if (!mod.js[dest]) + continue; + let test2 = await check(mod, `js.${dest}`, mod.js[dest], "array"); + if (test2) { + test2 = mod.js[dest].map((file) => check(mod, `js.${dest}.file`, file, "file", { extension: ".mjs" })); + } + tests.push(test2); + } + if (mod.js.electron) { + const isArray2 = await check(mod, "js.electron", mod.js.electron, "array"); + if (isArray2) { + for (const file of mod.js.electron) { + const isObject = await check(mod, "js.electron.file", file, "object"); + if (!isObject) { + tests.push(false); + continue; + } + tests.push([ + check(mod, "js.electron.file.source", file.source, "file", { + extension: ".cjs" + }), + check(mod, "js.electron.file.target", file.target, "string", { + extension: ".js" + }) + ]); + } + } else + tests.push(false); + } + return tests; +}; +var validateOptions = async (mod) => { + const isArray = await check(mod, "options", mod.options, "array"); + if (!isArray) + return false; + const tests = []; + for (const option of mod.options) { + const key = "options.option", optTypeValid = await check(mod, `${key}.type`, option.type, registry_exports.optionTypes); + if (!optTypeValid) { + tests.push(false); + continue; + } + option.environments = option.environments ?? registry_exports.supportedEnvs; + tests.push([ + check(mod, `${key}.key`, option.key, "alphanumeric"), + check(mod, `${key}.label`, option.label, "string"), + check(mod, `${key}.tooltip`, option.tooltip, "string", { + optional: true + }), + check(mod, `${key}.environments`, option.environments, "array").then((isArray2) => { + if (!isArray2) + return false; + return option.environments.map((environment) => check(mod, `${key}.environments.env`, environment, registry_exports.supportedEnvs)); + }) + ]); + switch (option.type) { + case "toggle": + tests.push(check(mod, `${key}.value`, option.value, "boolean")); + break; + case "select": { + let test2 = await check(mod, `${key}.values`, option.values, "array"); + if (test2) { + test2 = option.values.map((value) => check(mod, `${key}.values.value`, value, "string")); + } + tests.push(test2); + break; + } + case "text": + case "hotkey": + tests.push(check(mod, `${key}.value`, option.value, "string")); + break; + case "number": + case "color": + tests.push(check(mod, `${key}.value`, option.value, option.type)); + break; + case "file": { + let test2 = await check(mod, `${key}.extensions`, option.extensions, "array"); + if (test2) { + test2 = option.extensions.map((ext) => check(mod, `${key}.extensions.extension`, ext, "string")); + } + tests.push(test2); + break; + } + } + } + return tests; +}; +async function validate(mod) { + let conditions = [ + check(mod, "name", mod.name, "string"), + check(mod, "id", mod.id, "uuid"), + check(mod, "version", mod.version, "semver"), + validateEnvironments(mod), + check(mod, "description", mod.description, "string"), + check(mod, "preview", mod.preview, "file|url", { optional: true }), + validateTags(mod), + validateAuthors(mod), + validateCSS(mod), + validateJS(mod), + validateOptions(mod) + ]; + do { + conditions = await Promise.all(conditions.flat(Infinity)); + } while (conditions.some((condition) => Array.isArray(condition))); + return conditions.every((passed) => passed); +} + +// insert/api/registry.mjs +"use strict"; +var core = [ + "a6621988-551d-495a-97d8-3c568bca2e9e", + "0f0bf8b6-eae6-4273-b307-8fc43f2ee082", + "36a2ffc9-27ff-480e-84a7-c7700a7d232d" +]; +var supportedEnvs = ["linux", "win32", "darwin", "extension"]; +var optionTypes = ["toggle", "select", "text", "number", "color", "file", "hotkey"]; +var profileName = async () => storage_exports.get(["currentprofile"], "default"); +var profileDB = async () => storage_exports.db(["profiles", await profileName()]); +var _list; +var _errors = []; +var list = async (filter = (mod) => true) => { + if (!_list) { + _list = new Promise(async (res, rej) => { + const passed = []; + for (const dir of await fs_exports.getJSON("repo/registry.json")) { + try { + const mod = { + ...await fs_exports.getJSON(`repo/${dir}/mod.json`), + _dir: dir, + _err: (message) => _errors.push({ source: dir, message }) + }; + if (await validate(mod)) + passed.push(mod); + } catch { + _errors.push({ source: dir, message: "invalid mod.json" }); + } + } + res(passed); + }); + } + const filtered = []; + for (const mod of await _list) + if (await filter(mod)) + filtered.push(mod); + return filtered; +}; +var errors = async () => { + await list(); + return _errors; +}; +var get3 = async (id) => { + return (await list((mod) => mod.id === id))[0]; +}; +var enabled = async (id) => { + const mod = await get3(id); + if (!mod.environments.includes(env_exports.name)) + return false; + if (core.includes(id)) + return true; + return (await profileDB()).get(["_mods", id], false); +}; +var optionDefault = async (id, key) => { + const mod = await get3(id), opt = mod.options.find((opt2) => opt2.key === key); + if (!opt) + return void 0; + switch (opt.type) { + case "toggle": + case "text": + case "number": + case "color": + case "hotkey": + return opt.value; + case "select": + return opt.values[0]; + case "file": + return void 0; + } +}; +var db3 = async (id) => { + const db5 = await profileDB(); + return storage_exports.db([id], async (path, fallback = void 0) => { + if (typeof path === "string") + path = [path]; + if (path.length === 2) { + fallback = await optionDefault(id, path[1]) ?? fallback; + } + return db5.get(path, fallback); + }, db5.set); +}; + +// insert/api/web.mjs +var web_exports = {}; +__export(web_exports, { + addDocumentObserver: () => addDocumentObserver, + addHotkeyListener: () => addHotkeyListener, + copyToClipboard: () => copyToClipboard, + empty: () => empty, + escape: () => escape, + html: () => html, + loadStylesheet: () => loadStylesheet, + queryParams: () => queryParams, + raw: () => raw, + readFromClipboard: () => readFromClipboard, + removeDocumentObserver: () => removeDocumentObserver, + removeHotkeyListener: () => removeHotkeyListener, + render: () => render, + whenReady: () => whenReady +}); +"use strict"; +var _hotkeyListenersActivated = false; +var _hotkeyEventListeners = []; +var _documentObserver; +var _documentObserverListeners = []; +var _documentObserverEvents = []; +var whenReady = (selectors = []) => { + return new Promise((res, rej) => { + function onLoad() { + let isReadyInt; + isReadyInt = setInterval(isReadyTest, 100); + function isReadyTest() { + if (selectors.every((selector) => document.querySelector(selector))) { + clearInterval(isReadyInt); + res(true); + } + } + isReadyTest(); + } + if (document.readyState !== "complete") { + document.addEventListener("readystatechange", (event) => { + if (document.readyState === "complete") + onLoad(); + }); + } else + onLoad(); + }); +}; +var queryParams = () => new URLSearchParams(window.location.search); +var escape = (str) => str.replace(/&/g, "&").replace(//g, ">").replace(/'/g, "'").replace(/"/g, """).replace(/\\/g, "\"); +var raw = (str, ...templates) => { + const html2 = str.map((chunk) => chunk + (["string", "number"].includes(typeof templates[0]) ? templates.shift() : escape(JSON.stringify(templates.shift(), null, 2) ?? ""))).join(""); + return html2.includes(" line.trim()).filter((line) => line.length).join(" "); +}; +var html = (str, ...templates) => { + const $fragment = document.createRange().createContextualFragment(raw(str, ...templates)); + return $fragment.children.length === 1 ? $fragment.children[0] : $fragment.children; +}; +var render = ($container, ...$elems) => { + $elems = $elems.map(($elem) => $elem instanceof HTMLCollection ? [...$elem] : $elem).flat(Infinity).filter(($elem) => $elem); + $container.append(...$elems); + return $container; +}; +var empty = ($container) => { + while ($container.firstChild && $container.removeChild($container.firstChild)) + ; + return $container; +}; +var loadStylesheet = (path) => { + const $stylesheet4 = html``; + render(document.head, $stylesheet4); + return $stylesheet4; +}; +var copyToClipboard = async (str) => { + try { + await navigator.clipboard.writeText(str); + } catch { + 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); + } +}; +var readFromClipboard = () => { + return navigator.clipboard.readText(); +}; +var triggerHotkeyListener = (event, hotkey) => { + const inInput = document.activeElement.nodeName === "INPUT" && !hotkey.listenInInput; + if (inInput) + return; + const pressed = hotkey.keys.every((key) => { + key = key.toLowerCase(); + const modifiers = { + metaKey: ["meta", "os", "win", "cmd", "command"], + ctrlKey: ["ctrl", "control"], + shiftKey: ["shift"], + altKey: ["alt"] + }; + for (const modifier in modifiers) { + const pressed2 = modifiers[modifier].includes(key) && event[modifier]; + if (pressed2) + return true; + } + if (key === "space") + key = " "; + if (key === "plus") + key = "+"; + if (key === event.key.toLowerCase()) + return true; + }); + if (pressed) + hotkey.callback(event); +}; +var addHotkeyListener = (keys, callback, { listenInInput = false, keydown = false } = {}) => { + if (typeof keys === "string") + keys = keys.split("+"); + _hotkeyEventListeners.push({ keys, callback, listenInInput, keydown }); + if (!_hotkeyListenersActivated) { + _hotkeyListenersActivated = true; + document.addEventListener("keyup", (event) => { + for (const hotkey of _hotkeyEventListeners.filter(({ keydown: keydown2 }) => !keydown2)) { + triggerHotkeyListener(event, hotkey); + } + }); + document.addEventListener("keydown", (event) => { + for (const hotkey of _hotkeyEventListeners.filter(({ keydown: keydown2 }) => keydown2)) { + triggerHotkeyListener(event, hotkey); + } + }); + } +}; +var removeHotkeyListener = (callback) => { + _hotkeyEventListeners = _hotkeyEventListeners.filter((listener) => listener.callback !== callback); +}; +var addDocumentObserver = (callback, selectors = []) => { + if (!_documentObserver) { + const handle = (queue) => { + while (queue.length) { + const event = queue.shift(), matchesAddedNode = ($node, selector) => $node instanceof Element && ($node.matches(selector) || $node.matches(`${selector} *`) || $node.querySelector(selector)), matchesTarget = (selector) => event.target.matches(selector) || event.target.matches(`${selector} *`) || [...event.addedNodes].some(($node) => matchesAddedNode($node, selector)); + for (const listener of _documentObserverListeners) { + if (!listener.selectors.length || listener.selectors.some(matchesTarget)) { + listener.callback(event); + } + } + } + }; + _documentObserver = new MutationObserver((list2, observer) => { + if (!_documentObserverEvents.length) + requestIdleCallback(() => handle(_documentObserverEvents)); + _documentObserverEvents.push(...list2); + }); + _documentObserver.observe(document.body, { + childList: true, + subtree: true, + attributes: true + }); + } + _documentObserverListeners.push({ callback, selectors }); +}; +var removeDocumentObserver = (callback) => { + _documentObserverListeners = _documentObserverListeners.filter((listener) => listener.callback !== callback); +}; + +// insert/api/components/index.mjs +var components_exports = {}; +__export(components_exports, { + addCornerAction: () => addCornerAction, + addPanelView: () => addPanelView, + addTooltip: () => addTooltip, + feather: () => feather +}); + +// insert/api/components/tooltip.mjs +"use strict"; +var $stylesheet; +var _$tooltip; +var countLines = ($el) => [...$el.getClientRects()].reduce((prev, val) => prev.some((p) => p.y === val.y) ? prev : [...prev, val], []).length; +var position = async ($ref, offsetDirection, maxLines) => { + _$tooltip.style.top = `0px`; + _$tooltip.style.left = `0px`; + const rect = $ref.getBoundingClientRect(), { offsetWidth, offsetHeight } = _$tooltip, pad = 6; + let x = rect.x, y = Math.floor(rect.y); + if (["top", "bottom"].includes(offsetDirection)) { + if (offsetDirection === "top") + y -= offsetHeight + pad; + if (offsetDirection === "bottom") + y += rect.height + pad; + x -= offsetWidth / 2 - rect.width / 2; + _$tooltip.style.left = `${x}px`; + _$tooltip.style.top = `${y}px`; + const testLines = () => countLines(_$tooltip.firstElementChild) > maxLines, padEdgesX = testLines(); + while (testLines()) { + _$tooltip.style.left = `${window.innerWidth - x > x ? x++ : x--}px`; + } + if (padEdgesX) { + x += window.innerWidth - x > x ? pad : -pad; + _$tooltip.style.left = `${x}px`; + } + } + if (["left", "right"].includes(offsetDirection)) { + y -= offsetHeight / 2 - rect.height / 2; + if (offsetDirection === "left") + x -= offsetWidth + pad; + if (offsetDirection === "right") + x += rect.width + pad; + _$tooltip.style.left = `${x}px`; + _$tooltip.style.top = `${y}px`; + } + return true; +}; +var addTooltip = async ($ref, $content, { delay = 100, offsetDirection = "bottom", maxLines = 1 } = {}) => { + if (!$stylesheet) { + $stylesheet = web_exports.loadStylesheet("api/components/tooltip.css"); + _$tooltip = web_exports.html`
`; + web_exports.render(document.body, _$tooltip); + } + if (!globalThis.markdownit) + await import(fs_exports.localPath("dep/markdown-it.min.js")); + const md = markdownit({ linkify: true }); + if (!($content instanceof Element)) + $content = web_exports.html`
+ ${$content.split("\n").map((text) => md.renderInline(text)).join("
")} +
`; + let displayDelay; + $ref.addEventListener("mouseover", async (event) => { + if (!displayDelay) { + displayDelay = setTimeout(async () => { + if ($ref.matches(":hover")) { + if (_$tooltip.style.display !== "block") { + _$tooltip.style.display = "block"; + web_exports.render(web_exports.empty(_$tooltip), $content); + position($ref, offsetDirection, maxLines); + await _$tooltip.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 65 }).finished; + } + } + displayDelay = void 0; + }, delay); + } + }); + $ref.addEventListener("mouseout", async (event) => { + displayDelay = void 0; + if (_$tooltip.style.display === "block" && !$ref.matches(":hover")) { + await _$tooltip.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 65 }).finished; + _$tooltip.style.display = ""; + } + }); +}; + +// insert/api/components/feather.mjs +"use strict"; +var _$iconSheet; +var feather = async (name3, attrs = {}) => { + if (!_$iconSheet) { + _$iconSheet = web_exports.html`${await fs_exports.getText("dep/feather-sprite.svg")}`; + } + attrs.style = ((attrs.style ? attrs.style + ";" : "") + "stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;fill:none;").trim(); + attrs.viewBox = "0 0 24 24"; + return ` `${web_exports.escape(key)}="${web_exports.escape(val)}"`).join(" ")}>${_$iconSheet.getElementById(name3)?.innerHTML}`; +}; + +// insert/api/components/panel.mjs +"use strict"; +var _views = []; +var svgExpand = web_exports.raw` + + `; +var $stylesheet2; +var db4; +var $notionFrame; +var $notionRightSidebar; +var $panel; +var $hoverTrigger; +var $resizeHandle; +var dragStartX; +var dragStartWidth; +var dragEventsFired; +var panelWidth; +var $notionApp; +var $pinnedToggle; +var $panelTitle; +var $header; +var $panelContent; +var $switcher; +var $switcherTrigger; +var $switcherOverlayContainer; +var panelPinnedAttr = "data-enhancer-panel-pinned"; +var isPinned = () => $panel.hasAttribute(panelPinnedAttr); +var togglePanel = () => { + const $elems = [$notionFrame, $notionRightSidebar, $hoverTrigger, $panel].filter(($el) => $el); + if (isPinned()) { + closeSwitcher(); + for (const $elem of $elems) + $elem.removeAttribute(panelPinnedAttr); + } else { + for (const $elem of $elems) + $elem.setAttribute(panelPinnedAttr, "true"); + } + db4.set(["panel.pinned"], isPinned()); +}; +var updateWidth = async () => { + document.documentElement.style.setProperty("--component--panel-width", panelWidth + "px"); + db4.set(["panel.width"], panelWidth); +}; +var resizeDrag = (event) => { + event.preventDefault(); + dragEventsFired = true; + panelWidth = dragStartWidth + (dragStartX - event.clientX); + if (panelWidth < 190) + panelWidth = 190; + if (panelWidth > 480) + panelWidth = 480; + $panel.style.width = panelWidth + "px"; + $hoverTrigger.style.width = panelWidth + "px"; + $notionFrame.style.paddingRight = panelWidth + "px"; + if ($notionRightSidebar) + $notionRightSidebar.style.right = panelWidth + "px"; +}; +var resizeEnd = (event) => { + $panel.style.width = ""; + $hoverTrigger.style.width = ""; + $notionFrame.style.paddingRight = ""; + if ($notionRightSidebar) + $notionRightSidebar.style.right = ""; + updateWidth(); + $resizeHandle.style.cursor = ""; + document.body.removeEventListener("mousemove", resizeDrag); + document.body.removeEventListener("mouseup", resizeEnd); +}; +var resizeStart = (event) => { + dragStartX = event.clientX; + dragStartWidth = panelWidth; + $resizeHandle.style.cursor = "auto"; + document.body.addEventListener("mousemove", resizeDrag); + document.body.addEventListener("mouseup", resizeEnd); +}; +var isSwitcherOpen = () => document.body.contains($switcher); +var openSwitcher = () => { + if (!isPinned()) + return togglePanel(); + web_exports.render($notionApp, $switcherOverlayContainer); + web_exports.empty($switcher); + for (const view of _views) { + const open = $panelTitle.contains(view.$title), $item = web_exports.render(web_exports.html`
`, web_exports.render(web_exports.html``, view.$icon.cloneNode(true), view.$title.cloneNode(true))); + $item.addEventListener("click", () => { + renderView(view); + db4.set(["panel.open"], view.id); + }); + web_exports.render($switcher, $item); + } + const rect = $header.getBoundingClientRect(); + web_exports.render(web_exports.empty($switcherOverlayContainer), web_exports.render(web_exports.html`
`, web_exports.render(web_exports.html`
`, $switcher))); + $switcher.querySelector("[data-open]").focus(); + $switcher.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 200 }); + document.addEventListener("keydown", switcherKeyListeners); +}; +var closeSwitcher = () => { + document.removeEventListener("keydown", switcherKeyListeners); + $switcher.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 200 }).onfinish = () => $switcherOverlayContainer.remove(); +}; +var switcherKeyListeners = (event) => { + if (isSwitcherOpen()) { + switch (event.key) { + case "Escape": + closeSwitcher(); + event.stopPropagation(); + break; + case "Enter": + document.activeElement.click(); + event.stopPropagation(); + break; + case "ArrowUp": + const $prev = event.target.previousElementSibling; + ($prev || event.target.parentElement.lastElementChild).focus(); + event.stopPropagation(); + break; + case "ArrowDown": + const $next = event.target.nextElementSibling; + ($next || event.target.parentElement.firstElementChild).focus(); + event.stopPropagation(); + break; + } + } +}; +var renderView = (view) => { + const prevView = _views.find(({ $content }) => document.contains($content)); + web_exports.render(web_exports.empty($panelTitle), web_exports.render(web_exports.html``, view.$icon, view.$title)); + view.onFocus(); + web_exports.render(web_exports.empty($panelContent), view.$content); + if (prevView) + prevView.onBlur(); +}; +async function createPanel() { + await web_exports.whenReady([".notion-frame"]); + $notionFrame = document.querySelector(".notion-frame"); + $panel = web_exports.html`
`; + $hoverTrigger = web_exports.html`
`; + $resizeHandle = web_exports.html`
`; + $panelTitle = web_exports.html`
`; + $header = web_exports.render(web_exports.html`
`, $panelTitle); + $panelContent = web_exports.html`
`; + $switcher = web_exports.html`
`; + $switcherTrigger = web_exports.html`
+ ${svgExpand} +
`; + $switcherOverlayContainer = web_exports.html`
`; + const notionRightSidebarSelector = '.notion-cursor-listener > div[style*="flex-end"]', detectRightSidebar = () => { + if (!document.contains($notionRightSidebar)) { + $notionRightSidebar = document.querySelector(notionRightSidebarSelector); + if (isPinned() && $notionRightSidebar) { + $notionRightSidebar.setAttribute(panelPinnedAttr, "true"); + } + } + }; + $notionRightSidebar = document.querySelector(notionRightSidebarSelector); + web_exports.addDocumentObserver(detectRightSidebar, [notionRightSidebarSelector]); + if (await db4.get(["panel.pinned"])) + togglePanel(); + web_exports.addHotkeyListener(await db4.get(["panel.hotkey"]), togglePanel); + $pinnedToggle.addEventListener("click", (event) => { + event.stopPropagation(); + togglePanel(); + }); + web_exports.render($panel, web_exports.render($header, $panelTitle, $switcherTrigger, $pinnedToggle), $panelContent, $resizeHandle); + await enablePanelResize(); + await createViews(); + const cursorListenerSelector = '.notion-cursor-listener > .notion-sidebar-container ~ [style^="position: absolute"]'; + await web_exports.whenReady([cursorListenerSelector]); + document.querySelector(cursorListenerSelector).before($hoverTrigger, $panel); +} +async function enablePanelResize() { + panelWidth = await db4.get(["panel.width"], 240); + updateWidth(); + $resizeHandle.addEventListener("mousedown", resizeStart); + $resizeHandle.addEventListener("click", () => { + if (dragEventsFired) { + dragEventsFired = false; + } else + togglePanel(); + }); +} +async function createViews() { + $notionApp = document.querySelector(".notion-app-inner"); + $header.addEventListener("click", openSwitcher); + $switcherTrigger.addEventListener("click", openSwitcher); + $switcherOverlayContainer.addEventListener("click", closeSwitcher); +} +var addPanelView = async ({ + id, + icon, + title, + $content, + onFocus = () => { + }, + onBlur = () => { + } +}) => { + if (!$stylesheet2) { + $stylesheet2 = web_exports.loadStylesheet("api/components/panel.css"); + } + if (!db4) + db4 = await registry_exports.db("36a2ffc9-27ff-480e-84a7-c7700a7d232d"); + if (!$pinnedToggle) { + $pinnedToggle = web_exports.html`
+ ${await components_exports.feather("chevrons-right")} +
`; + } + const view = { + id, + $icon: web_exports.render(web_exports.html``, icon instanceof Element ? icon : web_exports.html`${icon}`), + $title: web_exports.render(web_exports.html``, title), + $content, + onFocus, + onBlur + }; + _views.push(view); + if (_views.length === 1) + await createPanel(); + if (_views.length === 1 || await db4.get(["panel.open"]) === id) + renderView(view); +}; + +// insert/api/components/corner-action.mjs +"use strict"; +var $stylesheet3; +var $cornerButtonsContainer; +var addCornerAction = async (icon, listener) => { + if (!$stylesheet3) { + $stylesheet3 = web_exports.loadStylesheet("api/components/corner-action.css"); + $cornerButtonsContainer = web_exports.html`
`; + } + await web_exports.whenReady([".notion-help-button"]); + const $helpButton = document.querySelector(".notion-help-button"), $onboardingButton = document.querySelector(".onboarding-checklist-button"); + if ($onboardingButton) + $cornerButtonsContainer.prepend($onboardingButton); + $cornerButtonsContainer.prepend($helpButton); + web_exports.render(document.querySelector(".notion-app-inner > .notion-cursor-listener"), $cornerButtonsContainer); + const $actionButton = web_exports.html`
${icon}
`; + $actionButton.addEventListener("click", listener); + web_exports.render($cornerButtonsContainer, $actionButton); + return $actionButton; +}; + +// insert/api/components/index.mjs +"use strict"; + +// insert/api/index.mjs +"use strict"; diff --git a/api/index.mjs b/api/index.mjs index a53a104..e90bf3e 100644 --- a/api/index.mjs +++ b/api/index.mjs @@ -1,5 +1,5 @@ -/* - * notion-enhancer core: api +/** + * notion-enhancer: api * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ @@ -8,23 +8,26 @@ /** @module notion-enhancer/api */ +// compiles to .cjs for use in electron: +// npx -y esbuild insert/api/index.mjs --bundle --format=cjs --outfile=insert/api/index.cjs + /** environment-specific methods and constants */ -export * as env from './client/env.mjs'; +export * as env from './env.mjs'; /** environment-specific file reading */ -export * as fs from './client/fs.mjs'; +export * as fs from './fs.mjs'; /** environment-specific data persistence */ -export * as storage from './client/storage.mjs'; +export * as storage from './storage.mjs'; /** access to electron renderer apis */ -export * as electron from './client/electron.mjs'; +export * as electron from './electron.mjs'; /** a basic wrapper around notion's unofficial api */ -export * as notion from './client/notion.mjs'; +// export * as notion from './notion.mjs'; /** helpers for formatting, validating and parsing values */ -export * as fmt from './client/fmt.mjs'; +export * as fmt from './fmt.mjs'; /** interactions with the enhancer's repository of mods */ -export * as registry from './client/registry.mjs'; +export * as registry from './registry.mjs'; /** helpers for manipulation of a webpage */ -export * as web from './client/web.mjs'; +export * as web from './web.mjs'; /** shared notion-style elements */ -export * as components from './client/components/index.mjs'; +export * as components from './components/index.mjs'; diff --git a/api/node/env.cjs b/api/node/env.cjs deleted file mode 100644 index 536e442..0000000 --- a/api/node/env.cjs +++ /dev/null @@ -1,53 +0,0 @@ -/* - * notion-enhancer core: api - * (c) 2021 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -'use strict'; -module.exports = {}; - -/** - * environment-specific methods and constants - * @module notion-enhancer/api/env - */ - -const env = require('notion-enhancer/env/env.cjs'); - -/** - * the environment/platform name code is currently being executed in - * @constant - * @type {string} - */ -module.exports.name = env.name; - -/** - * the current version of the enhancer - * @constant - * @type {string} - */ -module.exports.version = env.version; - -/** - * open the enhancer's menu - * @type {function} - */ -module.exports.focusMenu = env.focusMenu; - -/** - * focus an active notion tab - * @type {function} - */ -module.exports.focusNotion = env.focusNotion; - -/** - * reload all notion and enhancer menu tabs to apply changes - * @type {function} - */ -module.exports.reload = env.reload; - -/** - * require() notion app files - * @param {string} path - path from the root of notion/resources/app/ e.g. main/createWindow.js - */ -module.exports.notionRequire = env.notionRequire; diff --git a/api/node/fs.cjs b/api/node/fs.cjs deleted file mode 100644 index d189dcd..0000000 --- a/api/node/fs.cjs +++ /dev/null @@ -1,49 +0,0 @@ -/* - * notion-enhancer core: api - * (c) 2021 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -'use strict'; -module.exports = {}; - -/** - * environment-specific file reading - * @module notion-enhancer/api/fs - */ - -const fs = require('notion-enhancer/env/fs.cjs'); - -/** - * transform a path relative to the enhancer root directory into an absolute path - * @type {function} - * @param {string} path - a url or within-the-enhancer filepath - * @returns {string} an absolute filepath - */ -module.exports.localPath = fs.localPath; - -/** - * fetch and parse a json file's contents - * @type {function} - * @param {string} path - a url or within-the-enhancer filepath - * @param {object} [opts] - the second argument of a fetch() request - * @returns {object} the json value of the requested file as a js object - */ -module.exports.getJSON = fs.getJSON; - -/** - * fetch a text file's contents - * @type {function} - * @param {string} path - a url or within-the-enhancer filepath - * @param {object} [opts] - the second argument of a fetch() request - * @returns {string} the text content of the requested file - */ -module.exports.getText = fs.getText; - -/** - * check if a file exists - * @type {function} - * @param {string} path - a url or within-the-enhancer filepath - * @returns {boolean} whether or not the file exists - */ -module.exports.isFile = fs.isFile; diff --git a/api/node/registry-validation.cjs b/api/node/registry-validation.cjs deleted file mode 100644 index 9e385c4..0000000 --- a/api/node/registry-validation.cjs +++ /dev/null @@ -1,223 +0,0 @@ -/* - * notion-enhancer core: api - * (c) 2021 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -'use strict'; - -const check = async ( - mod, - key, - value, - types, - { - extension = '', - error = `invalid ${key} (${extension ? `${extension} ` : ''}${types}): ${JSON.stringify( - value - )}`, - optional = false, - } = {} -) => { - const { is } = require('notion-enhancer/api/node/fmt.cjs'); - let test; - for (const type of Array.isArray(types) ? [types] : types.split('|')) { - if (type === 'file') { - test = - value && !value.startsWith('http') - ? await is(`repo/${mod._dir}/${value}`, type, { extension }) - : false; - } else test = await is(value, type, { extension }); - if (test) break; - } - if (!test) { - if (optional && (await is(value, 'undefined'))) return true; - if (error) mod._err(error); - return false; - } - return true; -}; - -const validateEnvironments = async (mod) => { - const { supportedEnvs } = require('notion-enhancer/api/node/registry.cjs'); - mod.environments = mod.environments ?? supportedEnvs; - const isArray = await check(mod, 'environments', mod.environments, 'array'); - if (!isArray) return false; - return mod.environments.map((tag) => check(mod, 'environments.env', tag, supportedEnvs)); - }, - validateTags = async (mod) => { - const isArray = await check(mod, 'tags', mod.tags, 'array'); - if (!isArray) return false; - const categoryTags = ['core', 'extension', 'theme', 'integration'], - containsCategory = mod.tags.filter((tag) => categoryTags.includes(tag)).length; - if (!containsCategory) { - mod._err( - `invalid tags (must contain at least one of 'core', 'extension', 'theme' or 'integration'): - ${JSON.stringify(mod.tags)}` - ); - return false; - } - const isTheme = mod.tags.includes('theme'), - hasThemeMode = mod.tags.includes('light') || mod.tags.includes('dark'), - isBothThemeModes = mod.tags.includes('light') && mod.tags.includes('dark'); - if (isTheme && (!hasThemeMode || isBothThemeModes)) { - mod._err( - `invalid tags (themes must be either 'light' or 'dark', not neither or both): - ${JSON.stringify(mod.tags)}` - ); - return false; - } - return mod.tags.map((tag) => check(mod, 'tags.tag', tag, 'string')); - }, - validateAuthors = async (mod) => { - const isArray = await check(mod, 'authors', mod.authors, 'array'); - if (!isArray) return false; - return mod.authors.map((author) => [ - check(mod, 'authors.author.name', author.name, 'string'), - check(mod, 'authors.author.email', author.email, 'email', { optional: true }), - check(mod, 'authors.author.homepage', author.homepage, 'url'), - check(mod, 'authors.author.avatar', author.avatar, 'url'), - ]); - }, - validateCSS = async (mod) => { - const isArray = await check(mod, 'css', mod.css, 'object'); - if (!isArray) return false; - const tests = []; - for (let dest of ['frame', 'client', 'menu']) { - if (!mod.css[dest]) continue; - let test = await check(mod, `css.${dest}`, mod.css[dest], 'array'); - if (test) { - test = mod.css[dest].map((file) => - check(mod, `css.${dest}.file`, file, 'file', { extension: '.css' }) - ); - } - tests.push(test); - } - return tests; - }, - validateJS = async (mod) => { - const isArray = await check(mod, 'js', mod.js, 'object'); - if (!isArray) return false; - const tests = []; - for (let dest of ['frame', 'client', 'menu']) { - if (!mod.js[dest]) continue; - let test = await check(mod, `js.${dest}`, mod.js[dest], 'array'); - if (test) { - test = mod.js[dest].map((file) => - check(mod, `js.${dest}.file`, file, 'file', { extension: '.mjs' }) - ); - } - tests.push(test); - } - if (mod.js.electron) { - const isArray = await check(mod, 'js.electron', mod.js.electron, 'array'); - if (isArray) { - for (const file of mod.js.electron) { - const isObject = await check(mod, 'js.electron.file', file, 'object'); - if (!isObject) { - tests.push(false); - continue; - } - tests.push([ - check(mod, 'js.electron.file.source', file.source, 'file', { - extension: '.cjs', - }), - // referencing the file within the electron app - // existence can't be validated, so only format is - check(mod, 'js.electron.file.target', file.target, 'string', { - extension: '.js', - }), - ]); - } - } else tests.push(false); - } - return tests; - }, - validateOptions = async (mod) => { - const { supportedEnvs, optionTypes } = require('notion-enhancer/api/node/registry.cjs'), - isArray = await check(mod, 'options', mod.options, 'array'); - if (!isArray) return false; - const tests = []; - for (const option of mod.options) { - const key = 'options.option', - optTypeValid = await check(mod, `${key}.type`, option.type, optionTypes); - if (!optTypeValid) { - tests.push(false); - continue; - } - option.environments = option.environments ?? supportedEnvs; - tests.push([ - check(mod, `${key}.key`, option.key, 'alphanumeric'), - check(mod, `${key}.label`, option.label, 'string'), - check(mod, `${key}.tooltip`, option.tooltip, 'string', { - optional: true, - }), - check(mod, `${key}.environments`, option.environments, 'array').then((isArray) => { - if (!isArray) return false; - return option.environments.map((environment) => - check(mod, `${key}.environments.env`, environment, supportedEnvs) - ); - }), - ]); - switch (option.type) { - case 'toggle': - tests.push(check(mod, `${key}.value`, option.value, 'boolean')); - break; - case 'select': { - let test = await check(mod, `${key}.values`, option.values, 'array'); - if (test) { - test = option.values.map((value) => - check(mod, `${key}.values.value`, value, 'string') - ); - } - tests.push(test); - break; - } - case 'text': - case 'hotkey': - tests.push(check(mod, `${key}.value`, option.value, 'string')); - break; - case 'number': - case 'color': - tests.push(check(mod, `${key}.value`, option.value, option.type)); - break; - case 'file': { - let test = await check(mod, `${key}.extensions`, option.extensions, 'array'); - if (test) { - test = option.extensions.map((ext) => - check(mod, `${key}.extensions.extension`, ext, 'string') - ); - } - tests.push(test); - break; - } - } - } - return tests; - }; - -/** - * internally used to validate mod.json files and provide helpful errors - * @private - * @param {object} mod - a mod's mod.json in object form - * @returns {boolean} whether or not the mod has passed validation - */ -module.exports.validate = async function (mod) { - let conditions = [ - check(mod, 'name', mod.name, 'string'), - check(mod, 'id', mod.id, 'uuid'), - check(mod, 'version', mod.version, 'semver'), - validateEnvironments(mod), - check(mod, 'description', mod.description, 'string'), - check(mod, 'preview', mod.preview, 'file|url', { optional: true }), - validateTags(mod), - validateAuthors(mod), - validateCSS(mod), - validateJS(mod), - validateOptions(mod), - ]; - do { - conditions = await Promise.all(conditions.flat(Infinity)); - } while (conditions.some((condition) => Array.isArray(condition))); - return conditions.every((passed) => passed); -}; diff --git a/api/node/registry.cjs b/api/node/registry.cjs deleted file mode 100644 index 532705a..0000000 --- a/api/node/registry.cjs +++ /dev/null @@ -1,166 +0,0 @@ -/* - * notion-enhancer core: api - * (c) 2021 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -'use strict'; - -/** - * interactions with the enhancer's repository of mods - * @module notion-enhancer/api/registry - */ - -/** - * mod ids whitelisted as part of the enhancer's core, permanently enabled - * @constant - * @type {array} - */ -module.exports.core = [ - 'a6621988-551d-495a-97d8-3c568bca2e9e', - '0f0bf8b6-eae6-4273-b307-8fc43f2ee082', - '36a2ffc9-27ff-480e-84a7-c7700a7d232d', -]; - -/** - * all environments/platforms currently supported by the enhancer - * @constant - * @type {array} - */ -module.exports.supportedEnvs = ['linux', 'win32', 'darwin', 'extension']; - -/** - * all available configuration types - * @constant - * @type {array} - */ -module.exports.optionTypes = ['toggle', 'select', 'text', 'number', 'color', 'file', 'hotkey']; - -/** - * the name of the active configuration profile - * @returns {string} - */ -module.exports.profileName = async () => { - const storage = require('notion-enhancer/api/node/storage.cjs'); - return storage.get(['currentprofile'], 'default'); -}; - -/** - * the root database for the current profile - * @returns {object} the get/set functions for the profile's storage - */ -module.exports.profileDB = async () => { - const storage = require('notion-enhancer/api/node/storage.cjs'); - return storage.db(['profiles', await module.exports.profileName()]); -}; - -let _list, - _errors = []; -/** - * list all available mods in the repo - * @param {function} filter - a function to filter out mods - * @returns {array} a validated list of mod.json objects - */ -module.exports.list = async (filter = (mod) => true) => { - if (!_list) { - const { validate } = require('notion-enhancer/api/node/registry-validation.cjs'), - { getJSON } = require('notion-enhancer/api/node/fs.cjs'); - _list = new Promise(async (res, rej) => { - const passed = []; - for (const dir of await getJSON('repo/registry.json')) { - try { - const mod = { - ...(await getJSON(`repo/${dir}/mod.json`)), - _dir: dir, - _err: (message) => _errors.push({ source: dir, message }), - }; - if (await validate(mod)) passed.push(mod); - } catch { - _errors.push({ source: dir, message: 'invalid mod.json' }); - } - } - res(passed); - }); - } - const filtered = []; - for (const mod of await _list) if (await filter(mod)) filtered.push(mod); - return filtered; -}; - -/** - * list validation errors encountered when loading the repo - * @returns {array} error objects with an error message and a source directory - */ -module.exports.errors = async () => { - await module.exports.list(); - return _errors; -}; - -/** - * get a single mod from the repo - * @param {string} id - the uuid of the mod - * @returns {object} the mod's mod.json - */ -module.exports.get = async (id) => { - return (await module.exports.list((mod) => mod.id === id))[0]; -}; - -/** - * checks if a mod is enabled: affected by the core whitelist, - * environment and menu configuration - * @param {string} id - the uuid of the mod - * @returns {boolean} whether or not the mod is enabled - */ -module.exports.enabled = async (id) => { - const env = require('notion-enhancer/api/node/env.cjs'), - mod = await module.exports.get(id); - if (!mod.environments.includes(env.name)) return false; - if (module.exports.core.includes(id)) return true; - return (await module.exports.profileDB()).get(['_mods', id], false); -}; - -/** - * get a default value of a mod's option according to its mod.json - * @param {string} id - the uuid of the mod - * @param {string} key - the key of the option - * @returns {string|number|boolean|undefined} the option's default value - */ -module.exports.optionDefault = async (id, key) => { - const mod = await module.exports.get(id), - opt = mod.options.find((opt) => opt.key === key); - if (!opt) return undefined; - switch (opt.type) { - case 'toggle': - case 'text': - case 'number': - case 'color': - case 'hotkey': - return opt.value; - case 'select': - return opt.values[0]; - case 'file': - return undefined; - } -}; - -/** - * access the storage partition of a mod in the current profile - * @param {string} id - the uuid of the mod - * @returns {object} an object with the wrapped get/set functions - */ -module.exports.db = async (id) => { - const storage = require('notion-enhancer/api/node/storage.cjs'), - db = await module.exports.profileDB(); - return storage.db( - [id], - async (path, fallback = undefined) => { - if (typeof path === 'string') path = [path]; - if (path.length === 2) { - // profiles -> profile -> mod -> option - fallback = (await module.exports.optionDefault(id, path[1])) ?? fallback; - } - return db.get(path, fallback); - }, - db.set - ); -}; diff --git a/api/node/storage.cjs b/api/node/storage.cjs deleted file mode 100644 index db30e53..0000000 --- a/api/node/storage.cjs +++ /dev/null @@ -1,66 +0,0 @@ -/* - * notion-enhancer core: api - * (c) 2021 dragonwocky (https://dragonwocky.me/) - * (https://notion-enhancer.github.io/) under the MIT license - */ - -'use strict'; -module.exports = {}; - -/** - * environment-specific data persistence - * @module notion-enhancer/api/storage - */ - -const storage = require('notion-enhancer/env/storage.cjs'); - -/** - * get persisted data - * @type {function} - * @param {array} path - the path of keys to the value being fetched - * @param {*} [fallback] - a default value if the path is not matched - * @returns {Promise} value ?? fallback - */ -module.exports.get = storage.get; - -/** - * persist data - * @type {function} - * @param {array} path - the path of keys to the value being set - * @param {*} value - the data to save - * @returns {Promise} resolves when data has been saved - */ -module.exports.set = storage.set; - -/** - * create a wrapper for accessing a partition of the storage - * @type {function} - * @param {array} namespace - the path of keys to prefix all storage requests with - * @param {function} [get] - the storage get function to be wrapped - * @param {function} [set] - the storage set function to be wrapped - * @returns {object} an object with the wrapped get/set functions - */ -module.exports.db = storage.db; - -/** - * add an event listener for changes in storage - * @type {function} - * @param {onStorageChangeCallback} callback - called whenever a change in - * storage is initiated from the current process - */ -module.exports.addChangeListener = storage.addChangeListener; - -/** - * remove a listener added with storage.addChangeListener - * @type {function} - * @param {onStorageChangeCallback} callback - */ -module.exports.removeChangeListener = storage.removeChangeListener; - -/** - * @callback onStorageChangeCallback - * @param {object} event - * @param {string} event.path- the path of keys to the changed value - * @param {string} [event.new] - the new value being persisted to the store - * @param {string} [event.old] - the previous value associated with the key - */ diff --git a/api/client/notion.mjs b/api/notion.mjs similarity index 99% rename from api/client/notion.mjs rename to api/notion.mjs index 9834f90..8c2ba8d 100644 --- a/api/client/notion.mjs +++ b/api/notion.mjs @@ -1,5 +1,5 @@ -/* - * notion-enhancer core: api +/** + * notion-enhancer: api * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ @@ -11,7 +11,7 @@ * @module notion-enhancer/api/notion */ -import { web, fs, fmt } from '../index.mjs'; +import { web, fs, fmt } from './index.mjs'; const standardiseUUID = (uuid) => { if (uuid?.length === 32 && !uuid.includes('-')) { diff --git a/api/client/registry-validation.mjs b/api/registry-validation.mjs similarity index 98% rename from api/client/registry-validation.mjs rename to api/registry-validation.mjs index e75e34e..d467e8a 100644 --- a/api/client/registry-validation.mjs +++ b/api/registry-validation.mjs @@ -1,12 +1,12 @@ -/* - * notion-enhancer core: api +/** + * notion-enhancer: api * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ 'use strict'; -import { fmt, registry } from '../index.mjs'; +import { fmt, registry } from './index.mjs'; const check = async ( mod, diff --git a/api/client/registry.mjs b/api/registry.mjs similarity index 98% rename from api/client/registry.mjs rename to api/registry.mjs index eafd41c..cc667e6 100644 --- a/api/client/registry.mjs +++ b/api/registry.mjs @@ -1,5 +1,5 @@ -/* - * notion-enhancer core: api +/** + * notion-enhancer: api * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ @@ -11,7 +11,7 @@ * @module notion-enhancer/api/registry */ -import { env, fs, storage } from '../index.mjs'; +import { env, fs, storage } from './index.mjs'; import { validate } from './registry-validation.mjs'; /** diff --git a/api/client/storage.mjs b/api/storage.mjs similarity index 95% rename from api/client/storage.mjs rename to api/storage.mjs index a1d9c66..9d62c5b 100644 --- a/api/client/storage.mjs +++ b/api/storage.mjs @@ -1,5 +1,5 @@ -/* - * notion-enhancer core: api +/** + * notion-enhancer: api * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ @@ -11,7 +11,7 @@ * @module notion-enhancer/api/storage */ -import * as storage from '../../env/storage.mjs'; +import * as storage from '../env/storage.mjs'; /** * get persisted data diff --git a/api/client/web.mjs b/api/web.mjs similarity index 91% rename from api/client/web.mjs rename to api/web.mjs index 342824c..b547e77 100644 --- a/api/client/web.mjs +++ b/api/web.mjs @@ -1,5 +1,5 @@ -/* - * notion-enhancer core: api +/** + * notion-enhancer: api * (c) 2021 dragonwocky (https://dragonwocky.me/) * (https://notion-enhancer.github.io/) under the MIT license */ @@ -11,17 +11,14 @@ * @module notion-enhancer/api/web */ -import { fs } from '../index.mjs'; -import '../../dep/jscolor.min.js'; +import { fs } from './index.mjs'; -let _hotkeyEventListeners = [], +let _hotkeyListenersActivated = false, + _hotkeyEventListeners = [], _documentObserver, _documentObserverListeners = [], _documentObserverEvents = []; -/** color picker with alpha channel using https://jscolor.com/ */ -export const jscolor = JSColor; - /** * wait until a page is loaded and ready for modification * @param {array} [selectors] - wait for the existence of elements that match these css selectors @@ -133,14 +130,12 @@ export const empty = ($container) => { * @param {string} path - a url or within-the-enhancer filepath */ export const loadStylesheet = (path) => { - render( - document.head, - html`` - ); - return true; + const $stylesheet = html``; + render(document.head, $stylesheet); + return $stylesheet; }; /** @@ -193,16 +188,6 @@ const triggerHotkeyListener = (event, hotkey) => { }); if (pressed) hotkey.callback(event); }; -document.addEventListener('keyup', (event) => { - for (const hotkey of _hotkeyEventListeners.filter(({ keydown }) => !keydown)) { - triggerHotkeyListener(event, hotkey); - } -}); -document.addEventListener('keydown', (event) => { - for (const hotkey of _hotkeyEventListeners.filter(({ keydown }) => keydown)) { - triggerHotkeyListener(event, hotkey); - } -}); /** * register a hotkey listener to the page @@ -224,6 +209,20 @@ export const addHotkeyListener = ( ) => { if (typeof keys === 'string') keys = keys.split('+'); _hotkeyEventListeners.push({ keys, callback, listenInInput, keydown }); + + if (!_hotkeyListenersActivated) { + _hotkeyListenersActivated = true; + document.addEventListener('keyup', (event) => { + for (const hotkey of _hotkeyEventListeners.filter(({ keydown }) => !keydown)) { + triggerHotkeyListener(event, hotkey); + } + }); + document.addEventListener('keydown', (event) => { + for (const hotkey of _hotkeyEventListeners.filter(({ keydown }) => keydown)) { + triggerHotkeyListener(event, hotkey); + } + }); + } }; /** * remove a listener added with web.addHotkeyListener From 21d4991f38c2120a6f31f619ded4228f422e33a1 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Wed, 8 Dec 2021 22:40:09 +1100 Subject: [PATCH 41/52] fs.notionPath, minify index.cjs --- api/electron.mjs | 12 +- api/fs.mjs | 11 + api/index.cjs | 1049 +--------------------------------------------- api/index.mjs | 4 +- 4 files changed, 34 insertions(+), 1042 deletions(-) diff --git a/api/electron.mjs b/api/electron.mjs index 9fa95df..7251c93 100644 --- a/api/electron.mjs +++ b/api/electron.mjs @@ -48,7 +48,8 @@ export const webFrame = globalThis.__enhancerElectronApi?.webFrame; * @runtime client * @runtime menu */ -export const sendMessage = globalThis.__enhancerElectronApi?.ipcRenderer?.sendMessage; +export const sendMessage = (channel, data) => + globalThis.__enhancerElectronApi.ipcRenderer.sendMessage(channel, data); /** * send a message to the webview's parent renderer process @@ -61,14 +62,14 @@ export const sendMessage = globalThis.__enhancerElectronApi?.ipcRenderer?.sendMe * @runtime client * @runtime menu */ -export const sendMessageToHost = - globalThis.__enhancerElectronApi?.ipcRenderer?.sendMessageToHost; +export const sendMessageToHost = (channel, data) => + globalThis.__enhancerElectronApi.ipcRenderer.sendMessageToHost(channel, data); /** * receive a message from either the main process or * the webview's parent renderer process * @param {string} channel - the message identifier to listen for - * @param {function} listener - the message handler, passed the args (event, data) + * @param {function} callback - the message handler, passed the args (event, data) * * @env win32 * @env linux @@ -76,4 +77,5 @@ export const sendMessageToHost = * @runtime client * @runtime menu */ -export const onMessage = globalThis.__enhancerElectronApi?.ipcRenderer?.onMessage; +export const onMessage = (channel, callback) => + globalThis.__enhancerElectronApi.ipcRenderer.onMessage(channel, callback); diff --git a/api/fs.mjs b/api/fs.mjs index 659517f..a6e40ab 100644 --- a/api/fs.mjs +++ b/api/fs.mjs @@ -46,3 +46,14 @@ export const getText = fs.getText; * @returns {boolean} whether or not the file exists */ export const isFile = fs.isFile; + +/** + * get an absolute path to files within notion + * @param {string} path - relative to the root notion/resources/app/ e.g. renderer/search.js + * + * @env win32 + * @env linux + * @env darwin + * @runtime electron + */ +export const notionPath = fs.notionPath; diff --git a/api/index.cjs b/api/index.cjs index f3a32ce..f6d8f8c 100644 --- a/api/index.cjs +++ b/api/index.cjs @@ -1,1042 +1,21 @@ -var __defProp = Object.defineProperty; -var __markAsModule = (target) => __defProp(target, "__esModule", { value: true }); -var __export = (target, all) => { - __markAsModule(target); - for (var name3 in all) - __defProp(target, name3, { get: all[name3], enumerable: true }); -}; - -// insert/api/index.mjs -__export(exports, { - components: () => components_exports, - electron: () => electron_exports, - env: () => env_exports, - fmt: () => fmt_exports, - fs: () => fs_exports, - registry: () => registry_exports, - storage: () => storage_exports, - web: () => web_exports -}); - -// insert/api/env.mjs -var env_exports = {}; -__export(env_exports, { - focusMenu: () => focusMenu2, - focusNotion: () => focusNotion2, - name: () => name2, - notionRequire: () => notionRequire2, - reload: () => reload2, - version: () => version2 -}); - -// insert/env/env.mjs -"use strict"; -var name = globalThis.__enhancerElectronApi.platform; -var version = globalThis.__enhancerElectronApi.version; -var focusMenu = globalThis.__enhancerElectronApi.focusMenu; -var focusNotion = globalThis.__enhancerElectronApi.focusNotion; -var reload = globalThis.__enhancerElectronApi.reload; -var notionRequire = globalThis.__enhancerElectronApi.notionRequire; - -// insert/api/env.mjs -"use strict"; -var name2 = name; -var version2 = version; -var focusMenu2 = focusMenu; -var focusNotion2 = focusNotion; -var reload2 = reload; -var notionRequire2 = notionRequire; - -// insert/api/fs.mjs -var fs_exports = {}; -__export(fs_exports, { - getJSON: () => getJSON2, - getText: () => getText2, - isFile: () => isFile2, - localPath: () => localPath2 -}); - -// insert/env/fs.mjs -"use strict"; -var localPath = (path) => `notion://www.notion.so/__notion-enhancer/${path}`; -var getJSON = (path, opts = {}) => { - if (path.startsWith("http")) - return fetch(path, opts).then((res) => res.json()); - try { - return globalThis.__enhancerElectronApi.nodeRequire(`notion-enhancer/${path}`); - } catch (err) { - return fetch(localPath(path), opts).then((res) => res.json()); - } -}; -var getText = (path, opts = {}) => { - if (path.startsWith("http")) - return fetch(path, opts).then((res) => res.text()); - try { - const fs2 = globalThis.__enhancerElectronApi.nodeRequire("fs"), { resolve: resolvePath } = globalThis.__enhancerElectronApi.nodeRequire("path"); - return fs2.readFileSync(resolvePath(`${__dirname}/../../${path}`)); - } catch (err) { - return fetch(localPath(path), opts).then((res) => res.text()); - } -}; -var isFile = async (path) => { - try { - const fs2 = globalThis.__enhancerElectronApi.nodeRequire("fs"), { resolve: resolvePath } = globalThis.__enhancerElectronApi.nodeRequire("path"); - if (path.startsWith("http")) { - await fetch(path); - } else { - try { - fs2.existsSync(resolvePath(`${__dirname}/../../${path}`)); - } catch (err) { - await fetch(localPath(path)); - } - } - return true; - } catch { - return false; - } -}; - -// insert/api/fs.mjs -"use strict"; -var localPath2 = localPath; -var getJSON2 = getJSON; -var getText2 = getText; -var isFile2 = isFile; - -// insert/api/storage.mjs -var storage_exports = {}; -__export(storage_exports, { - addChangeListener: () => addChangeListener2, - db: () => db2, - get: () => get2, - removeChangeListener: () => removeChangeListener2, - set: () => set2 -}); - -// insert/env/storage.mjs -"use strict"; -var get = (path, fallback = void 0) => { - return globalThis.__enhancerElectronApi.db.get(path, fallback); -}; -var set = (path, value) => { - return globalThis.__enhancerElectronApi.db.set(path, value); -}; -var db = (namespace, getFunc = get, setFunc = set) => { - if (typeof namespace === "string") - namespace = [namespace]; - return { - get: (path = [], fallback = void 0) => getFunc([...namespace, ...path], fallback), - set: (path, value) => setFunc([...namespace, ...path], value) - }; -}; -var addChangeListener = (callback) => { - return globalThis.__enhancerElectronApi.db.addChangeListener(callback); -}; -var removeChangeListener = (callback) => { - return globalThis.__enhancerElectronApi.db.removeChangeListener(callback); -}; - -// insert/api/storage.mjs -"use strict"; -var get2 = get; -var set2 = set; -var db2 = db; -var addChangeListener2 = addChangeListener; -var removeChangeListener2 = removeChangeListener; - -// insert/api/electron.mjs -var electron_exports = {}; -__export(electron_exports, { - browser: () => browser, - onMessage: () => onMessage, - sendMessage: () => sendMessage, - sendMessageToHost: () => sendMessageToHost, - webFrame: () => webFrame -}); -"use strict"; -var browser = globalThis.__enhancerElectronApi?.browser; -var webFrame = globalThis.__enhancerElectronApi?.webFrame; -var sendMessage = globalThis.__enhancerElectronApi?.ipcRenderer?.sendMessage; -var sendMessageToHost = globalThis.__enhancerElectronApi?.ipcRenderer?.sendMessageToHost; -var onMessage = globalThis.__enhancerElectronApi?.ipcRenderer?.onMessage; - -// insert/api/fmt.mjs -var fmt_exports = {}; -__export(fmt_exports, { - is: () => is, - rgbContrast: () => rgbContrast, - rgbLogShade: () => rgbLogShade, - slugger: () => slugger, - uuidv4: () => uuidv4 -}); -"use strict"; -var slugger = (heading, slugs = new Set()) => { - heading = heading.replace(/\s/g, "-").replace(/[^A-Za-z0-9-_]/g, "").toLowerCase(); - let i = 0, slug = heading; - while (slugs.has(slug)) { - i++; - slug = `${heading}-${i}`; - } - return slug; -}; -var uuidv4 = () => { - if (crypto?.randomUUID) - return crypto.randomUUID(); - return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)); -}; -var rgbLogShade = (p, c) => { - var i = parseInt, r = Math.round, [a, b, c, d] = c.split(","), P = p < 0, t = P ? 0 : p * 255 ** 2, P = P ? 1 + p : 1 - p; - return "rgb" + (d ? "a(" : "(") + r((P * i(a[3] == "a" ? a.slice(5) : a.slice(4)) ** 2 + t) ** 0.5) + "," + r((P * i(b) ** 2 + t) ** 0.5) + "," + r((P * i(c) ** 2 + t) ** 0.5) + (d ? "," + d : ")"); -}; -var rgbContrast = (r, g, b) => { - return Math.sqrt(0.299 * (r * r) + 0.587 * (g * g) + 0.114 * (b * b)) > 165.75 ? "rgb(0,0,0)" : "rgb(255,255,255)"; -}; -var patterns = { - alphanumeric: /^[\w\.-]+$/, - uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i, - semver: /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i, - email: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i, - url: /^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,64}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i, - color: /^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i -}; -function test(str, pattern) { - const match = str.match(pattern); - return !!(match && match.length); -} -var is = async (value, type, { extension = "" } = {}) => { - extension = !value || !value.endsWith || value.endsWith(extension); - if (Array.isArray(type)) { - return type.includes(value); - } - switch (type) { - case "array": - return Array.isArray(value); - case "object": - return value && typeof value === "object" && !Array.isArray(value); - case "undefined": - case "boolean": - case "number": - return typeof value === type && extension; - case "string": - return typeof value === type && extension; - case "alphanumeric": - case "uuid": - case "semver": - case "email": - case "url": - case "color": - return typeof value === "string" && test(value, patterns[type]) && extension; - case "file": - return typeof value === "string" && value && await fs_exports.isFile(value) && extension; - } - return false; -}; - -// insert/api/registry.mjs -var registry_exports = {}; -__export(registry_exports, { - core: () => core, - db: () => db3, - enabled: () => enabled, - errors: () => errors, - get: () => get3, - list: () => list, - optionDefault: () => optionDefault, - optionTypes: () => optionTypes, - profileDB: () => profileDB, - profileName: () => profileName, - supportedEnvs: () => supportedEnvs -}); - -// insert/api/registry-validation.mjs -"use strict"; -var check = async (mod, key, value, types, { - extension = "", - error = `invalid ${key} (${extension ? `${extension} ` : ""}${types}): ${JSON.stringify(value)}`, - optional = false -} = {}) => { - let test2; - for (const type of Array.isArray(types) ? [types] : types.split("|")) { - if (type === "file") { - test2 = value && !value.startsWith("http") ? await fmt_exports.is(`repo/${mod._dir}/${value}`, type, { extension }) : false; - } else - test2 = await fmt_exports.is(value, type, { extension }); - if (test2) - break; - } - if (!test2) { - if (optional && await fmt_exports.is(value, "undefined")) - return true; - if (error) - mod._err(error); - return false; - } - return true; -}; -var validateEnvironments = async (mod) => { - mod.environments = mod.environments ?? registry_exports.supportedEnvs; - const isArray = await check(mod, "environments", mod.environments, "array"); - if (!isArray) - return false; - return mod.environments.map((tag) => check(mod, "environments.env", tag, registry_exports.supportedEnvs)); -}; -var validateTags = async (mod) => { - const isArray = await check(mod, "tags", mod.tags, "array"); - if (!isArray) - return false; - const categoryTags = ["core", "extension", "theme", "integration"], containsCategory = mod.tags.filter((tag) => categoryTags.includes(tag)).length; - if (!containsCategory) { - mod._err(`invalid tags (must contain at least one of 'core', 'extension', 'theme' or 'integration'): - ${JSON.stringify(mod.tags)}`); - return false; - } - const isTheme = mod.tags.includes("theme"), hasThemeMode = mod.tags.includes("light") || mod.tags.includes("dark"), isBothThemeModes = mod.tags.includes("light") && mod.tags.includes("dark"); - if (isTheme && (!hasThemeMode || isBothThemeModes)) { - mod._err(`invalid tags (themes must be either 'light' or 'dark', not neither or both): - ${JSON.stringify(mod.tags)}`); - return false; - } - return mod.tags.map((tag) => check(mod, "tags.tag", tag, "string")); -}; -var validateAuthors = async (mod) => { - const isArray = await check(mod, "authors", mod.authors, "array"); - if (!isArray) - return false; - return mod.authors.map((author) => [ - check(mod, "authors.author.name", author.name, "string"), - check(mod, "authors.author.email", author.email, "email", { optional: true }), - check(mod, "authors.author.homepage", author.homepage, "url"), - check(mod, "authors.author.avatar", author.avatar, "url") - ]); -}; -var validateCSS = async (mod) => { - const isArray = await check(mod, "css", mod.css, "object"); - if (!isArray) - return false; - const tests = []; - for (let dest of ["frame", "client", "menu"]) { - if (!mod.css[dest]) - continue; - let test2 = await check(mod, `css.${dest}`, mod.css[dest], "array"); - if (test2) { - test2 = mod.css[dest].map((file) => check(mod, `css.${dest}.file`, file, "file", { extension: ".css" })); - } - tests.push(test2); - } - return tests; -}; -var validateJS = async (mod) => { - const isArray = await check(mod, "js", mod.js, "object"); - if (!isArray) - return false; - const tests = []; - for (let dest of ["frame", "client", "menu"]) { - if (!mod.js[dest]) - continue; - let test2 = await check(mod, `js.${dest}`, mod.js[dest], "array"); - if (test2) { - test2 = mod.js[dest].map((file) => check(mod, `js.${dest}.file`, file, "file", { extension: ".mjs" })); - } - tests.push(test2); - } - if (mod.js.electron) { - const isArray2 = await check(mod, "js.electron", mod.js.electron, "array"); - if (isArray2) { - for (const file of mod.js.electron) { - const isObject = await check(mod, "js.electron.file", file, "object"); - if (!isObject) { - tests.push(false); - continue; - } - tests.push([ - check(mod, "js.electron.file.source", file.source, "file", { - extension: ".cjs" - }), - check(mod, "js.electron.file.target", file.target, "string", { - extension: ".js" - }) - ]); - } - } else - tests.push(false); - } - return tests; -}; -var validateOptions = async (mod) => { - const isArray = await check(mod, "options", mod.options, "array"); - if (!isArray) - return false; - const tests = []; - for (const option of mod.options) { - const key = "options.option", optTypeValid = await check(mod, `${key}.type`, option.type, registry_exports.optionTypes); - if (!optTypeValid) { - tests.push(false); - continue; - } - option.environments = option.environments ?? registry_exports.supportedEnvs; - tests.push([ - check(mod, `${key}.key`, option.key, "alphanumeric"), - check(mod, `${key}.label`, option.label, "string"), - check(mod, `${key}.tooltip`, option.tooltip, "string", { - optional: true - }), - check(mod, `${key}.environments`, option.environments, "array").then((isArray2) => { - if (!isArray2) - return false; - return option.environments.map((environment) => check(mod, `${key}.environments.env`, environment, registry_exports.supportedEnvs)); - }) - ]); - switch (option.type) { - case "toggle": - tests.push(check(mod, `${key}.value`, option.value, "boolean")); - break; - case "select": { - let test2 = await check(mod, `${key}.values`, option.values, "array"); - if (test2) { - test2 = option.values.map((value) => check(mod, `${key}.values.value`, value, "string")); - } - tests.push(test2); - break; - } - case "text": - case "hotkey": - tests.push(check(mod, `${key}.value`, option.value, "string")); - break; - case "number": - case "color": - tests.push(check(mod, `${key}.value`, option.value, option.type)); - break; - case "file": { - let test2 = await check(mod, `${key}.extensions`, option.extensions, "array"); - if (test2) { - test2 = option.extensions.map((ext) => check(mod, `${key}.extensions.extension`, ext, "string")); - } - tests.push(test2); - break; - } - } - } - return tests; -}; -async function validate(mod) { - let conditions = [ - check(mod, "name", mod.name, "string"), - check(mod, "id", mod.id, "uuid"), - check(mod, "version", mod.version, "semver"), - validateEnvironments(mod), - check(mod, "description", mod.description, "string"), - check(mod, "preview", mod.preview, "file|url", { optional: true }), - validateTags(mod), - validateAuthors(mod), - validateCSS(mod), - validateJS(mod), - validateOptions(mod) - ]; - do { - conditions = await Promise.all(conditions.flat(Infinity)); - } while (conditions.some((condition) => Array.isArray(condition))); - return conditions.every((passed) => passed); -} - -// insert/api/registry.mjs -"use strict"; -var core = [ - "a6621988-551d-495a-97d8-3c568bca2e9e", - "0f0bf8b6-eae6-4273-b307-8fc43f2ee082", - "36a2ffc9-27ff-480e-84a7-c7700a7d232d" -]; -var supportedEnvs = ["linux", "win32", "darwin", "extension"]; -var optionTypes = ["toggle", "select", "text", "number", "color", "file", "hotkey"]; -var profileName = async () => storage_exports.get(["currentprofile"], "default"); -var profileDB = async () => storage_exports.db(["profiles", await profileName()]); -var _list; -var _errors = []; -var list = async (filter = (mod) => true) => { - if (!_list) { - _list = new Promise(async (res, rej) => { - const passed = []; - for (const dir of await fs_exports.getJSON("repo/registry.json")) { - try { - const mod = { - ...await fs_exports.getJSON(`repo/${dir}/mod.json`), - _dir: dir, - _err: (message) => _errors.push({ source: dir, message }) - }; - if (await validate(mod)) - passed.push(mod); - } catch { - _errors.push({ source: dir, message: "invalid mod.json" }); - } - } - res(passed); - }); - } - const filtered = []; - for (const mod of await _list) - if (await filter(mod)) - filtered.push(mod); - return filtered; -}; -var errors = async () => { - await list(); - return _errors; -}; -var get3 = async (id) => { - return (await list((mod) => mod.id === id))[0]; -}; -var enabled = async (id) => { - const mod = await get3(id); - if (!mod.environments.includes(env_exports.name)) - return false; - if (core.includes(id)) - return true; - return (await profileDB()).get(["_mods", id], false); -}; -var optionDefault = async (id, key) => { - const mod = await get3(id), opt = mod.options.find((opt2) => opt2.key === key); - if (!opt) - return void 0; - switch (opt.type) { - case "toggle": - case "text": - case "number": - case "color": - case "hotkey": - return opt.value; - case "select": - return opt.values[0]; - case "file": - return void 0; - } -}; -var db3 = async (id) => { - const db5 = await profileDB(); - return storage_exports.db([id], async (path, fallback = void 0) => { - if (typeof path === "string") - path = [path]; - if (path.length === 2) { - fallback = await optionDefault(id, path[1]) ?? fallback; - } - return db5.get(path, fallback); - }, db5.set); -}; - -// insert/api/web.mjs -var web_exports = {}; -__export(web_exports, { - addDocumentObserver: () => addDocumentObserver, - addHotkeyListener: () => addHotkeyListener, - copyToClipboard: () => copyToClipboard, - empty: () => empty, - escape: () => escape, - html: () => html, - loadStylesheet: () => loadStylesheet, - queryParams: () => queryParams, - raw: () => raw, - readFromClipboard: () => readFromClipboard, - removeDocumentObserver: () => removeDocumentObserver, - removeHotkeyListener: () => removeHotkeyListener, - render: () => render, - whenReady: () => whenReady -}); -"use strict"; -var _hotkeyListenersActivated = false; -var _hotkeyEventListeners = []; -var _documentObserver; -var _documentObserverListeners = []; -var _documentObserverEvents = []; -var whenReady = (selectors = []) => { - return new Promise((res, rej) => { - function onLoad() { - let isReadyInt; - isReadyInt = setInterval(isReadyTest, 100); - function isReadyTest() { - if (selectors.every((selector) => document.querySelector(selector))) { - clearInterval(isReadyInt); - res(true); - } - } - isReadyTest(); - } - if (document.readyState !== "complete") { - document.addEventListener("readystatechange", (event) => { - if (document.readyState === "complete") - onLoad(); - }); - } else - onLoad(); - }); -}; -var queryParams = () => new URLSearchParams(window.location.search); -var escape = (str) => str.replace(/&/g, "&").replace(//g, ">").replace(/'/g, "'").replace(/"/g, """).replace(/\\/g, "\"); -var raw = (str, ...templates) => { - const html2 = str.map((chunk) => chunk + (["string", "number"].includes(typeof templates[0]) ? templates.shift() : escape(JSON.stringify(templates.shift(), null, 2) ?? ""))).join(""); - return html2.includes(" line.trim()).filter((line) => line.length).join(" "); -}; -var html = (str, ...templates) => { - const $fragment = document.createRange().createContextualFragment(raw(str, ...templates)); - return $fragment.children.length === 1 ? $fragment.children[0] : $fragment.children; -}; -var render = ($container, ...$elems) => { - $elems = $elems.map(($elem) => $elem instanceof HTMLCollection ? [...$elem] : $elem).flat(Infinity).filter(($elem) => $elem); - $container.append(...$elems); - return $container; -}; -var empty = ($container) => { - while ($container.firstChild && $container.removeChild($container.firstChild)) - ; - return $container; -}; -var loadStylesheet = (path) => { - const $stylesheet4 = html`ce(e,"__esModule",{value:!0});var g=(e,t)=>{Ge(e);for(var n in t)ce(e,n,{get:t[n],enumerable:!0})};g(exports,{components:()=>N,electron:()=>I,env:()=>A,fmt:()=>h,fs:()=>p,notion:()=>K,registry:()=>y,storage:()=>$,web:()=>o});var A={};g(A,{focusMenu:()=>De,focusNotion:()=>et,name:()=>Qe,notionRequire:()=>nt,reload:()=>tt,version:()=>Ye});"use strict";var le=globalThis.__enhancerElectronApi.platform,de=globalThis.__enhancerElectronApi.version,pe=globalThis.__enhancerElectronApi.focusMenu,ue=globalThis.__enhancerElectronApi.focusNotion,he=globalThis.__enhancerElectronApi.reload,fe=globalThis.__enhancerElectronApi.notionRequire;"use strict";var Qe=le,Ye=de,De=pe,et=ue,tt=he,nt=fe;var p={};g(p,{getJSON:()=>st,getText:()=>ot,isFile:()=>it,localPath:()=>rt,notionPath:()=>at});"use strict";var E=e=>`notion://www.notion.so/__notion-enhancer/${e}`,me=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.json());try{return globalThis.__enhancerElectronApi.nodeRequire(`notion-enhancer/${e}`)}catch{return fetch(E(e),t).then(r=>r.json())}},ye=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.text());try{let n=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:r}=globalThis.__enhancerElectronApi.nodeRequire("path");return n.readFileSync(r(`${__dirname}/../../${e}`))}catch{return fetch(E(e),t).then(r=>r.text())}},ge=async e=>{try{let t=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:n}=globalThis.__enhancerElectronApi.nodeRequire("path");if(e.startsWith("http"))await fetch(e);else try{t.existsSync(n(`${__dirname}/../../${e}`))}catch{await fetch(E(e))}return!0}catch{return!1}},ve=globalThis.__enhancerElectronApi.notionPath;"use strict";var rt=E,st=me,ot=ye,it=ge,at=ve;var $={};g($,{addChangeListener:()=>pt,db:()=>dt,get:()=>ct,removeChangeListener:()=>ut,set:()=>lt});"use strict";var F=(e,t=void 0)=>globalThis.__enhancerElectronApi.db.get(e,t),H=(e,t)=>globalThis.__enhancerElectronApi.db.set(e,t),we=(e,t=F,n=H)=>(typeof e=="string"&&(e=[e]),{get:(r=[],s=void 0)=>t([...e,...r],s),set:(r,s)=>n([...e,...r],s)}),be=e=>globalThis.__enhancerElectronApi.db.addChangeListener(e),xe=e=>globalThis.__enhancerElectronApi.db.removeChangeListener(e);"use strict";var ct=F,lt=H,dt=we,pt=be,ut=xe;var I={};g(I,{browser:()=>ht,onMessage:()=>gt,sendMessage:()=>mt,sendMessageToHost:()=>yt,webFrame:()=>ft});"use strict";var ht=globalThis.__enhancerElectronApi?.browser,ft=globalThis.__enhancerElectronApi?.webFrame,mt=(e,t)=>globalThis.__enhancerElectronApi.ipcRenderer.sendMessage(e,t),yt=(e,t)=>globalThis.__enhancerElectronApi.ipcRenderer.sendMessageToHost(e,t),gt=(e,t)=>globalThis.__enhancerElectronApi.ipcRenderer.onMessage(e,t);var K={};g(K,{create:()=>bt,get:()=>_e,getPageID:()=>M,getSpaceID:()=>S,getUserID:()=>$e,search:()=>vt,set:()=>wt,sign:()=>$t,upload:()=>xt});"use strict";var m=e=>(e?.length===32&&!e.includes("-")&&(e=e.replace(/([\d\w]{8})([\d\w]{4})([\d\w]{4})([\d\w]{4})([\d\w]{12})/,"$1-$2-$3-$4-$5")),e),$e=()=>JSON.parse(localStorage["LRU:KeyValueStore2:current-user-id"]||{}).value,M=()=>m(o.queryParams().get("p")||location.pathname.split(/(-|\/)/g).reverse()[0]),V,S=async()=>(V||(V=(await _e(M())).space_id),V),_e=async(e,t="block")=>(e=m(e),(await p.getJSON("https://www.notion.so/api/v3/getRecordValues",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requests:[{table:t,id:e}]}),method:"POST"})).results[0].value),vt=async(e="",t=20,n=S())=>(n=m(await n),await p.getJSON("https://www.notion.so/api/v3/search",{headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"BlocksInSpace",query:e,spaceId:n,limit:t,filters:{isDeletedOnly:!1,excludeTemplates:!1,isNavigableOnly:!1,requireEditPermissions:!1,ancestors:[],createdBy:[],editedBy:[],lastEditedTime:{},createdTime:{}},sort:"Relevance",source:"quick_find"}),method:"POST"})),wt=async({recordID:e,recordTable:t="block",spaceID:n=S(),path:r=[]},s={})=>{n=m(await n),e=m(e);let c=await p.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:n,operations:[{pointer:{table:t,id:e,spaceId:n},path:r,command:r.length?"set":"update",args:s}]}]}),method:"POST"});return c.errorId?c:!0},bt=async({recordValue:e={},recordTable:t="block"}={},{prepend:n=!1,siblingID:r=void 0,parentID:s=M(),parentTable:c="block",spaceID:a=S(),userID:i=$e()}={})=>{a=m(await a),s=m(s),r=m(r);let d=m(e?.id??h.uuidv4()),x=[],j={type:"text",id:d,version:0,created_time:new Date().getTime(),last_edited_time:new Date().getTime(),parent_id:s,parent_table:c,alive:!0,created_by_table:"notion_user",created_by_id:i,last_edited_by_table:"notion_user",last_edited_by_id:i,space_id:a,permissions:[{type:"user_permission",role:"editor",user_id:i}]};c==="space"?(s=a,j.parent_id=a,x.push("pages"),j.type="page"):c==="collection_view"?(x.push("page_sort"),j.type="page"):x.push("content");let ae=await p.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:a,operations:[{pointer:{table:c,id:s,spaceId:a},path:x,command:n?"listBefore":"listAfter",args:{...r?{after:r}:{},id:d}},{pointer:{table:t,id:d,spaceId:a},path:[],command:"set",args:{...j,...e}}]}]}),method:"POST"});return ae.errorId?ae:d},xt=async(e,{pageID:t=M(),spaceID:n=S()}={})=>{n=m(await n),t=m(t);let r=await p.getJSON("https://www.notion.so/api/v3/getUploadFileUrl",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({bucket:"secure",name:e.name,contentType:e.type,record:{table:"block",id:t,spaceId:n}})});return r.errorId?r:(fetch(r.signedPutUrl,{method:"PUT",headers:{"content-type":e.type},body:e}),r.url)},$t=(e,t,n="block")=>(e.startsWith("/")&&(e=`https://notion.so${e}`),e.includes("secure.notion-static.com")&&(e=new URL(e),e=`https://www.notion.so/signed/${encodeURIComponent(e.origin+e.pathname)}?table=${n}&id=${t}`),e);var h={};g(h,{is:()=>Tt,rgbContrast:()=>At,rgbLogShade:()=>Lt,slugger:()=>_t,uuidv4:()=>jt});"use strict";var _t=(e,t=new Set)=>{e=e.replace(/\s/g,"-").replace(/[^A-Za-z0-9-_]/g,"").toLowerCase();let n=0,r=e;for(;t.has(r);)n++,r=`${e}-${n}`;return r},jt=()=>crypto?.randomUUID?crypto.randomUUID():([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,e=>(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)),Lt=(e,t)=>{var n=parseInt,r=Math.round,[s,c,t,a]=t.split(","),i=e<0,d=i?0:e*255**2,i=i?1+e:1-e;return"rgb"+(a?"a(":"(")+r((i*n(s[3]=="a"?s.slice(5):s.slice(4))**2+d)**.5)+","+r((i*n(c)**2+d)**.5)+","+r((i*n(t)**2+d)**.5)+(a?","+a:")")},At=(e,t,n)=>Math.sqrt(.299*(e*e)+.587*(t*t)+.114*(n*n))>165.75?"rgb(0,0,0)":"rgb(255,255,255)",Et={alphanumeric:/^[\w\.-]+$/,uuid:/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,semver:/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i,email:/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i,url:/^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,64}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i,color:/^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i};function St(e,t){let n=e.match(t);return!!(n&&n.length)}var Tt=async(e,t,{extension:n=""}={})=>{if(n=!e||!e.endsWith||e.endsWith(n),Array.isArray(t))return t.includes(e);switch(t){case"array":return Array.isArray(e);case"object":return e&&typeof e=="object"&&!Array.isArray(e);case"undefined":case"boolean":case"number":return typeof e===t&&n;case"string":return typeof e===t&&n;case"alphanumeric":case"uuid":case"semver":case"email":case"url":case"color":return typeof e=="string"&&St(e,Et[t])&&n;case"file":return typeof e=="string"&&e&&await p.isFile(e)&&n}return!1};var y={};g(y,{core:()=>Le,db:()=>Ut,enabled:()=>zt,errors:()=>Jt,get:()=>D,list:()=>Y,optionDefault:()=>Ee,optionTypes:()=>Mt,profileDB:()=>X,profileName:()=>Ae,supportedEnvs:()=>qt});"use strict";var l=async(e,t,n,r,{extension:s="",error:c=`invalid ${t} (${s?`${s} `:""}${r}): ${JSON.stringify(n)}`,optional:a=!1}={})=>{let i;for(let d of Array.isArray(r)?[r]:r.split("|"))if(d==="file"?i=n&&!n.startsWith("http")?await h.is(`repo/${e._dir}/${n}`,d,{extension:s}):!1:i=await h.is(n,d,{extension:s}),i)break;return i||a&&await h.is(n,"undefined")?!0:(c&&e._err(c),!1)},kt=async e=>(e.environments=e.environments??y.supportedEnvs,await l(e,"environments",e.environments,"array")?e.environments.map(n=>l(e,"environments.env",n,y.supportedEnvs)):!1),Ct=async e=>{if(!await l(e,"tags",e.tags,"array"))return!1;let n=["core","extension","theme","integration"];if(!e.tags.filter(i=>n.includes(i)).length)return e._err(`invalid tags (must contain at least one of 'core', 'extension', 'theme' or 'integration'): + ${JSON.stringify(e.tags)}`),!1;let s=e.tags.includes("theme"),c=e.tags.includes("light")||e.tags.includes("dark"),a=e.tags.includes("light")&&e.tags.includes("dark");return s&&(!c||a)?(e._err(`invalid tags (themes must be either 'light' or 'dark', not neither or both): + ${JSON.stringify(e.tags)}`),!1):e.tags.map(i=>l(e,"tags.tag",i,"string"))},Pt=async e=>await l(e,"authors",e.authors,"array")?e.authors.map(n=>[l(e,"authors.author.name",n.name,"string"),l(e,"authors.author.email",n.email,"email",{optional:!0}),l(e,"authors.author.homepage",n.homepage,"url"),l(e,"authors.author.avatar",n.avatar,"url")]):!1,Ot=async e=>{if(!await l(e,"css",e.css,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.css[r])continue;let s=await l(e,`css.${r}`,e.css[r],"array");s&&(s=e.css[r].map(c=>l(e,`css.${r}.file`,c,"file",{extension:".css"}))),n.push(s)}return n},Rt=async e=>{if(!await l(e,"js",e.js,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.js[r])continue;let s=await l(e,`js.${r}`,e.js[r],"array");s&&(s=e.js[r].map(c=>l(e,`js.${r}.file`,c,"file",{extension:".mjs"}))),n.push(s)}if(e.js.electron)if(await l(e,"js.electron",e.js.electron,"array"))for(let s of e.js.electron){if(!await l(e,"js.electron.file",s,"object")){n.push(!1);continue}n.push([l(e,"js.electron.file.source",s.source,"file",{extension:".cjs"}),l(e,"js.electron.file.target",s.target,"string",{extension:".js"})])}else n.push(!1);return n},Nt=async e=>{if(!await l(e,"options",e.options,"array"))return!1;let n=[];for(let r of e.options){let s="options.option";if(!await l(e,`${s}.type`,r.type,y.optionTypes)){n.push(!1);continue}switch(r.environments=r.environments??y.supportedEnvs,n.push([l(e,`${s}.key`,r.key,"alphanumeric"),l(e,`${s}.label`,r.label,"string"),l(e,`${s}.tooltip`,r.tooltip,"string",{optional:!0}),l(e,`${s}.environments`,r.environments,"array").then(a=>a?r.environments.map(i=>l(e,`${s}.environments.env`,i,y.supportedEnvs)):!1)]),r.type){case"toggle":n.push(l(e,`${s}.value`,r.value,"boolean"));break;case"select":{let a=await l(e,`${s}.values`,r.values,"array");a&&(a=r.values.map(i=>l(e,`${s}.values.value`,i,"string"))),n.push(a);break}case"text":case"hotkey":n.push(l(e,`${s}.value`,r.value,"string"));break;case"number":case"color":n.push(l(e,`${s}.value`,r.value,r.type));break;case"file":{let a=await l(e,`${s}.extensions`,r.extensions,"array");a&&(a=r.extensions.map(i=>l(e,`${s}.extensions.extension`,i,"string"))),n.push(a);break}}}return n};async function je(e){let t=[l(e,"name",e.name,"string"),l(e,"id",e.id,"uuid"),l(e,"version",e.version,"semver"),kt(e),l(e,"description",e.description,"string"),l(e,"preview",e.preview,"file|url",{optional:!0}),Ct(e),Pt(e),Ot(e),Rt(e),Nt(e)];do t=await Promise.all(t.flat(1/0));while(t.some(n=>Array.isArray(n)));return t.every(n=>n)}"use strict";var Le=["a6621988-551d-495a-97d8-3c568bca2e9e","0f0bf8b6-eae6-4273-b307-8fc43f2ee082","36a2ffc9-27ff-480e-84a7-c7700a7d232d"],qt=["linux","win32","darwin","extension"],Mt=["toggle","select","text","number","color","file","hotkey"],Ae=async()=>$.get(["currentprofile"],"default"),X=async()=>$.db(["profiles",await Ae()]),G,Q=[],Y=async(e=t=>!0)=>{G||(G=new Promise(async(n,r)=>{let s=[];for(let c of await p.getJSON("repo/registry.json"))try{let a={...await p.getJSON(`repo/${c}/mod.json`),_dir:c,_err:i=>Q.push({source:c,message:i})};await je(a)&&s.push(a)}catch{Q.push({source:c,message:"invalid mod.json"})}n(s)}));let t=[];for(let n of await G)await e(n)&&t.push(n);return t},Jt=async()=>(await Y(),Q),D=async e=>(await Y(t=>t.id===e))[0],zt=async e=>(await D(e)).environments.includes(A.name)?Le.includes(e)?!0:(await X()).get(["_mods",e],!1):!1,Ee=async(e,t)=>{let n=await D(e),r=n.options.find(s=>s.key===t);if(!!r)switch(r.type){case"toggle":case"text":case"number":case"color":case"hotkey":return r.value;case"select":return r.values[0];case"file":return}},Ut=async e=>{let t=await X();return $.db([e],async(n,r=void 0)=>(typeof n=="string"&&(n=[n]),n.length===2&&(r=await Ee(e,n[1])??r),t.get(n,r)),t.set)};var o={};g(o,{addDocumentObserver:()=>Xt,addHotkeyListener:()=>Vt,copyToClipboard:()=>Ht,empty:()=>Wt,escape:()=>Te,html:()=>Ce,loadStylesheet:()=>Ft,queryParams:()=>Bt,raw:()=>ke,readFromClipboard:()=>It,removeDocumentObserver:()=>Gt,removeHotkeyListener:()=>Kt,render:()=>Pe,whenReady:()=>Zt});"use strict";var Se=!1,T=[],ee,J=[],te=[],Zt=(e=[])=>new Promise((t,n)=>{function r(){let s;s=setInterval(c,100);function c(){e.every(a=>document.querySelector(a))&&(clearInterval(s),t(!0))}c()}document.readyState!=="complete"?document.addEventListener("readystatechange",s=>{document.readyState==="complete"&&r()}):r()}),Bt=()=>new URLSearchParams(window.location.search),Te=e=>e.replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\\/g,"\"),ke=(e,...t)=>{let n=e.map(r=>r+(["string","number"].includes(typeof t[0])?t.shift():Te(JSON.stringify(t.shift(),null,2)??""))).join("");return n.includes("r.trim()).filter(r=>r.length).join(" ")},Ce=(e,...t)=>{let n=document.createRange().createContextualFragment(ke(e,...t));return n.children.length===1?n.children[0]:n.children},Pe=(e,...t)=>(t=t.map(n=>n instanceof HTMLCollection?[...n]:n).flat(1/0).filter(n=>n),e.append(...t),e),Wt=e=>{for(;e.firstChild&&e.removeChild(e.firstChild););return e},Ft=e=>{let t=Ce``; - render(document.head, $stylesheet4); - return $stylesheet4; -}; -var copyToClipboard = async (str) => { - try { - await navigator.clipboard.writeText(str); - } catch { - 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); - } -}; -var readFromClipboard = () => { - return navigator.clipboard.readText(); -}; -var triggerHotkeyListener = (event, hotkey) => { - const inInput = document.activeElement.nodeName === "INPUT" && !hotkey.listenInInput; - if (inInput) - return; - const pressed = hotkey.keys.every((key) => { - key = key.toLowerCase(); - const modifiers = { - metaKey: ["meta", "os", "win", "cmd", "command"], - ctrlKey: ["ctrl", "control"], - shiftKey: ["shift"], - altKey: ["alt"] - }; - for (const modifier in modifiers) { - const pressed2 = modifiers[modifier].includes(key) && event[modifier]; - if (pressed2) - return true; - } - if (key === "space") - key = " "; - if (key === "plus") - key = "+"; - if (key === event.key.toLowerCase()) - return true; - }); - if (pressed) - hotkey.callback(event); -}; -var addHotkeyListener = (keys, callback, { listenInInput = false, keydown = false } = {}) => { - if (typeof keys === "string") - keys = keys.split("+"); - _hotkeyEventListeners.push({ keys, callback, listenInInput, keydown }); - if (!_hotkeyListenersActivated) { - _hotkeyListenersActivated = true; - document.addEventListener("keyup", (event) => { - for (const hotkey of _hotkeyEventListeners.filter(({ keydown: keydown2 }) => !keydown2)) { - triggerHotkeyListener(event, hotkey); - } - }); - document.addEventListener("keydown", (event) => { - for (const hotkey of _hotkeyEventListeners.filter(({ keydown: keydown2 }) => keydown2)) { - triggerHotkeyListener(event, hotkey); - } - }); - } -}; -var removeHotkeyListener = (callback) => { - _hotkeyEventListeners = _hotkeyEventListeners.filter((listener) => listener.callback !== callback); -}; -var addDocumentObserver = (callback, selectors = []) => { - if (!_documentObserver) { - const handle = (queue) => { - while (queue.length) { - const event = queue.shift(), matchesAddedNode = ($node, selector) => $node instanceof Element && ($node.matches(selector) || $node.matches(`${selector} *`) || $node.querySelector(selector)), matchesTarget = (selector) => event.target.matches(selector) || event.target.matches(`${selector} *`) || [...event.addedNodes].some(($node) => matchesAddedNode($node, selector)); - for (const listener of _documentObserverListeners) { - if (!listener.selectors.length || listener.selectors.some(matchesTarget)) { - listener.callback(event); - } - } - } - }; - _documentObserver = new MutationObserver((list2, observer) => { - if (!_documentObserverEvents.length) - requestIdleCallback(() => handle(_documentObserverEvents)); - _documentObserverEvents.push(...list2); - }); - _documentObserver.observe(document.body, { - childList: true, - subtree: true, - attributes: true - }); - } - _documentObserverListeners.push({ callback, selectors }); -}; -var removeDocumentObserver = (callback) => { - _documentObserverListeners = _documentObserverListeners.filter((listener) => listener.callback !== callback); -}; - -// insert/api/components/index.mjs -var components_exports = {}; -__export(components_exports, { - addCornerAction: () => addCornerAction, - addPanelView: () => addPanelView, - addTooltip: () => addTooltip, - feather: () => feather -}); - -// insert/api/components/tooltip.mjs -"use strict"; -var $stylesheet; -var _$tooltip; -var countLines = ($el) => [...$el.getClientRects()].reduce((prev, val) => prev.some((p) => p.y === val.y) ? prev : [...prev, val], []).length; -var position = async ($ref, offsetDirection, maxLines) => { - _$tooltip.style.top = `0px`; - _$tooltip.style.left = `0px`; - const rect = $ref.getBoundingClientRect(), { offsetWidth, offsetHeight } = _$tooltip, pad = 6; - let x = rect.x, y = Math.floor(rect.y); - if (["top", "bottom"].includes(offsetDirection)) { - if (offsetDirection === "top") - y -= offsetHeight + pad; - if (offsetDirection === "bottom") - y += rect.height + pad; - x -= offsetWidth / 2 - rect.width / 2; - _$tooltip.style.left = `${x}px`; - _$tooltip.style.top = `${y}px`; - const testLines = () => countLines(_$tooltip.firstElementChild) > maxLines, padEdgesX = testLines(); - while (testLines()) { - _$tooltip.style.left = `${window.innerWidth - x > x ? x++ : x--}px`; - } - if (padEdgesX) { - x += window.innerWidth - x > x ? pad : -pad; - _$tooltip.style.left = `${x}px`; - } - } - if (["left", "right"].includes(offsetDirection)) { - y -= offsetHeight / 2 - rect.height / 2; - if (offsetDirection === "left") - x -= offsetWidth + pad; - if (offsetDirection === "right") - x += rect.width + pad; - _$tooltip.style.left = `${x}px`; - _$tooltip.style.top = `${y}px`; - } - return true; -}; -var addTooltip = async ($ref, $content, { delay = 100, offsetDirection = "bottom", maxLines = 1 } = {}) => { - if (!$stylesheet) { - $stylesheet = web_exports.loadStylesheet("api/components/tooltip.css"); - _$tooltip = web_exports.html`
`; - web_exports.render(document.body, _$tooltip); - } - if (!globalThis.markdownit) - await import(fs_exports.localPath("dep/markdown-it.min.js")); - const md = markdownit({ linkify: true }); - if (!($content instanceof Element)) - $content = web_exports.html`
- ${$content.split("\n").map((text) => md.renderInline(text)).join("
")} -
`; - let displayDelay; - $ref.addEventListener("mouseover", async (event) => { - if (!displayDelay) { - displayDelay = setTimeout(async () => { - if ($ref.matches(":hover")) { - if (_$tooltip.style.display !== "block") { - _$tooltip.style.display = "block"; - web_exports.render(web_exports.empty(_$tooltip), $content); - position($ref, offsetDirection, maxLines); - await _$tooltip.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 65 }).finished; - } - } - displayDelay = void 0; - }, delay); - } - }); - $ref.addEventListener("mouseout", async (event) => { - displayDelay = void 0; - if (_$tooltip.style.display === "block" && !$ref.matches(":hover")) { - await _$tooltip.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 65 }).finished; - _$tooltip.style.display = ""; - } - }); -}; - -// insert/api/components/feather.mjs -"use strict"; -var _$iconSheet; -var feather = async (name3, attrs = {}) => { - if (!_$iconSheet) { - _$iconSheet = web_exports.html`${await fs_exports.getText("dep/feather-sprite.svg")}`; - } - attrs.style = ((attrs.style ? attrs.style + ";" : "") + "stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;fill:none;").trim(); - attrs.viewBox = "0 0 24 24"; - return ` `${web_exports.escape(key)}="${web_exports.escape(val)}"`).join(" ")}>${_$iconSheet.getElementById(name3)?.innerHTML}`; -}; - -// insert/api/components/panel.mjs -"use strict"; -var _views = []; -var svgExpand = web_exports.raw` + href="${e.startsWith("https://")?e:p.localPath(e)}" + />`;return Pe(document.head,t),t},Ht=async e=>{try{await navigator.clipboard.writeText(e)}catch{let t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}},It=()=>navigator.clipboard.readText(),Oe=(e,t)=>{if(document.activeElement.nodeName==="INPUT"&&!t.listenInInput)return;t.keys.every(s=>{s=s.toLowerCase();let c={metaKey:["meta","os","win","cmd","command"],ctrlKey:["ctrl","control"],shiftKey:["shift"],altKey:["alt"]};for(let a in c)if(c[a].includes(s)&&e[a])return!0;if(s==="space"&&(s=" "),s==="plus"&&(s="+"),s===e.key.toLowerCase())return!0})&&t.callback(e)},Vt=(e,t,{listenInInput:n=!1,keydown:r=!1}={})=>{typeof e=="string"&&(e=e.split("+")),T.push({keys:e,callback:t,listenInInput:n,keydown:r}),Se||(Se=!0,document.addEventListener("keyup",s=>{for(let c of T.filter(({keydown:a})=>!a))Oe(s,c)}),document.addEventListener("keydown",s=>{for(let c of T.filter(({keydown:a})=>a))Oe(s,c)}))},Kt=e=>{T=T.filter(t=>t.callback!==e)},Xt=(e,t=[])=>{if(!ee){let n=r=>{for(;r.length;){let s=r.shift(),c=(i,d)=>i instanceof Element&&(i.matches(d)||i.matches(`${d} *`)||i.querySelector(d)),a=i=>s.target.matches(i)||s.target.matches(`${i} *`)||[...s.addedNodes].some(d=>c(d,i));for(let i of J)(!i.selectors.length||i.selectors.some(a))&&i.callback(s)}};ee=new MutationObserver((r,s)=>{te.length||requestIdleCallback(()=>n(te)),te.push(...r)}),ee.observe(document.body,{childList:!0,subtree:!0,attributes:!0})}J.push({callback:e,selectors:t})},Gt=e=>{J=J.filter(t=>t.callback!==e)};var N={};g(N,{addCornerAction:()=>Xe,addPanelView:()=>Ve,addTooltip:()=>Ne,feather:()=>qe});"use strict";var Re,u,Qt=e=>[...e.getClientRects()].reduce((t,n)=>t.some(r=>r.y===n.y)?t:[...t,n],[]).length,Yt=async(e,t,n)=>{u.style.top="0px",u.style.left="0px";let r=e.getBoundingClientRect(),{offsetWidth:s,offsetHeight:c}=u,a=6,i=r.x,d=Math.floor(r.y);if(["top","bottom"].includes(t)){t==="top"&&(d-=c+a),t==="bottom"&&(d+=r.height+a),i-=s/2-r.width/2,u.style.left=`${i}px`,u.style.top=`${d}px`;let x=()=>Qt(u.firstElementChild)>n,j=x();for(;x();)u.style.left=`${window.innerWidth-i>i?i++:i--}px`;j&&(i+=window.innerWidth-i>i?a:-a,u.style.left=`${i}px`)}return["left","right"].includes(t)&&(d-=c/2-r.height/2,t==="left"&&(i-=s+a),t==="right"&&(i+=r.width+a),u.style.left=`${i}px`,u.style.top=`${d}px`),!0},Ne=async(e,t,{delay:n=100,offsetDirection:r="bottom",maxLines:s=1}={})=>{Re||(Re=o.loadStylesheet("api/components/tooltip.css"),u=o.html`
`,o.render(document.body,u)),globalThis.markdownit||await import(p.localPath("dep/markdown-it.min.js"));let c=markdownit({linkify:!0});t instanceof Element||(t=o.html`
+ ${t.split(` +`).map(i=>c.renderInline(i)).join("
")} +
`);let a;e.addEventListener("mouseover",async i=>{a||(a=setTimeout(async()=>{e.matches(":hover")&&u.style.display!=="block"&&(u.style.display="block",o.render(o.empty(u),t),Yt(e,r,s),await u.animate([{opacity:0},{opacity:1}],{duration:65}).finished),a=void 0},n))}),e.addEventListener("mouseout",async i=>{a=void 0,u.style.display==="block"&&!e.matches(":hover")&&(await u.animate([{opacity:1},{opacity:0}],{duration:65}).finished,u.style.display="")})};"use strict";var ne,qe=async(e,t={})=>(ne||(ne=o.html`${await p.getText("dep/feather-sprite.svg")}`),t.style=((t.style?t.style+";":"")+"stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;fill:none;").trim(),t.viewBox="0 0 24 24",``${o.escape(n)}="${o.escape(r)}"`).join(" ")}>${ne.getElementById(e)?.innerHTML}`);"use strict";var k=[],Dt=o.raw` - `; -var $stylesheet2; -var db4; -var $notionFrame; -var $notionRightSidebar; -var $panel; -var $hoverTrigger; -var $resizeHandle; -var dragStartX; -var dragStartWidth; -var dragEventsFired; -var panelWidth; -var $notionApp; -var $pinnedToggle; -var $panelTitle; -var $header; -var $panelContent; -var $switcher; -var $switcherTrigger; -var $switcherOverlayContainer; -var panelPinnedAttr = "data-enhancer-panel-pinned"; -var isPinned = () => $panel.hasAttribute(panelPinnedAttr); -var togglePanel = () => { - const $elems = [$notionFrame, $notionRightSidebar, $hoverTrigger, $panel].filter(($el) => $el); - if (isPinned()) { - closeSwitcher(); - for (const $elem of $elems) - $elem.removeAttribute(panelPinnedAttr); - } else { - for (const $elem of $elems) - $elem.setAttribute(panelPinnedAttr, "true"); - } - db4.set(["panel.pinned"], isPinned()); -}; -var updateWidth = async () => { - document.documentElement.style.setProperty("--component--panel-width", panelWidth + "px"); - db4.set(["panel.width"], panelWidth); -}; -var resizeDrag = (event) => { - event.preventDefault(); - dragEventsFired = true; - panelWidth = dragStartWidth + (dragStartX - event.clientX); - if (panelWidth < 190) - panelWidth = 190; - if (panelWidth > 480) - panelWidth = 480; - $panel.style.width = panelWidth + "px"; - $hoverTrigger.style.width = panelWidth + "px"; - $notionFrame.style.paddingRight = panelWidth + "px"; - if ($notionRightSidebar) - $notionRightSidebar.style.right = panelWidth + "px"; -}; -var resizeEnd = (event) => { - $panel.style.width = ""; - $hoverTrigger.style.width = ""; - $notionFrame.style.paddingRight = ""; - if ($notionRightSidebar) - $notionRightSidebar.style.right = ""; - updateWidth(); - $resizeHandle.style.cursor = ""; - document.body.removeEventListener("mousemove", resizeDrag); - document.body.removeEventListener("mouseup", resizeEnd); -}; -var resizeStart = (event) => { - dragStartX = event.clientX; - dragStartWidth = panelWidth; - $resizeHandle.style.cursor = "auto"; - document.body.addEventListener("mousemove", resizeDrag); - document.body.addEventListener("mouseup", resizeEnd); -}; -var isSwitcherOpen = () => document.body.contains($switcher); -var openSwitcher = () => { - if (!isPinned()) - return togglePanel(); - web_exports.render($notionApp, $switcherOverlayContainer); - web_exports.empty($switcher); - for (const view of _views) { - const open = $panelTitle.contains(view.$title), $item = web_exports.render(web_exports.html`
`, web_exports.render(web_exports.html``, view.$icon.cloneNode(true), view.$title.cloneNode(true))); - $item.addEventListener("click", () => { - renderView(view); - db4.set(["panel.open"], view.id); - }); - web_exports.render($switcher, $item); - } - const rect = $header.getBoundingClientRect(); - web_exports.render(web_exports.empty($switcherOverlayContainer), web_exports.render(web_exports.html`
`, web_exports.render(web_exports.html`
`, $switcher))); - $switcher.querySelector("[data-open]").focus(); - $switcher.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 200 }); - document.addEventListener("keydown", switcherKeyListeners); -}; -var closeSwitcher = () => { - document.removeEventListener("keydown", switcherKeyListeners); - $switcher.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 200 }).onfinish = () => $switcherOverlayContainer.remove(); -}; -var switcherKeyListeners = (event) => { - if (isSwitcherOpen()) { - switch (event.key) { - case "Escape": - closeSwitcher(); - event.stopPropagation(); - break; - case "Enter": - document.activeElement.click(); - event.stopPropagation(); - break; - case "ArrowUp": - const $prev = event.target.previousElementSibling; - ($prev || event.target.parentElement.lastElementChild).focus(); - event.stopPropagation(); - break; - case "ArrowDown": - const $next = event.target.nextElementSibling; - ($next || event.target.parentElement.firstElementChild).focus(); - event.stopPropagation(); - break; - } - } -}; -var renderView = (view) => { - const prevView = _views.find(({ $content }) => document.contains($content)); - web_exports.render(web_exports.empty($panelTitle), web_exports.render(web_exports.html``, view.$icon, view.$title)); - view.onFocus(); - web_exports.render(web_exports.empty($panelContent), view.$content); - if (prevView) - prevView.onBlur(); -}; -async function createPanel() { - await web_exports.whenReady([".notion-frame"]); - $notionFrame = document.querySelector(".notion-frame"); - $panel = web_exports.html`
`; - $hoverTrigger = web_exports.html`
`; - $resizeHandle = web_exports.html`
`; - $panelTitle = web_exports.html`
`; - $header = web_exports.render(web_exports.html`
`, $panelTitle); - $panelContent = web_exports.html`
`; - $switcher = web_exports.html`
`; - $switcherTrigger = web_exports.html`
- ${svgExpand} -
`; - $switcherOverlayContainer = web_exports.html`
`; - const notionRightSidebarSelector = '.notion-cursor-listener > div[style*="flex-end"]', detectRightSidebar = () => { - if (!document.contains($notionRightSidebar)) { - $notionRightSidebar = document.querySelector(notionRightSidebarSelector); - if (isPinned() && $notionRightSidebar) { - $notionRightSidebar.setAttribute(panelPinnedAttr, "true"); - } - } - }; - $notionRightSidebar = document.querySelector(notionRightSidebarSelector); - web_exports.addDocumentObserver(detectRightSidebar, [notionRightSidebarSelector]); - if (await db4.get(["panel.pinned"])) - togglePanel(); - web_exports.addHotkeyListener(await db4.get(["panel.hotkey"]), togglePanel); - $pinnedToggle.addEventListener("click", (event) => { - event.stopPropagation(); - togglePanel(); - }); - web_exports.render($panel, web_exports.render($header, $panelTitle, $switcherTrigger, $pinnedToggle), $panelContent, $resizeHandle); - await enablePanelResize(); - await createViews(); - const cursorListenerSelector = '.notion-cursor-listener > .notion-sidebar-container ~ [style^="position: absolute"]'; - await web_exports.whenReady([cursorListenerSelector]); - document.querySelector(cursorListenerSelector).before($hoverTrigger, $panel); -} -async function enablePanelResize() { - panelWidth = await db4.get(["panel.width"], 240); - updateWidth(); - $resizeHandle.addEventListener("mousedown", resizeStart); - $resizeHandle.addEventListener("click", () => { - if (dragEventsFired) { - dragEventsFired = false; - } else - togglePanel(); - }); -} -async function createViews() { - $notionApp = document.querySelector(".notion-app-inner"); - $header.addEventListener("click", openSwitcher); - $switcherTrigger.addEventListener("click", openSwitcher); - $switcherOverlayContainer.addEventListener("click", closeSwitcher); -} -var addPanelView = async ({ - id, - icon, - title, - $content, - onFocus = () => { - }, - onBlur = () => { - } -}) => { - if (!$stylesheet2) { - $stylesheet2 = web_exports.loadStylesheet("api/components/panel.css"); - } - if (!db4) - db4 = await registry_exports.db("36a2ffc9-27ff-480e-84a7-c7700a7d232d"); - if (!$pinnedToggle) { - $pinnedToggle = web_exports.html`
- ${await components_exports.feather("chevrons-right")} -
`; - } - const view = { - id, - $icon: web_exports.render(web_exports.html``, icon instanceof Element ? icon : web_exports.html`${icon}`), - $title: web_exports.render(web_exports.html``, title), - $content, - onFocus, - onBlur - }; - _views.push(view); - if (_views.length === 1) - await createPanel(); - if (_views.length === 1 || await db4.get(["panel.open"]) === id) - renderView(view); -}; - -// insert/api/components/corner-action.mjs -"use strict"; -var $stylesheet3; -var $cornerButtonsContainer; -var addCornerAction = async (icon, listener) => { - if (!$stylesheet3) { - $stylesheet3 = web_exports.loadStylesheet("api/components/corner-action.css"); - $cornerButtonsContainer = web_exports.html`
`; - } - await web_exports.whenReady([".notion-help-button"]); - const $helpButton = document.querySelector(".notion-help-button"), $onboardingButton = document.querySelector(".onboarding-checklist-button"); - if ($onboardingButton) - $cornerButtonsContainer.prepend($onboardingButton); - $cornerButtonsContainer.prepend($helpButton); - web_exports.render(document.querySelector(".notion-app-inner > .notion-cursor-listener"), $cornerButtonsContainer); - const $actionButton = web_exports.html`
${icon}
`; - $actionButton.addEventListener("click", listener); - web_exports.render($cornerButtonsContainer, $actionButton); - return $actionButton; -}; - -// insert/api/components/index.mjs -"use strict"; - -// insert/api/index.mjs -"use strict"; + `,Me,w,z,v,_,C,L,Je,ze,re,f,Ue,U,P,Z,se,b,oe,O,B="data-enhancer-panel-pinned",W=()=>_.hasAttribute(B),R=()=>{let e=[z,v,C,_].filter(t=>t);if(W()){ie();for(let t of e)t.removeAttribute(B)}else for(let t of e)t.setAttribute(B,"true");w.set(["panel.pinned"],W())},Ze=async()=>{document.documentElement.style.setProperty("--component--panel-width",f+"px"),w.set(["panel.width"],f)},Be=e=>{e.preventDefault(),re=!0,f=ze+(Je-e.clientX),f<190&&(f=190),f>480&&(f=480),_.style.width=f+"px",C.style.width=f+"px",z.style.paddingRight=f+"px",v&&(v.style.right=f+"px")},We=e=>{_.style.width="",C.style.width="",z.style.paddingRight="",v&&(v.style.right=""),Ze(),L.style.cursor="",document.body.removeEventListener("mousemove",Be),document.body.removeEventListener("mouseup",We)},en=e=>{Je=e.clientX,ze=f,L.style.cursor="auto",document.body.addEventListener("mousemove",Be),document.body.addEventListener("mouseup",We)},tn=()=>document.body.contains(b),Fe=()=>{if(!W())return R();o.render(Ue,O),o.empty(b);for(let t of k){let n=P.contains(t.$title),r=o.render(o.html`
`,o.render(o.html``,t.$icon.cloneNode(!0),t.$title.cloneNode(!0)));r.addEventListener("click",()=>{Ie(t),w.set(["panel.open"],t.id)}),o.render(b,r)}let e=Z.getBoundingClientRect();o.render(o.empty(O),o.render(o.html`
`,o.render(o.html`
`,b))),b.querySelector("[data-open]").focus(),b.animate([{opacity:0},{opacity:1}],{duration:200}),document.addEventListener("keydown",He)},ie=()=>{document.removeEventListener("keydown",He),b.animate([{opacity:1},{opacity:0}],{duration:200}).onfinish=()=>O.remove()},He=e=>{if(tn())switch(e.key){case"Escape":ie(),e.stopPropagation();break;case"Enter":document.activeElement.click(),e.stopPropagation();break;case"ArrowUp":(e.target.previousElementSibling||e.target.parentElement.lastElementChild).focus(),e.stopPropagation();break;case"ArrowDown":(e.target.nextElementSibling||e.target.parentElement.firstElementChild).focus(),e.stopPropagation();break}},Ie=e=>{let t=k.find(({$content:n})=>document.contains(n));o.render(o.empty(P),o.render(o.html``,e.$icon,e.$title)),e.onFocus(),o.render(o.empty(se),e.$content),t&&t.onBlur()};async function nn(){await o.whenReady([".notion-frame"]),z=document.querySelector(".notion-frame"),_=o.html`
`,C=o.html`
`,L=o.html`
`,P=o.html`
`,Z=o.render(o.html`
`,P),se=o.html`
`,b=o.html`
`,oe=o.html`
+ ${Dt} +
`,O=o.html`
`;let e='.notion-cursor-listener > div[style*="flex-end"]',t=()=>{document.contains(v)||(v=document.querySelector(e),W()&&v&&v.setAttribute(B,"true"))};v=document.querySelector(e),o.addDocumentObserver(t,[e]),await w.get(["panel.pinned"])&&R(),o.addHotkeyListener(await w.get(["panel.hotkey"]),R),U.addEventListener("click",r=>{r.stopPropagation(),R()}),o.render(_,o.render(Z,P,oe,U),se,L),await rn(),await sn();let n='.notion-cursor-listener > .notion-sidebar-container ~ [style^="position: absolute"]';await o.whenReady([n]),document.querySelector(n).before(C,_)}async function rn(){f=await w.get(["panel.width"],240),Ze(),L.addEventListener("mousedown",en),L.addEventListener("click",()=>{re?re=!1:R()})}async function sn(){Ue=document.querySelector(".notion-app-inner"),Z.addEventListener("click",Fe),oe.addEventListener("click",Fe),O.addEventListener("click",ie)}var Ve=async({id:e,icon:t,title:n,$content:r,onFocus:s=()=>{},onBlur:c=()=>{}})=>{Me||(Me=o.loadStylesheet("api/components/panel.css")),w||(w=await y.db("36a2ffc9-27ff-480e-84a7-c7700a7d232d")),U||(U=o.html`
+ ${await N.feather("chevrons-right")} +
`);let a={id:e,$icon:o.render(o.html``,t instanceof Element?t:o.html`${t}`),$title:o.render(o.html``,n),$content:r,onFocus:s,onBlur:c};k.push(a),k.length===1&&await nn(),(k.length===1||await w.get(["panel.open"])===e)&&Ie(a)};"use strict";var Ke,q,Xe=async(e,t)=>{Ke||(Ke=o.loadStylesheet("api/components/corner-action.css"),q=o.html`
`),await o.whenReady([".notion-help-button"]);let n=document.querySelector(".notion-help-button"),r=document.querySelector(".onboarding-checklist-button");r&&q.prepend(r),q.prepend(n),o.render(document.querySelector(".notion-app-inner > .notion-cursor-listener"),q);let s=o.html`
${e}
`;return s.addEventListener("click",t),o.render(q,s),s};"use strict";"use strict"; diff --git a/api/index.mjs b/api/index.mjs index e90bf3e..8fdfab8 100644 --- a/api/index.mjs +++ b/api/index.mjs @@ -9,7 +9,7 @@ /** @module notion-enhancer/api */ // compiles to .cjs for use in electron: -// npx -y esbuild insert/api/index.mjs --bundle --format=cjs --outfile=insert/api/index.cjs +// npx -y esbuild insert/api/index.mjs --minify --bundle --format=cjs --outfile=insert/api/index.cjs /** environment-specific methods and constants */ export * as env from './env.mjs'; @@ -22,7 +22,7 @@ export * as storage from './storage.mjs'; export * as electron from './electron.mjs'; /** a basic wrapper around notion's unofficial api */ -// export * as notion from './notion.mjs'; +export * as notion from './notion.mjs'; /** helpers for formatting, validating and parsing values */ export * as fmt from './fmt.mjs'; /** interactions with the enhancer's repository of mods */ From 9b47ede63e860a4088af109c4c4ae0959b75ad21 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Fri, 10 Dec 2021 00:04:36 +1100 Subject: [PATCH 42/52] optionally imitate notion ipc channels --- api/electron.mjs | 30 ++++++++++++++++++++++++------ api/index.cjs | 12 ++++++------ 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/api/electron.mjs b/api/electron.mjs index 7251c93..1201f57 100644 --- a/api/electron.mjs +++ b/api/electron.mjs @@ -41,6 +41,9 @@ export const webFrame = globalThis.__enhancerElectronApi?.webFrame; * send a message to the main electron process * @param {string} channel - the message identifier * @param {any} data - the data to pass along with the message + * @param {string=} namespace - a prefix for the message to categorise + * it as e.g. enhancer-related. this should not be changed unless replicating + * builtin ipc events. * * @env win32 * @env linux @@ -48,13 +51,19 @@ export const webFrame = globalThis.__enhancerElectronApi?.webFrame; * @runtime client * @runtime menu */ -export const sendMessage = (channel, data) => - globalThis.__enhancerElectronApi.ipcRenderer.sendMessage(channel, data); +export const sendMessage = (channel, data, namespace = 'notion-enhancer') => { + if (globalThis.__enhancerElectronApi) { + globalThis.__enhancerElectronApi.ipcRenderer.sendMessage(channel, data, namespace); + } +}; /** * send a message to the webview's parent renderer process * @param {string} channel - the message identifier * @param {any} data - the data to pass along with the message + * @param {string=} namespace - a prefix for the message to categorise + * it as e.g. enhancer-related. this should not be changed unless replicating + * builtin ipc events. * * @env win32 * @env linux @@ -62,14 +71,20 @@ export const sendMessage = (channel, data) => * @runtime client * @runtime menu */ -export const sendMessageToHost = (channel, data) => - globalThis.__enhancerElectronApi.ipcRenderer.sendMessageToHost(channel, data); +export const sendMessageToHost = (channel, data, namespace = 'notion-enhancer') => { + if (globalThis.__enhancerElectronApi) { + globalThis.__enhancerElectronApi.ipcRenderer.sendMessageToHost(channel, data, namespace); + } +}; /** * receive a message from either the main process or * the webview's parent renderer process * @param {string} channel - the message identifier to listen for * @param {function} callback - the message handler, passed the args (event, data) + * @param {string=} namespace - a prefix for the message to categorise + * it as e.g. enhancer-related. this should not be changed unless replicating + * builtin ipc events. * * @env win32 * @env linux @@ -77,5 +92,8 @@ export const sendMessageToHost = (channel, data) => * @runtime client * @runtime menu */ -export const onMessage = (channel, callback) => - globalThis.__enhancerElectronApi.ipcRenderer.onMessage(channel, callback); +export const onMessage = (channel, callback, namespace = 'notion-enhancer') => { + if (globalThis.__enhancerElectronApi) { + globalThis.__enhancerElectronApi.ipcRenderer.onMessage(channel, callback, namespace); + } +}; diff --git a/api/index.cjs b/api/index.cjs index f6d8f8c..239818f 100644 --- a/api/index.cjs +++ b/api/index.cjs @@ -1,9 +1,9 @@ -var ce=Object.defineProperty;var Ge=e=>ce(e,"__esModule",{value:!0});var g=(e,t)=>{Ge(e);for(var n in t)ce(e,n,{get:t[n],enumerable:!0})};g(exports,{components:()=>N,electron:()=>I,env:()=>A,fmt:()=>h,fs:()=>p,notion:()=>K,registry:()=>y,storage:()=>$,web:()=>o});var A={};g(A,{focusMenu:()=>De,focusNotion:()=>et,name:()=>Qe,notionRequire:()=>nt,reload:()=>tt,version:()=>Ye});"use strict";var le=globalThis.__enhancerElectronApi.platform,de=globalThis.__enhancerElectronApi.version,pe=globalThis.__enhancerElectronApi.focusMenu,ue=globalThis.__enhancerElectronApi.focusNotion,he=globalThis.__enhancerElectronApi.reload,fe=globalThis.__enhancerElectronApi.notionRequire;"use strict";var Qe=le,Ye=de,De=pe,et=ue,tt=he,nt=fe;var p={};g(p,{getJSON:()=>st,getText:()=>ot,isFile:()=>it,localPath:()=>rt,notionPath:()=>at});"use strict";var E=e=>`notion://www.notion.so/__notion-enhancer/${e}`,me=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.json());try{return globalThis.__enhancerElectronApi.nodeRequire(`notion-enhancer/${e}`)}catch{return fetch(E(e),t).then(r=>r.json())}},ye=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.text());try{let n=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:r}=globalThis.__enhancerElectronApi.nodeRequire("path");return n.readFileSync(r(`${__dirname}/../../${e}`))}catch{return fetch(E(e),t).then(r=>r.text())}},ge=async e=>{try{let t=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:n}=globalThis.__enhancerElectronApi.nodeRequire("path");if(e.startsWith("http"))await fetch(e);else try{t.existsSync(n(`${__dirname}/../../${e}`))}catch{await fetch(E(e))}return!0}catch{return!1}},ve=globalThis.__enhancerElectronApi.notionPath;"use strict";var rt=E,st=me,ot=ye,it=ge,at=ve;var $={};g($,{addChangeListener:()=>pt,db:()=>dt,get:()=>ct,removeChangeListener:()=>ut,set:()=>lt});"use strict";var F=(e,t=void 0)=>globalThis.__enhancerElectronApi.db.get(e,t),H=(e,t)=>globalThis.__enhancerElectronApi.db.set(e,t),we=(e,t=F,n=H)=>(typeof e=="string"&&(e=[e]),{get:(r=[],s=void 0)=>t([...e,...r],s),set:(r,s)=>n([...e,...r],s)}),be=e=>globalThis.__enhancerElectronApi.db.addChangeListener(e),xe=e=>globalThis.__enhancerElectronApi.db.removeChangeListener(e);"use strict";var ct=F,lt=H,dt=we,pt=be,ut=xe;var I={};g(I,{browser:()=>ht,onMessage:()=>gt,sendMessage:()=>mt,sendMessageToHost:()=>yt,webFrame:()=>ft});"use strict";var ht=globalThis.__enhancerElectronApi?.browser,ft=globalThis.__enhancerElectronApi?.webFrame,mt=(e,t)=>globalThis.__enhancerElectronApi.ipcRenderer.sendMessage(e,t),yt=(e,t)=>globalThis.__enhancerElectronApi.ipcRenderer.sendMessageToHost(e,t),gt=(e,t)=>globalThis.__enhancerElectronApi.ipcRenderer.onMessage(e,t);var K={};g(K,{create:()=>bt,get:()=>_e,getPageID:()=>M,getSpaceID:()=>S,getUserID:()=>$e,search:()=>vt,set:()=>wt,sign:()=>$t,upload:()=>xt});"use strict";var m=e=>(e?.length===32&&!e.includes("-")&&(e=e.replace(/([\d\w]{8})([\d\w]{4})([\d\w]{4})([\d\w]{4})([\d\w]{12})/,"$1-$2-$3-$4-$5")),e),$e=()=>JSON.parse(localStorage["LRU:KeyValueStore2:current-user-id"]||{}).value,M=()=>m(o.queryParams().get("p")||location.pathname.split(/(-|\/)/g).reverse()[0]),V,S=async()=>(V||(V=(await _e(M())).space_id),V),_e=async(e,t="block")=>(e=m(e),(await p.getJSON("https://www.notion.so/api/v3/getRecordValues",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requests:[{table:t,id:e}]}),method:"POST"})).results[0].value),vt=async(e="",t=20,n=S())=>(n=m(await n),await p.getJSON("https://www.notion.so/api/v3/search",{headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"BlocksInSpace",query:e,spaceId:n,limit:t,filters:{isDeletedOnly:!1,excludeTemplates:!1,isNavigableOnly:!1,requireEditPermissions:!1,ancestors:[],createdBy:[],editedBy:[],lastEditedTime:{},createdTime:{}},sort:"Relevance",source:"quick_find"}),method:"POST"})),wt=async({recordID:e,recordTable:t="block",spaceID:n=S(),path:r=[]},s={})=>{n=m(await n),e=m(e);let c=await p.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:n,operations:[{pointer:{table:t,id:e,spaceId:n},path:r,command:r.length?"set":"update",args:s}]}]}),method:"POST"});return c.errorId?c:!0},bt=async({recordValue:e={},recordTable:t="block"}={},{prepend:n=!1,siblingID:r=void 0,parentID:s=M(),parentTable:c="block",spaceID:a=S(),userID:i=$e()}={})=>{a=m(await a),s=m(s),r=m(r);let d=m(e?.id??h.uuidv4()),x=[],j={type:"text",id:d,version:0,created_time:new Date().getTime(),last_edited_time:new Date().getTime(),parent_id:s,parent_table:c,alive:!0,created_by_table:"notion_user",created_by_id:i,last_edited_by_table:"notion_user",last_edited_by_id:i,space_id:a,permissions:[{type:"user_permission",role:"editor",user_id:i}]};c==="space"?(s=a,j.parent_id=a,x.push("pages"),j.type="page"):c==="collection_view"?(x.push("page_sort"),j.type="page"):x.push("content");let ae=await p.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:a,operations:[{pointer:{table:c,id:s,spaceId:a},path:x,command:n?"listBefore":"listAfter",args:{...r?{after:r}:{},id:d}},{pointer:{table:t,id:d,spaceId:a},path:[],command:"set",args:{...j,...e}}]}]}),method:"POST"});return ae.errorId?ae:d},xt=async(e,{pageID:t=M(),spaceID:n=S()}={})=>{n=m(await n),t=m(t);let r=await p.getJSON("https://www.notion.so/api/v3/getUploadFileUrl",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({bucket:"secure",name:e.name,contentType:e.type,record:{table:"block",id:t,spaceId:n}})});return r.errorId?r:(fetch(r.signedPutUrl,{method:"PUT",headers:{"content-type":e.type},body:e}),r.url)},$t=(e,t,n="block")=>(e.startsWith("/")&&(e=`https://notion.so${e}`),e.includes("secure.notion-static.com")&&(e=new URL(e),e=`https://www.notion.so/signed/${encodeURIComponent(e.origin+e.pathname)}?table=${n}&id=${t}`),e);var h={};g(h,{is:()=>Tt,rgbContrast:()=>At,rgbLogShade:()=>Lt,slugger:()=>_t,uuidv4:()=>jt});"use strict";var _t=(e,t=new Set)=>{e=e.replace(/\s/g,"-").replace(/[^A-Za-z0-9-_]/g,"").toLowerCase();let n=0,r=e;for(;t.has(r);)n++,r=`${e}-${n}`;return r},jt=()=>crypto?.randomUUID?crypto.randomUUID():([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,e=>(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)),Lt=(e,t)=>{var n=parseInt,r=Math.round,[s,c,t,a]=t.split(","),i=e<0,d=i?0:e*255**2,i=i?1+e:1-e;return"rgb"+(a?"a(":"(")+r((i*n(s[3]=="a"?s.slice(5):s.slice(4))**2+d)**.5)+","+r((i*n(c)**2+d)**.5)+","+r((i*n(t)**2+d)**.5)+(a?","+a:")")},At=(e,t,n)=>Math.sqrt(.299*(e*e)+.587*(t*t)+.114*(n*n))>165.75?"rgb(0,0,0)":"rgb(255,255,255)",Et={alphanumeric:/^[\w\.-]+$/,uuid:/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,semver:/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i,email:/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i,url:/^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,64}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i,color:/^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i};function St(e,t){let n=e.match(t);return!!(n&&n.length)}var Tt=async(e,t,{extension:n=""}={})=>{if(n=!e||!e.endsWith||e.endsWith(n),Array.isArray(t))return t.includes(e);switch(t){case"array":return Array.isArray(e);case"object":return e&&typeof e=="object"&&!Array.isArray(e);case"undefined":case"boolean":case"number":return typeof e===t&&n;case"string":return typeof e===t&&n;case"alphanumeric":case"uuid":case"semver":case"email":case"url":case"color":return typeof e=="string"&&St(e,Et[t])&&n;case"file":return typeof e=="string"&&e&&await p.isFile(e)&&n}return!1};var y={};g(y,{core:()=>Le,db:()=>Ut,enabled:()=>zt,errors:()=>Jt,get:()=>D,list:()=>Y,optionDefault:()=>Ee,optionTypes:()=>Mt,profileDB:()=>X,profileName:()=>Ae,supportedEnvs:()=>qt});"use strict";var l=async(e,t,n,r,{extension:s="",error:c=`invalid ${t} (${s?`${s} `:""}${r}): ${JSON.stringify(n)}`,optional:a=!1}={})=>{let i;for(let d of Array.isArray(r)?[r]:r.split("|"))if(d==="file"?i=n&&!n.startsWith("http")?await h.is(`repo/${e._dir}/${n}`,d,{extension:s}):!1:i=await h.is(n,d,{extension:s}),i)break;return i||a&&await h.is(n,"undefined")?!0:(c&&e._err(c),!1)},kt=async e=>(e.environments=e.environments??y.supportedEnvs,await l(e,"environments",e.environments,"array")?e.environments.map(n=>l(e,"environments.env",n,y.supportedEnvs)):!1),Ct=async e=>{if(!await l(e,"tags",e.tags,"array"))return!1;let n=["core","extension","theme","integration"];if(!e.tags.filter(i=>n.includes(i)).length)return e._err(`invalid tags (must contain at least one of 'core', 'extension', 'theme' or 'integration'): +var ce=Object.defineProperty;var Ge=e=>ce(e,"__esModule",{value:!0});var g=(e,t)=>{Ge(e);for(var n in t)ce(e,n,{get:t[n],enumerable:!0})};g(exports,{components:()=>N,electron:()=>I,env:()=>E,fmt:()=>h,fs:()=>p,notion:()=>K,registry:()=>y,storage:()=>_,web:()=>o});var E={};g(E,{focusMenu:()=>De,focusNotion:()=>et,name:()=>Qe,notionRequire:()=>nt,reload:()=>tt,version:()=>Ye});"use strict";var le=globalThis.__enhancerElectronApi.platform,de=globalThis.__enhancerElectronApi.version,pe=globalThis.__enhancerElectronApi.focusMenu,ue=globalThis.__enhancerElectronApi.focusNotion,he=globalThis.__enhancerElectronApi.reload,fe=globalThis.__enhancerElectronApi.notionRequire;"use strict";var Qe=le,Ye=de,De=pe,et=ue,tt=he,nt=fe;var p={};g(p,{getJSON:()=>st,getText:()=>ot,isFile:()=>it,localPath:()=>rt,notionPath:()=>at});"use strict";var L=e=>`notion://www.notion.so/__notion-enhancer/${e}`,me=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.json());try{return globalThis.__enhancerElectronApi.nodeRequire(`notion-enhancer/${e}`)}catch{return fetch(L(e),t).then(r=>r.json())}},ye=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.text());try{let n=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:r}=globalThis.__enhancerElectronApi.nodeRequire("path");return n.readFileSync(r(`${__dirname}/../../${e}`))}catch{return fetch(L(e),t).then(r=>r.text())}},ge=async e=>{try{let t=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:n}=globalThis.__enhancerElectronApi.nodeRequire("path");if(e.startsWith("http"))await fetch(e);else try{t.existsSync(n(`${__dirname}/../../${e}`))}catch{await fetch(L(e))}return!0}catch{return!1}},ve=globalThis.__enhancerElectronApi.notionPath;"use strict";var rt=L,st=me,ot=ye,it=ge,at=ve;var _={};g(_,{addChangeListener:()=>pt,db:()=>dt,get:()=>ct,removeChangeListener:()=>ut,set:()=>lt});"use strict";var F=(e,t=void 0)=>globalThis.__enhancerElectronApi.db.get(e,t),H=(e,t)=>globalThis.__enhancerElectronApi.db.set(e,t),we=(e,t=F,n=H)=>(typeof e=="string"&&(e=[e]),{get:(r=[],s=void 0)=>t([...e,...r],s),set:(r,s)=>n([...e,...r],s)}),be=e=>globalThis.__enhancerElectronApi.db.addChangeListener(e),xe=e=>globalThis.__enhancerElectronApi.db.removeChangeListener(e);"use strict";var ct=F,lt=H,dt=we,pt=be,ut=xe;var I={};g(I,{browser:()=>ht,onMessage:()=>gt,sendMessage:()=>mt,sendMessageToHost:()=>yt,webFrame:()=>ft});"use strict";var ht=globalThis.__enhancerElectronApi?.browser,ft=globalThis.__enhancerElectronApi?.webFrame,mt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessage(e,t,n)},yt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessageToHost(e,t,n)},gt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.onMessage(e,t,n)};var K={};g(K,{create:()=>bt,get:()=>$e,getPageID:()=>M,getSpaceID:()=>T,getUserID:()=>_e,search:()=>vt,set:()=>wt,sign:()=>_t,upload:()=>xt});"use strict";var m=e=>(e?.length===32&&!e.includes("-")&&(e=e.replace(/([\d\w]{8})([\d\w]{4})([\d\w]{4})([\d\w]{4})([\d\w]{12})/,"$1-$2-$3-$4-$5")),e),_e=()=>JSON.parse(localStorage["LRU:KeyValueStore2:current-user-id"]||{}).value,M=()=>m(o.queryParams().get("p")||location.pathname.split(/(-|\/)/g).reverse()[0]),V,T=async()=>(V||(V=(await $e(M())).space_id),V),$e=async(e,t="block")=>(e=m(e),(await p.getJSON("https://www.notion.so/api/v3/getRecordValues",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requests:[{table:t,id:e}]}),method:"POST"})).results[0].value),vt=async(e="",t=20,n=T())=>(n=m(await n),await p.getJSON("https://www.notion.so/api/v3/search",{headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"BlocksInSpace",query:e,spaceId:n,limit:t,filters:{isDeletedOnly:!1,excludeTemplates:!1,isNavigableOnly:!1,requireEditPermissions:!1,ancestors:[],createdBy:[],editedBy:[],lastEditedTime:{},createdTime:{}},sort:"Relevance",source:"quick_find"}),method:"POST"})),wt=async({recordID:e,recordTable:t="block",spaceID:n=T(),path:r=[]},s={})=>{n=m(await n),e=m(e);let c=await p.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:n,operations:[{pointer:{table:t,id:e,spaceId:n},path:r,command:r.length?"set":"update",args:s}]}]}),method:"POST"});return c.errorId?c:!0},bt=async({recordValue:e={},recordTable:t="block"}={},{prepend:n=!1,siblingID:r=void 0,parentID:s=M(),parentTable:c="block",spaceID:a=T(),userID:i=_e()}={})=>{a=m(await a),s=m(s),r=m(r);let d=m(e?.id??h.uuidv4()),x=[],j={type:"text",id:d,version:0,created_time:new Date().getTime(),last_edited_time:new Date().getTime(),parent_id:s,parent_table:c,alive:!0,created_by_table:"notion_user",created_by_id:i,last_edited_by_table:"notion_user",last_edited_by_id:i,space_id:a,permissions:[{type:"user_permission",role:"editor",user_id:i}]};c==="space"?(s=a,j.parent_id=a,x.push("pages"),j.type="page"):c==="collection_view"?(x.push("page_sort"),j.type="page"):x.push("content");let ae=await p.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:a,operations:[{pointer:{table:c,id:s,spaceId:a},path:x,command:n?"listBefore":"listAfter",args:{...r?{after:r}:{},id:d}},{pointer:{table:t,id:d,spaceId:a},path:[],command:"set",args:{...j,...e}}]}]}),method:"POST"});return ae.errorId?ae:d},xt=async(e,{pageID:t=M(),spaceID:n=T()}={})=>{n=m(await n),t=m(t);let r=await p.getJSON("https://www.notion.so/api/v3/getUploadFileUrl",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({bucket:"secure",name:e.name,contentType:e.type,record:{table:"block",id:t,spaceId:n}})});return r.errorId?r:(fetch(r.signedPutUrl,{method:"PUT",headers:{"content-type":e.type},body:e}),r.url)},_t=(e,t,n="block")=>(e.startsWith("/")&&(e=`https://notion.so${e}`),e.includes("secure.notion-static.com")&&(e=new URL(e),e=`https://www.notion.so/signed/${encodeURIComponent(e.origin+e.pathname)}?table=${n}&id=${t}`),e);var h={};g(h,{is:()=>St,rgbContrast:()=>Et,rgbLogShade:()=>At,slugger:()=>$t,uuidv4:()=>jt});"use strict";var $t=(e,t=new Set)=>{e=e.replace(/\s/g,"-").replace(/[^A-Za-z0-9-_]/g,"").toLowerCase();let n=0,r=e;for(;t.has(r);)n++,r=`${e}-${n}`;return r},jt=()=>crypto?.randomUUID?crypto.randomUUID():([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,e=>(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)),At=(e,t)=>{var n=parseInt,r=Math.round,[s,c,t,a]=t.split(","),i=e<0,d=i?0:e*255**2,i=i?1+e:1-e;return"rgb"+(a?"a(":"(")+r((i*n(s[3]=="a"?s.slice(5):s.slice(4))**2+d)**.5)+","+r((i*n(c)**2+d)**.5)+","+r((i*n(t)**2+d)**.5)+(a?","+a:")")},Et=(e,t,n)=>Math.sqrt(.299*(e*e)+.587*(t*t)+.114*(n*n))>165.75?"rgb(0,0,0)":"rgb(255,255,255)",Lt={alphanumeric:/^[\w\.-]+$/,uuid:/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,semver:/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i,email:/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i,url:/^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,64}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i,color:/^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i};function Tt(e,t){let n=e.match(t);return!!(n&&n.length)}var St=async(e,t,{extension:n=""}={})=>{if(n=!e||!e.endsWith||e.endsWith(n),Array.isArray(t))return t.includes(e);switch(t){case"array":return Array.isArray(e);case"object":return e&&typeof e=="object"&&!Array.isArray(e);case"undefined":case"boolean":case"number":return typeof e===t&&n;case"string":return typeof e===t&&n;case"alphanumeric":case"uuid":case"semver":case"email":case"url":case"color":return typeof e=="string"&&Tt(e,Lt[t])&&n;case"file":return typeof e=="string"&&e&&await p.isFile(e)&&n}return!1};var y={};g(y,{core:()=>Ae,db:()=>Ut,enabled:()=>zt,errors:()=>Jt,get:()=>D,list:()=>Y,optionDefault:()=>Le,optionTypes:()=>Mt,profileDB:()=>X,profileName:()=>Ee,supportedEnvs:()=>qt});"use strict";var l=async(e,t,n,r,{extension:s="",error:c=`invalid ${t} (${s?`${s} `:""}${r}): ${JSON.stringify(n)}`,optional:a=!1}={})=>{let i;for(let d of Array.isArray(r)?[r]:r.split("|"))if(d==="file"?i=n&&!n.startsWith("http")?await h.is(`repo/${e._dir}/${n}`,d,{extension:s}):!1:i=await h.is(n,d,{extension:s}),i)break;return i||a&&await h.is(n,"undefined")?!0:(c&&e._err(c),!1)},kt=async e=>(e.environments=e.environments??y.supportedEnvs,await l(e,"environments",e.environments,"array")?e.environments.map(n=>l(e,"environments.env",n,y.supportedEnvs)):!1),Ct=async e=>{if(!await l(e,"tags",e.tags,"array"))return!1;let n=["core","extension","theme","integration"];if(!e.tags.filter(i=>n.includes(i)).length)return e._err(`invalid tags (must contain at least one of 'core', 'extension', 'theme' or 'integration'): ${JSON.stringify(e.tags)}`),!1;let s=e.tags.includes("theme"),c=e.tags.includes("light")||e.tags.includes("dark"),a=e.tags.includes("light")&&e.tags.includes("dark");return s&&(!c||a)?(e._err(`invalid tags (themes must be either 'light' or 'dark', not neither or both): - ${JSON.stringify(e.tags)}`),!1):e.tags.map(i=>l(e,"tags.tag",i,"string"))},Pt=async e=>await l(e,"authors",e.authors,"array")?e.authors.map(n=>[l(e,"authors.author.name",n.name,"string"),l(e,"authors.author.email",n.email,"email",{optional:!0}),l(e,"authors.author.homepage",n.homepage,"url"),l(e,"authors.author.avatar",n.avatar,"url")]):!1,Ot=async e=>{if(!await l(e,"css",e.css,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.css[r])continue;let s=await l(e,`css.${r}`,e.css[r],"array");s&&(s=e.css[r].map(c=>l(e,`css.${r}.file`,c,"file",{extension:".css"}))),n.push(s)}return n},Rt=async e=>{if(!await l(e,"js",e.js,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.js[r])continue;let s=await l(e,`js.${r}`,e.js[r],"array");s&&(s=e.js[r].map(c=>l(e,`js.${r}.file`,c,"file",{extension:".mjs"}))),n.push(s)}if(e.js.electron)if(await l(e,"js.electron",e.js.electron,"array"))for(let s of e.js.electron){if(!await l(e,"js.electron.file",s,"object")){n.push(!1);continue}n.push([l(e,"js.electron.file.source",s.source,"file",{extension:".cjs"}),l(e,"js.electron.file.target",s.target,"string",{extension:".js"})])}else n.push(!1);return n},Nt=async e=>{if(!await l(e,"options",e.options,"array"))return!1;let n=[];for(let r of e.options){let s="options.option";if(!await l(e,`${s}.type`,r.type,y.optionTypes)){n.push(!1);continue}switch(r.environments=r.environments??y.supportedEnvs,n.push([l(e,`${s}.key`,r.key,"alphanumeric"),l(e,`${s}.label`,r.label,"string"),l(e,`${s}.tooltip`,r.tooltip,"string",{optional:!0}),l(e,`${s}.environments`,r.environments,"array").then(a=>a?r.environments.map(i=>l(e,`${s}.environments.env`,i,y.supportedEnvs)):!1)]),r.type){case"toggle":n.push(l(e,`${s}.value`,r.value,"boolean"));break;case"select":{let a=await l(e,`${s}.values`,r.values,"array");a&&(a=r.values.map(i=>l(e,`${s}.values.value`,i,"string"))),n.push(a);break}case"text":case"hotkey":n.push(l(e,`${s}.value`,r.value,"string"));break;case"number":case"color":n.push(l(e,`${s}.value`,r.value,r.type));break;case"file":{let a=await l(e,`${s}.extensions`,r.extensions,"array");a&&(a=r.extensions.map(i=>l(e,`${s}.extensions.extension`,i,"string"))),n.push(a);break}}}return n};async function je(e){let t=[l(e,"name",e.name,"string"),l(e,"id",e.id,"uuid"),l(e,"version",e.version,"semver"),kt(e),l(e,"description",e.description,"string"),l(e,"preview",e.preview,"file|url",{optional:!0}),Ct(e),Pt(e),Ot(e),Rt(e),Nt(e)];do t=await Promise.all(t.flat(1/0));while(t.some(n=>Array.isArray(n)));return t.every(n=>n)}"use strict";var Le=["a6621988-551d-495a-97d8-3c568bca2e9e","0f0bf8b6-eae6-4273-b307-8fc43f2ee082","36a2ffc9-27ff-480e-84a7-c7700a7d232d"],qt=["linux","win32","darwin","extension"],Mt=["toggle","select","text","number","color","file","hotkey"],Ae=async()=>$.get(["currentprofile"],"default"),X=async()=>$.db(["profiles",await Ae()]),G,Q=[],Y=async(e=t=>!0)=>{G||(G=new Promise(async(n,r)=>{let s=[];for(let c of await p.getJSON("repo/registry.json"))try{let a={...await p.getJSON(`repo/${c}/mod.json`),_dir:c,_err:i=>Q.push({source:c,message:i})};await je(a)&&s.push(a)}catch{Q.push({source:c,message:"invalid mod.json"})}n(s)}));let t=[];for(let n of await G)await e(n)&&t.push(n);return t},Jt=async()=>(await Y(),Q),D=async e=>(await Y(t=>t.id===e))[0],zt=async e=>(await D(e)).environments.includes(A.name)?Le.includes(e)?!0:(await X()).get(["_mods",e],!1):!1,Ee=async(e,t)=>{let n=await D(e),r=n.options.find(s=>s.key===t);if(!!r)switch(r.type){case"toggle":case"text":case"number":case"color":case"hotkey":return r.value;case"select":return r.values[0];case"file":return}},Ut=async e=>{let t=await X();return $.db([e],async(n,r=void 0)=>(typeof n=="string"&&(n=[n]),n.length===2&&(r=await Ee(e,n[1])??r),t.get(n,r)),t.set)};var o={};g(o,{addDocumentObserver:()=>Xt,addHotkeyListener:()=>Vt,copyToClipboard:()=>Ht,empty:()=>Wt,escape:()=>Te,html:()=>Ce,loadStylesheet:()=>Ft,queryParams:()=>Bt,raw:()=>ke,readFromClipboard:()=>It,removeDocumentObserver:()=>Gt,removeHotkeyListener:()=>Kt,render:()=>Pe,whenReady:()=>Zt});"use strict";var Se=!1,T=[],ee,J=[],te=[],Zt=(e=[])=>new Promise((t,n)=>{function r(){let s;s=setInterval(c,100);function c(){e.every(a=>document.querySelector(a))&&(clearInterval(s),t(!0))}c()}document.readyState!=="complete"?document.addEventListener("readystatechange",s=>{document.readyState==="complete"&&r()}):r()}),Bt=()=>new URLSearchParams(window.location.search),Te=e=>e.replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\\/g,"\"),ke=(e,...t)=>{let n=e.map(r=>r+(["string","number"].includes(typeof t[0])?t.shift():Te(JSON.stringify(t.shift(),null,2)??""))).join("");return n.includes("r.trim()).filter(r=>r.length).join(" ")},Ce=(e,...t)=>{let n=document.createRange().createContextualFragment(ke(e,...t));return n.children.length===1?n.children[0]:n.children},Pe=(e,...t)=>(t=t.map(n=>n instanceof HTMLCollection?[...n]:n).flat(1/0).filter(n=>n),e.append(...t),e),Wt=e=>{for(;e.firstChild&&e.removeChild(e.firstChild););return e},Ft=e=>{let t=Ce`l(e,"tags.tag",i,"string"))},Pt=async e=>await l(e,"authors",e.authors,"array")?e.authors.map(n=>[l(e,"authors.author.name",n.name,"string"),l(e,"authors.author.email",n.email,"email",{optional:!0}),l(e,"authors.author.homepage",n.homepage,"url"),l(e,"authors.author.avatar",n.avatar,"url")]):!1,Ot=async e=>{if(!await l(e,"css",e.css,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.css[r])continue;let s=await l(e,`css.${r}`,e.css[r],"array");s&&(s=e.css[r].map(c=>l(e,`css.${r}.file`,c,"file",{extension:".css"}))),n.push(s)}return n},Rt=async e=>{if(!await l(e,"js",e.js,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.js[r])continue;let s=await l(e,`js.${r}`,e.js[r],"array");s&&(s=e.js[r].map(c=>l(e,`js.${r}.file`,c,"file",{extension:".mjs"}))),n.push(s)}if(e.js.electron)if(await l(e,"js.electron",e.js.electron,"array"))for(let s of e.js.electron){if(!await l(e,"js.electron.file",s,"object")){n.push(!1);continue}n.push([l(e,"js.electron.file.source",s.source,"file",{extension:".cjs"}),l(e,"js.electron.file.target",s.target,"string",{extension:".js"})])}else n.push(!1);return n},Nt=async e=>{if(!await l(e,"options",e.options,"array"))return!1;let n=[];for(let r of e.options){let s="options.option";if(!await l(e,`${s}.type`,r.type,y.optionTypes)){n.push(!1);continue}switch(r.environments=r.environments??y.supportedEnvs,n.push([l(e,`${s}.key`,r.key,"alphanumeric"),l(e,`${s}.label`,r.label,"string"),l(e,`${s}.tooltip`,r.tooltip,"string",{optional:!0}),l(e,`${s}.environments`,r.environments,"array").then(a=>a?r.environments.map(i=>l(e,`${s}.environments.env`,i,y.supportedEnvs)):!1)]),r.type){case"toggle":n.push(l(e,`${s}.value`,r.value,"boolean"));break;case"select":{let a=await l(e,`${s}.values`,r.values,"array");a&&(a=r.values.map(i=>l(e,`${s}.values.value`,i,"string"))),n.push(a);break}case"text":case"hotkey":n.push(l(e,`${s}.value`,r.value,"string"));break;case"number":case"color":n.push(l(e,`${s}.value`,r.value,r.type));break;case"file":{let a=await l(e,`${s}.extensions`,r.extensions,"array");a&&(a=r.extensions.map(i=>l(e,`${s}.extensions.extension`,i,"string"))),n.push(a);break}}}return n};async function je(e){let t=[l(e,"name",e.name,"string"),l(e,"id",e.id,"uuid"),l(e,"version",e.version,"semver"),kt(e),l(e,"description",e.description,"string"),l(e,"preview",e.preview,"file|url",{optional:!0}),Ct(e),Pt(e),Ot(e),Rt(e),Nt(e)];do t=await Promise.all(t.flat(1/0));while(t.some(n=>Array.isArray(n)));return t.every(n=>n)}"use strict";var Ae=["a6621988-551d-495a-97d8-3c568bca2e9e","0f0bf8b6-eae6-4273-b307-8fc43f2ee082","36a2ffc9-27ff-480e-84a7-c7700a7d232d"],qt=["linux","win32","darwin","extension"],Mt=["toggle","select","text","number","color","file","hotkey"],Ee=async()=>_.get(["currentprofile"],"default"),X=async()=>_.db(["profiles",await Ee()]),G,Q=[],Y=async(e=t=>!0)=>{G||(G=new Promise(async(n,r)=>{let s=[];for(let c of await p.getJSON("repo/registry.json"))try{let a={...await p.getJSON(`repo/${c}/mod.json`),_dir:c,_err:i=>Q.push({source:c,message:i})};await je(a)&&s.push(a)}catch{Q.push({source:c,message:"invalid mod.json"})}n(s)}));let t=[];for(let n of await G)await e(n)&&t.push(n);return t},Jt=async()=>(await Y(),Q),D=async e=>(await Y(t=>t.id===e))[0],zt=async e=>(await D(e)).environments.includes(E.name)?Ae.includes(e)?!0:(await X()).get(["_mods",e],!1):!1,Le=async(e,t)=>{let n=await D(e),r=n.options.find(s=>s.key===t);if(!!r)switch(r.type){case"toggle":case"text":case"number":case"color":case"hotkey":return r.value;case"select":return r.values[0];case"file":return}},Ut=async e=>{let t=await X();return _.db([e],async(n,r=void 0)=>(typeof n=="string"&&(n=[n]),n.length===2&&(r=await Le(e,n[1])??r),t.get(n,r)),t.set)};var o={};g(o,{addDocumentObserver:()=>Xt,addHotkeyListener:()=>Vt,copyToClipboard:()=>Ht,empty:()=>Wt,escape:()=>Se,html:()=>Ce,loadStylesheet:()=>Ft,queryParams:()=>Bt,raw:()=>ke,readFromClipboard:()=>It,removeDocumentObserver:()=>Gt,removeHotkeyListener:()=>Kt,render:()=>Pe,whenReady:()=>Zt});"use strict";var Te=!1,S=[],ee,J=[],te=[],Zt=(e=[])=>new Promise((t,n)=>{function r(){let s;s=setInterval(c,100);function c(){e.every(a=>document.querySelector(a))&&(clearInterval(s),t(!0))}c()}document.readyState!=="complete"?document.addEventListener("readystatechange",s=>{document.readyState==="complete"&&r()}):r()}),Bt=()=>new URLSearchParams(window.location.search),Se=e=>e.replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\\/g,"\"),ke=(e,...t)=>{let n=e.map(r=>r+(["string","number"].includes(typeof t[0])?t.shift():Se(JSON.stringify(t.shift(),null,2)??""))).join("");return n.includes("r.trim()).filter(r=>r.length).join(" ")},Ce=(e,...t)=>{let n=document.createRange().createContextualFragment(ke(e,...t));return n.children.length===1?n.children[0]:n.children},Pe=(e,...t)=>(t=t.map(n=>n instanceof HTMLCollection?[...n]:n).flat(1/0).filter(n=>n),e.append(...t),e),Wt=e=>{for(;e.firstChild&&e.removeChild(e.firstChild););return e},Ft=e=>{let t=Ce``;return Pe(document.head,t),t},Ht=async e=>{try{await navigator.clipboard.writeText(e)}catch{let t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}},It=()=>navigator.clipboard.readText(),Oe=(e,t)=>{if(document.activeElement.nodeName==="INPUT"&&!t.listenInInput)return;t.keys.every(s=>{s=s.toLowerCase();let c={metaKey:["meta","os","win","cmd","command"],ctrlKey:["ctrl","control"],shiftKey:["shift"],altKey:["alt"]};for(let a in c)if(c[a].includes(s)&&e[a])return!0;if(s==="space"&&(s=" "),s==="plus"&&(s="+"),s===e.key.toLowerCase())return!0})&&t.callback(e)},Vt=(e,t,{listenInInput:n=!1,keydown:r=!1}={})=>{typeof e=="string"&&(e=e.split("+")),T.push({keys:e,callback:t,listenInInput:n,keydown:r}),Se||(Se=!0,document.addEventListener("keyup",s=>{for(let c of T.filter(({keydown:a})=>!a))Oe(s,c)}),document.addEventListener("keydown",s=>{for(let c of T.filter(({keydown:a})=>a))Oe(s,c)}))},Kt=e=>{T=T.filter(t=>t.callback!==e)},Xt=(e,t=[])=>{if(!ee){let n=r=>{for(;r.length;){let s=r.shift(),c=(i,d)=>i instanceof Element&&(i.matches(d)||i.matches(`${d} *`)||i.querySelector(d)),a=i=>s.target.matches(i)||s.target.matches(`${i} *`)||[...s.addedNodes].some(d=>c(d,i));for(let i of J)(!i.selectors.length||i.selectors.some(a))&&i.callback(s)}};ee=new MutationObserver((r,s)=>{te.length||requestIdleCallback(()=>n(te)),te.push(...r)}),ee.observe(document.body,{childList:!0,subtree:!0,attributes:!0})}J.push({callback:e,selectors:t})},Gt=e=>{J=J.filter(t=>t.callback!==e)};var N={};g(N,{addCornerAction:()=>Xe,addPanelView:()=>Ve,addTooltip:()=>Ne,feather:()=>qe});"use strict";var Re,u,Qt=e=>[...e.getClientRects()].reduce((t,n)=>t.some(r=>r.y===n.y)?t:[...t,n],[]).length,Yt=async(e,t,n)=>{u.style.top="0px",u.style.left="0px";let r=e.getBoundingClientRect(),{offsetWidth:s,offsetHeight:c}=u,a=6,i=r.x,d=Math.floor(r.y);if(["top","bottom"].includes(t)){t==="top"&&(d-=c+a),t==="bottom"&&(d+=r.height+a),i-=s/2-r.width/2,u.style.left=`${i}px`,u.style.top=`${d}px`;let x=()=>Qt(u.firstElementChild)>n,j=x();for(;x();)u.style.left=`${window.innerWidth-i>i?i++:i--}px`;j&&(i+=window.innerWidth-i>i?a:-a,u.style.left=`${i}px`)}return["left","right"].includes(t)&&(d-=c/2-r.height/2,t==="left"&&(i-=s+a),t==="right"&&(i+=r.width+a),u.style.left=`${i}px`,u.style.top=`${d}px`),!0},Ne=async(e,t,{delay:n=100,offsetDirection:r="bottom",maxLines:s=1}={})=>{Re||(Re=o.loadStylesheet("api/components/tooltip.css"),u=o.html`
`,o.render(document.body,u)),globalThis.markdownit||await import(p.localPath("dep/markdown-it.min.js"));let c=markdownit({linkify:!0});t instanceof Element||(t=o.html`
+ />`;return Pe(document.head,t),t},Ht=async e=>{try{await navigator.clipboard.writeText(e)}catch{let t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}},It=()=>navigator.clipboard.readText(),Oe=(e,t)=>{if(document.activeElement.nodeName==="INPUT"&&!t.listenInInput)return;t.keys.every(s=>{s=s.toLowerCase();let c={metaKey:["meta","os","win","cmd","command"],ctrlKey:["ctrl","control"],shiftKey:["shift"],altKey:["alt"]};for(let a in c)if(c[a].includes(s)&&e[a])return!0;if(s==="space"&&(s=" "),s==="plus"&&(s="+"),s===e.key.toLowerCase())return!0})&&t.callback(e)},Vt=(e,t,{listenInInput:n=!1,keydown:r=!1}={})=>{typeof e=="string"&&(e=e.split("+")),S.push({keys:e,callback:t,listenInInput:n,keydown:r}),Te||(Te=!0,document.addEventListener("keyup",s=>{for(let c of S.filter(({keydown:a})=>!a))Oe(s,c)}),document.addEventListener("keydown",s=>{for(let c of S.filter(({keydown:a})=>a))Oe(s,c)}))},Kt=e=>{S=S.filter(t=>t.callback!==e)},Xt=(e,t=[])=>{if(!ee){let n=r=>{for(;r.length;){let s=r.shift(),c=(i,d)=>i instanceof Element&&(i.matches(d)||i.matches(`${d} *`)||i.querySelector(d)),a=i=>s.target.matches(i)||s.target.matches(`${i} *`)||[...s.addedNodes].some(d=>c(d,i));for(let i of J)(!i.selectors.length||i.selectors.some(a))&&i.callback(s)}};ee=new MutationObserver((r,s)=>{te.length||requestIdleCallback(()=>n(te)),te.push(...r)}),ee.observe(document.body,{childList:!0,subtree:!0,attributes:!0})}J.push({callback:e,selectors:t})},Gt=e=>{J=J.filter(t=>t.callback!==e)};var N={};g(N,{addCornerAction:()=>Xe,addPanelView:()=>Ve,addTooltip:()=>Ne,feather:()=>qe});"use strict";var Re,u,Qt=e=>[...e.getClientRects()].reduce((t,n)=>t.some(r=>r.y===n.y)?t:[...t,n],[]).length,Yt=async(e,t,n)=>{u.style.top="0px",u.style.left="0px";let r=e.getBoundingClientRect(),{offsetWidth:s,offsetHeight:c}=u,a=6,i=r.x,d=Math.floor(r.y);if(["top","bottom"].includes(t)){t==="top"&&(d-=c+a),t==="bottom"&&(d+=r.height+a),i-=s/2-r.width/2,u.style.left=`${i}px`,u.style.top=`${d}px`;let x=()=>Qt(u.firstElementChild)>n,j=x();for(;x();)u.style.left=`${window.innerWidth-i>i?i++:i--}px`;j&&(i+=window.innerWidth-i>i?a:-a,u.style.left=`${i}px`)}return["left","right"].includes(t)&&(d-=c/2-r.height/2,t==="left"&&(i-=s+a),t==="right"&&(i+=r.width+a),u.style.left=`${i}px`,u.style.top=`${d}px`),!0},Ne=async(e,t,{delay:n=100,offsetDirection:r="bottom",maxLines:s=1}={})=>{Re||(Re=o.loadStylesheet("api/components/tooltip.css"),u=o.html`
`,o.render(document.body,u)),globalThis.markdownit||await import(p.localPath("dep/markdown-it.min.js"));let c=markdownit({linkify:!0});t instanceof Element||(t=o.html`
${t.split(` `).map(i=>c.renderInline(i)).join("
")}
`);let a;e.addEventListener("mouseover",async i=>{a||(a=setTimeout(async()=>{e.matches(":hover")&&u.style.display!=="block"&&(u.style.display="block",o.render(o.empty(u),t),Yt(e,r,s),await u.animate([{opacity:0},{opacity:1}],{duration:65}).finished),a=void 0},n))}),e.addEventListener("mouseout",async i=>{a=void 0,u.style.display==="block"&&!e.matches(":hover")&&(await u.animate([{opacity:1},{opacity:0}],{duration:65}).finished,u.style.display="")})};"use strict";var ne,qe=async(e,t={})=>(ne||(ne=o.html`${await p.getText("dep/feather-sprite.svg")}`),t.style=((t.style?t.style+";":"")+"stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;fill:none;").trim(),t.viewBox="0 0 24 24",``${o.escape(n)}="${o.escape(r)}"`).join(" ")}>${ne.getElementById(e)?.innerHTML}`);"use strict";var k=[],Dt=o.raw` @@ -13,9 +13,9 @@ var ce=Object.defineProperty;var Ge=e=>ce(e,"__esModule",{value:!0});var g=(e,t) 2.43056L 3.98809 -0.569442L 3.01191 0.569442ZM -0.488094 6.56944L 3.01191 9.56944L 3.98809 8.43056L 0.488094 5.43056L -0.488094 6.56944ZM 3.98809 9.56944L 7.48809 6.56944L 6.51191 5.43056L 3.01191 8.43056L 3.98809 9.56944Z"> - `,Me,w,z,v,_,C,L,Je,ze,re,f,Ue,U,P,Z,se,b,oe,O,B="data-enhancer-panel-pinned",W=()=>_.hasAttribute(B),R=()=>{let e=[z,v,C,_].filter(t=>t);if(W()){ie();for(let t of e)t.removeAttribute(B)}else for(let t of e)t.setAttribute(B,"true");w.set(["panel.pinned"],W())},Ze=async()=>{document.documentElement.style.setProperty("--component--panel-width",f+"px"),w.set(["panel.width"],f)},Be=e=>{e.preventDefault(),re=!0,f=ze+(Je-e.clientX),f<190&&(f=190),f>480&&(f=480),_.style.width=f+"px",C.style.width=f+"px",z.style.paddingRight=f+"px",v&&(v.style.right=f+"px")},We=e=>{_.style.width="",C.style.width="",z.style.paddingRight="",v&&(v.style.right=""),Ze(),L.style.cursor="",document.body.removeEventListener("mousemove",Be),document.body.removeEventListener("mouseup",We)},en=e=>{Je=e.clientX,ze=f,L.style.cursor="auto",document.body.addEventListener("mousemove",Be),document.body.addEventListener("mouseup",We)},tn=()=>document.body.contains(b),Fe=()=>{if(!W())return R();o.render(Ue,O),o.empty(b);for(let t of k){let n=P.contains(t.$title),r=o.render(o.html`
`,o.render(o.html``,t.$icon.cloneNode(!0),t.$title.cloneNode(!0)));r.addEventListener("click",()=>{Ie(t),w.set(["panel.open"],t.id)}),o.render(b,r)}let e=Z.getBoundingClientRect();o.render(o.empty(O),o.render(o.html`
`,o.render(o.html`
`,b))),b.querySelector("[data-open]").focus(),b.animate([{opacity:0},{opacity:1}],{duration:200}),document.addEventListener("keydown",He)},ie=()=>{document.removeEventListener("keydown",He),b.animate([{opacity:1},{opacity:0}],{duration:200}).onfinish=()=>O.remove()},He=e=>{if(tn())switch(e.key){case"Escape":ie(),e.stopPropagation();break;case"Enter":document.activeElement.click(),e.stopPropagation();break;case"ArrowUp":(e.target.previousElementSibling||e.target.parentElement.lastElementChild).focus(),e.stopPropagation();break;case"ArrowDown":(e.target.nextElementSibling||e.target.parentElement.firstElementChild).focus(),e.stopPropagation();break}},Ie=e=>{let t=k.find(({$content:n})=>document.contains(n));o.render(o.empty(P),o.render(o.html``,e.$icon,e.$title)),e.onFocus(),o.render(o.empty(se),e.$content),t&&t.onBlur()};async function nn(){await o.whenReady([".notion-frame"]),z=document.querySelector(".notion-frame"),_=o.html`
`,C=o.html`
`,L=o.html`
`,P=o.html`
`,Z=o.render(o.html`
`,P),se=o.html`
`,b=o.html`
`,oe=o.html`
+ `,Me,w,z,v,$,C,A,Je,ze,re,f,Ue,U,P,Z,se,b,oe,O,B="data-enhancer-panel-pinned",W=()=>$.hasAttribute(B),R=()=>{let e=[z,v,C,$].filter(t=>t);if(W()){ie();for(let t of e)t.removeAttribute(B)}else for(let t of e)t.setAttribute(B,"true");w.set(["panel.pinned"],W())},Ze=async()=>{document.documentElement.style.setProperty("--component--panel-width",f+"px"),w.set(["panel.width"],f)},Be=e=>{e.preventDefault(),re=!0,f=ze+(Je-e.clientX),f<190&&(f=190),f>480&&(f=480),$.style.width=f+"px",C.style.width=f+"px",z.style.paddingRight=f+"px",v&&(v.style.right=f+"px")},We=e=>{$.style.width="",C.style.width="",z.style.paddingRight="",v&&(v.style.right=""),Ze(),A.style.cursor="",document.body.removeEventListener("mousemove",Be),document.body.removeEventListener("mouseup",We)},en=e=>{Je=e.clientX,ze=f,A.style.cursor="auto",document.body.addEventListener("mousemove",Be),document.body.addEventListener("mouseup",We)},tn=()=>document.body.contains(b),Fe=()=>{if(!W())return R();o.render(Ue,O),o.empty(b);for(let t of k){let n=P.contains(t.$title),r=o.render(o.html`
`,o.render(o.html``,t.$icon.cloneNode(!0),t.$title.cloneNode(!0)));r.addEventListener("click",()=>{Ie(t),w.set(["panel.open"],t.id)}),o.render(b,r)}let e=Z.getBoundingClientRect();o.render(o.empty(O),o.render(o.html`
`,o.render(o.html`
`,b))),b.querySelector("[data-open]").focus(),b.animate([{opacity:0},{opacity:1}],{duration:200}),document.addEventListener("keydown",He)},ie=()=>{document.removeEventListener("keydown",He),b.animate([{opacity:1},{opacity:0}],{duration:200}).onfinish=()=>O.remove()},He=e=>{if(tn())switch(e.key){case"Escape":ie(),e.stopPropagation();break;case"Enter":document.activeElement.click(),e.stopPropagation();break;case"ArrowUp":(e.target.previousElementSibling||e.target.parentElement.lastElementChild).focus(),e.stopPropagation();break;case"ArrowDown":(e.target.nextElementSibling||e.target.parentElement.firstElementChild).focus(),e.stopPropagation();break}},Ie=e=>{let t=k.find(({$content:n})=>document.contains(n));o.render(o.empty(P),o.render(o.html``,e.$icon,e.$title)),e.onFocus(),o.render(o.empty(se),e.$content),t&&t.onBlur()};async function nn(){await o.whenReady([".notion-frame"]),z=document.querySelector(".notion-frame"),$=o.html`
`,C=o.html`
`,A=o.html`
`,P=o.html`
`,Z=o.render(o.html`
`,P),se=o.html`
`,b=o.html`
`,oe=o.html`
${Dt} -
`,O=o.html`
`;let e='.notion-cursor-listener > div[style*="flex-end"]',t=()=>{document.contains(v)||(v=document.querySelector(e),W()&&v&&v.setAttribute(B,"true"))};v=document.querySelector(e),o.addDocumentObserver(t,[e]),await w.get(["panel.pinned"])&&R(),o.addHotkeyListener(await w.get(["panel.hotkey"]),R),U.addEventListener("click",r=>{r.stopPropagation(),R()}),o.render(_,o.render(Z,P,oe,U),se,L),await rn(),await sn();let n='.notion-cursor-listener > .notion-sidebar-container ~ [style^="position: absolute"]';await o.whenReady([n]),document.querySelector(n).before(C,_)}async function rn(){f=await w.get(["panel.width"],240),Ze(),L.addEventListener("mousedown",en),L.addEventListener("click",()=>{re?re=!1:R()})}async function sn(){Ue=document.querySelector(".notion-app-inner"),Z.addEventListener("click",Fe),oe.addEventListener("click",Fe),O.addEventListener("click",ie)}var Ve=async({id:e,icon:t,title:n,$content:r,onFocus:s=()=>{},onBlur:c=()=>{}})=>{Me||(Me=o.loadStylesheet("api/components/panel.css")),w||(w=await y.db("36a2ffc9-27ff-480e-84a7-c7700a7d232d")),U||(U=o.html`
+
`,O=o.html`
`;let e='.notion-cursor-listener > div[style*="flex-end"]',t=()=>{document.contains(v)||(v=document.querySelector(e),W()&&v&&v.setAttribute(B,"true"))};v=document.querySelector(e),o.addDocumentObserver(t,[e]),await w.get(["panel.pinned"])&&R(),o.addHotkeyListener(await w.get(["panel.hotkey"]),R),U.addEventListener("click",r=>{r.stopPropagation(),R()}),o.render($,o.render(Z,P,oe,U),se,A),await rn(),await sn();let n='.notion-cursor-listener > .notion-sidebar-container ~ [style^="position: absolute"]';await o.whenReady([n]),document.querySelector(n).before(C,$)}async function rn(){f=await w.get(["panel.width"],240),Ze(),A.addEventListener("mousedown",en),A.addEventListener("click",()=>{re?re=!1:R()})}async function sn(){Ue=document.querySelector(".notion-app-inner"),Z.addEventListener("click",Fe),oe.addEventListener("click",Fe),O.addEventListener("click",ie)}var Ve=async({id:e,icon:t,title:n,$content:r,onFocus:s=()=>{},onBlur:c=()=>{}})=>{Me||(Me=o.loadStylesheet("api/components/panel.css")),w||(w=await y.db("36a2ffc9-27ff-480e-84a7-c7700a7d232d")),U||(U=o.html`
${await N.feather("chevrons-right")}
`);let a={id:e,$icon:o.render(o.html``,t instanceof Element?t:o.html`${t}`),$title:o.render(o.html``,n),$content:r,onFocus:s,onBlur:c};k.push(a),k.length===1&&await nn(),(k.length===1||await w.get(["panel.open"])===e)&&Ie(a)};"use strict";var Ke,q,Xe=async(e,t)=>{Ke||(Ke=o.loadStylesheet("api/components/corner-action.css"),q=o.html`
`),await o.whenReady([".notion-help-button"]);let n=document.querySelector(".notion-help-button"),r=document.querySelector(".onboarding-checklist-button");r&&q.prepend(r),q.prepend(n),o.render(document.querySelector(".notion-app-inner > .notion-cursor-listener"),q);let s=o.html`
${e}
`;return s.addEventListener("click",t),o.render(q,s),s};"use strict";"use strict"; From d984c8bc36b847fe5c5c4edfe5306013e3dcf2a3 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Fri, 10 Dec 2021 14:41:29 +1100 Subject: [PATCH 43/52] perf: only observe if window/document/tab is focused --- api/index.cjs | 14 +++++++------- api/web.mjs | 25 +++++++++++++------------ 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/api/index.cjs b/api/index.cjs index 239818f..3e83a06 100644 --- a/api/index.cjs +++ b/api/index.cjs @@ -1,9 +1,9 @@ -var ce=Object.defineProperty;var Ge=e=>ce(e,"__esModule",{value:!0});var g=(e,t)=>{Ge(e);for(var n in t)ce(e,n,{get:t[n],enumerable:!0})};g(exports,{components:()=>N,electron:()=>I,env:()=>E,fmt:()=>h,fs:()=>p,notion:()=>K,registry:()=>y,storage:()=>_,web:()=>o});var E={};g(E,{focusMenu:()=>De,focusNotion:()=>et,name:()=>Qe,notionRequire:()=>nt,reload:()=>tt,version:()=>Ye});"use strict";var le=globalThis.__enhancerElectronApi.platform,de=globalThis.__enhancerElectronApi.version,pe=globalThis.__enhancerElectronApi.focusMenu,ue=globalThis.__enhancerElectronApi.focusNotion,he=globalThis.__enhancerElectronApi.reload,fe=globalThis.__enhancerElectronApi.notionRequire;"use strict";var Qe=le,Ye=de,De=pe,et=ue,tt=he,nt=fe;var p={};g(p,{getJSON:()=>st,getText:()=>ot,isFile:()=>it,localPath:()=>rt,notionPath:()=>at});"use strict";var L=e=>`notion://www.notion.so/__notion-enhancer/${e}`,me=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.json());try{return globalThis.__enhancerElectronApi.nodeRequire(`notion-enhancer/${e}`)}catch{return fetch(L(e),t).then(r=>r.json())}},ye=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.text());try{let n=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:r}=globalThis.__enhancerElectronApi.nodeRequire("path");return n.readFileSync(r(`${__dirname}/../../${e}`))}catch{return fetch(L(e),t).then(r=>r.text())}},ge=async e=>{try{let t=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:n}=globalThis.__enhancerElectronApi.nodeRequire("path");if(e.startsWith("http"))await fetch(e);else try{t.existsSync(n(`${__dirname}/../../${e}`))}catch{await fetch(L(e))}return!0}catch{return!1}},ve=globalThis.__enhancerElectronApi.notionPath;"use strict";var rt=L,st=me,ot=ye,it=ge,at=ve;var _={};g(_,{addChangeListener:()=>pt,db:()=>dt,get:()=>ct,removeChangeListener:()=>ut,set:()=>lt});"use strict";var F=(e,t=void 0)=>globalThis.__enhancerElectronApi.db.get(e,t),H=(e,t)=>globalThis.__enhancerElectronApi.db.set(e,t),we=(e,t=F,n=H)=>(typeof e=="string"&&(e=[e]),{get:(r=[],s=void 0)=>t([...e,...r],s),set:(r,s)=>n([...e,...r],s)}),be=e=>globalThis.__enhancerElectronApi.db.addChangeListener(e),xe=e=>globalThis.__enhancerElectronApi.db.removeChangeListener(e);"use strict";var ct=F,lt=H,dt=we,pt=be,ut=xe;var I={};g(I,{browser:()=>ht,onMessage:()=>gt,sendMessage:()=>mt,sendMessageToHost:()=>yt,webFrame:()=>ft});"use strict";var ht=globalThis.__enhancerElectronApi?.browser,ft=globalThis.__enhancerElectronApi?.webFrame,mt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessage(e,t,n)},yt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessageToHost(e,t,n)},gt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.onMessage(e,t,n)};var K={};g(K,{create:()=>bt,get:()=>$e,getPageID:()=>M,getSpaceID:()=>T,getUserID:()=>_e,search:()=>vt,set:()=>wt,sign:()=>_t,upload:()=>xt});"use strict";var m=e=>(e?.length===32&&!e.includes("-")&&(e=e.replace(/([\d\w]{8})([\d\w]{4})([\d\w]{4})([\d\w]{4})([\d\w]{12})/,"$1-$2-$3-$4-$5")),e),_e=()=>JSON.parse(localStorage["LRU:KeyValueStore2:current-user-id"]||{}).value,M=()=>m(o.queryParams().get("p")||location.pathname.split(/(-|\/)/g).reverse()[0]),V,T=async()=>(V||(V=(await $e(M())).space_id),V),$e=async(e,t="block")=>(e=m(e),(await p.getJSON("https://www.notion.so/api/v3/getRecordValues",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requests:[{table:t,id:e}]}),method:"POST"})).results[0].value),vt=async(e="",t=20,n=T())=>(n=m(await n),await p.getJSON("https://www.notion.so/api/v3/search",{headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"BlocksInSpace",query:e,spaceId:n,limit:t,filters:{isDeletedOnly:!1,excludeTemplates:!1,isNavigableOnly:!1,requireEditPermissions:!1,ancestors:[],createdBy:[],editedBy:[],lastEditedTime:{},createdTime:{}},sort:"Relevance",source:"quick_find"}),method:"POST"})),wt=async({recordID:e,recordTable:t="block",spaceID:n=T(),path:r=[]},s={})=>{n=m(await n),e=m(e);let c=await p.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:n,operations:[{pointer:{table:t,id:e,spaceId:n},path:r,command:r.length?"set":"update",args:s}]}]}),method:"POST"});return c.errorId?c:!0},bt=async({recordValue:e={},recordTable:t="block"}={},{prepend:n=!1,siblingID:r=void 0,parentID:s=M(),parentTable:c="block",spaceID:a=T(),userID:i=_e()}={})=>{a=m(await a),s=m(s),r=m(r);let d=m(e?.id??h.uuidv4()),x=[],j={type:"text",id:d,version:0,created_time:new Date().getTime(),last_edited_time:new Date().getTime(),parent_id:s,parent_table:c,alive:!0,created_by_table:"notion_user",created_by_id:i,last_edited_by_table:"notion_user",last_edited_by_id:i,space_id:a,permissions:[{type:"user_permission",role:"editor",user_id:i}]};c==="space"?(s=a,j.parent_id=a,x.push("pages"),j.type="page"):c==="collection_view"?(x.push("page_sort"),j.type="page"):x.push("content");let ae=await p.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:a,operations:[{pointer:{table:c,id:s,spaceId:a},path:x,command:n?"listBefore":"listAfter",args:{...r?{after:r}:{},id:d}},{pointer:{table:t,id:d,spaceId:a},path:[],command:"set",args:{...j,...e}}]}]}),method:"POST"});return ae.errorId?ae:d},xt=async(e,{pageID:t=M(),spaceID:n=T()}={})=>{n=m(await n),t=m(t);let r=await p.getJSON("https://www.notion.so/api/v3/getUploadFileUrl",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({bucket:"secure",name:e.name,contentType:e.type,record:{table:"block",id:t,spaceId:n}})});return r.errorId?r:(fetch(r.signedPutUrl,{method:"PUT",headers:{"content-type":e.type},body:e}),r.url)},_t=(e,t,n="block")=>(e.startsWith("/")&&(e=`https://notion.so${e}`),e.includes("secure.notion-static.com")&&(e=new URL(e),e=`https://www.notion.so/signed/${encodeURIComponent(e.origin+e.pathname)}?table=${n}&id=${t}`),e);var h={};g(h,{is:()=>St,rgbContrast:()=>Et,rgbLogShade:()=>At,slugger:()=>$t,uuidv4:()=>jt});"use strict";var $t=(e,t=new Set)=>{e=e.replace(/\s/g,"-").replace(/[^A-Za-z0-9-_]/g,"").toLowerCase();let n=0,r=e;for(;t.has(r);)n++,r=`${e}-${n}`;return r},jt=()=>crypto?.randomUUID?crypto.randomUUID():([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,e=>(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)),At=(e,t)=>{var n=parseInt,r=Math.round,[s,c,t,a]=t.split(","),i=e<0,d=i?0:e*255**2,i=i?1+e:1-e;return"rgb"+(a?"a(":"(")+r((i*n(s[3]=="a"?s.slice(5):s.slice(4))**2+d)**.5)+","+r((i*n(c)**2+d)**.5)+","+r((i*n(t)**2+d)**.5)+(a?","+a:")")},Et=(e,t,n)=>Math.sqrt(.299*(e*e)+.587*(t*t)+.114*(n*n))>165.75?"rgb(0,0,0)":"rgb(255,255,255)",Lt={alphanumeric:/^[\w\.-]+$/,uuid:/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,semver:/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i,email:/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i,url:/^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,64}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i,color:/^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i};function Tt(e,t){let n=e.match(t);return!!(n&&n.length)}var St=async(e,t,{extension:n=""}={})=>{if(n=!e||!e.endsWith||e.endsWith(n),Array.isArray(t))return t.includes(e);switch(t){case"array":return Array.isArray(e);case"object":return e&&typeof e=="object"&&!Array.isArray(e);case"undefined":case"boolean":case"number":return typeof e===t&&n;case"string":return typeof e===t&&n;case"alphanumeric":case"uuid":case"semver":case"email":case"url":case"color":return typeof e=="string"&&Tt(e,Lt[t])&&n;case"file":return typeof e=="string"&&e&&await p.isFile(e)&&n}return!1};var y={};g(y,{core:()=>Ae,db:()=>Ut,enabled:()=>zt,errors:()=>Jt,get:()=>D,list:()=>Y,optionDefault:()=>Le,optionTypes:()=>Mt,profileDB:()=>X,profileName:()=>Ee,supportedEnvs:()=>qt});"use strict";var l=async(e,t,n,r,{extension:s="",error:c=`invalid ${t} (${s?`${s} `:""}${r}): ${JSON.stringify(n)}`,optional:a=!1}={})=>{let i;for(let d of Array.isArray(r)?[r]:r.split("|"))if(d==="file"?i=n&&!n.startsWith("http")?await h.is(`repo/${e._dir}/${n}`,d,{extension:s}):!1:i=await h.is(n,d,{extension:s}),i)break;return i||a&&await h.is(n,"undefined")?!0:(c&&e._err(c),!1)},kt=async e=>(e.environments=e.environments??y.supportedEnvs,await l(e,"environments",e.environments,"array")?e.environments.map(n=>l(e,"environments.env",n,y.supportedEnvs)):!1),Ct=async e=>{if(!await l(e,"tags",e.tags,"array"))return!1;let n=["core","extension","theme","integration"];if(!e.tags.filter(i=>n.includes(i)).length)return e._err(`invalid tags (must contain at least one of 'core', 'extension', 'theme' or 'integration'): +var ce=Object.defineProperty;var Ge=e=>ce(e,"__esModule",{value:!0});var g=(e,t)=>{Ge(e);for(var n in t)ce(e,n,{get:t[n],enumerable:!0})};g(exports,{components:()=>N,electron:()=>V,env:()=>E,fmt:()=>h,fs:()=>p,notion:()=>K,registry:()=>y,storage:()=>_,web:()=>o});var E={};g(E,{focusMenu:()=>De,focusNotion:()=>et,name:()=>Qe,notionRequire:()=>nt,reload:()=>tt,version:()=>Ye});"use strict";var le=globalThis.__enhancerElectronApi.platform,de=globalThis.__enhancerElectronApi.version,pe=globalThis.__enhancerElectronApi.focusMenu,ue=globalThis.__enhancerElectronApi.focusNotion,he=globalThis.__enhancerElectronApi.reload,fe=globalThis.__enhancerElectronApi.notionRequire;"use strict";var Qe=le,Ye=de,De=pe,et=ue,tt=he,nt=fe;var p={};g(p,{getJSON:()=>st,getText:()=>ot,isFile:()=>it,localPath:()=>rt,notionPath:()=>at});"use strict";var L=e=>`notion://www.notion.so/__notion-enhancer/${e}`,me=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.json());try{return globalThis.__enhancerElectronApi.nodeRequire(`notion-enhancer/${e}`)}catch{return fetch(L(e),t).then(r=>r.json())}},ye=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.text());try{let n=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:r}=globalThis.__enhancerElectronApi.nodeRequire("path");return n.readFileSync(r(`${__dirname}/../../${e}`))}catch{return fetch(L(e),t).then(r=>r.text())}},ge=async e=>{try{let t=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:n}=globalThis.__enhancerElectronApi.nodeRequire("path");if(e.startsWith("http"))await fetch(e);else try{t.existsSync(n(`${__dirname}/../../${e}`))}catch{await fetch(L(e))}return!0}catch{return!1}},ve=globalThis.__enhancerElectronApi.notionPath;"use strict";var rt=L,st=me,ot=ye,it=ge,at=ve;var _={};g(_,{addChangeListener:()=>pt,db:()=>dt,get:()=>ct,removeChangeListener:()=>ut,set:()=>lt});"use strict";var W=(e,t=void 0)=>globalThis.__enhancerElectronApi.db.get(e,t),H=(e,t)=>globalThis.__enhancerElectronApi.db.set(e,t),we=(e,t=W,n=H)=>(typeof e=="string"&&(e=[e]),{get:(r=[],s=void 0)=>t([...e,...r],s),set:(r,s)=>n([...e,...r],s)}),be=e=>globalThis.__enhancerElectronApi.db.addChangeListener(e),xe=e=>globalThis.__enhancerElectronApi.db.removeChangeListener(e);"use strict";var ct=W,lt=H,dt=we,pt=be,ut=xe;var V={};g(V,{browser:()=>ht,onMessage:()=>gt,sendMessage:()=>mt,sendMessageToHost:()=>yt,webFrame:()=>ft});"use strict";var ht=globalThis.__enhancerElectronApi?.browser,ft=globalThis.__enhancerElectronApi?.webFrame,mt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessage(e,t,n)},yt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessageToHost(e,t,n)},gt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.onMessage(e,t,n)};var K={};g(K,{create:()=>bt,get:()=>$e,getPageID:()=>M,getSpaceID:()=>S,getUserID:()=>_e,search:()=>vt,set:()=>wt,sign:()=>_t,upload:()=>xt});"use strict";var m=e=>(e?.length===32&&!e.includes("-")&&(e=e.replace(/([\d\w]{8})([\d\w]{4})([\d\w]{4})([\d\w]{4})([\d\w]{12})/,"$1-$2-$3-$4-$5")),e),_e=()=>JSON.parse(localStorage["LRU:KeyValueStore2:current-user-id"]||{}).value,M=()=>m(o.queryParams().get("p")||location.pathname.split(/(-|\/)/g).reverse()[0]),I,S=async()=>(I||(I=(await $e(M())).space_id),I),$e=async(e,t="block")=>(e=m(e),(await p.getJSON("https://www.notion.so/api/v3/getRecordValues",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requests:[{table:t,id:e}]}),method:"POST"})).results[0].value),vt=async(e="",t=20,n=S())=>(n=m(await n),await p.getJSON("https://www.notion.so/api/v3/search",{headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"BlocksInSpace",query:e,spaceId:n,limit:t,filters:{isDeletedOnly:!1,excludeTemplates:!1,isNavigableOnly:!1,requireEditPermissions:!1,ancestors:[],createdBy:[],editedBy:[],lastEditedTime:{},createdTime:{}},sort:"Relevance",source:"quick_find"}),method:"POST"})),wt=async({recordID:e,recordTable:t="block",spaceID:n=S(),path:r=[]},s={})=>{n=m(await n),e=m(e);let c=await p.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:n,operations:[{pointer:{table:t,id:e,spaceId:n},path:r,command:r.length?"set":"update",args:s}]}]}),method:"POST"});return c.errorId?c:!0},bt=async({recordValue:e={},recordTable:t="block"}={},{prepend:n=!1,siblingID:r=void 0,parentID:s=M(),parentTable:c="block",spaceID:a=S(),userID:i=_e()}={})=>{a=m(await a),s=m(s),r=m(r);let d=m(e?.id??h.uuidv4()),x=[],j={type:"text",id:d,version:0,created_time:new Date().getTime(),last_edited_time:new Date().getTime(),parent_id:s,parent_table:c,alive:!0,created_by_table:"notion_user",created_by_id:i,last_edited_by_table:"notion_user",last_edited_by_id:i,space_id:a,permissions:[{type:"user_permission",role:"editor",user_id:i}]};c==="space"?(s=a,j.parent_id=a,x.push("pages"),j.type="page"):c==="collection_view"?(x.push("page_sort"),j.type="page"):x.push("content");let ae=await p.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:a,operations:[{pointer:{table:c,id:s,spaceId:a},path:x,command:n?"listBefore":"listAfter",args:{...r?{after:r}:{},id:d}},{pointer:{table:t,id:d,spaceId:a},path:[],command:"set",args:{...j,...e}}]}]}),method:"POST"});return ae.errorId?ae:d},xt=async(e,{pageID:t=M(),spaceID:n=S()}={})=>{n=m(await n),t=m(t);let r=await p.getJSON("https://www.notion.so/api/v3/getUploadFileUrl",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({bucket:"secure",name:e.name,contentType:e.type,record:{table:"block",id:t,spaceId:n}})});return r.errorId?r:(fetch(r.signedPutUrl,{method:"PUT",headers:{"content-type":e.type},body:e}),r.url)},_t=(e,t,n="block")=>(e.startsWith("/")&&(e=`https://notion.so${e}`),e.includes("secure.notion-static.com")&&(e=new URL(e),e=`https://www.notion.so/signed/${encodeURIComponent(e.origin+e.pathname)}?table=${n}&id=${t}`),e);var h={};g(h,{is:()=>Tt,rgbContrast:()=>Et,rgbLogShade:()=>At,slugger:()=>$t,uuidv4:()=>jt});"use strict";var $t=(e,t=new Set)=>{e=e.replace(/\s/g,"-").replace(/[^A-Za-z0-9-_]/g,"").toLowerCase();let n=0,r=e;for(;t.has(r);)n++,r=`${e}-${n}`;return r},jt=()=>crypto?.randomUUID?crypto.randomUUID():([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,e=>(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)),At=(e,t)=>{var n=parseInt,r=Math.round,[s,c,t,a]=t.split(","),i=e<0,d=i?0:e*255**2,i=i?1+e:1-e;return"rgb"+(a?"a(":"(")+r((i*n(s[3]=="a"?s.slice(5):s.slice(4))**2+d)**.5)+","+r((i*n(c)**2+d)**.5)+","+r((i*n(t)**2+d)**.5)+(a?","+a:")")},Et=(e,t,n)=>Math.sqrt(.299*(e*e)+.587*(t*t)+.114*(n*n))>165.75?"rgb(0,0,0)":"rgb(255,255,255)",Lt={alphanumeric:/^[\w\.-]+$/,uuid:/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,semver:/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i,email:/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i,url:/^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,64}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i,color:/^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i};function St(e,t){let n=e.match(t);return!!(n&&n.length)}var Tt=async(e,t,{extension:n=""}={})=>{if(n=!e||!e.endsWith||e.endsWith(n),Array.isArray(t))return t.includes(e);switch(t){case"array":return Array.isArray(e);case"object":return e&&typeof e=="object"&&!Array.isArray(e);case"undefined":case"boolean":case"number":return typeof e===t&&n;case"string":return typeof e===t&&n;case"alphanumeric":case"uuid":case"semver":case"email":case"url":case"color":return typeof e=="string"&&St(e,Lt[t])&&n;case"file":return typeof e=="string"&&e&&await p.isFile(e)&&n}return!1};var y={};g(y,{core:()=>Ae,db:()=>Ut,enabled:()=>zt,errors:()=>Jt,get:()=>D,list:()=>Y,optionDefault:()=>Le,optionTypes:()=>Mt,profileDB:()=>X,profileName:()=>Ee,supportedEnvs:()=>qt});"use strict";var l=async(e,t,n,r,{extension:s="",error:c=`invalid ${t} (${s?`${s} `:""}${r}): ${JSON.stringify(n)}`,optional:a=!1}={})=>{let i;for(let d of Array.isArray(r)?[r]:r.split("|"))if(d==="file"?i=n&&!n.startsWith("http")?await h.is(`repo/${e._dir}/${n}`,d,{extension:s}):!1:i=await h.is(n,d,{extension:s}),i)break;return i||a&&await h.is(n,"undefined")?!0:(c&&e._err(c),!1)},kt=async e=>(e.environments=e.environments??y.supportedEnvs,await l(e,"environments",e.environments,"array")?e.environments.map(n=>l(e,"environments.env",n,y.supportedEnvs)):!1),Ct=async e=>{if(!await l(e,"tags",e.tags,"array"))return!1;let n=["core","extension","theme","integration"];if(!e.tags.filter(i=>n.includes(i)).length)return e._err(`invalid tags (must contain at least one of 'core', 'extension', 'theme' or 'integration'): ${JSON.stringify(e.tags)}`),!1;let s=e.tags.includes("theme"),c=e.tags.includes("light")||e.tags.includes("dark"),a=e.tags.includes("light")&&e.tags.includes("dark");return s&&(!c||a)?(e._err(`invalid tags (themes must be either 'light' or 'dark', not neither or both): - ${JSON.stringify(e.tags)}`),!1):e.tags.map(i=>l(e,"tags.tag",i,"string"))},Pt=async e=>await l(e,"authors",e.authors,"array")?e.authors.map(n=>[l(e,"authors.author.name",n.name,"string"),l(e,"authors.author.email",n.email,"email",{optional:!0}),l(e,"authors.author.homepage",n.homepage,"url"),l(e,"authors.author.avatar",n.avatar,"url")]):!1,Ot=async e=>{if(!await l(e,"css",e.css,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.css[r])continue;let s=await l(e,`css.${r}`,e.css[r],"array");s&&(s=e.css[r].map(c=>l(e,`css.${r}.file`,c,"file",{extension:".css"}))),n.push(s)}return n},Rt=async e=>{if(!await l(e,"js",e.js,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.js[r])continue;let s=await l(e,`js.${r}`,e.js[r],"array");s&&(s=e.js[r].map(c=>l(e,`js.${r}.file`,c,"file",{extension:".mjs"}))),n.push(s)}if(e.js.electron)if(await l(e,"js.electron",e.js.electron,"array"))for(let s of e.js.electron){if(!await l(e,"js.electron.file",s,"object")){n.push(!1);continue}n.push([l(e,"js.electron.file.source",s.source,"file",{extension:".cjs"}),l(e,"js.electron.file.target",s.target,"string",{extension:".js"})])}else n.push(!1);return n},Nt=async e=>{if(!await l(e,"options",e.options,"array"))return!1;let n=[];for(let r of e.options){let s="options.option";if(!await l(e,`${s}.type`,r.type,y.optionTypes)){n.push(!1);continue}switch(r.environments=r.environments??y.supportedEnvs,n.push([l(e,`${s}.key`,r.key,"alphanumeric"),l(e,`${s}.label`,r.label,"string"),l(e,`${s}.tooltip`,r.tooltip,"string",{optional:!0}),l(e,`${s}.environments`,r.environments,"array").then(a=>a?r.environments.map(i=>l(e,`${s}.environments.env`,i,y.supportedEnvs)):!1)]),r.type){case"toggle":n.push(l(e,`${s}.value`,r.value,"boolean"));break;case"select":{let a=await l(e,`${s}.values`,r.values,"array");a&&(a=r.values.map(i=>l(e,`${s}.values.value`,i,"string"))),n.push(a);break}case"text":case"hotkey":n.push(l(e,`${s}.value`,r.value,"string"));break;case"number":case"color":n.push(l(e,`${s}.value`,r.value,r.type));break;case"file":{let a=await l(e,`${s}.extensions`,r.extensions,"array");a&&(a=r.extensions.map(i=>l(e,`${s}.extensions.extension`,i,"string"))),n.push(a);break}}}return n};async function je(e){let t=[l(e,"name",e.name,"string"),l(e,"id",e.id,"uuid"),l(e,"version",e.version,"semver"),kt(e),l(e,"description",e.description,"string"),l(e,"preview",e.preview,"file|url",{optional:!0}),Ct(e),Pt(e),Ot(e),Rt(e),Nt(e)];do t=await Promise.all(t.flat(1/0));while(t.some(n=>Array.isArray(n)));return t.every(n=>n)}"use strict";var Ae=["a6621988-551d-495a-97d8-3c568bca2e9e","0f0bf8b6-eae6-4273-b307-8fc43f2ee082","36a2ffc9-27ff-480e-84a7-c7700a7d232d"],qt=["linux","win32","darwin","extension"],Mt=["toggle","select","text","number","color","file","hotkey"],Ee=async()=>_.get(["currentprofile"],"default"),X=async()=>_.db(["profiles",await Ee()]),G,Q=[],Y=async(e=t=>!0)=>{G||(G=new Promise(async(n,r)=>{let s=[];for(let c of await p.getJSON("repo/registry.json"))try{let a={...await p.getJSON(`repo/${c}/mod.json`),_dir:c,_err:i=>Q.push({source:c,message:i})};await je(a)&&s.push(a)}catch{Q.push({source:c,message:"invalid mod.json"})}n(s)}));let t=[];for(let n of await G)await e(n)&&t.push(n);return t},Jt=async()=>(await Y(),Q),D=async e=>(await Y(t=>t.id===e))[0],zt=async e=>(await D(e)).environments.includes(E.name)?Ae.includes(e)?!0:(await X()).get(["_mods",e],!1):!1,Le=async(e,t)=>{let n=await D(e),r=n.options.find(s=>s.key===t);if(!!r)switch(r.type){case"toggle":case"text":case"number":case"color":case"hotkey":return r.value;case"select":return r.values[0];case"file":return}},Ut=async e=>{let t=await X();return _.db([e],async(n,r=void 0)=>(typeof n=="string"&&(n=[n]),n.length===2&&(r=await Le(e,n[1])??r),t.get(n,r)),t.set)};var o={};g(o,{addDocumentObserver:()=>Xt,addHotkeyListener:()=>Vt,copyToClipboard:()=>Ht,empty:()=>Wt,escape:()=>Se,html:()=>Ce,loadStylesheet:()=>Ft,queryParams:()=>Bt,raw:()=>ke,readFromClipboard:()=>It,removeDocumentObserver:()=>Gt,removeHotkeyListener:()=>Kt,render:()=>Pe,whenReady:()=>Zt});"use strict";var Te=!1,S=[],ee,J=[],te=[],Zt=(e=[])=>new Promise((t,n)=>{function r(){let s;s=setInterval(c,100);function c(){e.every(a=>document.querySelector(a))&&(clearInterval(s),t(!0))}c()}document.readyState!=="complete"?document.addEventListener("readystatechange",s=>{document.readyState==="complete"&&r()}):r()}),Bt=()=>new URLSearchParams(window.location.search),Se=e=>e.replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\\/g,"\"),ke=(e,...t)=>{let n=e.map(r=>r+(["string","number"].includes(typeof t[0])?t.shift():Se(JSON.stringify(t.shift(),null,2)??""))).join("");return n.includes("r.trim()).filter(r=>r.length).join(" ")},Ce=(e,...t)=>{let n=document.createRange().createContextualFragment(ke(e,...t));return n.children.length===1?n.children[0]:n.children},Pe=(e,...t)=>(t=t.map(n=>n instanceof HTMLCollection?[...n]:n).flat(1/0).filter(n=>n),e.append(...t),e),Wt=e=>{for(;e.firstChild&&e.removeChild(e.firstChild););return e},Ft=e=>{let t=Ce`l(e,"tags.tag",i,"string"))},Pt=async e=>await l(e,"authors",e.authors,"array")?e.authors.map(n=>[l(e,"authors.author.name",n.name,"string"),l(e,"authors.author.email",n.email,"email",{optional:!0}),l(e,"authors.author.homepage",n.homepage,"url"),l(e,"authors.author.avatar",n.avatar,"url")]):!1,Ot=async e=>{if(!await l(e,"css",e.css,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.css[r])continue;let s=await l(e,`css.${r}`,e.css[r],"array");s&&(s=e.css[r].map(c=>l(e,`css.${r}.file`,c,"file",{extension:".css"}))),n.push(s)}return n},Rt=async e=>{if(!await l(e,"js",e.js,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.js[r])continue;let s=await l(e,`js.${r}`,e.js[r],"array");s&&(s=e.js[r].map(c=>l(e,`js.${r}.file`,c,"file",{extension:".mjs"}))),n.push(s)}if(e.js.electron)if(await l(e,"js.electron",e.js.electron,"array"))for(let s of e.js.electron){if(!await l(e,"js.electron.file",s,"object")){n.push(!1);continue}n.push([l(e,"js.electron.file.source",s.source,"file",{extension:".cjs"}),l(e,"js.electron.file.target",s.target,"string",{extension:".js"})])}else n.push(!1);return n},Nt=async e=>{if(!await l(e,"options",e.options,"array"))return!1;let n=[];for(let r of e.options){let s="options.option";if(!await l(e,`${s}.type`,r.type,y.optionTypes)){n.push(!1);continue}switch(r.environments=r.environments??y.supportedEnvs,n.push([l(e,`${s}.key`,r.key,"alphanumeric"),l(e,`${s}.label`,r.label,"string"),l(e,`${s}.tooltip`,r.tooltip,"string",{optional:!0}),l(e,`${s}.environments`,r.environments,"array").then(a=>a?r.environments.map(i=>l(e,`${s}.environments.env`,i,y.supportedEnvs)):!1)]),r.type){case"toggle":n.push(l(e,`${s}.value`,r.value,"boolean"));break;case"select":{let a=await l(e,`${s}.values`,r.values,"array");a&&(a=r.values.map(i=>l(e,`${s}.values.value`,i,"string"))),n.push(a);break}case"text":case"hotkey":n.push(l(e,`${s}.value`,r.value,"string"));break;case"number":case"color":n.push(l(e,`${s}.value`,r.value,r.type));break;case"file":{let a=await l(e,`${s}.extensions`,r.extensions,"array");a&&(a=r.extensions.map(i=>l(e,`${s}.extensions.extension`,i,"string"))),n.push(a);break}}}return n};async function je(e){let t=[l(e,"name",e.name,"string"),l(e,"id",e.id,"uuid"),l(e,"version",e.version,"semver"),kt(e),l(e,"description",e.description,"string"),l(e,"preview",e.preview,"file|url",{optional:!0}),Ct(e),Pt(e),Ot(e),Rt(e),Nt(e)];do t=await Promise.all(t.flat(1/0));while(t.some(n=>Array.isArray(n)));return t.every(n=>n)}"use strict";var Ae=["a6621988-551d-495a-97d8-3c568bca2e9e","0f0bf8b6-eae6-4273-b307-8fc43f2ee082","36a2ffc9-27ff-480e-84a7-c7700a7d232d"],qt=["linux","win32","darwin","extension"],Mt=["toggle","select","text","number","color","file","hotkey"],Ee=async()=>_.get(["currentprofile"],"default"),X=async()=>_.db(["profiles",await Ee()]),G,Q=[],Y=async(e=t=>!0)=>{G||(G=new Promise(async(n,r)=>{let s=[];for(let c of await p.getJSON("repo/registry.json"))try{let a={...await p.getJSON(`repo/${c}/mod.json`),_dir:c,_err:i=>Q.push({source:c,message:i})};await je(a)&&s.push(a)}catch{Q.push({source:c,message:"invalid mod.json"})}n(s)}));let t=[];for(let n of await G)await e(n)&&t.push(n);return t},Jt=async()=>(await Y(),Q),D=async e=>(await Y(t=>t.id===e))[0],zt=async e=>(await D(e)).environments.includes(E.name)?Ae.includes(e)?!0:(await X()).get(["_mods",e],!1):!1,Le=async(e,t)=>{let n=await D(e),r=n.options.find(s=>s.key===t);if(!!r)switch(r.type){case"toggle":case"text":case"number":case"color":case"hotkey":return r.value;case"select":return r.values[0];case"file":return}},Ut=async e=>{let t=await X();return _.db([e],async(n,r=void 0)=>(typeof n=="string"&&(n=[n]),n.length===2&&(r=await Le(e,n[1])??r),t.get(n,r)),t.set)};var o={};g(o,{addDocumentObserver:()=>Xt,addHotkeyListener:()=>It,copyToClipboard:()=>Ht,empty:()=>Ft,escape:()=>Te,html:()=>Ce,loadStylesheet:()=>Wt,queryParams:()=>Bt,raw:()=>ke,readFromClipboard:()=>Vt,removeDocumentObserver:()=>Gt,removeHotkeyListener:()=>Kt,render:()=>Pe,whenReady:()=>Zt});"use strict";var Se=!1,T=[],ee,J=[],te=[],Zt=(e=[])=>new Promise((t,n)=>{let r=()=>{let s=setInterval(c,100);function c(){!(document.hasFocus()&&e.every(i=>document.querySelector(i)))||(clearInterval(s),t(!0))}c()};document.readyState!=="complete"?document.addEventListener("readystatechange",s=>{document.readyState==="complete"&&r()}):r()}),Bt=()=>new URLSearchParams(window.location.search),Te=e=>e.replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\\/g,"\"),ke=(e,...t)=>{let n=e.map(r=>r+(["string","number"].includes(typeof t[0])?t.shift():Te(JSON.stringify(t.shift(),null,2)??""))).join("");return n.includes("r.trim()).filter(r=>r.length).join(" ")},Ce=(e,...t)=>{let n=document.createRange().createContextualFragment(ke(e,...t));return n.children.length===1?n.children[0]:n.children},Pe=(e,...t)=>(t=t.map(n=>n instanceof HTMLCollection?[...n]:n).flat(1/0).filter(n=>n),e.append(...t),e),Ft=e=>{for(;e.firstChild&&e.removeChild(e.firstChild););return e},Wt=e=>{let t=Ce``;return Pe(document.head,t),t},Ht=async e=>{try{await navigator.clipboard.writeText(e)}catch{let t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}},It=()=>navigator.clipboard.readText(),Oe=(e,t)=>{if(document.activeElement.nodeName==="INPUT"&&!t.listenInInput)return;t.keys.every(s=>{s=s.toLowerCase();let c={metaKey:["meta","os","win","cmd","command"],ctrlKey:["ctrl","control"],shiftKey:["shift"],altKey:["alt"]};for(let a in c)if(c[a].includes(s)&&e[a])return!0;if(s==="space"&&(s=" "),s==="plus"&&(s="+"),s===e.key.toLowerCase())return!0})&&t.callback(e)},Vt=(e,t,{listenInInput:n=!1,keydown:r=!1}={})=>{typeof e=="string"&&(e=e.split("+")),S.push({keys:e,callback:t,listenInInput:n,keydown:r}),Te||(Te=!0,document.addEventListener("keyup",s=>{for(let c of S.filter(({keydown:a})=>!a))Oe(s,c)}),document.addEventListener("keydown",s=>{for(let c of S.filter(({keydown:a})=>a))Oe(s,c)}))},Kt=e=>{S=S.filter(t=>t.callback!==e)},Xt=(e,t=[])=>{if(!ee){let n=r=>{for(;r.length;){let s=r.shift(),c=(i,d)=>i instanceof Element&&(i.matches(d)||i.matches(`${d} *`)||i.querySelector(d)),a=i=>s.target.matches(i)||s.target.matches(`${i} *`)||[...s.addedNodes].some(d=>c(d,i));for(let i of J)(!i.selectors.length||i.selectors.some(a))&&i.callback(s)}};ee=new MutationObserver((r,s)=>{te.length||requestIdleCallback(()=>n(te)),te.push(...r)}),ee.observe(document.body,{childList:!0,subtree:!0,attributes:!0})}J.push({callback:e,selectors:t})},Gt=e=>{J=J.filter(t=>t.callback!==e)};var N={};g(N,{addCornerAction:()=>Xe,addPanelView:()=>Ve,addTooltip:()=>Ne,feather:()=>qe});"use strict";var Re,u,Qt=e=>[...e.getClientRects()].reduce((t,n)=>t.some(r=>r.y===n.y)?t:[...t,n],[]).length,Yt=async(e,t,n)=>{u.style.top="0px",u.style.left="0px";let r=e.getBoundingClientRect(),{offsetWidth:s,offsetHeight:c}=u,a=6,i=r.x,d=Math.floor(r.y);if(["top","bottom"].includes(t)){t==="top"&&(d-=c+a),t==="bottom"&&(d+=r.height+a),i-=s/2-r.width/2,u.style.left=`${i}px`,u.style.top=`${d}px`;let x=()=>Qt(u.firstElementChild)>n,j=x();for(;x();)u.style.left=`${window.innerWidth-i>i?i++:i--}px`;j&&(i+=window.innerWidth-i>i?a:-a,u.style.left=`${i}px`)}return["left","right"].includes(t)&&(d-=c/2-r.height/2,t==="left"&&(i-=s+a),t==="right"&&(i+=r.width+a),u.style.left=`${i}px`,u.style.top=`${d}px`),!0},Ne=async(e,t,{delay:n=100,offsetDirection:r="bottom",maxLines:s=1}={})=>{Re||(Re=o.loadStylesheet("api/components/tooltip.css"),u=o.html`
`,o.render(document.body,u)),globalThis.markdownit||await import(p.localPath("dep/markdown-it.min.js"));let c=markdownit({linkify:!0});t instanceof Element||(t=o.html`
+ />`;return Pe(document.head,t),t},Ht=async e=>{try{await navigator.clipboard.writeText(e)}catch{let t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}},Vt=()=>navigator.clipboard.readText(),Oe=(e,t)=>{if(document.activeElement.nodeName==="INPUT"&&!t.listenInInput||!document.hasFocus())return;t.keys.every(s=>{s=s.toLowerCase();let c={metaKey:["meta","os","win","cmd","command"],ctrlKey:["ctrl","control"],shiftKey:["shift"],altKey:["alt"]};for(let a in c)if(c[a].includes(s)&&e[a])return!0;if(s==="space"&&(s=" "),s==="plus"&&(s="+"),s===e.key.toLowerCase())return!0})&&t.callback(e)},It=(e,t,{listenInInput:n=!1,keydown:r=!1}={})=>{typeof e=="string"&&(e=e.split("+")),T.push({keys:e,callback:t,listenInInput:n,keydown:r}),Se||(Se=!0,document.addEventListener("keyup",s=>{for(let c of T.filter(({keydown:a})=>!a))Oe(s,c)}),document.addEventListener("keydown",s=>{for(let c of T.filter(({keydown:a})=>a))Oe(s,c)}))},Kt=e=>{T=T.filter(t=>t.callback!==e)},Xt=(e,t=[])=>{if(!ee){let n=r=>{for(;r.length;){let s=r.shift(),c=(i,d)=>i instanceof Element&&(i.matches(d)||i.matches(`${d} *`)||i.querySelector(d)),a=i=>s.target.matches(i)||s.target.matches(`${i} *`)||[...s.addedNodes].some(d=>c(d,i));for(let i of J)(!i.selectors.length||i.selectors.some(a))&&i.callback(s)}};ee=new MutationObserver((r,s)=>{!te.length&&document.hasFocus()&&requestIdleCallback(()=>n(te)),te.push(...r)}),ee.observe(document.body,{childList:!0,subtree:!0,attributes:!0})}J.push({callback:e,selectors:t})},Gt=e=>{J=J.filter(t=>t.callback!==e)};var N={};g(N,{addCornerAction:()=>Xe,addPanelView:()=>Ie,addTooltip:()=>Ne,feather:()=>qe});"use strict";var Re,u,Qt=e=>[...e.getClientRects()].reduce((t,n)=>t.some(r=>r.y===n.y)?t:[...t,n],[]).length,Yt=async(e,t,n)=>{u.style.top="0px",u.style.left="0px";let r=e.getBoundingClientRect(),{offsetWidth:s,offsetHeight:c}=u,a=6,i=r.x,d=Math.floor(r.y);if(["top","bottom"].includes(t)){t==="top"&&(d-=c+a),t==="bottom"&&(d+=r.height+a),i-=s/2-r.width/2,u.style.left=`${i}px`,u.style.top=`${d}px`;let x=()=>Qt(u.firstElementChild)>n,j=x();for(;x();)u.style.left=`${window.innerWidth-i>i?i++:i--}px`;j&&(i+=window.innerWidth-i>i?a:-a,u.style.left=`${i}px`)}return["left","right"].includes(t)&&(d-=c/2-r.height/2,t==="left"&&(i-=s+a),t==="right"&&(i+=r.width+a),u.style.left=`${i}px`,u.style.top=`${d}px`),!0},Ne=async(e,t,{delay:n=100,offsetDirection:r="bottom",maxLines:s=1}={})=>{Re||(Re=o.loadStylesheet("api/components/tooltip.css"),u=o.html`
`,o.render(document.body,u)),globalThis.markdownit||await import(p.localPath("dep/markdown-it.min.js"));let c=markdownit({linkify:!0});t instanceof Element||(t=o.html`
${t.split(` `).map(i=>c.renderInline(i)).join("
")}
`);let a;e.addEventListener("mouseover",async i=>{a||(a=setTimeout(async()=>{e.matches(":hover")&&u.style.display!=="block"&&(u.style.display="block",o.render(o.empty(u),t),Yt(e,r,s),await u.animate([{opacity:0},{opacity:1}],{duration:65}).finished),a=void 0},n))}),e.addEventListener("mouseout",async i=>{a=void 0,u.style.display==="block"&&!e.matches(":hover")&&(await u.animate([{opacity:1},{opacity:0}],{duration:65}).finished,u.style.display="")})};"use strict";var ne,qe=async(e,t={})=>(ne||(ne=o.html`${await p.getText("dep/feather-sprite.svg")}`),t.style=((t.style?t.style+";":"")+"stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;fill:none;").trim(),t.viewBox="0 0 24 24",``${o.escape(n)}="${o.escape(r)}"`).join(" ")}>${ne.getElementById(e)?.innerHTML}`);"use strict";var k=[],Dt=o.raw` @@ -13,9 +13,9 @@ var ce=Object.defineProperty;var Ge=e=>ce(e,"__esModule",{value:!0});var g=(e,t) 2.43056L 3.98809 -0.569442L 3.01191 0.569442ZM -0.488094 6.56944L 3.01191 9.56944L 3.98809 8.43056L 0.488094 5.43056L -0.488094 6.56944ZM 3.98809 9.56944L 7.48809 6.56944L 6.51191 5.43056L 3.01191 8.43056L 3.98809 9.56944Z"> - `,Me,w,z,v,$,C,A,Je,ze,re,f,Ue,U,P,Z,se,b,oe,O,B="data-enhancer-panel-pinned",W=()=>$.hasAttribute(B),R=()=>{let e=[z,v,C,$].filter(t=>t);if(W()){ie();for(let t of e)t.removeAttribute(B)}else for(let t of e)t.setAttribute(B,"true");w.set(["panel.pinned"],W())},Ze=async()=>{document.documentElement.style.setProperty("--component--panel-width",f+"px"),w.set(["panel.width"],f)},Be=e=>{e.preventDefault(),re=!0,f=ze+(Je-e.clientX),f<190&&(f=190),f>480&&(f=480),$.style.width=f+"px",C.style.width=f+"px",z.style.paddingRight=f+"px",v&&(v.style.right=f+"px")},We=e=>{$.style.width="",C.style.width="",z.style.paddingRight="",v&&(v.style.right=""),Ze(),A.style.cursor="",document.body.removeEventListener("mousemove",Be),document.body.removeEventListener("mouseup",We)},en=e=>{Je=e.clientX,ze=f,A.style.cursor="auto",document.body.addEventListener("mousemove",Be),document.body.addEventListener("mouseup",We)},tn=()=>document.body.contains(b),Fe=()=>{if(!W())return R();o.render(Ue,O),o.empty(b);for(let t of k){let n=P.contains(t.$title),r=o.render(o.html`
`,o.render(o.html``,t.$icon.cloneNode(!0),t.$title.cloneNode(!0)));r.addEventListener("click",()=>{Ie(t),w.set(["panel.open"],t.id)}),o.render(b,r)}let e=Z.getBoundingClientRect();o.render(o.empty(O),o.render(o.html`
`,o.render(o.html`
`,b))),b.querySelector("[data-open]").focus(),b.animate([{opacity:0},{opacity:1}],{duration:200}),document.addEventListener("keydown",He)},ie=()=>{document.removeEventListener("keydown",He),b.animate([{opacity:1},{opacity:0}],{duration:200}).onfinish=()=>O.remove()},He=e=>{if(tn())switch(e.key){case"Escape":ie(),e.stopPropagation();break;case"Enter":document.activeElement.click(),e.stopPropagation();break;case"ArrowUp":(e.target.previousElementSibling||e.target.parentElement.lastElementChild).focus(),e.stopPropagation();break;case"ArrowDown":(e.target.nextElementSibling||e.target.parentElement.firstElementChild).focus(),e.stopPropagation();break}},Ie=e=>{let t=k.find(({$content:n})=>document.contains(n));o.render(o.empty(P),o.render(o.html``,e.$icon,e.$title)),e.onFocus(),o.render(o.empty(se),e.$content),t&&t.onBlur()};async function nn(){await o.whenReady([".notion-frame"]),z=document.querySelector(".notion-frame"),$=o.html`
`,C=o.html`
`,A=o.html`
`,P=o.html`
`,Z=o.render(o.html`
`,P),se=o.html`
`,b=o.html`
`,oe=o.html`
+ `,Me,w,z,v,$,C,A,Je,ze,re,f,Ue,U,P,Z,se,b,oe,O,B="data-enhancer-panel-pinned",F=()=>$.hasAttribute(B),R=()=>{let e=[z,v,C,$].filter(t=>t);if(F()){ie();for(let t of e)t.removeAttribute(B)}else for(let t of e)t.setAttribute(B,"true");w.set(["panel.pinned"],F())},Ze=async()=>{document.documentElement.style.setProperty("--component--panel-width",f+"px"),w.set(["panel.width"],f)},Be=e=>{e.preventDefault(),re=!0,f=ze+(Je-e.clientX),f<190&&(f=190),f>480&&(f=480),$.style.width=f+"px",C.style.width=f+"px",z.style.paddingRight=f+"px",v&&(v.style.right=f+"px")},Fe=e=>{$.style.width="",C.style.width="",z.style.paddingRight="",v&&(v.style.right=""),Ze(),A.style.cursor="",document.body.removeEventListener("mousemove",Be),document.body.removeEventListener("mouseup",Fe)},en=e=>{Je=e.clientX,ze=f,A.style.cursor="auto",document.body.addEventListener("mousemove",Be),document.body.addEventListener("mouseup",Fe)},tn=()=>document.body.contains(b),We=()=>{if(!F())return R();o.render(Ue,O),o.empty(b);for(let t of k){let n=P.contains(t.$title),r=o.render(o.html`
`,o.render(o.html``,t.$icon.cloneNode(!0),t.$title.cloneNode(!0)));r.addEventListener("click",()=>{Ve(t),w.set(["panel.open"],t.id)}),o.render(b,r)}let e=Z.getBoundingClientRect();o.render(o.empty(O),o.render(o.html`
`,o.render(o.html`
`,b))),b.querySelector("[data-open]").focus(),b.animate([{opacity:0},{opacity:1}],{duration:200}),document.addEventListener("keydown",He)},ie=()=>{document.removeEventListener("keydown",He),b.animate([{opacity:1},{opacity:0}],{duration:200}).onfinish=()=>O.remove()},He=e=>{if(tn())switch(e.key){case"Escape":ie(),e.stopPropagation();break;case"Enter":document.activeElement.click(),e.stopPropagation();break;case"ArrowUp":(e.target.previousElementSibling||e.target.parentElement.lastElementChild).focus(),e.stopPropagation();break;case"ArrowDown":(e.target.nextElementSibling||e.target.parentElement.firstElementChild).focus(),e.stopPropagation();break}},Ve=e=>{let t=k.find(({$content:n})=>document.contains(n));o.render(o.empty(P),o.render(o.html``,e.$icon,e.$title)),e.onFocus(),o.render(o.empty(se),e.$content),t&&t.onBlur()};async function nn(){await o.whenReady([".notion-frame"]),z=document.querySelector(".notion-frame"),$=o.html`
`,C=o.html`
`,A=o.html`
`,P=o.html`
`,Z=o.render(o.html`
`,P),se=o.html`
`,b=o.html`
`,oe=o.html`
${Dt} -
`,O=o.html`
`;let e='.notion-cursor-listener > div[style*="flex-end"]',t=()=>{document.contains(v)||(v=document.querySelector(e),W()&&v&&v.setAttribute(B,"true"))};v=document.querySelector(e),o.addDocumentObserver(t,[e]),await w.get(["panel.pinned"])&&R(),o.addHotkeyListener(await w.get(["panel.hotkey"]),R),U.addEventListener("click",r=>{r.stopPropagation(),R()}),o.render($,o.render(Z,P,oe,U),se,A),await rn(),await sn();let n='.notion-cursor-listener > .notion-sidebar-container ~ [style^="position: absolute"]';await o.whenReady([n]),document.querySelector(n).before(C,$)}async function rn(){f=await w.get(["panel.width"],240),Ze(),A.addEventListener("mousedown",en),A.addEventListener("click",()=>{re?re=!1:R()})}async function sn(){Ue=document.querySelector(".notion-app-inner"),Z.addEventListener("click",Fe),oe.addEventListener("click",Fe),O.addEventListener("click",ie)}var Ve=async({id:e,icon:t,title:n,$content:r,onFocus:s=()=>{},onBlur:c=()=>{}})=>{Me||(Me=o.loadStylesheet("api/components/panel.css")),w||(w=await y.db("36a2ffc9-27ff-480e-84a7-c7700a7d232d")),U||(U=o.html`
+
`,O=o.html`
`;let e='.notion-cursor-listener > div[style*="flex-end"]',t=()=>{document.contains(v)||(v=document.querySelector(e),F()&&v&&v.setAttribute(B,"true"))};v=document.querySelector(e),o.addDocumentObserver(t,[e]),await w.get(["panel.pinned"])&&R(),o.addHotkeyListener(await w.get(["panel.hotkey"]),R),U.addEventListener("click",r=>{r.stopPropagation(),R()}),o.render($,o.render(Z,P,oe,U),se,A),await rn(),await sn();let n='.notion-cursor-listener > .notion-sidebar-container ~ [style^="position: absolute"]';await o.whenReady([n]),document.querySelector(n).before(C,$)}async function rn(){f=await w.get(["panel.width"],240),Ze(),A.addEventListener("mousedown",en),A.addEventListener("click",()=>{re?re=!1:R()})}async function sn(){Ue=document.querySelector(".notion-app-inner"),Z.addEventListener("click",We),oe.addEventListener("click",We),O.addEventListener("click",ie)}var Ie=async({id:e,icon:t,title:n,$content:r,onFocus:s=()=>{},onBlur:c=()=>{}})=>{Me||(Me=o.loadStylesheet("api/components/panel.css")),w||(w=await y.db("36a2ffc9-27ff-480e-84a7-c7700a7d232d")),U||(U=o.html`
${await N.feather("chevrons-right")} -
`);let a={id:e,$icon:o.render(o.html``,t instanceof Element?t:o.html`${t}`),$title:o.render(o.html``,n),$content:r,onFocus:s,onBlur:c};k.push(a),k.length===1&&await nn(),(k.length===1||await w.get(["panel.open"])===e)&&Ie(a)};"use strict";var Ke,q,Xe=async(e,t)=>{Ke||(Ke=o.loadStylesheet("api/components/corner-action.css"),q=o.html`
`),await o.whenReady([".notion-help-button"]);let n=document.querySelector(".notion-help-button"),r=document.querySelector(".onboarding-checklist-button");r&&q.prepend(r),q.prepend(n),o.render(document.querySelector(".notion-app-inner > .notion-cursor-listener"),q);let s=o.html`
${e}
`;return s.addEventListener("click",t),o.render(q,s),s};"use strict";"use strict"; +
`);let a={id:e,$icon:o.render(o.html``,t instanceof Element?t:o.html`${t}`),$title:o.render(o.html``,n),$content:r,onFocus:s,onBlur:c};k.push(a),k.length===1&&await nn(),(k.length===1||await w.get(["panel.open"])===e)&&Ve(a)};"use strict";var Ke,q,Xe=async(e,t)=>{Ke||(Ke=o.loadStylesheet("api/components/corner-action.css"),q=o.html`
`),await o.whenReady([".notion-help-button"]);let n=document.querySelector(".notion-help-button"),r=document.querySelector(".onboarding-checklist-button");r&&q.prepend(r),q.prepend(n),o.render(document.querySelector(".notion-app-inner > .notion-cursor-listener"),q);let s=o.html`
${e}
`;return s.addEventListener("click",t),o.render(q,s),s};"use strict";"use strict"; diff --git a/api/web.mjs b/api/web.mjs index b547e77..a82d5e0 100644 --- a/api/web.mjs +++ b/api/web.mjs @@ -26,17 +26,18 @@ let _hotkeyListenersActivated = false, */ export const whenReady = (selectors = []) => { return new Promise((res, rej) => { - function onLoad() { - let isReadyInt; - isReadyInt = setInterval(isReadyTest, 100); - function isReadyTest() { - if (selectors.every((selector) => document.querySelector(selector))) { - clearInterval(isReadyInt); - res(true); - } + const onLoad = () => { + const interval = setInterval(isReady, 100); + function isReady() { + const ready = + document.hasFocus() && + selectors.every((selector) => document.querySelector(selector)); + if (!ready) return; + clearInterval(interval); + res(true); } - isReadyTest(); - } + isReady(); + }; if (document.readyState !== 'complete') { document.addEventListener('readystatechange', (event) => { if (document.readyState === 'complete') onLoad(); @@ -169,7 +170,7 @@ export const readFromClipboard = () => { const triggerHotkeyListener = (event, hotkey) => { const inInput = document.activeElement.nodeName === 'INPUT' && !hotkey.listenInInput; - if (inInput) return; + if (inInput || !document.hasFocus()) return; const pressed = hotkey.keys.every((key) => { key = key.toLowerCase(); const modifiers = { @@ -261,7 +262,7 @@ export const addDocumentObserver = (callback, selectors = []) => { } }; _documentObserver = new MutationObserver((list, observer) => { - if (!_documentObserverEvents.length) + if (!_documentObserverEvents.length && document.hasFocus()) requestIdleCallback(() => handle(_documentObserverEvents)); _documentObserverEvents.push(...list); }); From e4187700b202d2663fa6dc1a005405f906924600 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Fri, 10 Dec 2021 23:38:47 +1100 Subject: [PATCH 44/52] remove hasFocus checks: not all events were being responded to --- api/index.cjs | 10 +++++----- api/web.mjs | 8 +++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/api/index.cjs b/api/index.cjs index 3e83a06..21961dd 100644 --- a/api/index.cjs +++ b/api/index.cjs @@ -1,9 +1,9 @@ -var ce=Object.defineProperty;var Ge=e=>ce(e,"__esModule",{value:!0});var g=(e,t)=>{Ge(e);for(var n in t)ce(e,n,{get:t[n],enumerable:!0})};g(exports,{components:()=>N,electron:()=>V,env:()=>E,fmt:()=>h,fs:()=>p,notion:()=>K,registry:()=>y,storage:()=>_,web:()=>o});var E={};g(E,{focusMenu:()=>De,focusNotion:()=>et,name:()=>Qe,notionRequire:()=>nt,reload:()=>tt,version:()=>Ye});"use strict";var le=globalThis.__enhancerElectronApi.platform,de=globalThis.__enhancerElectronApi.version,pe=globalThis.__enhancerElectronApi.focusMenu,ue=globalThis.__enhancerElectronApi.focusNotion,he=globalThis.__enhancerElectronApi.reload,fe=globalThis.__enhancerElectronApi.notionRequire;"use strict";var Qe=le,Ye=de,De=pe,et=ue,tt=he,nt=fe;var p={};g(p,{getJSON:()=>st,getText:()=>ot,isFile:()=>it,localPath:()=>rt,notionPath:()=>at});"use strict";var L=e=>`notion://www.notion.so/__notion-enhancer/${e}`,me=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.json());try{return globalThis.__enhancerElectronApi.nodeRequire(`notion-enhancer/${e}`)}catch{return fetch(L(e),t).then(r=>r.json())}},ye=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.text());try{let n=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:r}=globalThis.__enhancerElectronApi.nodeRequire("path");return n.readFileSync(r(`${__dirname}/../../${e}`))}catch{return fetch(L(e),t).then(r=>r.text())}},ge=async e=>{try{let t=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:n}=globalThis.__enhancerElectronApi.nodeRequire("path");if(e.startsWith("http"))await fetch(e);else try{t.existsSync(n(`${__dirname}/../../${e}`))}catch{await fetch(L(e))}return!0}catch{return!1}},ve=globalThis.__enhancerElectronApi.notionPath;"use strict";var rt=L,st=me,ot=ye,it=ge,at=ve;var _={};g(_,{addChangeListener:()=>pt,db:()=>dt,get:()=>ct,removeChangeListener:()=>ut,set:()=>lt});"use strict";var W=(e,t=void 0)=>globalThis.__enhancerElectronApi.db.get(e,t),H=(e,t)=>globalThis.__enhancerElectronApi.db.set(e,t),we=(e,t=W,n=H)=>(typeof e=="string"&&(e=[e]),{get:(r=[],s=void 0)=>t([...e,...r],s),set:(r,s)=>n([...e,...r],s)}),be=e=>globalThis.__enhancerElectronApi.db.addChangeListener(e),xe=e=>globalThis.__enhancerElectronApi.db.removeChangeListener(e);"use strict";var ct=W,lt=H,dt=we,pt=be,ut=xe;var V={};g(V,{browser:()=>ht,onMessage:()=>gt,sendMessage:()=>mt,sendMessageToHost:()=>yt,webFrame:()=>ft});"use strict";var ht=globalThis.__enhancerElectronApi?.browser,ft=globalThis.__enhancerElectronApi?.webFrame,mt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessage(e,t,n)},yt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessageToHost(e,t,n)},gt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.onMessage(e,t,n)};var K={};g(K,{create:()=>bt,get:()=>$e,getPageID:()=>M,getSpaceID:()=>S,getUserID:()=>_e,search:()=>vt,set:()=>wt,sign:()=>_t,upload:()=>xt});"use strict";var m=e=>(e?.length===32&&!e.includes("-")&&(e=e.replace(/([\d\w]{8})([\d\w]{4})([\d\w]{4})([\d\w]{4})([\d\w]{12})/,"$1-$2-$3-$4-$5")),e),_e=()=>JSON.parse(localStorage["LRU:KeyValueStore2:current-user-id"]||{}).value,M=()=>m(o.queryParams().get("p")||location.pathname.split(/(-|\/)/g).reverse()[0]),I,S=async()=>(I||(I=(await $e(M())).space_id),I),$e=async(e,t="block")=>(e=m(e),(await p.getJSON("https://www.notion.so/api/v3/getRecordValues",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requests:[{table:t,id:e}]}),method:"POST"})).results[0].value),vt=async(e="",t=20,n=S())=>(n=m(await n),await p.getJSON("https://www.notion.so/api/v3/search",{headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"BlocksInSpace",query:e,spaceId:n,limit:t,filters:{isDeletedOnly:!1,excludeTemplates:!1,isNavigableOnly:!1,requireEditPermissions:!1,ancestors:[],createdBy:[],editedBy:[],lastEditedTime:{},createdTime:{}},sort:"Relevance",source:"quick_find"}),method:"POST"})),wt=async({recordID:e,recordTable:t="block",spaceID:n=S(),path:r=[]},s={})=>{n=m(await n),e=m(e);let c=await p.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:n,operations:[{pointer:{table:t,id:e,spaceId:n},path:r,command:r.length?"set":"update",args:s}]}]}),method:"POST"});return c.errorId?c:!0},bt=async({recordValue:e={},recordTable:t="block"}={},{prepend:n=!1,siblingID:r=void 0,parentID:s=M(),parentTable:c="block",spaceID:a=S(),userID:i=_e()}={})=>{a=m(await a),s=m(s),r=m(r);let d=m(e?.id??h.uuidv4()),x=[],j={type:"text",id:d,version:0,created_time:new Date().getTime(),last_edited_time:new Date().getTime(),parent_id:s,parent_table:c,alive:!0,created_by_table:"notion_user",created_by_id:i,last_edited_by_table:"notion_user",last_edited_by_id:i,space_id:a,permissions:[{type:"user_permission",role:"editor",user_id:i}]};c==="space"?(s=a,j.parent_id=a,x.push("pages"),j.type="page"):c==="collection_view"?(x.push("page_sort"),j.type="page"):x.push("content");let ae=await p.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:a,operations:[{pointer:{table:c,id:s,spaceId:a},path:x,command:n?"listBefore":"listAfter",args:{...r?{after:r}:{},id:d}},{pointer:{table:t,id:d,spaceId:a},path:[],command:"set",args:{...j,...e}}]}]}),method:"POST"});return ae.errorId?ae:d},xt=async(e,{pageID:t=M(),spaceID:n=S()}={})=>{n=m(await n),t=m(t);let r=await p.getJSON("https://www.notion.so/api/v3/getUploadFileUrl",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({bucket:"secure",name:e.name,contentType:e.type,record:{table:"block",id:t,spaceId:n}})});return r.errorId?r:(fetch(r.signedPutUrl,{method:"PUT",headers:{"content-type":e.type},body:e}),r.url)},_t=(e,t,n="block")=>(e.startsWith("/")&&(e=`https://notion.so${e}`),e.includes("secure.notion-static.com")&&(e=new URL(e),e=`https://www.notion.so/signed/${encodeURIComponent(e.origin+e.pathname)}?table=${n}&id=${t}`),e);var h={};g(h,{is:()=>Tt,rgbContrast:()=>Et,rgbLogShade:()=>At,slugger:()=>$t,uuidv4:()=>jt});"use strict";var $t=(e,t=new Set)=>{e=e.replace(/\s/g,"-").replace(/[^A-Za-z0-9-_]/g,"").toLowerCase();let n=0,r=e;for(;t.has(r);)n++,r=`${e}-${n}`;return r},jt=()=>crypto?.randomUUID?crypto.randomUUID():([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,e=>(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)),At=(e,t)=>{var n=parseInt,r=Math.round,[s,c,t,a]=t.split(","),i=e<0,d=i?0:e*255**2,i=i?1+e:1-e;return"rgb"+(a?"a(":"(")+r((i*n(s[3]=="a"?s.slice(5):s.slice(4))**2+d)**.5)+","+r((i*n(c)**2+d)**.5)+","+r((i*n(t)**2+d)**.5)+(a?","+a:")")},Et=(e,t,n)=>Math.sqrt(.299*(e*e)+.587*(t*t)+.114*(n*n))>165.75?"rgb(0,0,0)":"rgb(255,255,255)",Lt={alphanumeric:/^[\w\.-]+$/,uuid:/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,semver:/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i,email:/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i,url:/^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,64}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i,color:/^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i};function St(e,t){let n=e.match(t);return!!(n&&n.length)}var Tt=async(e,t,{extension:n=""}={})=>{if(n=!e||!e.endsWith||e.endsWith(n),Array.isArray(t))return t.includes(e);switch(t){case"array":return Array.isArray(e);case"object":return e&&typeof e=="object"&&!Array.isArray(e);case"undefined":case"boolean":case"number":return typeof e===t&&n;case"string":return typeof e===t&&n;case"alphanumeric":case"uuid":case"semver":case"email":case"url":case"color":return typeof e=="string"&&St(e,Lt[t])&&n;case"file":return typeof e=="string"&&e&&await p.isFile(e)&&n}return!1};var y={};g(y,{core:()=>Ae,db:()=>Ut,enabled:()=>zt,errors:()=>Jt,get:()=>D,list:()=>Y,optionDefault:()=>Le,optionTypes:()=>Mt,profileDB:()=>X,profileName:()=>Ee,supportedEnvs:()=>qt});"use strict";var l=async(e,t,n,r,{extension:s="",error:c=`invalid ${t} (${s?`${s} `:""}${r}): ${JSON.stringify(n)}`,optional:a=!1}={})=>{let i;for(let d of Array.isArray(r)?[r]:r.split("|"))if(d==="file"?i=n&&!n.startsWith("http")?await h.is(`repo/${e._dir}/${n}`,d,{extension:s}):!1:i=await h.is(n,d,{extension:s}),i)break;return i||a&&await h.is(n,"undefined")?!0:(c&&e._err(c),!1)},kt=async e=>(e.environments=e.environments??y.supportedEnvs,await l(e,"environments",e.environments,"array")?e.environments.map(n=>l(e,"environments.env",n,y.supportedEnvs)):!1),Ct=async e=>{if(!await l(e,"tags",e.tags,"array"))return!1;let n=["core","extension","theme","integration"];if(!e.tags.filter(i=>n.includes(i)).length)return e._err(`invalid tags (must contain at least one of 'core', 'extension', 'theme' or 'integration'): +var ce=Object.defineProperty;var Ge=e=>ce(e,"__esModule",{value:!0});var g=(e,t)=>{Ge(e);for(var n in t)ce(e,n,{get:t[n],enumerable:!0})};g(exports,{components:()=>N,electron:()=>V,env:()=>E,fmt:()=>h,fs:()=>p,notion:()=>K,registry:()=>y,storage:()=>_,web:()=>o});var E={};g(E,{focusMenu:()=>De,focusNotion:()=>et,name:()=>Qe,notionRequire:()=>nt,reload:()=>tt,version:()=>Ye});"use strict";var le=globalThis.__enhancerElectronApi.platform,de=globalThis.__enhancerElectronApi.version,pe=globalThis.__enhancerElectronApi.focusMenu,ue=globalThis.__enhancerElectronApi.focusNotion,he=globalThis.__enhancerElectronApi.reload,fe=globalThis.__enhancerElectronApi.notionRequire;"use strict";var Qe=le,Ye=de,De=pe,et=ue,tt=he,nt=fe;var p={};g(p,{getJSON:()=>st,getText:()=>ot,isFile:()=>it,localPath:()=>rt,notionPath:()=>at});"use strict";var L=e=>`notion://www.notion.so/__notion-enhancer/${e}`,me=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.json());try{return globalThis.__enhancerElectronApi.nodeRequire(`notion-enhancer/${e}`)}catch{return fetch(L(e),t).then(r=>r.json())}},ye=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.text());try{let n=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:r}=globalThis.__enhancerElectronApi.nodeRequire("path");return n.readFileSync(r(`${__dirname}/../../${e}`))}catch{return fetch(L(e),t).then(r=>r.text())}},ge=async e=>{try{let t=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:n}=globalThis.__enhancerElectronApi.nodeRequire("path");if(e.startsWith("http"))await fetch(e);else try{t.existsSync(n(`${__dirname}/../../${e}`))}catch{await fetch(L(e))}return!0}catch{return!1}},ve=globalThis.__enhancerElectronApi.notionPath;"use strict";var rt=L,st=me,ot=ye,it=ge,at=ve;var _={};g(_,{addChangeListener:()=>pt,db:()=>dt,get:()=>ct,removeChangeListener:()=>ut,set:()=>lt});"use strict";var F=(e,t=void 0)=>globalThis.__enhancerElectronApi.db.get(e,t),H=(e,t)=>globalThis.__enhancerElectronApi.db.set(e,t),we=(e,t=F,n=H)=>(typeof e=="string"&&(e=[e]),{get:(r=[],s=void 0)=>t([...e,...r],s),set:(r,s)=>n([...e,...r],s)}),be=e=>globalThis.__enhancerElectronApi.db.addChangeListener(e),xe=e=>globalThis.__enhancerElectronApi.db.removeChangeListener(e);"use strict";var ct=F,lt=H,dt=we,pt=be,ut=xe;var V={};g(V,{browser:()=>ht,onMessage:()=>gt,sendMessage:()=>mt,sendMessageToHost:()=>yt,webFrame:()=>ft});"use strict";var ht=globalThis.__enhancerElectronApi?.browser,ft=globalThis.__enhancerElectronApi?.webFrame,mt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessage(e,t,n)},yt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessageToHost(e,t,n)},gt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.onMessage(e,t,n)};var K={};g(K,{create:()=>bt,get:()=>$e,getPageID:()=>M,getSpaceID:()=>S,getUserID:()=>_e,search:()=>vt,set:()=>wt,sign:()=>_t,upload:()=>xt});"use strict";var m=e=>(e?.length===32&&!e.includes("-")&&(e=e.replace(/([\d\w]{8})([\d\w]{4})([\d\w]{4})([\d\w]{4})([\d\w]{12})/,"$1-$2-$3-$4-$5")),e),_e=()=>JSON.parse(localStorage["LRU:KeyValueStore2:current-user-id"]||{}).value,M=()=>m(o.queryParams().get("p")||location.pathname.split(/(-|\/)/g).reverse()[0]),I,S=async()=>(I||(I=(await $e(M())).space_id),I),$e=async(e,t="block")=>(e=m(e),(await p.getJSON("https://www.notion.so/api/v3/getRecordValues",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requests:[{table:t,id:e}]}),method:"POST"})).results[0].value),vt=async(e="",t=20,n=S())=>(n=m(await n),await p.getJSON("https://www.notion.so/api/v3/search",{headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"BlocksInSpace",query:e,spaceId:n,limit:t,filters:{isDeletedOnly:!1,excludeTemplates:!1,isNavigableOnly:!1,requireEditPermissions:!1,ancestors:[],createdBy:[],editedBy:[],lastEditedTime:{},createdTime:{}},sort:"Relevance",source:"quick_find"}),method:"POST"})),wt=async({recordID:e,recordTable:t="block",spaceID:n=S(),path:r=[]},s={})=>{n=m(await n),e=m(e);let c=await p.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:n,operations:[{pointer:{table:t,id:e,spaceId:n},path:r,command:r.length?"set":"update",args:s}]}]}),method:"POST"});return c.errorId?c:!0},bt=async({recordValue:e={},recordTable:t="block"}={},{prepend:n=!1,siblingID:r=void 0,parentID:s=M(),parentTable:c="block",spaceID:a=S(),userID:i=_e()}={})=>{a=m(await a),s=m(s),r=m(r);let d=m(e?.id??h.uuidv4()),x=[],j={type:"text",id:d,version:0,created_time:new Date().getTime(),last_edited_time:new Date().getTime(),parent_id:s,parent_table:c,alive:!0,created_by_table:"notion_user",created_by_id:i,last_edited_by_table:"notion_user",last_edited_by_id:i,space_id:a,permissions:[{type:"user_permission",role:"editor",user_id:i}]};c==="space"?(s=a,j.parent_id=a,x.push("pages"),j.type="page"):c==="collection_view"?(x.push("page_sort"),j.type="page"):x.push("content");let ae=await p.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:a,operations:[{pointer:{table:c,id:s,spaceId:a},path:x,command:n?"listBefore":"listAfter",args:{...r?{after:r}:{},id:d}},{pointer:{table:t,id:d,spaceId:a},path:[],command:"set",args:{...j,...e}}]}]}),method:"POST"});return ae.errorId?ae:d},xt=async(e,{pageID:t=M(),spaceID:n=S()}={})=>{n=m(await n),t=m(t);let r=await p.getJSON("https://www.notion.so/api/v3/getUploadFileUrl",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({bucket:"secure",name:e.name,contentType:e.type,record:{table:"block",id:t,spaceId:n}})});return r.errorId?r:(fetch(r.signedPutUrl,{method:"PUT",headers:{"content-type":e.type},body:e}),r.url)},_t=(e,t,n="block")=>(e.startsWith("/")&&(e=`https://notion.so${e}`),e.includes("secure.notion-static.com")&&(e=new URL(e),e=`https://www.notion.so/signed/${encodeURIComponent(e.origin+e.pathname)}?table=${n}&id=${t}`),e);var h={};g(h,{is:()=>Tt,rgbContrast:()=>Et,rgbLogShade:()=>At,slugger:()=>$t,uuidv4:()=>jt});"use strict";var $t=(e,t=new Set)=>{e=e.replace(/\s/g,"-").replace(/[^A-Za-z0-9-_]/g,"").toLowerCase();let n=0,r=e;for(;t.has(r);)n++,r=`${e}-${n}`;return r},jt=()=>crypto?.randomUUID?crypto.randomUUID():([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,e=>(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)),At=(e,t)=>{var n=parseInt,r=Math.round,[s,c,t,a]=t.split(","),i=e<0,d=i?0:e*255**2,i=i?1+e:1-e;return"rgb"+(a?"a(":"(")+r((i*n(s[3]=="a"?s.slice(5):s.slice(4))**2+d)**.5)+","+r((i*n(c)**2+d)**.5)+","+r((i*n(t)**2+d)**.5)+(a?","+a:")")},Et=(e,t,n)=>Math.sqrt(.299*(e*e)+.587*(t*t)+.114*(n*n))>165.75?"rgb(0,0,0)":"rgb(255,255,255)",Lt={alphanumeric:/^[\w\.-]+$/,uuid:/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,semver:/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i,email:/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i,url:/^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,64}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i,color:/^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i};function St(e,t){let n=e.match(t);return!!(n&&n.length)}var Tt=async(e,t,{extension:n=""}={})=>{if(n=!e||!e.endsWith||e.endsWith(n),Array.isArray(t))return t.includes(e);switch(t){case"array":return Array.isArray(e);case"object":return e&&typeof e=="object"&&!Array.isArray(e);case"undefined":case"boolean":case"number":return typeof e===t&&n;case"string":return typeof e===t&&n;case"alphanumeric":case"uuid":case"semver":case"email":case"url":case"color":return typeof e=="string"&&St(e,Lt[t])&&n;case"file":return typeof e=="string"&&e&&await p.isFile(e)&&n}return!1};var y={};g(y,{core:()=>Ae,db:()=>Ut,enabled:()=>zt,errors:()=>Jt,get:()=>D,list:()=>Y,optionDefault:()=>Le,optionTypes:()=>Mt,profileDB:()=>X,profileName:()=>Ee,supportedEnvs:()=>qt});"use strict";var l=async(e,t,n,r,{extension:s="",error:c=`invalid ${t} (${s?`${s} `:""}${r}): ${JSON.stringify(n)}`,optional:a=!1}={})=>{let i;for(let d of Array.isArray(r)?[r]:r.split("|"))if(d==="file"?i=n&&!n.startsWith("http")?await h.is(`repo/${e._dir}/${n}`,d,{extension:s}):!1:i=await h.is(n,d,{extension:s}),i)break;return i||a&&await h.is(n,"undefined")?!0:(c&&e._err(c),!1)},kt=async e=>(e.environments=e.environments??y.supportedEnvs,await l(e,"environments",e.environments,"array")?e.environments.map(n=>l(e,"environments.env",n,y.supportedEnvs)):!1),Ct=async e=>{if(!await l(e,"tags",e.tags,"array"))return!1;let n=["core","extension","theme","integration"];if(!e.tags.filter(i=>n.includes(i)).length)return e._err(`invalid tags (must contain at least one of 'core', 'extension', 'theme' or 'integration'): ${JSON.stringify(e.tags)}`),!1;let s=e.tags.includes("theme"),c=e.tags.includes("light")||e.tags.includes("dark"),a=e.tags.includes("light")&&e.tags.includes("dark");return s&&(!c||a)?(e._err(`invalid tags (themes must be either 'light' or 'dark', not neither or both): - ${JSON.stringify(e.tags)}`),!1):e.tags.map(i=>l(e,"tags.tag",i,"string"))},Pt=async e=>await l(e,"authors",e.authors,"array")?e.authors.map(n=>[l(e,"authors.author.name",n.name,"string"),l(e,"authors.author.email",n.email,"email",{optional:!0}),l(e,"authors.author.homepage",n.homepage,"url"),l(e,"authors.author.avatar",n.avatar,"url")]):!1,Ot=async e=>{if(!await l(e,"css",e.css,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.css[r])continue;let s=await l(e,`css.${r}`,e.css[r],"array");s&&(s=e.css[r].map(c=>l(e,`css.${r}.file`,c,"file",{extension:".css"}))),n.push(s)}return n},Rt=async e=>{if(!await l(e,"js",e.js,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.js[r])continue;let s=await l(e,`js.${r}`,e.js[r],"array");s&&(s=e.js[r].map(c=>l(e,`js.${r}.file`,c,"file",{extension:".mjs"}))),n.push(s)}if(e.js.electron)if(await l(e,"js.electron",e.js.electron,"array"))for(let s of e.js.electron){if(!await l(e,"js.electron.file",s,"object")){n.push(!1);continue}n.push([l(e,"js.electron.file.source",s.source,"file",{extension:".cjs"}),l(e,"js.electron.file.target",s.target,"string",{extension:".js"})])}else n.push(!1);return n},Nt=async e=>{if(!await l(e,"options",e.options,"array"))return!1;let n=[];for(let r of e.options){let s="options.option";if(!await l(e,`${s}.type`,r.type,y.optionTypes)){n.push(!1);continue}switch(r.environments=r.environments??y.supportedEnvs,n.push([l(e,`${s}.key`,r.key,"alphanumeric"),l(e,`${s}.label`,r.label,"string"),l(e,`${s}.tooltip`,r.tooltip,"string",{optional:!0}),l(e,`${s}.environments`,r.environments,"array").then(a=>a?r.environments.map(i=>l(e,`${s}.environments.env`,i,y.supportedEnvs)):!1)]),r.type){case"toggle":n.push(l(e,`${s}.value`,r.value,"boolean"));break;case"select":{let a=await l(e,`${s}.values`,r.values,"array");a&&(a=r.values.map(i=>l(e,`${s}.values.value`,i,"string"))),n.push(a);break}case"text":case"hotkey":n.push(l(e,`${s}.value`,r.value,"string"));break;case"number":case"color":n.push(l(e,`${s}.value`,r.value,r.type));break;case"file":{let a=await l(e,`${s}.extensions`,r.extensions,"array");a&&(a=r.extensions.map(i=>l(e,`${s}.extensions.extension`,i,"string"))),n.push(a);break}}}return n};async function je(e){let t=[l(e,"name",e.name,"string"),l(e,"id",e.id,"uuid"),l(e,"version",e.version,"semver"),kt(e),l(e,"description",e.description,"string"),l(e,"preview",e.preview,"file|url",{optional:!0}),Ct(e),Pt(e),Ot(e),Rt(e),Nt(e)];do t=await Promise.all(t.flat(1/0));while(t.some(n=>Array.isArray(n)));return t.every(n=>n)}"use strict";var Ae=["a6621988-551d-495a-97d8-3c568bca2e9e","0f0bf8b6-eae6-4273-b307-8fc43f2ee082","36a2ffc9-27ff-480e-84a7-c7700a7d232d"],qt=["linux","win32","darwin","extension"],Mt=["toggle","select","text","number","color","file","hotkey"],Ee=async()=>_.get(["currentprofile"],"default"),X=async()=>_.db(["profiles",await Ee()]),G,Q=[],Y=async(e=t=>!0)=>{G||(G=new Promise(async(n,r)=>{let s=[];for(let c of await p.getJSON("repo/registry.json"))try{let a={...await p.getJSON(`repo/${c}/mod.json`),_dir:c,_err:i=>Q.push({source:c,message:i})};await je(a)&&s.push(a)}catch{Q.push({source:c,message:"invalid mod.json"})}n(s)}));let t=[];for(let n of await G)await e(n)&&t.push(n);return t},Jt=async()=>(await Y(),Q),D=async e=>(await Y(t=>t.id===e))[0],zt=async e=>(await D(e)).environments.includes(E.name)?Ae.includes(e)?!0:(await X()).get(["_mods",e],!1):!1,Le=async(e,t)=>{let n=await D(e),r=n.options.find(s=>s.key===t);if(!!r)switch(r.type){case"toggle":case"text":case"number":case"color":case"hotkey":return r.value;case"select":return r.values[0];case"file":return}},Ut=async e=>{let t=await X();return _.db([e],async(n,r=void 0)=>(typeof n=="string"&&(n=[n]),n.length===2&&(r=await Le(e,n[1])??r),t.get(n,r)),t.set)};var o={};g(o,{addDocumentObserver:()=>Xt,addHotkeyListener:()=>It,copyToClipboard:()=>Ht,empty:()=>Ft,escape:()=>Te,html:()=>Ce,loadStylesheet:()=>Wt,queryParams:()=>Bt,raw:()=>ke,readFromClipboard:()=>Vt,removeDocumentObserver:()=>Gt,removeHotkeyListener:()=>Kt,render:()=>Pe,whenReady:()=>Zt});"use strict";var Se=!1,T=[],ee,J=[],te=[],Zt=(e=[])=>new Promise((t,n)=>{let r=()=>{let s=setInterval(c,100);function c(){!(document.hasFocus()&&e.every(i=>document.querySelector(i)))||(clearInterval(s),t(!0))}c()};document.readyState!=="complete"?document.addEventListener("readystatechange",s=>{document.readyState==="complete"&&r()}):r()}),Bt=()=>new URLSearchParams(window.location.search),Te=e=>e.replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\\/g,"\"),ke=(e,...t)=>{let n=e.map(r=>r+(["string","number"].includes(typeof t[0])?t.shift():Te(JSON.stringify(t.shift(),null,2)??""))).join("");return n.includes("r.trim()).filter(r=>r.length).join(" ")},Ce=(e,...t)=>{let n=document.createRange().createContextualFragment(ke(e,...t));return n.children.length===1?n.children[0]:n.children},Pe=(e,...t)=>(t=t.map(n=>n instanceof HTMLCollection?[...n]:n).flat(1/0).filter(n=>n),e.append(...t),e),Ft=e=>{for(;e.firstChild&&e.removeChild(e.firstChild););return e},Wt=e=>{let t=Ce`l(e,"tags.tag",i,"string"))},Pt=async e=>await l(e,"authors",e.authors,"array")?e.authors.map(n=>[l(e,"authors.author.name",n.name,"string"),l(e,"authors.author.email",n.email,"email",{optional:!0}),l(e,"authors.author.homepage",n.homepage,"url"),l(e,"authors.author.avatar",n.avatar,"url")]):!1,Ot=async e=>{if(!await l(e,"css",e.css,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.css[r])continue;let s=await l(e,`css.${r}`,e.css[r],"array");s&&(s=e.css[r].map(c=>l(e,`css.${r}.file`,c,"file",{extension:".css"}))),n.push(s)}return n},Rt=async e=>{if(!await l(e,"js",e.js,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.js[r])continue;let s=await l(e,`js.${r}`,e.js[r],"array");s&&(s=e.js[r].map(c=>l(e,`js.${r}.file`,c,"file",{extension:".mjs"}))),n.push(s)}if(e.js.electron)if(await l(e,"js.electron",e.js.electron,"array"))for(let s of e.js.electron){if(!await l(e,"js.electron.file",s,"object")){n.push(!1);continue}n.push([l(e,"js.electron.file.source",s.source,"file",{extension:".cjs"}),l(e,"js.electron.file.target",s.target,"string",{extension:".js"})])}else n.push(!1);return n},Nt=async e=>{if(!await l(e,"options",e.options,"array"))return!1;let n=[];for(let r of e.options){let s="options.option";if(!await l(e,`${s}.type`,r.type,y.optionTypes)){n.push(!1);continue}switch(r.environments=r.environments??y.supportedEnvs,n.push([l(e,`${s}.key`,r.key,"alphanumeric"),l(e,`${s}.label`,r.label,"string"),l(e,`${s}.tooltip`,r.tooltip,"string",{optional:!0}),l(e,`${s}.environments`,r.environments,"array").then(a=>a?r.environments.map(i=>l(e,`${s}.environments.env`,i,y.supportedEnvs)):!1)]),r.type){case"toggle":n.push(l(e,`${s}.value`,r.value,"boolean"));break;case"select":{let a=await l(e,`${s}.values`,r.values,"array");a&&(a=r.values.map(i=>l(e,`${s}.values.value`,i,"string"))),n.push(a);break}case"text":case"hotkey":n.push(l(e,`${s}.value`,r.value,"string"));break;case"number":case"color":n.push(l(e,`${s}.value`,r.value,r.type));break;case"file":{let a=await l(e,`${s}.extensions`,r.extensions,"array");a&&(a=r.extensions.map(i=>l(e,`${s}.extensions.extension`,i,"string"))),n.push(a);break}}}return n};async function je(e){let t=[l(e,"name",e.name,"string"),l(e,"id",e.id,"uuid"),l(e,"version",e.version,"semver"),kt(e),l(e,"description",e.description,"string"),l(e,"preview",e.preview,"file|url",{optional:!0}),Ct(e),Pt(e),Ot(e),Rt(e),Nt(e)];do t=await Promise.all(t.flat(1/0));while(t.some(n=>Array.isArray(n)));return t.every(n=>n)}"use strict";var Ae=["a6621988-551d-495a-97d8-3c568bca2e9e","0f0bf8b6-eae6-4273-b307-8fc43f2ee082","36a2ffc9-27ff-480e-84a7-c7700a7d232d"],qt=["linux","win32","darwin","extension"],Mt=["toggle","select","text","number","color","file","hotkey"],Ee=async()=>_.get(["currentprofile"],"default"),X=async()=>_.db(["profiles",await Ee()]),G,Q=[],Y=async(e=t=>!0)=>{G||(G=new Promise(async(n,r)=>{let s=[];for(let c of await p.getJSON("repo/registry.json"))try{let a={...await p.getJSON(`repo/${c}/mod.json`),_dir:c,_err:i=>Q.push({source:c,message:i})};await je(a)&&s.push(a)}catch{Q.push({source:c,message:"invalid mod.json"})}n(s)}));let t=[];for(let n of await G)await e(n)&&t.push(n);return t},Jt=async()=>(await Y(),Q),D=async e=>(await Y(t=>t.id===e))[0],zt=async e=>(await D(e)).environments.includes(E.name)?Ae.includes(e)?!0:(await X()).get(["_mods",e],!1):!1,Le=async(e,t)=>{let n=await D(e),r=n.options.find(s=>s.key===t);if(!!r)switch(r.type){case"toggle":case"text":case"number":case"color":case"hotkey":return r.value;case"select":return r.values[0];case"file":return}},Ut=async e=>{let t=await X();return _.db([e],async(n,r=void 0)=>(typeof n=="string"&&(n=[n]),n.length===2&&(r=await Le(e,n[1])??r),t.get(n,r)),t.set)};var o={};g(o,{addDocumentObserver:()=>Xt,addHotkeyListener:()=>It,copyToClipboard:()=>Ht,empty:()=>Wt,escape:()=>Te,html:()=>Ce,loadStylesheet:()=>Ft,queryParams:()=>Bt,raw:()=>ke,readFromClipboard:()=>Vt,removeDocumentObserver:()=>Gt,removeHotkeyListener:()=>Kt,render:()=>Pe,whenReady:()=>Zt});"use strict";var Se=!1,T=[],ee,J=[],te=[],Zt=(e=[])=>new Promise((t,n)=>{let r=()=>{let s=setInterval(c,100);function c(){!e.every(i=>document.querySelector(i))||(clearInterval(s),t(!0))}c()};document.readyState!=="complete"?document.addEventListener("readystatechange",s=>{document.readyState==="complete"&&r()}):r()}),Bt=()=>new URLSearchParams(window.location.search),Te=e=>e.replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\\/g,"\"),ke=(e,...t)=>{let n=e.map(r=>r+(["string","number"].includes(typeof t[0])?t.shift():Te(JSON.stringify(t.shift(),null,2)??""))).join("");return n.includes("r.trim()).filter(r=>r.length).join(" ")},Ce=(e,...t)=>{let n=document.createRange().createContextualFragment(ke(e,...t));return n.children.length===1?n.children[0]:n.children},Pe=(e,...t)=>(t=t.map(n=>n instanceof HTMLCollection?[...n]:n).flat(1/0).filter(n=>n),e.append(...t),e),Wt=e=>{for(;e.firstChild&&e.removeChild(e.firstChild););return e},Ft=e=>{let t=Ce``;return Pe(document.head,t),t},Ht=async e=>{try{await navigator.clipboard.writeText(e)}catch{let t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}},Vt=()=>navigator.clipboard.readText(),Oe=(e,t)=>{if(document.activeElement.nodeName==="INPUT"&&!t.listenInInput||!document.hasFocus())return;t.keys.every(s=>{s=s.toLowerCase();let c={metaKey:["meta","os","win","cmd","command"],ctrlKey:["ctrl","control"],shiftKey:["shift"],altKey:["alt"]};for(let a in c)if(c[a].includes(s)&&e[a])return!0;if(s==="space"&&(s=" "),s==="plus"&&(s="+"),s===e.key.toLowerCase())return!0})&&t.callback(e)},It=(e,t,{listenInInput:n=!1,keydown:r=!1}={})=>{typeof e=="string"&&(e=e.split("+")),T.push({keys:e,callback:t,listenInInput:n,keydown:r}),Se||(Se=!0,document.addEventListener("keyup",s=>{for(let c of T.filter(({keydown:a})=>!a))Oe(s,c)}),document.addEventListener("keydown",s=>{for(let c of T.filter(({keydown:a})=>a))Oe(s,c)}))},Kt=e=>{T=T.filter(t=>t.callback!==e)},Xt=(e,t=[])=>{if(!ee){let n=r=>{for(;r.length;){let s=r.shift(),c=(i,d)=>i instanceof Element&&(i.matches(d)||i.matches(`${d} *`)||i.querySelector(d)),a=i=>s.target.matches(i)||s.target.matches(`${i} *`)||[...s.addedNodes].some(d=>c(d,i));for(let i of J)(!i.selectors.length||i.selectors.some(a))&&i.callback(s)}};ee=new MutationObserver((r,s)=>{!te.length&&document.hasFocus()&&requestIdleCallback(()=>n(te)),te.push(...r)}),ee.observe(document.body,{childList:!0,subtree:!0,attributes:!0})}J.push({callback:e,selectors:t})},Gt=e=>{J=J.filter(t=>t.callback!==e)};var N={};g(N,{addCornerAction:()=>Xe,addPanelView:()=>Ie,addTooltip:()=>Ne,feather:()=>qe});"use strict";var Re,u,Qt=e=>[...e.getClientRects()].reduce((t,n)=>t.some(r=>r.y===n.y)?t:[...t,n],[]).length,Yt=async(e,t,n)=>{u.style.top="0px",u.style.left="0px";let r=e.getBoundingClientRect(),{offsetWidth:s,offsetHeight:c}=u,a=6,i=r.x,d=Math.floor(r.y);if(["top","bottom"].includes(t)){t==="top"&&(d-=c+a),t==="bottom"&&(d+=r.height+a),i-=s/2-r.width/2,u.style.left=`${i}px`,u.style.top=`${d}px`;let x=()=>Qt(u.firstElementChild)>n,j=x();for(;x();)u.style.left=`${window.innerWidth-i>i?i++:i--}px`;j&&(i+=window.innerWidth-i>i?a:-a,u.style.left=`${i}px`)}return["left","right"].includes(t)&&(d-=c/2-r.height/2,t==="left"&&(i-=s+a),t==="right"&&(i+=r.width+a),u.style.left=`${i}px`,u.style.top=`${d}px`),!0},Ne=async(e,t,{delay:n=100,offsetDirection:r="bottom",maxLines:s=1}={})=>{Re||(Re=o.loadStylesheet("api/components/tooltip.css"),u=o.html`
`,o.render(document.body,u)),globalThis.markdownit||await import(p.localPath("dep/markdown-it.min.js"));let c=markdownit({linkify:!0});t instanceof Element||(t=o.html`
+ />`;return Pe(document.head,t),t},Ht=async e=>{try{await navigator.clipboard.writeText(e)}catch{let t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}},Vt=()=>navigator.clipboard.readText(),Oe=(e,t)=>{if(document.activeElement.nodeName==="INPUT"&&!t.listenInInput)return;t.keys.every(s=>{s=s.toLowerCase();let c={metaKey:["meta","os","win","cmd","command"],ctrlKey:["ctrl","control"],shiftKey:["shift"],altKey:["alt"]};for(let a in c)if(c[a].includes(s)&&e[a])return!0;if(s==="space"&&(s=" "),s==="plus"&&(s="+"),s===e.key.toLowerCase())return!0})&&t.callback(e)},It=(e,t,{listenInInput:n=!1,keydown:r=!1}={})=>{typeof e=="string"&&(e=e.split("+")),T.push({keys:e,callback:t,listenInInput:n,keydown:r}),Se||(Se=!0,document.addEventListener("keyup",s=>{for(let c of T.filter(({keydown:a})=>!a))Oe(s,c)}),document.addEventListener("keydown",s=>{for(let c of T.filter(({keydown:a})=>a))Oe(s,c)}))},Kt=e=>{T=T.filter(t=>t.callback!==e)},Xt=(e,t=[])=>{if(!ee){let n=r=>{for(;r.length;){let s=r.shift(),c=(i,d)=>i instanceof Element&&(i.matches(d)||i.matches(`${d} *`)||i.querySelector(d)),a=i=>s.target.matches(i)||s.target.matches(`${i} *`)||[...s.addedNodes].some(d=>c(d,i));for(let i of J)(!i.selectors.length||i.selectors.some(a))&&i.callback(s)}};ee=new MutationObserver((r,s)=>{te.length||requestIdleCallback(()=>n(te)),te.push(...r)}),ee.observe(document.body,{childList:!0,subtree:!0,attributes:!0})}J.push({callback:e,selectors:t})},Gt=e=>{J=J.filter(t=>t.callback!==e)};var N={};g(N,{addCornerAction:()=>Xe,addPanelView:()=>Ie,addTooltip:()=>Ne,feather:()=>qe});"use strict";var Re,u,Qt=e=>[...e.getClientRects()].reduce((t,n)=>t.some(r=>r.y===n.y)?t:[...t,n],[]).length,Yt=async(e,t,n)=>{u.style.top="0px",u.style.left="0px";let r=e.getBoundingClientRect(),{offsetWidth:s,offsetHeight:c}=u,a=6,i=r.x,d=Math.floor(r.y);if(["top","bottom"].includes(t)){t==="top"&&(d-=c+a),t==="bottom"&&(d+=r.height+a),i-=s/2-r.width/2,u.style.left=`${i}px`,u.style.top=`${d}px`;let x=()=>Qt(u.firstElementChild)>n,j=x();for(;x();)u.style.left=`${window.innerWidth-i>i?i++:i--}px`;j&&(i+=window.innerWidth-i>i?a:-a,u.style.left=`${i}px`)}return["left","right"].includes(t)&&(d-=c/2-r.height/2,t==="left"&&(i-=s+a),t==="right"&&(i+=r.width+a),u.style.left=`${i}px`,u.style.top=`${d}px`),!0},Ne=async(e,t,{delay:n=100,offsetDirection:r="bottom",maxLines:s=1}={})=>{Re||(Re=o.loadStylesheet("api/components/tooltip.css"),u=o.html`
`,o.render(document.body,u)),globalThis.markdownit||await import(p.localPath("dep/markdown-it.min.js"));let c=markdownit({linkify:!0});t instanceof Element||(t=o.html`
${t.split(` `).map(i=>c.renderInline(i)).join("
")}
`);let a;e.addEventListener("mouseover",async i=>{a||(a=setTimeout(async()=>{e.matches(":hover")&&u.style.display!=="block"&&(u.style.display="block",o.render(o.empty(u),t),Yt(e,r,s),await u.animate([{opacity:0},{opacity:1}],{duration:65}).finished),a=void 0},n))}),e.addEventListener("mouseout",async i=>{a=void 0,u.style.display==="block"&&!e.matches(":hover")&&(await u.animate([{opacity:1},{opacity:0}],{duration:65}).finished,u.style.display="")})};"use strict";var ne,qe=async(e,t={})=>(ne||(ne=o.html`${await p.getText("dep/feather-sprite.svg")}`),t.style=((t.style?t.style+";":"")+"stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;fill:none;").trim(),t.viewBox="0 0 24 24",``${o.escape(n)}="${o.escape(r)}"`).join(" ")}>${ne.getElementById(e)?.innerHTML}`);"use strict";var k=[],Dt=o.raw` @@ -13,9 +13,9 @@ var ce=Object.defineProperty;var Ge=e=>ce(e,"__esModule",{value:!0});var g=(e,t) 2.43056L 3.98809 -0.569442L 3.01191 0.569442ZM -0.488094 6.56944L 3.01191 9.56944L 3.98809 8.43056L 0.488094 5.43056L -0.488094 6.56944ZM 3.98809 9.56944L 7.48809 6.56944L 6.51191 5.43056L 3.01191 8.43056L 3.98809 9.56944Z"> - `,Me,w,z,v,$,C,A,Je,ze,re,f,Ue,U,P,Z,se,b,oe,O,B="data-enhancer-panel-pinned",F=()=>$.hasAttribute(B),R=()=>{let e=[z,v,C,$].filter(t=>t);if(F()){ie();for(let t of e)t.removeAttribute(B)}else for(let t of e)t.setAttribute(B,"true");w.set(["panel.pinned"],F())},Ze=async()=>{document.documentElement.style.setProperty("--component--panel-width",f+"px"),w.set(["panel.width"],f)},Be=e=>{e.preventDefault(),re=!0,f=ze+(Je-e.clientX),f<190&&(f=190),f>480&&(f=480),$.style.width=f+"px",C.style.width=f+"px",z.style.paddingRight=f+"px",v&&(v.style.right=f+"px")},Fe=e=>{$.style.width="",C.style.width="",z.style.paddingRight="",v&&(v.style.right=""),Ze(),A.style.cursor="",document.body.removeEventListener("mousemove",Be),document.body.removeEventListener("mouseup",Fe)},en=e=>{Je=e.clientX,ze=f,A.style.cursor="auto",document.body.addEventListener("mousemove",Be),document.body.addEventListener("mouseup",Fe)},tn=()=>document.body.contains(b),We=()=>{if(!F())return R();o.render(Ue,O),o.empty(b);for(let t of k){let n=P.contains(t.$title),r=o.render(o.html`
`,o.render(o.html``,t.$icon.cloneNode(!0),t.$title.cloneNode(!0)));r.addEventListener("click",()=>{Ve(t),w.set(["panel.open"],t.id)}),o.render(b,r)}let e=Z.getBoundingClientRect();o.render(o.empty(O),o.render(o.html`
$.hasAttribute(B),R=()=>{let e=[z,v,C,$].filter(t=>t);if(W()){ie();for(let t of e)t.removeAttribute(B)}else for(let t of e)t.setAttribute(B,"true");w.set(["panel.pinned"],W())},Ze=async()=>{document.documentElement.style.setProperty("--component--panel-width",f+"px"),w.set(["panel.width"],f)},Be=e=>{e.preventDefault(),re=!0,f=ze+(Je-e.clientX),f<190&&(f=190),f>480&&(f=480),$.style.width=f+"px",C.style.width=f+"px",z.style.paddingRight=f+"px",v&&(v.style.right=f+"px")},We=e=>{$.style.width="",C.style.width="",z.style.paddingRight="",v&&(v.style.right=""),Ze(),A.style.cursor="",document.body.removeEventListener("mousemove",Be),document.body.removeEventListener("mouseup",We)},en=e=>{Je=e.clientX,ze=f,A.style.cursor="auto",document.body.addEventListener("mousemove",Be),document.body.addEventListener("mouseup",We)},tn=()=>document.body.contains(b),Fe=()=>{if(!W())return R();o.render(Ue,O),o.empty(b);for(let t of k){let n=P.contains(t.$title),r=o.render(o.html`
`,o.render(o.html``,t.$icon.cloneNode(!0),t.$title.cloneNode(!0)));r.addEventListener("click",()=>{Ve(t),w.set(["panel.open"],t.id)}),o.render(b,r)}let e=Z.getBoundingClientRect();o.render(o.empty(O),o.render(o.html`
`,o.render(o.html`
`,b))),b.querySelector("[data-open]").focus(),b.animate([{opacity:0},{opacity:1}],{duration:200}),document.addEventListener("keydown",He)},ie=()=>{document.removeEventListener("keydown",He),b.animate([{opacity:1},{opacity:0}],{duration:200}).onfinish=()=>O.remove()},He=e=>{if(tn())switch(e.key){case"Escape":ie(),e.stopPropagation();break;case"Enter":document.activeElement.click(),e.stopPropagation();break;case"ArrowUp":(e.target.previousElementSibling||e.target.parentElement.lastElementChild).focus(),e.stopPropagation();break;case"ArrowDown":(e.target.nextElementSibling||e.target.parentElement.firstElementChild).focus(),e.stopPropagation();break}},Ve=e=>{let t=k.find(({$content:n})=>document.contains(n));o.render(o.empty(P),o.render(o.html``,e.$icon,e.$title)),e.onFocus(),o.render(o.empty(se),e.$content),t&&t.onBlur()};async function nn(){await o.whenReady([".notion-frame"]),z=document.querySelector(".notion-frame"),$=o.html`
`,C=o.html`
`,A=o.html`
`,P=o.html`
`,Z=o.render(o.html`
`,P),se=o.html`
`,b=o.html`
`,oe=o.html`
${Dt} -
`,O=o.html`
`;let e='.notion-cursor-listener > div[style*="flex-end"]',t=()=>{document.contains(v)||(v=document.querySelector(e),F()&&v&&v.setAttribute(B,"true"))};v=document.querySelector(e),o.addDocumentObserver(t,[e]),await w.get(["panel.pinned"])&&R(),o.addHotkeyListener(await w.get(["panel.hotkey"]),R),U.addEventListener("click",r=>{r.stopPropagation(),R()}),o.render($,o.render(Z,P,oe,U),se,A),await rn(),await sn();let n='.notion-cursor-listener > .notion-sidebar-container ~ [style^="position: absolute"]';await o.whenReady([n]),document.querySelector(n).before(C,$)}async function rn(){f=await w.get(["panel.width"],240),Ze(),A.addEventListener("mousedown",en),A.addEventListener("click",()=>{re?re=!1:R()})}async function sn(){Ue=document.querySelector(".notion-app-inner"),Z.addEventListener("click",We),oe.addEventListener("click",We),O.addEventListener("click",ie)}var Ie=async({id:e,icon:t,title:n,$content:r,onFocus:s=()=>{},onBlur:c=()=>{}})=>{Me||(Me=o.loadStylesheet("api/components/panel.css")),w||(w=await y.db("36a2ffc9-27ff-480e-84a7-c7700a7d232d")),U||(U=o.html`
+
`,O=o.html`
`;let e='.notion-cursor-listener > div[style*="flex-end"]',t=()=>{document.contains(v)||(v=document.querySelector(e),W()&&v&&v.setAttribute(B,"true"))};v=document.querySelector(e),o.addDocumentObserver(t,[e]),await w.get(["panel.pinned"])&&R(),o.addHotkeyListener(await w.get(["panel.hotkey"]),R),U.addEventListener("click",r=>{r.stopPropagation(),R()}),o.render($,o.render(Z,P,oe,U),se,A),await rn(),await sn();let n='.notion-cursor-listener > .notion-sidebar-container ~ [style^="position: absolute"]';await o.whenReady([n]),document.querySelector(n).before(C,$)}async function rn(){f=await w.get(["panel.width"],240),Ze(),A.addEventListener("mousedown",en),A.addEventListener("click",()=>{re?re=!1:R()})}async function sn(){Ue=document.querySelector(".notion-app-inner"),Z.addEventListener("click",Fe),oe.addEventListener("click",Fe),O.addEventListener("click",ie)}var Ie=async({id:e,icon:t,title:n,$content:r,onFocus:s=()=>{},onBlur:c=()=>{}})=>{Me||(Me=o.loadStylesheet("api/components/panel.css")),w||(w=await y.db("36a2ffc9-27ff-480e-84a7-c7700a7d232d")),U||(U=o.html`
${await N.feather("chevrons-right")}
`);let a={id:e,$icon:o.render(o.html``,t instanceof Element?t:o.html`${t}`),$title:o.render(o.html``,n),$content:r,onFocus:s,onBlur:c};k.push(a),k.length===1&&await nn(),(k.length===1||await w.get(["panel.open"])===e)&&Ve(a)};"use strict";var Ke,q,Xe=async(e,t)=>{Ke||(Ke=o.loadStylesheet("api/components/corner-action.css"),q=o.html`
`),await o.whenReady([".notion-help-button"]);let n=document.querySelector(".notion-help-button"),r=document.querySelector(".onboarding-checklist-button");r&&q.prepend(r),q.prepend(n),o.render(document.querySelector(".notion-app-inner > .notion-cursor-listener"),q);let s=o.html`
${e}
`;return s.addEventListener("click",t),o.render(q,s),s};"use strict";"use strict"; diff --git a/api/web.mjs b/api/web.mjs index a82d5e0..42a0f25 100644 --- a/api/web.mjs +++ b/api/web.mjs @@ -29,9 +29,7 @@ export const whenReady = (selectors = []) => { const onLoad = () => { const interval = setInterval(isReady, 100); function isReady() { - const ready = - document.hasFocus() && - selectors.every((selector) => document.querySelector(selector)); + const ready = selectors.every((selector) => document.querySelector(selector)); if (!ready) return; clearInterval(interval); res(true); @@ -170,7 +168,7 @@ export const readFromClipboard = () => { const triggerHotkeyListener = (event, hotkey) => { const inInput = document.activeElement.nodeName === 'INPUT' && !hotkey.listenInInput; - if (inInput || !document.hasFocus()) return; + if (inInput) return; const pressed = hotkey.keys.every((key) => { key = key.toLowerCase(); const modifiers = { @@ -262,7 +260,7 @@ export const addDocumentObserver = (callback, selectors = []) => { } }; _documentObserver = new MutationObserver((list, observer) => { - if (!_documentObserverEvents.length && document.hasFocus()) + if (!_documentObserverEvents.length) requestIdleCallback(() => handle(_documentObserverEvents)); _documentObserverEvents.push(...list); }); From 4fcb4673517410e303de20245363a156214832f5 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Sat, 11 Dec 2021 14:20:14 +1100 Subject: [PATCH 45/52] make tooltip text alignment match relative pos --- api/components/tooltip.css | 1 - api/components/tooltip.mjs | 2 ++ api/index.cjs | 16 ++++++++-------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/api/components/tooltip.css b/api/components/tooltip.css index 35c3f62..32664dd 100644 --- a/api/components/tooltip.css +++ b/api/components/tooltip.css @@ -20,7 +20,6 @@ position: absolute; z-index: 999999999999999999; pointer-events: none; - text-align: center; } #enhancer--tooltip p { margin: 0; diff --git a/api/components/tooltip.mjs b/api/components/tooltip.mjs index 8e996b2..11cc418 100644 --- a/api/components/tooltip.mjs +++ b/api/components/tooltip.mjs @@ -44,6 +44,7 @@ const countLines = ($el) => x += window.innerWidth - x > x ? pad : -pad; _$tooltip.style.left = `${x}px`; } + _$tooltip.style.textAlign = 'center'; } if (['left', 'right'].includes(offsetDirection)) { @@ -52,6 +53,7 @@ const countLines = ($el) => if (offsetDirection === 'right') x += rect.width + pad; _$tooltip.style.left = `${x}px`; _$tooltip.style.top = `${y}px`; + _$tooltip.style.textAlign = 'auto'; } return true; diff --git a/api/index.cjs b/api/index.cjs index 21961dd..de549b0 100644 --- a/api/index.cjs +++ b/api/index.cjs @@ -1,21 +1,21 @@ -var ce=Object.defineProperty;var Ge=e=>ce(e,"__esModule",{value:!0});var g=(e,t)=>{Ge(e);for(var n in t)ce(e,n,{get:t[n],enumerable:!0})};g(exports,{components:()=>N,electron:()=>V,env:()=>E,fmt:()=>h,fs:()=>p,notion:()=>K,registry:()=>y,storage:()=>_,web:()=>o});var E={};g(E,{focusMenu:()=>De,focusNotion:()=>et,name:()=>Qe,notionRequire:()=>nt,reload:()=>tt,version:()=>Ye});"use strict";var le=globalThis.__enhancerElectronApi.platform,de=globalThis.__enhancerElectronApi.version,pe=globalThis.__enhancerElectronApi.focusMenu,ue=globalThis.__enhancerElectronApi.focusNotion,he=globalThis.__enhancerElectronApi.reload,fe=globalThis.__enhancerElectronApi.notionRequire;"use strict";var Qe=le,Ye=de,De=pe,et=ue,tt=he,nt=fe;var p={};g(p,{getJSON:()=>st,getText:()=>ot,isFile:()=>it,localPath:()=>rt,notionPath:()=>at});"use strict";var L=e=>`notion://www.notion.so/__notion-enhancer/${e}`,me=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.json());try{return globalThis.__enhancerElectronApi.nodeRequire(`notion-enhancer/${e}`)}catch{return fetch(L(e),t).then(r=>r.json())}},ye=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.text());try{let n=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:r}=globalThis.__enhancerElectronApi.nodeRequire("path");return n.readFileSync(r(`${__dirname}/../../${e}`))}catch{return fetch(L(e),t).then(r=>r.text())}},ge=async e=>{try{let t=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:n}=globalThis.__enhancerElectronApi.nodeRequire("path");if(e.startsWith("http"))await fetch(e);else try{t.existsSync(n(`${__dirname}/../../${e}`))}catch{await fetch(L(e))}return!0}catch{return!1}},ve=globalThis.__enhancerElectronApi.notionPath;"use strict";var rt=L,st=me,ot=ye,it=ge,at=ve;var _={};g(_,{addChangeListener:()=>pt,db:()=>dt,get:()=>ct,removeChangeListener:()=>ut,set:()=>lt});"use strict";var F=(e,t=void 0)=>globalThis.__enhancerElectronApi.db.get(e,t),H=(e,t)=>globalThis.__enhancerElectronApi.db.set(e,t),we=(e,t=F,n=H)=>(typeof e=="string"&&(e=[e]),{get:(r=[],s=void 0)=>t([...e,...r],s),set:(r,s)=>n([...e,...r],s)}),be=e=>globalThis.__enhancerElectronApi.db.addChangeListener(e),xe=e=>globalThis.__enhancerElectronApi.db.removeChangeListener(e);"use strict";var ct=F,lt=H,dt=we,pt=be,ut=xe;var V={};g(V,{browser:()=>ht,onMessage:()=>gt,sendMessage:()=>mt,sendMessageToHost:()=>yt,webFrame:()=>ft});"use strict";var ht=globalThis.__enhancerElectronApi?.browser,ft=globalThis.__enhancerElectronApi?.webFrame,mt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessage(e,t,n)},yt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessageToHost(e,t,n)},gt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.onMessage(e,t,n)};var K={};g(K,{create:()=>bt,get:()=>$e,getPageID:()=>M,getSpaceID:()=>S,getUserID:()=>_e,search:()=>vt,set:()=>wt,sign:()=>_t,upload:()=>xt});"use strict";var m=e=>(e?.length===32&&!e.includes("-")&&(e=e.replace(/([\d\w]{8})([\d\w]{4})([\d\w]{4})([\d\w]{4})([\d\w]{12})/,"$1-$2-$3-$4-$5")),e),_e=()=>JSON.parse(localStorage["LRU:KeyValueStore2:current-user-id"]||{}).value,M=()=>m(o.queryParams().get("p")||location.pathname.split(/(-|\/)/g).reverse()[0]),I,S=async()=>(I||(I=(await $e(M())).space_id),I),$e=async(e,t="block")=>(e=m(e),(await p.getJSON("https://www.notion.so/api/v3/getRecordValues",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requests:[{table:t,id:e}]}),method:"POST"})).results[0].value),vt=async(e="",t=20,n=S())=>(n=m(await n),await p.getJSON("https://www.notion.so/api/v3/search",{headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"BlocksInSpace",query:e,spaceId:n,limit:t,filters:{isDeletedOnly:!1,excludeTemplates:!1,isNavigableOnly:!1,requireEditPermissions:!1,ancestors:[],createdBy:[],editedBy:[],lastEditedTime:{},createdTime:{}},sort:"Relevance",source:"quick_find"}),method:"POST"})),wt=async({recordID:e,recordTable:t="block",spaceID:n=S(),path:r=[]},s={})=>{n=m(await n),e=m(e);let c=await p.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:n,operations:[{pointer:{table:t,id:e,spaceId:n},path:r,command:r.length?"set":"update",args:s}]}]}),method:"POST"});return c.errorId?c:!0},bt=async({recordValue:e={},recordTable:t="block"}={},{prepend:n=!1,siblingID:r=void 0,parentID:s=M(),parentTable:c="block",spaceID:a=S(),userID:i=_e()}={})=>{a=m(await a),s=m(s),r=m(r);let d=m(e?.id??h.uuidv4()),x=[],j={type:"text",id:d,version:0,created_time:new Date().getTime(),last_edited_time:new Date().getTime(),parent_id:s,parent_table:c,alive:!0,created_by_table:"notion_user",created_by_id:i,last_edited_by_table:"notion_user",last_edited_by_id:i,space_id:a,permissions:[{type:"user_permission",role:"editor",user_id:i}]};c==="space"?(s=a,j.parent_id=a,x.push("pages"),j.type="page"):c==="collection_view"?(x.push("page_sort"),j.type="page"):x.push("content");let ae=await p.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:a,operations:[{pointer:{table:c,id:s,spaceId:a},path:x,command:n?"listBefore":"listAfter",args:{...r?{after:r}:{},id:d}},{pointer:{table:t,id:d,spaceId:a},path:[],command:"set",args:{...j,...e}}]}]}),method:"POST"});return ae.errorId?ae:d},xt=async(e,{pageID:t=M(),spaceID:n=S()}={})=>{n=m(await n),t=m(t);let r=await p.getJSON("https://www.notion.so/api/v3/getUploadFileUrl",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({bucket:"secure",name:e.name,contentType:e.type,record:{table:"block",id:t,spaceId:n}})});return r.errorId?r:(fetch(r.signedPutUrl,{method:"PUT",headers:{"content-type":e.type},body:e}),r.url)},_t=(e,t,n="block")=>(e.startsWith("/")&&(e=`https://notion.so${e}`),e.includes("secure.notion-static.com")&&(e=new URL(e),e=`https://www.notion.so/signed/${encodeURIComponent(e.origin+e.pathname)}?table=${n}&id=${t}`),e);var h={};g(h,{is:()=>Tt,rgbContrast:()=>Et,rgbLogShade:()=>At,slugger:()=>$t,uuidv4:()=>jt});"use strict";var $t=(e,t=new Set)=>{e=e.replace(/\s/g,"-").replace(/[^A-Za-z0-9-_]/g,"").toLowerCase();let n=0,r=e;for(;t.has(r);)n++,r=`${e}-${n}`;return r},jt=()=>crypto?.randomUUID?crypto.randomUUID():([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,e=>(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)),At=(e,t)=>{var n=parseInt,r=Math.round,[s,c,t,a]=t.split(","),i=e<0,d=i?0:e*255**2,i=i?1+e:1-e;return"rgb"+(a?"a(":"(")+r((i*n(s[3]=="a"?s.slice(5):s.slice(4))**2+d)**.5)+","+r((i*n(c)**2+d)**.5)+","+r((i*n(t)**2+d)**.5)+(a?","+a:")")},Et=(e,t,n)=>Math.sqrt(.299*(e*e)+.587*(t*t)+.114*(n*n))>165.75?"rgb(0,0,0)":"rgb(255,255,255)",Lt={alphanumeric:/^[\w\.-]+$/,uuid:/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,semver:/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i,email:/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i,url:/^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,64}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i,color:/^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i};function St(e,t){let n=e.match(t);return!!(n&&n.length)}var Tt=async(e,t,{extension:n=""}={})=>{if(n=!e||!e.endsWith||e.endsWith(n),Array.isArray(t))return t.includes(e);switch(t){case"array":return Array.isArray(e);case"object":return e&&typeof e=="object"&&!Array.isArray(e);case"undefined":case"boolean":case"number":return typeof e===t&&n;case"string":return typeof e===t&&n;case"alphanumeric":case"uuid":case"semver":case"email":case"url":case"color":return typeof e=="string"&&St(e,Lt[t])&&n;case"file":return typeof e=="string"&&e&&await p.isFile(e)&&n}return!1};var y={};g(y,{core:()=>Ae,db:()=>Ut,enabled:()=>zt,errors:()=>Jt,get:()=>D,list:()=>Y,optionDefault:()=>Le,optionTypes:()=>Mt,profileDB:()=>X,profileName:()=>Ee,supportedEnvs:()=>qt});"use strict";var l=async(e,t,n,r,{extension:s="",error:c=`invalid ${t} (${s?`${s} `:""}${r}): ${JSON.stringify(n)}`,optional:a=!1}={})=>{let i;for(let d of Array.isArray(r)?[r]:r.split("|"))if(d==="file"?i=n&&!n.startsWith("http")?await h.is(`repo/${e._dir}/${n}`,d,{extension:s}):!1:i=await h.is(n,d,{extension:s}),i)break;return i||a&&await h.is(n,"undefined")?!0:(c&&e._err(c),!1)},kt=async e=>(e.environments=e.environments??y.supportedEnvs,await l(e,"environments",e.environments,"array")?e.environments.map(n=>l(e,"environments.env",n,y.supportedEnvs)):!1),Ct=async e=>{if(!await l(e,"tags",e.tags,"array"))return!1;let n=["core","extension","theme","integration"];if(!e.tags.filter(i=>n.includes(i)).length)return e._err(`invalid tags (must contain at least one of 'core', 'extension', 'theme' or 'integration'): +var ce=Object.defineProperty;var Ge=e=>ce(e,"__esModule",{value:!0});var g=(e,t)=>{Ge(e);for(var n in t)ce(e,n,{get:t[n],enumerable:!0})};g(exports,{components:()=>N,electron:()=>V,env:()=>E,fmt:()=>h,fs:()=>u,notion:()=>K,registry:()=>y,storage:()=>_,web:()=>o});var E={};g(E,{focusMenu:()=>De,focusNotion:()=>et,name:()=>Qe,notionRequire:()=>nt,reload:()=>tt,version:()=>Ye});"use strict";var le=globalThis.__enhancerElectronApi.platform,de=globalThis.__enhancerElectronApi.version,pe=globalThis.__enhancerElectronApi.focusMenu,ue=globalThis.__enhancerElectronApi.focusNotion,he=globalThis.__enhancerElectronApi.reload,fe=globalThis.__enhancerElectronApi.notionRequire;"use strict";var Qe=le,Ye=de,De=pe,et=ue,tt=he,nt=fe;var u={};g(u,{getJSON:()=>st,getText:()=>ot,isFile:()=>it,localPath:()=>rt,notionPath:()=>at});"use strict";var L=e=>`notion://www.notion.so/__notion-enhancer/${e}`,me=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.json());try{return globalThis.__enhancerElectronApi.nodeRequire(`notion-enhancer/${e}`)}catch{return fetch(L(e),t).then(r=>r.json())}},ye=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.text());try{let n=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:r}=globalThis.__enhancerElectronApi.nodeRequire("path");return n.readFileSync(r(`${__dirname}/../../${e}`))}catch{return fetch(L(e),t).then(r=>r.text())}},ge=async e=>{try{let t=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:n}=globalThis.__enhancerElectronApi.nodeRequire("path");if(e.startsWith("http"))await fetch(e);else try{t.existsSync(n(`${__dirname}/../../${e}`))}catch{await fetch(L(e))}return!0}catch{return!1}},ve=globalThis.__enhancerElectronApi.notionPath;"use strict";var rt=L,st=me,ot=ye,it=ge,at=ve;var _={};g(_,{addChangeListener:()=>pt,db:()=>dt,get:()=>ct,removeChangeListener:()=>ut,set:()=>lt});"use strict";var F=(e,t=void 0)=>globalThis.__enhancerElectronApi.db.get(e,t),H=(e,t)=>globalThis.__enhancerElectronApi.db.set(e,t),we=(e,t=F,n=H)=>(typeof e=="string"&&(e=[e]),{get:(r=[],s=void 0)=>t([...e,...r],s),set:(r,s)=>n([...e,...r],s)}),be=e=>globalThis.__enhancerElectronApi.db.addChangeListener(e),xe=e=>globalThis.__enhancerElectronApi.db.removeChangeListener(e);"use strict";var ct=F,lt=H,dt=we,pt=be,ut=xe;var V={};g(V,{browser:()=>ht,onMessage:()=>gt,sendMessage:()=>mt,sendMessageToHost:()=>yt,webFrame:()=>ft});"use strict";var ht=globalThis.__enhancerElectronApi?.browser,ft=globalThis.__enhancerElectronApi?.webFrame,mt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessage(e,t,n)},yt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessageToHost(e,t,n)},gt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.onMessage(e,t,n)};var K={};g(K,{create:()=>bt,get:()=>$e,getPageID:()=>M,getSpaceID:()=>S,getUserID:()=>_e,search:()=>vt,set:()=>wt,sign:()=>_t,upload:()=>xt});"use strict";var m=e=>(e?.length===32&&!e.includes("-")&&(e=e.replace(/([\d\w]{8})([\d\w]{4})([\d\w]{4})([\d\w]{4})([\d\w]{12})/,"$1-$2-$3-$4-$5")),e),_e=()=>JSON.parse(localStorage["LRU:KeyValueStore2:current-user-id"]||{}).value,M=()=>m(o.queryParams().get("p")||location.pathname.split(/(-|\/)/g).reverse()[0]),I,S=async()=>(I||(I=(await $e(M())).space_id),I),$e=async(e,t="block")=>(e=m(e),(await u.getJSON("https://www.notion.so/api/v3/getRecordValues",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requests:[{table:t,id:e}]}),method:"POST"})).results[0].value),vt=async(e="",t=20,n=S())=>(n=m(await n),await u.getJSON("https://www.notion.so/api/v3/search",{headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"BlocksInSpace",query:e,spaceId:n,limit:t,filters:{isDeletedOnly:!1,excludeTemplates:!1,isNavigableOnly:!1,requireEditPermissions:!1,ancestors:[],createdBy:[],editedBy:[],lastEditedTime:{},createdTime:{}},sort:"Relevance",source:"quick_find"}),method:"POST"})),wt=async({recordID:e,recordTable:t="block",spaceID:n=S(),path:r=[]},s={})=>{n=m(await n),e=m(e);let c=await u.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:n,operations:[{pointer:{table:t,id:e,spaceId:n},path:r,command:r.length?"set":"update",args:s}]}]}),method:"POST"});return c.errorId?c:!0},bt=async({recordValue:e={},recordTable:t="block"}={},{prepend:n=!1,siblingID:r=void 0,parentID:s=M(),parentTable:c="block",spaceID:a=S(),userID:i=_e()}={})=>{a=m(await a),s=m(s),r=m(r);let d=m(e?.id??h.uuidv4()),x=[],A={type:"text",id:d,version:0,created_time:new Date().getTime(),last_edited_time:new Date().getTime(),parent_id:s,parent_table:c,alive:!0,created_by_table:"notion_user",created_by_id:i,last_edited_by_table:"notion_user",last_edited_by_id:i,space_id:a,permissions:[{type:"user_permission",role:"editor",user_id:i}]};c==="space"?(s=a,A.parent_id=a,x.push("pages"),A.type="page"):c==="collection_view"?(x.push("page_sort"),A.type="page"):x.push("content");let ae=await u.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:a,operations:[{pointer:{table:c,id:s,spaceId:a},path:x,command:n?"listBefore":"listAfter",args:{...r?{after:r}:{},id:d}},{pointer:{table:t,id:d,spaceId:a},path:[],command:"set",args:{...A,...e}}]}]}),method:"POST"});return ae.errorId?ae:d},xt=async(e,{pageID:t=M(),spaceID:n=S()}={})=>{n=m(await n),t=m(t);let r=await u.getJSON("https://www.notion.so/api/v3/getUploadFileUrl",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({bucket:"secure",name:e.name,contentType:e.type,record:{table:"block",id:t,spaceId:n}})});return r.errorId?r:(fetch(r.signedPutUrl,{method:"PUT",headers:{"content-type":e.type},body:e}),r.url)},_t=(e,t,n="block")=>(e.startsWith("/")&&(e=`https://notion.so${e}`),e.includes("secure.notion-static.com")&&(e=new URL(e),e=`https://www.notion.so/signed/${encodeURIComponent(e.origin+e.pathname)}?table=${n}&id=${t}`),e);var h={};g(h,{is:()=>Tt,rgbContrast:()=>Et,rgbLogShade:()=>jt,slugger:()=>$t,uuidv4:()=>At});"use strict";var $t=(e,t=new Set)=>{e=e.replace(/\s/g,"-").replace(/[^A-Za-z0-9-_]/g,"").toLowerCase();let n=0,r=e;for(;t.has(r);)n++,r=`${e}-${n}`;return r},At=()=>crypto?.randomUUID?crypto.randomUUID():([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,e=>(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)),jt=(e,t)=>{var n=parseInt,r=Math.round,[s,c,t,a]=t.split(","),i=e<0,d=i?0:e*255**2,i=i?1+e:1-e;return"rgb"+(a?"a(":"(")+r((i*n(s[3]=="a"?s.slice(5):s.slice(4))**2+d)**.5)+","+r((i*n(c)**2+d)**.5)+","+r((i*n(t)**2+d)**.5)+(a?","+a:")")},Et=(e,t,n)=>Math.sqrt(.299*(e*e)+.587*(t*t)+.114*(n*n))>165.75?"rgb(0,0,0)":"rgb(255,255,255)",Lt={alphanumeric:/^[\w\.-]+$/,uuid:/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,semver:/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i,email:/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i,url:/^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,64}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i,color:/^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i};function St(e,t){let n=e.match(t);return!!(n&&n.length)}var Tt=async(e,t,{extension:n=""}={})=>{if(n=!e||!e.endsWith||e.endsWith(n),Array.isArray(t))return t.includes(e);switch(t){case"array":return Array.isArray(e);case"object":return e&&typeof e=="object"&&!Array.isArray(e);case"undefined":case"boolean":case"number":return typeof e===t&&n;case"string":return typeof e===t&&n;case"alphanumeric":case"uuid":case"semver":case"email":case"url":case"color":return typeof e=="string"&&St(e,Lt[t])&&n;case"file":return typeof e=="string"&&e&&await u.isFile(e)&&n}return!1};var y={};g(y,{core:()=>je,db:()=>Ut,enabled:()=>zt,errors:()=>Jt,get:()=>D,list:()=>Y,optionDefault:()=>Le,optionTypes:()=>Mt,profileDB:()=>X,profileName:()=>Ee,supportedEnvs:()=>qt});"use strict";var l=async(e,t,n,r,{extension:s="",error:c=`invalid ${t} (${s?`${s} `:""}${r}): ${JSON.stringify(n)}`,optional:a=!1}={})=>{let i;for(let d of Array.isArray(r)?[r]:r.split("|"))if(d==="file"?i=n&&!n.startsWith("http")?await h.is(`repo/${e._dir}/${n}`,d,{extension:s}):!1:i=await h.is(n,d,{extension:s}),i)break;return i||a&&await h.is(n,"undefined")?!0:(c&&e._err(c),!1)},kt=async e=>(e.environments=e.environments??y.supportedEnvs,await l(e,"environments",e.environments,"array")?e.environments.map(n=>l(e,"environments.env",n,y.supportedEnvs)):!1),Ct=async e=>{if(!await l(e,"tags",e.tags,"array"))return!1;let n=["core","extension","theme","integration"];if(!e.tags.filter(i=>n.includes(i)).length)return e._err(`invalid tags (must contain at least one of 'core', 'extension', 'theme' or 'integration'): ${JSON.stringify(e.tags)}`),!1;let s=e.tags.includes("theme"),c=e.tags.includes("light")||e.tags.includes("dark"),a=e.tags.includes("light")&&e.tags.includes("dark");return s&&(!c||a)?(e._err(`invalid tags (themes must be either 'light' or 'dark', not neither or both): - ${JSON.stringify(e.tags)}`),!1):e.tags.map(i=>l(e,"tags.tag",i,"string"))},Pt=async e=>await l(e,"authors",e.authors,"array")?e.authors.map(n=>[l(e,"authors.author.name",n.name,"string"),l(e,"authors.author.email",n.email,"email",{optional:!0}),l(e,"authors.author.homepage",n.homepage,"url"),l(e,"authors.author.avatar",n.avatar,"url")]):!1,Ot=async e=>{if(!await l(e,"css",e.css,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.css[r])continue;let s=await l(e,`css.${r}`,e.css[r],"array");s&&(s=e.css[r].map(c=>l(e,`css.${r}.file`,c,"file",{extension:".css"}))),n.push(s)}return n},Rt=async e=>{if(!await l(e,"js",e.js,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.js[r])continue;let s=await l(e,`js.${r}`,e.js[r],"array");s&&(s=e.js[r].map(c=>l(e,`js.${r}.file`,c,"file",{extension:".mjs"}))),n.push(s)}if(e.js.electron)if(await l(e,"js.electron",e.js.electron,"array"))for(let s of e.js.electron){if(!await l(e,"js.electron.file",s,"object")){n.push(!1);continue}n.push([l(e,"js.electron.file.source",s.source,"file",{extension:".cjs"}),l(e,"js.electron.file.target",s.target,"string",{extension:".js"})])}else n.push(!1);return n},Nt=async e=>{if(!await l(e,"options",e.options,"array"))return!1;let n=[];for(let r of e.options){let s="options.option";if(!await l(e,`${s}.type`,r.type,y.optionTypes)){n.push(!1);continue}switch(r.environments=r.environments??y.supportedEnvs,n.push([l(e,`${s}.key`,r.key,"alphanumeric"),l(e,`${s}.label`,r.label,"string"),l(e,`${s}.tooltip`,r.tooltip,"string",{optional:!0}),l(e,`${s}.environments`,r.environments,"array").then(a=>a?r.environments.map(i=>l(e,`${s}.environments.env`,i,y.supportedEnvs)):!1)]),r.type){case"toggle":n.push(l(e,`${s}.value`,r.value,"boolean"));break;case"select":{let a=await l(e,`${s}.values`,r.values,"array");a&&(a=r.values.map(i=>l(e,`${s}.values.value`,i,"string"))),n.push(a);break}case"text":case"hotkey":n.push(l(e,`${s}.value`,r.value,"string"));break;case"number":case"color":n.push(l(e,`${s}.value`,r.value,r.type));break;case"file":{let a=await l(e,`${s}.extensions`,r.extensions,"array");a&&(a=r.extensions.map(i=>l(e,`${s}.extensions.extension`,i,"string"))),n.push(a);break}}}return n};async function je(e){let t=[l(e,"name",e.name,"string"),l(e,"id",e.id,"uuid"),l(e,"version",e.version,"semver"),kt(e),l(e,"description",e.description,"string"),l(e,"preview",e.preview,"file|url",{optional:!0}),Ct(e),Pt(e),Ot(e),Rt(e),Nt(e)];do t=await Promise.all(t.flat(1/0));while(t.some(n=>Array.isArray(n)));return t.every(n=>n)}"use strict";var Ae=["a6621988-551d-495a-97d8-3c568bca2e9e","0f0bf8b6-eae6-4273-b307-8fc43f2ee082","36a2ffc9-27ff-480e-84a7-c7700a7d232d"],qt=["linux","win32","darwin","extension"],Mt=["toggle","select","text","number","color","file","hotkey"],Ee=async()=>_.get(["currentprofile"],"default"),X=async()=>_.db(["profiles",await Ee()]),G,Q=[],Y=async(e=t=>!0)=>{G||(G=new Promise(async(n,r)=>{let s=[];for(let c of await p.getJSON("repo/registry.json"))try{let a={...await p.getJSON(`repo/${c}/mod.json`),_dir:c,_err:i=>Q.push({source:c,message:i})};await je(a)&&s.push(a)}catch{Q.push({source:c,message:"invalid mod.json"})}n(s)}));let t=[];for(let n of await G)await e(n)&&t.push(n);return t},Jt=async()=>(await Y(),Q),D=async e=>(await Y(t=>t.id===e))[0],zt=async e=>(await D(e)).environments.includes(E.name)?Ae.includes(e)?!0:(await X()).get(["_mods",e],!1):!1,Le=async(e,t)=>{let n=await D(e),r=n.options.find(s=>s.key===t);if(!!r)switch(r.type){case"toggle":case"text":case"number":case"color":case"hotkey":return r.value;case"select":return r.values[0];case"file":return}},Ut=async e=>{let t=await X();return _.db([e],async(n,r=void 0)=>(typeof n=="string"&&(n=[n]),n.length===2&&(r=await Le(e,n[1])??r),t.get(n,r)),t.set)};var o={};g(o,{addDocumentObserver:()=>Xt,addHotkeyListener:()=>It,copyToClipboard:()=>Ht,empty:()=>Wt,escape:()=>Te,html:()=>Ce,loadStylesheet:()=>Ft,queryParams:()=>Bt,raw:()=>ke,readFromClipboard:()=>Vt,removeDocumentObserver:()=>Gt,removeHotkeyListener:()=>Kt,render:()=>Pe,whenReady:()=>Zt});"use strict";var Se=!1,T=[],ee,J=[],te=[],Zt=(e=[])=>new Promise((t,n)=>{let r=()=>{let s=setInterval(c,100);function c(){!e.every(i=>document.querySelector(i))||(clearInterval(s),t(!0))}c()};document.readyState!=="complete"?document.addEventListener("readystatechange",s=>{document.readyState==="complete"&&r()}):r()}),Bt=()=>new URLSearchParams(window.location.search),Te=e=>e.replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\\/g,"\"),ke=(e,...t)=>{let n=e.map(r=>r+(["string","number"].includes(typeof t[0])?t.shift():Te(JSON.stringify(t.shift(),null,2)??""))).join("");return n.includes("r.trim()).filter(r=>r.length).join(" ")},Ce=(e,...t)=>{let n=document.createRange().createContextualFragment(ke(e,...t));return n.children.length===1?n.children[0]:n.children},Pe=(e,...t)=>(t=t.map(n=>n instanceof HTMLCollection?[...n]:n).flat(1/0).filter(n=>n),e.append(...t),e),Wt=e=>{for(;e.firstChild&&e.removeChild(e.firstChild););return e},Ft=e=>{let t=Ce`l(e,"tags.tag",i,"string"))},Pt=async e=>await l(e,"authors",e.authors,"array")?e.authors.map(n=>[l(e,"authors.author.name",n.name,"string"),l(e,"authors.author.email",n.email,"email",{optional:!0}),l(e,"authors.author.homepage",n.homepage,"url"),l(e,"authors.author.avatar",n.avatar,"url")]):!1,Ot=async e=>{if(!await l(e,"css",e.css,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.css[r])continue;let s=await l(e,`css.${r}`,e.css[r],"array");s&&(s=e.css[r].map(c=>l(e,`css.${r}.file`,c,"file",{extension:".css"}))),n.push(s)}return n},Rt=async e=>{if(!await l(e,"js",e.js,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.js[r])continue;let s=await l(e,`js.${r}`,e.js[r],"array");s&&(s=e.js[r].map(c=>l(e,`js.${r}.file`,c,"file",{extension:".mjs"}))),n.push(s)}if(e.js.electron)if(await l(e,"js.electron",e.js.electron,"array"))for(let s of e.js.electron){if(!await l(e,"js.electron.file",s,"object")){n.push(!1);continue}n.push([l(e,"js.electron.file.source",s.source,"file",{extension:".cjs"}),l(e,"js.electron.file.target",s.target,"string",{extension:".js"})])}else n.push(!1);return n},Nt=async e=>{if(!await l(e,"options",e.options,"array"))return!1;let n=[];for(let r of e.options){let s="options.option";if(!await l(e,`${s}.type`,r.type,y.optionTypes)){n.push(!1);continue}switch(r.environments=r.environments??y.supportedEnvs,n.push([l(e,`${s}.key`,r.key,"alphanumeric"),l(e,`${s}.label`,r.label,"string"),l(e,`${s}.tooltip`,r.tooltip,"string",{optional:!0}),l(e,`${s}.environments`,r.environments,"array").then(a=>a?r.environments.map(i=>l(e,`${s}.environments.env`,i,y.supportedEnvs)):!1)]),r.type){case"toggle":n.push(l(e,`${s}.value`,r.value,"boolean"));break;case"select":{let a=await l(e,`${s}.values`,r.values,"array");a&&(a=r.values.map(i=>l(e,`${s}.values.value`,i,"string"))),n.push(a);break}case"text":case"hotkey":n.push(l(e,`${s}.value`,r.value,"string"));break;case"number":case"color":n.push(l(e,`${s}.value`,r.value,r.type));break;case"file":{let a=await l(e,`${s}.extensions`,r.extensions,"array");a&&(a=r.extensions.map(i=>l(e,`${s}.extensions.extension`,i,"string"))),n.push(a);break}}}return n};async function Ae(e){let t=[l(e,"name",e.name,"string"),l(e,"id",e.id,"uuid"),l(e,"version",e.version,"semver"),kt(e),l(e,"description",e.description,"string"),l(e,"preview",e.preview,"file|url",{optional:!0}),Ct(e),Pt(e),Ot(e),Rt(e),Nt(e)];do t=await Promise.all(t.flat(1/0));while(t.some(n=>Array.isArray(n)));return t.every(n=>n)}"use strict";var je=["a6621988-551d-495a-97d8-3c568bca2e9e","0f0bf8b6-eae6-4273-b307-8fc43f2ee082","36a2ffc9-27ff-480e-84a7-c7700a7d232d"],qt=["linux","win32","darwin","extension"],Mt=["toggle","select","text","number","color","file","hotkey"],Ee=async()=>_.get(["currentprofile"],"default"),X=async()=>_.db(["profiles",await Ee()]),G,Q=[],Y=async(e=t=>!0)=>{G||(G=new Promise(async(n,r)=>{let s=[];for(let c of await u.getJSON("repo/registry.json"))try{let a={...await u.getJSON(`repo/${c}/mod.json`),_dir:c,_err:i=>Q.push({source:c,message:i})};await Ae(a)&&s.push(a)}catch{Q.push({source:c,message:"invalid mod.json"})}n(s)}));let t=[];for(let n of await G)await e(n)&&t.push(n);return t},Jt=async()=>(await Y(),Q),D=async e=>(await Y(t=>t.id===e))[0],zt=async e=>(await D(e)).environments.includes(E.name)?je.includes(e)?!0:(await X()).get(["_mods",e],!1):!1,Le=async(e,t)=>{let n=await D(e),r=n.options.find(s=>s.key===t);if(!!r)switch(r.type){case"toggle":case"text":case"number":case"color":case"hotkey":return r.value;case"select":return r.values[0];case"file":return}},Ut=async e=>{let t=await X();return _.db([e],async(n,r=void 0)=>(typeof n=="string"&&(n=[n]),n.length===2&&(r=await Le(e,n[1])??r),t.get(n,r)),t.set)};var o={};g(o,{addDocumentObserver:()=>Xt,addHotkeyListener:()=>It,copyToClipboard:()=>Ht,empty:()=>Wt,escape:()=>Te,html:()=>Ce,loadStylesheet:()=>Ft,queryParams:()=>Bt,raw:()=>ke,readFromClipboard:()=>Vt,removeDocumentObserver:()=>Gt,removeHotkeyListener:()=>Kt,render:()=>Pe,whenReady:()=>Zt});"use strict";var Se=!1,T=[],ee,J=[],te=[],Zt=(e=[])=>new Promise((t,n)=>{let r=()=>{let s=setInterval(c,100);function c(){!e.every(i=>document.querySelector(i))||(clearInterval(s),t(!0))}c()};document.readyState!=="complete"?document.addEventListener("readystatechange",s=>{document.readyState==="complete"&&r()}):r()}),Bt=()=>new URLSearchParams(window.location.search),Te=e=>e.replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\\/g,"\"),ke=(e,...t)=>{let n=e.map(r=>r+(["string","number"].includes(typeof t[0])?t.shift():Te(JSON.stringify(t.shift(),null,2)??""))).join("");return n.includes("r.trim()).filter(r=>r.length).join(" ")},Ce=(e,...t)=>{let n=document.createRange().createContextualFragment(ke(e,...t));return n.children.length===1?n.children[0]:n.children},Pe=(e,...t)=>(t=t.map(n=>n instanceof HTMLCollection?[...n]:n).flat(1/0).filter(n=>n),e.append(...t),e),Wt=e=>{for(;e.firstChild&&e.removeChild(e.firstChild););return e},Ft=e=>{let t=Ce``;return Pe(document.head,t),t},Ht=async e=>{try{await navigator.clipboard.writeText(e)}catch{let t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}},Vt=()=>navigator.clipboard.readText(),Oe=(e,t)=>{if(document.activeElement.nodeName==="INPUT"&&!t.listenInInput)return;t.keys.every(s=>{s=s.toLowerCase();let c={metaKey:["meta","os","win","cmd","command"],ctrlKey:["ctrl","control"],shiftKey:["shift"],altKey:["alt"]};for(let a in c)if(c[a].includes(s)&&e[a])return!0;if(s==="space"&&(s=" "),s==="plus"&&(s="+"),s===e.key.toLowerCase())return!0})&&t.callback(e)},It=(e,t,{listenInInput:n=!1,keydown:r=!1}={})=>{typeof e=="string"&&(e=e.split("+")),T.push({keys:e,callback:t,listenInInput:n,keydown:r}),Se||(Se=!0,document.addEventListener("keyup",s=>{for(let c of T.filter(({keydown:a})=>!a))Oe(s,c)}),document.addEventListener("keydown",s=>{for(let c of T.filter(({keydown:a})=>a))Oe(s,c)}))},Kt=e=>{T=T.filter(t=>t.callback!==e)},Xt=(e,t=[])=>{if(!ee){let n=r=>{for(;r.length;){let s=r.shift(),c=(i,d)=>i instanceof Element&&(i.matches(d)||i.matches(`${d} *`)||i.querySelector(d)),a=i=>s.target.matches(i)||s.target.matches(`${i} *`)||[...s.addedNodes].some(d=>c(d,i));for(let i of J)(!i.selectors.length||i.selectors.some(a))&&i.callback(s)}};ee=new MutationObserver((r,s)=>{te.length||requestIdleCallback(()=>n(te)),te.push(...r)}),ee.observe(document.body,{childList:!0,subtree:!0,attributes:!0})}J.push({callback:e,selectors:t})},Gt=e=>{J=J.filter(t=>t.callback!==e)};var N={};g(N,{addCornerAction:()=>Xe,addPanelView:()=>Ie,addTooltip:()=>Ne,feather:()=>qe});"use strict";var Re,u,Qt=e=>[...e.getClientRects()].reduce((t,n)=>t.some(r=>r.y===n.y)?t:[...t,n],[]).length,Yt=async(e,t,n)=>{u.style.top="0px",u.style.left="0px";let r=e.getBoundingClientRect(),{offsetWidth:s,offsetHeight:c}=u,a=6,i=r.x,d=Math.floor(r.y);if(["top","bottom"].includes(t)){t==="top"&&(d-=c+a),t==="bottom"&&(d+=r.height+a),i-=s/2-r.width/2,u.style.left=`${i}px`,u.style.top=`${d}px`;let x=()=>Qt(u.firstElementChild)>n,j=x();for(;x();)u.style.left=`${window.innerWidth-i>i?i++:i--}px`;j&&(i+=window.innerWidth-i>i?a:-a,u.style.left=`${i}px`)}return["left","right"].includes(t)&&(d-=c/2-r.height/2,t==="left"&&(i-=s+a),t==="right"&&(i+=r.width+a),u.style.left=`${i}px`,u.style.top=`${d}px`),!0},Ne=async(e,t,{delay:n=100,offsetDirection:r="bottom",maxLines:s=1}={})=>{Re||(Re=o.loadStylesheet("api/components/tooltip.css"),u=o.html`
`,o.render(document.body,u)),globalThis.markdownit||await import(p.localPath("dep/markdown-it.min.js"));let c=markdownit({linkify:!0});t instanceof Element||(t=o.html`
+ href="${e.startsWith("https://")?e:u.localPath(e)}" + />`;return Pe(document.head,t),t},Ht=async e=>{try{await navigator.clipboard.writeText(e)}catch{let t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}},Vt=()=>navigator.clipboard.readText(),Oe=(e,t)=>{if(document.activeElement.nodeName==="INPUT"&&!t.listenInInput)return;t.keys.every(s=>{s=s.toLowerCase();let c={metaKey:["meta","os","win","cmd","command"],ctrlKey:["ctrl","control"],shiftKey:["shift"],altKey:["alt"]};for(let a in c)if(c[a].includes(s)&&e[a])return!0;if(s==="space"&&(s=" "),s==="plus"&&(s="+"),s===e.key.toLowerCase())return!0})&&t.callback(e)},It=(e,t,{listenInInput:n=!1,keydown:r=!1}={})=>{typeof e=="string"&&(e=e.split("+")),T.push({keys:e,callback:t,listenInInput:n,keydown:r}),Se||(Se=!0,document.addEventListener("keyup",s=>{for(let c of T.filter(({keydown:a})=>!a))Oe(s,c)}),document.addEventListener("keydown",s=>{for(let c of T.filter(({keydown:a})=>a))Oe(s,c)}))},Kt=e=>{T=T.filter(t=>t.callback!==e)},Xt=(e,t=[])=>{if(!ee){let n=r=>{for(;r.length;){let s=r.shift(),c=(i,d)=>i instanceof Element&&(i.matches(d)||i.matches(`${d} *`)||i.querySelector(d)),a=i=>s.target.matches(i)||s.target.matches(`${i} *`)||[...s.addedNodes].some(d=>c(d,i));for(let i of J)(!i.selectors.length||i.selectors.some(a))&&i.callback(s)}};ee=new MutationObserver((r,s)=>{te.length||requestIdleCallback(()=>n(te)),te.push(...r)}),ee.observe(document.body,{childList:!0,subtree:!0,attributes:!0})}J.push({callback:e,selectors:t})},Gt=e=>{J=J.filter(t=>t.callback!==e)};var N={};g(N,{addCornerAction:()=>Xe,addPanelView:()=>Ie,addTooltip:()=>Ne,feather:()=>qe});"use strict";var Re,p,Qt=e=>[...e.getClientRects()].reduce((t,n)=>t.some(r=>r.y===n.y)?t:[...t,n],[]).length,Yt=async(e,t,n)=>{p.style.top="0px",p.style.left="0px";let r=e.getBoundingClientRect(),{offsetWidth:s,offsetHeight:c}=p,a=6,i=r.x,d=Math.floor(r.y);if(["top","bottom"].includes(t)){t==="top"&&(d-=c+a),t==="bottom"&&(d+=r.height+a),i-=s/2-r.width/2,p.style.left=`${i}px`,p.style.top=`${d}px`;let x=()=>Qt(p.firstElementChild)>n,A=x();for(;x();)p.style.left=`${window.innerWidth-i>i?i++:i--}px`;A&&(i+=window.innerWidth-i>i?a:-a,p.style.left=`${i}px`),p.style.textAlign="center"}return["left","right"].includes(t)&&(d-=c/2-r.height/2,t==="left"&&(i-=s+a),t==="right"&&(i+=r.width+a),p.style.left=`${i}px`,p.style.top=`${d}px`,p.style.textAlign="auto"),!0},Ne=async(e,t,{delay:n=100,offsetDirection:r="bottom",maxLines:s=1}={})=>{Re||(Re=o.loadStylesheet("api/components/tooltip.css"),p=o.html`
`,o.render(document.body,p)),globalThis.markdownit||await import(u.localPath("dep/markdown-it.min.js"));let c=markdownit({linkify:!0});t instanceof Element||(t=o.html`
${t.split(` `).map(i=>c.renderInline(i)).join("
")} -
`);let a;e.addEventListener("mouseover",async i=>{a||(a=setTimeout(async()=>{e.matches(":hover")&&u.style.display!=="block"&&(u.style.display="block",o.render(o.empty(u),t),Yt(e,r,s),await u.animate([{opacity:0},{opacity:1}],{duration:65}).finished),a=void 0},n))}),e.addEventListener("mouseout",async i=>{a=void 0,u.style.display==="block"&&!e.matches(":hover")&&(await u.animate([{opacity:1},{opacity:0}],{duration:65}).finished,u.style.display="")})};"use strict";var ne,qe=async(e,t={})=>(ne||(ne=o.html`${await p.getText("dep/feather-sprite.svg")}`),t.style=((t.style?t.style+";":"")+"stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;fill:none;").trim(),t.viewBox="0 0 24 24",``${o.escape(n)}="${o.escape(r)}"`).join(" ")}>${ne.getElementById(e)?.innerHTML}`);"use strict";var k=[],Dt=o.raw` +
`);let a;e.addEventListener("mouseover",async i=>{a||(a=setTimeout(async()=>{e.matches(":hover")&&p.style.display!=="block"&&(p.style.display="block",o.render(o.empty(p),t),Yt(e,r,s),await p.animate([{opacity:0},{opacity:1}],{duration:65}).finished),a=void 0},n))}),e.addEventListener("mouseout",async i=>{a=void 0,p.style.display==="block"&&!e.matches(":hover")&&(await p.animate([{opacity:1},{opacity:0}],{duration:65}).finished,p.style.display="")})};"use strict";var ne,qe=async(e,t={})=>(ne||(ne=o.html`${await u.getText("dep/feather-sprite.svg")}`),t.style=((t.style?t.style+";":"")+"stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;fill:none;").trim(),t.viewBox="0 0 24 24",``${o.escape(n)}="${o.escape(r)}"`).join(" ")}>${ne.getElementById(e)?.innerHTML}`);"use strict";var k=[],Dt=o.raw` - `,Me,w,z,v,$,C,A,Je,ze,re,f,Ue,U,P,Z,se,b,oe,O,B="data-enhancer-panel-pinned",W=()=>$.hasAttribute(B),R=()=>{let e=[z,v,C,$].filter(t=>t);if(W()){ie();for(let t of e)t.removeAttribute(B)}else for(let t of e)t.setAttribute(B,"true");w.set(["panel.pinned"],W())},Ze=async()=>{document.documentElement.style.setProperty("--component--panel-width",f+"px"),w.set(["panel.width"],f)},Be=e=>{e.preventDefault(),re=!0,f=ze+(Je-e.clientX),f<190&&(f=190),f>480&&(f=480),$.style.width=f+"px",C.style.width=f+"px",z.style.paddingRight=f+"px",v&&(v.style.right=f+"px")},We=e=>{$.style.width="",C.style.width="",z.style.paddingRight="",v&&(v.style.right=""),Ze(),A.style.cursor="",document.body.removeEventListener("mousemove",Be),document.body.removeEventListener("mouseup",We)},en=e=>{Je=e.clientX,ze=f,A.style.cursor="auto",document.body.addEventListener("mousemove",Be),document.body.addEventListener("mouseup",We)},tn=()=>document.body.contains(b),Fe=()=>{if(!W())return R();o.render(Ue,O),o.empty(b);for(let t of k){let n=P.contains(t.$title),r=o.render(o.html`
`,o.render(o.html``,t.$icon.cloneNode(!0),t.$title.cloneNode(!0)));r.addEventListener("click",()=>{Ve(t),w.set(["panel.open"],t.id)}),o.render(b,r)}let e=Z.getBoundingClientRect();o.render(o.empty(O),o.render(o.html`
`,o.render(o.html`
`,b))),b.querySelector("[data-open]").focus(),b.animate([{opacity:0},{opacity:1}],{duration:200}),document.addEventListener("keydown",He)},ie=()=>{document.removeEventListener("keydown",He),b.animate([{opacity:1},{opacity:0}],{duration:200}).onfinish=()=>O.remove()},He=e=>{if(tn())switch(e.key){case"Escape":ie(),e.stopPropagation();break;case"Enter":document.activeElement.click(),e.stopPropagation();break;case"ArrowUp":(e.target.previousElementSibling||e.target.parentElement.lastElementChild).focus(),e.stopPropagation();break;case"ArrowDown":(e.target.nextElementSibling||e.target.parentElement.firstElementChild).focus(),e.stopPropagation();break}},Ve=e=>{let t=k.find(({$content:n})=>document.contains(n));o.render(o.empty(P),o.render(o.html``,e.$icon,e.$title)),e.onFocus(),o.render(o.empty(se),e.$content),t&&t.onBlur()};async function nn(){await o.whenReady([".notion-frame"]),z=document.querySelector(".notion-frame"),$=o.html`
`,C=o.html`
`,A=o.html`
`,P=o.html`
`,Z=o.render(o.html`
`,P),se=o.html`
`,b=o.html`
`,oe=o.html`
+ `,Me,w,z,v,$,C,j,Je,ze,re,f,Ue,U,P,Z,se,b,oe,O,B="data-enhancer-panel-pinned",W=()=>$.hasAttribute(B),R=()=>{let e=[z,v,C,$].filter(t=>t);if(W()){ie();for(let t of e)t.removeAttribute(B)}else for(let t of e)t.setAttribute(B,"true");w.set(["panel.pinned"],W())},Ze=async()=>{document.documentElement.style.setProperty("--component--panel-width",f+"px"),w.set(["panel.width"],f)},Be=e=>{e.preventDefault(),re=!0,f=ze+(Je-e.clientX),f<190&&(f=190),f>480&&(f=480),$.style.width=f+"px",C.style.width=f+"px",z.style.paddingRight=f+"px",v&&(v.style.right=f+"px")},We=e=>{$.style.width="",C.style.width="",z.style.paddingRight="",v&&(v.style.right=""),Ze(),j.style.cursor="",document.body.removeEventListener("mousemove",Be),document.body.removeEventListener("mouseup",We)},en=e=>{Je=e.clientX,ze=f,j.style.cursor="auto",document.body.addEventListener("mousemove",Be),document.body.addEventListener("mouseup",We)},tn=()=>document.body.contains(b),Fe=()=>{if(!W())return R();o.render(Ue,O),o.empty(b);for(let t of k){let n=P.contains(t.$title),r=o.render(o.html`
`,o.render(o.html``,t.$icon.cloneNode(!0),t.$title.cloneNode(!0)));r.addEventListener("click",()=>{Ve(t),w.set(["panel.open"],t.id)}),o.render(b,r)}let e=Z.getBoundingClientRect();o.render(o.empty(O),o.render(o.html`
`,o.render(o.html`
`,b))),b.querySelector("[data-open]").focus(),b.animate([{opacity:0},{opacity:1}],{duration:200}),document.addEventListener("keydown",He)},ie=()=>{document.removeEventListener("keydown",He),b.animate([{opacity:1},{opacity:0}],{duration:200}).onfinish=()=>O.remove()},He=e=>{if(tn())switch(e.key){case"Escape":ie(),e.stopPropagation();break;case"Enter":document.activeElement.click(),e.stopPropagation();break;case"ArrowUp":(e.target.previousElementSibling||e.target.parentElement.lastElementChild).focus(),e.stopPropagation();break;case"ArrowDown":(e.target.nextElementSibling||e.target.parentElement.firstElementChild).focus(),e.stopPropagation();break}},Ve=e=>{let t=k.find(({$content:n})=>document.contains(n));o.render(o.empty(P),o.render(o.html``,e.$icon,e.$title)),e.onFocus(),o.render(o.empty(se),e.$content),t&&t.onBlur()};async function nn(){await o.whenReady([".notion-frame"]),z=document.querySelector(".notion-frame"),$=o.html`
`,C=o.html`
`,j=o.html`
`,P=o.html`
`,Z=o.render(o.html`
`,P),se=o.html`
`,b=o.html`
`,oe=o.html`
${Dt} -
`,O=o.html`
`;let e='.notion-cursor-listener > div[style*="flex-end"]',t=()=>{document.contains(v)||(v=document.querySelector(e),W()&&v&&v.setAttribute(B,"true"))};v=document.querySelector(e),o.addDocumentObserver(t,[e]),await w.get(["panel.pinned"])&&R(),o.addHotkeyListener(await w.get(["panel.hotkey"]),R),U.addEventListener("click",r=>{r.stopPropagation(),R()}),o.render($,o.render(Z,P,oe,U),se,A),await rn(),await sn();let n='.notion-cursor-listener > .notion-sidebar-container ~ [style^="position: absolute"]';await o.whenReady([n]),document.querySelector(n).before(C,$)}async function rn(){f=await w.get(["panel.width"],240),Ze(),A.addEventListener("mousedown",en),A.addEventListener("click",()=>{re?re=!1:R()})}async function sn(){Ue=document.querySelector(".notion-app-inner"),Z.addEventListener("click",Fe),oe.addEventListener("click",Fe),O.addEventListener("click",ie)}var Ie=async({id:e,icon:t,title:n,$content:r,onFocus:s=()=>{},onBlur:c=()=>{}})=>{Me||(Me=o.loadStylesheet("api/components/panel.css")),w||(w=await y.db("36a2ffc9-27ff-480e-84a7-c7700a7d232d")),U||(U=o.html`
+
`,O=o.html`
`;let e='.notion-cursor-listener > div[style*="flex-end"]',t=()=>{document.contains(v)||(v=document.querySelector(e),W()&&v&&v.setAttribute(B,"true"))};v=document.querySelector(e),o.addDocumentObserver(t,[e]),await w.get(["panel.pinned"])&&R(),o.addHotkeyListener(await w.get(["panel.hotkey"]),R),U.addEventListener("click",r=>{r.stopPropagation(),R()}),o.render($,o.render(Z,P,oe,U),se,j),await rn(),await sn();let n='.notion-cursor-listener > .notion-sidebar-container ~ [style^="position: absolute"]';await o.whenReady([n]),document.querySelector(n).before(C,$)}async function rn(){f=await w.get(["panel.width"],240),Ze(),j.addEventListener("mousedown",en),j.addEventListener("click",()=>{re?re=!1:R()})}async function sn(){Ue=document.querySelector(".notion-app-inner"),Z.addEventListener("click",Fe),oe.addEventListener("click",Fe),O.addEventListener("click",ie)}var Ie=async({id:e,icon:t,title:n,$content:r,onFocus:s=()=>{},onBlur:c=()=>{}})=>{Me||(Me=o.loadStylesheet("api/components/panel.css")),w||(w=await y.db("36a2ffc9-27ff-480e-84a7-c7700a7d232d")),U||(U=o.html`
${await N.feather("chevrons-right")}
`);let a={id:e,$icon:o.render(o.html``,t instanceof Element?t:o.html`${t}`),$title:o.render(o.html``,n),$content:r,onFocus:s,onBlur:c};k.push(a),k.length===1&&await nn(),(k.length===1||await w.get(["panel.open"])===e)&&Ve(a)};"use strict";var Ke,q,Xe=async(e,t)=>{Ke||(Ke=o.loadStylesheet("api/components/corner-action.css"),q=o.html`
`),await o.whenReady([".notion-help-button"]);let n=document.querySelector(".notion-help-button"),r=document.querySelector(".onboarding-checklist-button");r&&q.prepend(r),q.prepend(n),o.render(document.querySelector(".notion-app-inner > .notion-cursor-listener"),q);let s=o.html`
${e}
`;return s.addEventListener("click",t),o.render(q,s),s};"use strict";"use strict"; From 8cc057a5f81709959343cf594d325684cd33d8fc Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Sat, 11 Dec 2021 18:03:05 +1100 Subject: [PATCH 46/52] tooltip text-align: auto -> start --- api/components/tooltip.mjs | 2 +- api/index.cjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/components/tooltip.mjs b/api/components/tooltip.mjs index 11cc418..f78cdc7 100644 --- a/api/components/tooltip.mjs +++ b/api/components/tooltip.mjs @@ -53,7 +53,7 @@ const countLines = ($el) => if (offsetDirection === 'right') x += rect.width + pad; _$tooltip.style.left = `${x}px`; _$tooltip.style.top = `${y}px`; - _$tooltip.style.textAlign = 'auto'; + _$tooltip.style.textAlign = 'start'; } return true; diff --git a/api/index.cjs b/api/index.cjs index de549b0..aaea141 100644 --- a/api/index.cjs +++ b/api/index.cjs @@ -3,7 +3,7 @@ var ce=Object.defineProperty;var Ge=e=>ce(e,"__esModule",{value:!0});var g=(e,t) ${JSON.stringify(e.tags)}`),!1):e.tags.map(i=>l(e,"tags.tag",i,"string"))},Pt=async e=>await l(e,"authors",e.authors,"array")?e.authors.map(n=>[l(e,"authors.author.name",n.name,"string"),l(e,"authors.author.email",n.email,"email",{optional:!0}),l(e,"authors.author.homepage",n.homepage,"url"),l(e,"authors.author.avatar",n.avatar,"url")]):!1,Ot=async e=>{if(!await l(e,"css",e.css,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.css[r])continue;let s=await l(e,`css.${r}`,e.css[r],"array");s&&(s=e.css[r].map(c=>l(e,`css.${r}.file`,c,"file",{extension:".css"}))),n.push(s)}return n},Rt=async e=>{if(!await l(e,"js",e.js,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.js[r])continue;let s=await l(e,`js.${r}`,e.js[r],"array");s&&(s=e.js[r].map(c=>l(e,`js.${r}.file`,c,"file",{extension:".mjs"}))),n.push(s)}if(e.js.electron)if(await l(e,"js.electron",e.js.electron,"array"))for(let s of e.js.electron){if(!await l(e,"js.electron.file",s,"object")){n.push(!1);continue}n.push([l(e,"js.electron.file.source",s.source,"file",{extension:".cjs"}),l(e,"js.electron.file.target",s.target,"string",{extension:".js"})])}else n.push(!1);return n},Nt=async e=>{if(!await l(e,"options",e.options,"array"))return!1;let n=[];for(let r of e.options){let s="options.option";if(!await l(e,`${s}.type`,r.type,y.optionTypes)){n.push(!1);continue}switch(r.environments=r.environments??y.supportedEnvs,n.push([l(e,`${s}.key`,r.key,"alphanumeric"),l(e,`${s}.label`,r.label,"string"),l(e,`${s}.tooltip`,r.tooltip,"string",{optional:!0}),l(e,`${s}.environments`,r.environments,"array").then(a=>a?r.environments.map(i=>l(e,`${s}.environments.env`,i,y.supportedEnvs)):!1)]),r.type){case"toggle":n.push(l(e,`${s}.value`,r.value,"boolean"));break;case"select":{let a=await l(e,`${s}.values`,r.values,"array");a&&(a=r.values.map(i=>l(e,`${s}.values.value`,i,"string"))),n.push(a);break}case"text":case"hotkey":n.push(l(e,`${s}.value`,r.value,"string"));break;case"number":case"color":n.push(l(e,`${s}.value`,r.value,r.type));break;case"file":{let a=await l(e,`${s}.extensions`,r.extensions,"array");a&&(a=r.extensions.map(i=>l(e,`${s}.extensions.extension`,i,"string"))),n.push(a);break}}}return n};async function Ae(e){let t=[l(e,"name",e.name,"string"),l(e,"id",e.id,"uuid"),l(e,"version",e.version,"semver"),kt(e),l(e,"description",e.description,"string"),l(e,"preview",e.preview,"file|url",{optional:!0}),Ct(e),Pt(e),Ot(e),Rt(e),Nt(e)];do t=await Promise.all(t.flat(1/0));while(t.some(n=>Array.isArray(n)));return t.every(n=>n)}"use strict";var je=["a6621988-551d-495a-97d8-3c568bca2e9e","0f0bf8b6-eae6-4273-b307-8fc43f2ee082","36a2ffc9-27ff-480e-84a7-c7700a7d232d"],qt=["linux","win32","darwin","extension"],Mt=["toggle","select","text","number","color","file","hotkey"],Ee=async()=>_.get(["currentprofile"],"default"),X=async()=>_.db(["profiles",await Ee()]),G,Q=[],Y=async(e=t=>!0)=>{G||(G=new Promise(async(n,r)=>{let s=[];for(let c of await u.getJSON("repo/registry.json"))try{let a={...await u.getJSON(`repo/${c}/mod.json`),_dir:c,_err:i=>Q.push({source:c,message:i})};await Ae(a)&&s.push(a)}catch{Q.push({source:c,message:"invalid mod.json"})}n(s)}));let t=[];for(let n of await G)await e(n)&&t.push(n);return t},Jt=async()=>(await Y(),Q),D=async e=>(await Y(t=>t.id===e))[0],zt=async e=>(await D(e)).environments.includes(E.name)?je.includes(e)?!0:(await X()).get(["_mods",e],!1):!1,Le=async(e,t)=>{let n=await D(e),r=n.options.find(s=>s.key===t);if(!!r)switch(r.type){case"toggle":case"text":case"number":case"color":case"hotkey":return r.value;case"select":return r.values[0];case"file":return}},Ut=async e=>{let t=await X();return _.db([e],async(n,r=void 0)=>(typeof n=="string"&&(n=[n]),n.length===2&&(r=await Le(e,n[1])??r),t.get(n,r)),t.set)};var o={};g(o,{addDocumentObserver:()=>Xt,addHotkeyListener:()=>It,copyToClipboard:()=>Ht,empty:()=>Wt,escape:()=>Te,html:()=>Ce,loadStylesheet:()=>Ft,queryParams:()=>Bt,raw:()=>ke,readFromClipboard:()=>Vt,removeDocumentObserver:()=>Gt,removeHotkeyListener:()=>Kt,render:()=>Pe,whenReady:()=>Zt});"use strict";var Se=!1,T=[],ee,J=[],te=[],Zt=(e=[])=>new Promise((t,n)=>{let r=()=>{let s=setInterval(c,100);function c(){!e.every(i=>document.querySelector(i))||(clearInterval(s),t(!0))}c()};document.readyState!=="complete"?document.addEventListener("readystatechange",s=>{document.readyState==="complete"&&r()}):r()}),Bt=()=>new URLSearchParams(window.location.search),Te=e=>e.replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\\/g,"\"),ke=(e,...t)=>{let n=e.map(r=>r+(["string","number"].includes(typeof t[0])?t.shift():Te(JSON.stringify(t.shift(),null,2)??""))).join("");return n.includes("r.trim()).filter(r=>r.length).join(" ")},Ce=(e,...t)=>{let n=document.createRange().createContextualFragment(ke(e,...t));return n.children.length===1?n.children[0]:n.children},Pe=(e,...t)=>(t=t.map(n=>n instanceof HTMLCollection?[...n]:n).flat(1/0).filter(n=>n),e.append(...t),e),Wt=e=>{for(;e.firstChild&&e.removeChild(e.firstChild););return e},Ft=e=>{let t=Ce``;return Pe(document.head,t),t},Ht=async e=>{try{await navigator.clipboard.writeText(e)}catch{let t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}},Vt=()=>navigator.clipboard.readText(),Oe=(e,t)=>{if(document.activeElement.nodeName==="INPUT"&&!t.listenInInput)return;t.keys.every(s=>{s=s.toLowerCase();let c={metaKey:["meta","os","win","cmd","command"],ctrlKey:["ctrl","control"],shiftKey:["shift"],altKey:["alt"]};for(let a in c)if(c[a].includes(s)&&e[a])return!0;if(s==="space"&&(s=" "),s==="plus"&&(s="+"),s===e.key.toLowerCase())return!0})&&t.callback(e)},It=(e,t,{listenInInput:n=!1,keydown:r=!1}={})=>{typeof e=="string"&&(e=e.split("+")),T.push({keys:e,callback:t,listenInInput:n,keydown:r}),Se||(Se=!0,document.addEventListener("keyup",s=>{for(let c of T.filter(({keydown:a})=>!a))Oe(s,c)}),document.addEventListener("keydown",s=>{for(let c of T.filter(({keydown:a})=>a))Oe(s,c)}))},Kt=e=>{T=T.filter(t=>t.callback!==e)},Xt=(e,t=[])=>{if(!ee){let n=r=>{for(;r.length;){let s=r.shift(),c=(i,d)=>i instanceof Element&&(i.matches(d)||i.matches(`${d} *`)||i.querySelector(d)),a=i=>s.target.matches(i)||s.target.matches(`${i} *`)||[...s.addedNodes].some(d=>c(d,i));for(let i of J)(!i.selectors.length||i.selectors.some(a))&&i.callback(s)}};ee=new MutationObserver((r,s)=>{te.length||requestIdleCallback(()=>n(te)),te.push(...r)}),ee.observe(document.body,{childList:!0,subtree:!0,attributes:!0})}J.push({callback:e,selectors:t})},Gt=e=>{J=J.filter(t=>t.callback!==e)};var N={};g(N,{addCornerAction:()=>Xe,addPanelView:()=>Ie,addTooltip:()=>Ne,feather:()=>qe});"use strict";var Re,p,Qt=e=>[...e.getClientRects()].reduce((t,n)=>t.some(r=>r.y===n.y)?t:[...t,n],[]).length,Yt=async(e,t,n)=>{p.style.top="0px",p.style.left="0px";let r=e.getBoundingClientRect(),{offsetWidth:s,offsetHeight:c}=p,a=6,i=r.x,d=Math.floor(r.y);if(["top","bottom"].includes(t)){t==="top"&&(d-=c+a),t==="bottom"&&(d+=r.height+a),i-=s/2-r.width/2,p.style.left=`${i}px`,p.style.top=`${d}px`;let x=()=>Qt(p.firstElementChild)>n,A=x();for(;x();)p.style.left=`${window.innerWidth-i>i?i++:i--}px`;A&&(i+=window.innerWidth-i>i?a:-a,p.style.left=`${i}px`),p.style.textAlign="center"}return["left","right"].includes(t)&&(d-=c/2-r.height/2,t==="left"&&(i-=s+a),t==="right"&&(i+=r.width+a),p.style.left=`${i}px`,p.style.top=`${d}px`,p.style.textAlign="auto"),!0},Ne=async(e,t,{delay:n=100,offsetDirection:r="bottom",maxLines:s=1}={})=>{Re||(Re=o.loadStylesheet("api/components/tooltip.css"),p=o.html`
`,o.render(document.body,p)),globalThis.markdownit||await import(u.localPath("dep/markdown-it.min.js"));let c=markdownit({linkify:!0});t instanceof Element||(t=o.html`
+ />`;return Pe(document.head,t),t},Ht=async e=>{try{await navigator.clipboard.writeText(e)}catch{let t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}},Vt=()=>navigator.clipboard.readText(),Oe=(e,t)=>{if(document.activeElement.nodeName==="INPUT"&&!t.listenInInput)return;t.keys.every(s=>{s=s.toLowerCase();let c={metaKey:["meta","os","win","cmd","command"],ctrlKey:["ctrl","control"],shiftKey:["shift"],altKey:["alt"]};for(let a in c)if(c[a].includes(s)&&e[a])return!0;if(s==="space"&&(s=" "),s==="plus"&&(s="+"),s===e.key.toLowerCase())return!0})&&t.callback(e)},It=(e,t,{listenInInput:n=!1,keydown:r=!1}={})=>{typeof e=="string"&&(e=e.split("+")),T.push({keys:e,callback:t,listenInInput:n,keydown:r}),Se||(Se=!0,document.addEventListener("keyup",s=>{for(let c of T.filter(({keydown:a})=>!a))Oe(s,c)}),document.addEventListener("keydown",s=>{for(let c of T.filter(({keydown:a})=>a))Oe(s,c)}))},Kt=e=>{T=T.filter(t=>t.callback!==e)},Xt=(e,t=[])=>{if(!ee){let n=r=>{for(;r.length;){let s=r.shift(),c=(i,d)=>i instanceof Element&&(i.matches(d)||i.matches(`${d} *`)||i.querySelector(d)),a=i=>s.target.matches(i)||s.target.matches(`${i} *`)||[...s.addedNodes].some(d=>c(d,i));for(let i of J)(!i.selectors.length||i.selectors.some(a))&&i.callback(s)}};ee=new MutationObserver((r,s)=>{te.length||requestIdleCallback(()=>n(te)),te.push(...r)}),ee.observe(document.body,{childList:!0,subtree:!0,attributes:!0})}J.push({callback:e,selectors:t})},Gt=e=>{J=J.filter(t=>t.callback!==e)};var N={};g(N,{addCornerAction:()=>Xe,addPanelView:()=>Ie,addTooltip:()=>Ne,feather:()=>qe});"use strict";var Re,p,Qt=e=>[...e.getClientRects()].reduce((t,n)=>t.some(r=>r.y===n.y)?t:[...t,n],[]).length,Yt=async(e,t,n)=>{p.style.top="0px",p.style.left="0px";let r=e.getBoundingClientRect(),{offsetWidth:s,offsetHeight:c}=p,a=6,i=r.x,d=Math.floor(r.y);if(["top","bottom"].includes(t)){t==="top"&&(d-=c+a),t==="bottom"&&(d+=r.height+a),i-=s/2-r.width/2,p.style.left=`${i}px`,p.style.top=`${d}px`;let x=()=>Qt(p.firstElementChild)>n,A=x();for(;x();)p.style.left=`${window.innerWidth-i>i?i++:i--}px`;A&&(i+=window.innerWidth-i>i?a:-a,p.style.left=`${i}px`),p.style.textAlign="center"}return["left","right"].includes(t)&&(d-=c/2-r.height/2,t==="left"&&(i-=s+a),t==="right"&&(i+=r.width+a),p.style.left=`${i}px`,p.style.top=`${d}px`,p.style.textAlign="start"),!0},Ne=async(e,t,{delay:n=100,offsetDirection:r="bottom",maxLines:s=1}={})=>{Re||(Re=o.loadStylesheet("api/components/tooltip.css"),p=o.html`
`,o.render(document.body,p)),globalThis.markdownit||await import(u.localPath("dep/markdown-it.min.js"));let c=markdownit({linkify:!0});t instanceof Element||(t=o.html`
${t.split(` `).map(i=>c.renderInline(i)).join("
")}
`);let a;e.addEventListener("mouseover",async i=>{a||(a=setTimeout(async()=>{e.matches(":hover")&&p.style.display!=="block"&&(p.style.display="block",o.render(o.empty(p),t),Yt(e,r,s),await p.animate([{opacity:0},{opacity:1}],{duration:65}).finished),a=void 0},n))}),e.addEventListener("mouseout",async i=>{a=void 0,p.style.display==="block"&&!e.matches(":hover")&&(await p.animate([{opacity:1},{opacity:0}],{duration:65}).finished,p.style.display="")})};"use strict";var ne,qe=async(e,t={})=>(ne||(ne=o.html`${await u.getText("dep/feather-sprite.svg")}`),t.style=((t.style?t.style+";":"")+"stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;fill:none;").trim(),t.viewBox="0 0 24 24",``${o.escape(n)}="${o.escape(r)}"`).join(" ")}>${ne.getElementById(e)?.innerHTML}`);"use strict";var k=[],Dt=o.raw` From 2428a4210328aed1aeb74c9777fb23ed211d3df2 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Sun, 12 Dec 2021 23:12:38 +1100 Subject: [PATCH 47/52] strict hotkey modifiers + electron window helpers --- api/electron.mjs | 66 ++++++++++++++++++++++++++---------------------- api/env.mjs | 11 -------- api/fs.mjs | 4 --- api/index.cjs | 20 +++++++-------- api/web.mjs | 38 ++++++++++++++++++---------- 5 files changed, 71 insertions(+), 68 deletions(-) diff --git a/api/electron.mjs b/api/electron.mjs index 1201f57..f39be56 100644 --- a/api/electron.mjs +++ b/api/electron.mjs @@ -15,12 +15,7 @@ * access to the electron BrowserWindow instance for the current window * see https://www.electronjs.org/docs/latest/api/browser-window * @type {BrowserWindow} - * - * @env win32 - * @env linux - * @env darwin - * @runtime client - * @runtime menu + * @runtime electron (renderer process) */ export const browser = globalThis.__enhancerElectronApi?.browser; @@ -28,12 +23,7 @@ export const browser = globalThis.__enhancerElectronApi?.browser; * access to the electron webFrame instance for the current page * see https://www.electronjs.org/docs/latest/api/web-frame * @type {webFrame} - * - * @env win32 - * @env linux - * @env darwin - * @runtime client - * @runtime menu + * @runtime electron (renderer process) */ export const webFrame = globalThis.__enhancerElectronApi?.webFrame; @@ -44,12 +34,7 @@ export const webFrame = globalThis.__enhancerElectronApi?.webFrame; * @param {string=} namespace - a prefix for the message to categorise * it as e.g. enhancer-related. this should not be changed unless replicating * builtin ipc events. - * - * @env win32 - * @env linux - * @env darwin - * @runtime client - * @runtime menu + * @runtime electron (renderer process) */ export const sendMessage = (channel, data, namespace = 'notion-enhancer') => { if (globalThis.__enhancerElectronApi) { @@ -64,12 +49,7 @@ export const sendMessage = (channel, data, namespace = 'notion-enhancer') => { * @param {string=} namespace - a prefix for the message to categorise * it as e.g. enhancer-related. this should not be changed unless replicating * builtin ipc events. - * - * @env win32 - * @env linux - * @env darwin - * @runtime client - * @runtime menu + * @runtime electron (renderer process) */ export const sendMessageToHost = (channel, data, namespace = 'notion-enhancer') => { if (globalThis.__enhancerElectronApi) { @@ -85,15 +65,41 @@ export const sendMessageToHost = (channel, data, namespace = 'notion-enhancer') * @param {string=} namespace - a prefix for the message to categorise * it as e.g. enhancer-related. this should not be changed unless replicating * builtin ipc events. - * - * @env win32 - * @env linux - * @env darwin - * @runtime client - * @runtime menu + * @runtime electron (renderer process) */ export const onMessage = (channel, callback, namespace = 'notion-enhancer') => { if (globalThis.__enhancerElectronApi) { globalThis.__enhancerElectronApi.ipcRenderer.onMessage(channel, callback, namespace); } }; + +/** + * require() notion app files + * @param {string} path - within notion/resources/app/ e.g. main/createWindow.js + * @runtime electron (main process) + */ +export const notionRequire = (path) => { + return globalThis.__enhancerElectronApi + ? globalThis.__enhancerElectronApi.notionRequire(path) + : null; +}; + +/** + * get all available app windows excluding the menu + * @runtime electron (main process) + */ +export const getNotionWindows = () => { + return globalThis.__enhancerElectronApi + ? globalThis.__enhancerElectronApi.getNotionWindows() + : null; +}; + +/** + * get the currently focused notion window + * @runtime electron (main process) + */ +export const getFocusedNotionWindow = () => { + return globalThis.__enhancerElectronApi + ? globalThis.__enhancerElectronApi.getFocusedNotionWindow() + : null; +}; diff --git a/api/env.mjs b/api/env.mjs index d8a1c06..36598e6 100644 --- a/api/env.mjs +++ b/api/env.mjs @@ -44,14 +44,3 @@ export const focusNotion = env.focusNotion; * @type {function} */ export const reload = env.reload; - -/** - * require() notion app files - * @param {string} path - within notion/resources/app/ e.g. main/createWindow.js - * - * @env win32 - * @env linux - * @env darwin - * @runtime electron - */ -export const notionRequire = env.notionRequire; diff --git a/api/fs.mjs b/api/fs.mjs index a6e40ab..bf20afe 100644 --- a/api/fs.mjs +++ b/api/fs.mjs @@ -50,10 +50,6 @@ export const isFile = fs.isFile; /** * get an absolute path to files within notion * @param {string} path - relative to the root notion/resources/app/ e.g. renderer/search.js - * - * @env win32 - * @env linux - * @env darwin * @runtime electron */ export const notionPath = fs.notionPath; diff --git a/api/index.cjs b/api/index.cjs index aaea141..ab42feb 100644 --- a/api/index.cjs +++ b/api/index.cjs @@ -1,21 +1,21 @@ -var ce=Object.defineProperty;var Ge=e=>ce(e,"__esModule",{value:!0});var g=(e,t)=>{Ge(e);for(var n in t)ce(e,n,{get:t[n],enumerable:!0})};g(exports,{components:()=>N,electron:()=>V,env:()=>E,fmt:()=>h,fs:()=>u,notion:()=>K,registry:()=>y,storage:()=>_,web:()=>o});var E={};g(E,{focusMenu:()=>De,focusNotion:()=>et,name:()=>Qe,notionRequire:()=>nt,reload:()=>tt,version:()=>Ye});"use strict";var le=globalThis.__enhancerElectronApi.platform,de=globalThis.__enhancerElectronApi.version,pe=globalThis.__enhancerElectronApi.focusMenu,ue=globalThis.__enhancerElectronApi.focusNotion,he=globalThis.__enhancerElectronApi.reload,fe=globalThis.__enhancerElectronApi.notionRequire;"use strict";var Qe=le,Ye=de,De=pe,et=ue,tt=he,nt=fe;var u={};g(u,{getJSON:()=>st,getText:()=>ot,isFile:()=>it,localPath:()=>rt,notionPath:()=>at});"use strict";var L=e=>`notion://www.notion.so/__notion-enhancer/${e}`,me=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.json());try{return globalThis.__enhancerElectronApi.nodeRequire(`notion-enhancer/${e}`)}catch{return fetch(L(e),t).then(r=>r.json())}},ye=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.text());try{let n=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:r}=globalThis.__enhancerElectronApi.nodeRequire("path");return n.readFileSync(r(`${__dirname}/../../${e}`))}catch{return fetch(L(e),t).then(r=>r.text())}},ge=async e=>{try{let t=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:n}=globalThis.__enhancerElectronApi.nodeRequire("path");if(e.startsWith("http"))await fetch(e);else try{t.existsSync(n(`${__dirname}/../../${e}`))}catch{await fetch(L(e))}return!0}catch{return!1}},ve=globalThis.__enhancerElectronApi.notionPath;"use strict";var rt=L,st=me,ot=ye,it=ge,at=ve;var _={};g(_,{addChangeListener:()=>pt,db:()=>dt,get:()=>ct,removeChangeListener:()=>ut,set:()=>lt});"use strict";var F=(e,t=void 0)=>globalThis.__enhancerElectronApi.db.get(e,t),H=(e,t)=>globalThis.__enhancerElectronApi.db.set(e,t),we=(e,t=F,n=H)=>(typeof e=="string"&&(e=[e]),{get:(r=[],s=void 0)=>t([...e,...r],s),set:(r,s)=>n([...e,...r],s)}),be=e=>globalThis.__enhancerElectronApi.db.addChangeListener(e),xe=e=>globalThis.__enhancerElectronApi.db.removeChangeListener(e);"use strict";var ct=F,lt=H,dt=we,pt=be,ut=xe;var V={};g(V,{browser:()=>ht,onMessage:()=>gt,sendMessage:()=>mt,sendMessageToHost:()=>yt,webFrame:()=>ft});"use strict";var ht=globalThis.__enhancerElectronApi?.browser,ft=globalThis.__enhancerElectronApi?.webFrame,mt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessage(e,t,n)},yt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessageToHost(e,t,n)},gt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.onMessage(e,t,n)};var K={};g(K,{create:()=>bt,get:()=>$e,getPageID:()=>M,getSpaceID:()=>S,getUserID:()=>_e,search:()=>vt,set:()=>wt,sign:()=>_t,upload:()=>xt});"use strict";var m=e=>(e?.length===32&&!e.includes("-")&&(e=e.replace(/([\d\w]{8})([\d\w]{4})([\d\w]{4})([\d\w]{4})([\d\w]{12})/,"$1-$2-$3-$4-$5")),e),_e=()=>JSON.parse(localStorage["LRU:KeyValueStore2:current-user-id"]||{}).value,M=()=>m(o.queryParams().get("p")||location.pathname.split(/(-|\/)/g).reverse()[0]),I,S=async()=>(I||(I=(await $e(M())).space_id),I),$e=async(e,t="block")=>(e=m(e),(await u.getJSON("https://www.notion.so/api/v3/getRecordValues",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requests:[{table:t,id:e}]}),method:"POST"})).results[0].value),vt=async(e="",t=20,n=S())=>(n=m(await n),await u.getJSON("https://www.notion.so/api/v3/search",{headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"BlocksInSpace",query:e,spaceId:n,limit:t,filters:{isDeletedOnly:!1,excludeTemplates:!1,isNavigableOnly:!1,requireEditPermissions:!1,ancestors:[],createdBy:[],editedBy:[],lastEditedTime:{},createdTime:{}},sort:"Relevance",source:"quick_find"}),method:"POST"})),wt=async({recordID:e,recordTable:t="block",spaceID:n=S(),path:r=[]},s={})=>{n=m(await n),e=m(e);let c=await u.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:n,operations:[{pointer:{table:t,id:e,spaceId:n},path:r,command:r.length?"set":"update",args:s}]}]}),method:"POST"});return c.errorId?c:!0},bt=async({recordValue:e={},recordTable:t="block"}={},{prepend:n=!1,siblingID:r=void 0,parentID:s=M(),parentTable:c="block",spaceID:a=S(),userID:i=_e()}={})=>{a=m(await a),s=m(s),r=m(r);let d=m(e?.id??h.uuidv4()),x=[],A={type:"text",id:d,version:0,created_time:new Date().getTime(),last_edited_time:new Date().getTime(),parent_id:s,parent_table:c,alive:!0,created_by_table:"notion_user",created_by_id:i,last_edited_by_table:"notion_user",last_edited_by_id:i,space_id:a,permissions:[{type:"user_permission",role:"editor",user_id:i}]};c==="space"?(s=a,A.parent_id=a,x.push("pages"),A.type="page"):c==="collection_view"?(x.push("page_sort"),A.type="page"):x.push("content");let ae=await u.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:a,operations:[{pointer:{table:c,id:s,spaceId:a},path:x,command:n?"listBefore":"listAfter",args:{...r?{after:r}:{},id:d}},{pointer:{table:t,id:d,spaceId:a},path:[],command:"set",args:{...A,...e}}]}]}),method:"POST"});return ae.errorId?ae:d},xt=async(e,{pageID:t=M(),spaceID:n=S()}={})=>{n=m(await n),t=m(t);let r=await u.getJSON("https://www.notion.so/api/v3/getUploadFileUrl",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({bucket:"secure",name:e.name,contentType:e.type,record:{table:"block",id:t,spaceId:n}})});return r.errorId?r:(fetch(r.signedPutUrl,{method:"PUT",headers:{"content-type":e.type},body:e}),r.url)},_t=(e,t,n="block")=>(e.startsWith("/")&&(e=`https://notion.so${e}`),e.includes("secure.notion-static.com")&&(e=new URL(e),e=`https://www.notion.so/signed/${encodeURIComponent(e.origin+e.pathname)}?table=${n}&id=${t}`),e);var h={};g(h,{is:()=>Tt,rgbContrast:()=>Et,rgbLogShade:()=>jt,slugger:()=>$t,uuidv4:()=>At});"use strict";var $t=(e,t=new Set)=>{e=e.replace(/\s/g,"-").replace(/[^A-Za-z0-9-_]/g,"").toLowerCase();let n=0,r=e;for(;t.has(r);)n++,r=`${e}-${n}`;return r},At=()=>crypto?.randomUUID?crypto.randomUUID():([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,e=>(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)),jt=(e,t)=>{var n=parseInt,r=Math.round,[s,c,t,a]=t.split(","),i=e<0,d=i?0:e*255**2,i=i?1+e:1-e;return"rgb"+(a?"a(":"(")+r((i*n(s[3]=="a"?s.slice(5):s.slice(4))**2+d)**.5)+","+r((i*n(c)**2+d)**.5)+","+r((i*n(t)**2+d)**.5)+(a?","+a:")")},Et=(e,t,n)=>Math.sqrt(.299*(e*e)+.587*(t*t)+.114*(n*n))>165.75?"rgb(0,0,0)":"rgb(255,255,255)",Lt={alphanumeric:/^[\w\.-]+$/,uuid:/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,semver:/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i,email:/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i,url:/^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,64}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i,color:/^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i};function St(e,t){let n=e.match(t);return!!(n&&n.length)}var Tt=async(e,t,{extension:n=""}={})=>{if(n=!e||!e.endsWith||e.endsWith(n),Array.isArray(t))return t.includes(e);switch(t){case"array":return Array.isArray(e);case"object":return e&&typeof e=="object"&&!Array.isArray(e);case"undefined":case"boolean":case"number":return typeof e===t&&n;case"string":return typeof e===t&&n;case"alphanumeric":case"uuid":case"semver":case"email":case"url":case"color":return typeof e=="string"&&St(e,Lt[t])&&n;case"file":return typeof e=="string"&&e&&await u.isFile(e)&&n}return!1};var y={};g(y,{core:()=>je,db:()=>Ut,enabled:()=>zt,errors:()=>Jt,get:()=>D,list:()=>Y,optionDefault:()=>Le,optionTypes:()=>Mt,profileDB:()=>X,profileName:()=>Ee,supportedEnvs:()=>qt});"use strict";var l=async(e,t,n,r,{extension:s="",error:c=`invalid ${t} (${s?`${s} `:""}${r}): ${JSON.stringify(n)}`,optional:a=!1}={})=>{let i;for(let d of Array.isArray(r)?[r]:r.split("|"))if(d==="file"?i=n&&!n.startsWith("http")?await h.is(`repo/${e._dir}/${n}`,d,{extension:s}):!1:i=await h.is(n,d,{extension:s}),i)break;return i||a&&await h.is(n,"undefined")?!0:(c&&e._err(c),!1)},kt=async e=>(e.environments=e.environments??y.supportedEnvs,await l(e,"environments",e.environments,"array")?e.environments.map(n=>l(e,"environments.env",n,y.supportedEnvs)):!1),Ct=async e=>{if(!await l(e,"tags",e.tags,"array"))return!1;let n=["core","extension","theme","integration"];if(!e.tags.filter(i=>n.includes(i)).length)return e._err(`invalid tags (must contain at least one of 'core', 'extension', 'theme' or 'integration'): +var ce=Object.defineProperty;var Xe=e=>ce(e,"__esModule",{value:!0});var y=(e,t)=>{Xe(e);for(var n in t)ce(e,n,{get:t[n],enumerable:!0})};y(exports,{components:()=>R,electron:()=>I,env:()=>j,fmt:()=>h,fs:()=>u,notion:()=>K,registry:()=>g,storage:()=>_,web:()=>o});var j={};y(j,{focusMenu:()=>Ye,focusNotion:()=>De,name:()=>Ge,reload:()=>et,version:()=>Qe});"use strict";var le=globalThis.__enhancerElectronApi.platform,de=globalThis.__enhancerElectronApi.version,pe=globalThis.__enhancerElectronApi.focusMenu,ue=globalThis.__enhancerElectronApi.focusNotion,he=globalThis.__enhancerElectronApi.reload;"use strict";var Ge=le,Qe=de,Ye=pe,De=ue,et=he;var u={};y(u,{getJSON:()=>nt,getText:()=>rt,isFile:()=>st,localPath:()=>tt,notionPath:()=>ot});"use strict";var T=e=>`notion://www.notion.so/__notion-enhancer/${e}`,fe=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.json());try{return globalThis.__enhancerElectronApi.nodeRequire(`notion-enhancer/${e}`)}catch{return fetch(T(e),t).then(r=>r.json())}},me=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.text());try{let n=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:r}=globalThis.__enhancerElectronApi.nodeRequire("path");return n.readFileSync(r(`${__dirname}/../../${e}`))}catch{return fetch(T(e),t).then(r=>r.text())}},ge=async e=>{try{let t=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:n}=globalThis.__enhancerElectronApi.nodeRequire("path");if(e.startsWith("http"))await fetch(e);else try{t.existsSync(n(`${__dirname}/../../${e}`))}catch{await fetch(T(e))}return!0}catch{return!1}},ye=globalThis.__enhancerElectronApi.notionPath;"use strict";var tt=T,nt=fe,rt=me,st=ge,ot=ye;var _={};y(_,{addChangeListener:()=>lt,db:()=>ct,get:()=>it,removeChangeListener:()=>dt,set:()=>at});"use strict";var F=(e,t=void 0)=>globalThis.__enhancerElectronApi.db.get(e,t),H=(e,t)=>globalThis.__enhancerElectronApi.db.set(e,t),ve=(e,t=F,n=H)=>(typeof e=="string"&&(e=[e]),{get:(r=[],s=void 0)=>t([...e,...r],s),set:(r,s)=>n([...e,...r],s)}),we=e=>globalThis.__enhancerElectronApi.db.addChangeListener(e),be=e=>globalThis.__enhancerElectronApi.db.removeChangeListener(e);"use strict";var it=F,at=H,ct=ve,lt=we,dt=be;var I={};y(I,{browser:()=>pt,getFocusedNotionWindow:()=>vt,getNotionWindows:()=>yt,notionRequire:()=>gt,onMessage:()=>mt,sendMessage:()=>ht,sendMessageToHost:()=>ft,webFrame:()=>ut});"use strict";var pt=globalThis.__enhancerElectronApi?.browser,ut=globalThis.__enhancerElectronApi?.webFrame,ht=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessage(e,t,n)},ft=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessageToHost(e,t,n)},mt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.onMessage(e,t,n)},gt=e=>globalThis.__enhancerElectronApi?globalThis.__enhancerElectronApi.notionRequire(e):null,yt=()=>globalThis.__enhancerElectronApi?globalThis.__enhancerElectronApi.getNotionWindows():null,vt=()=>globalThis.__enhancerElectronApi?globalThis.__enhancerElectronApi.getFocusedNotionWindow():null;var K={};y(K,{create:()=>xt,get:()=>_e,getPageID:()=>M,getSpaceID:()=>L,getUserID:()=>xe,search:()=>wt,set:()=>bt,sign:()=>$t,upload:()=>_t});"use strict";var m=e=>(e?.length===32&&!e.includes("-")&&(e=e.replace(/([\d\w]{8})([\d\w]{4})([\d\w]{4})([\d\w]{4})([\d\w]{12})/,"$1-$2-$3-$4-$5")),e),xe=()=>JSON.parse(localStorage["LRU:KeyValueStore2:current-user-id"]||{}).value,M=()=>m(o.queryParams().get("p")||location.pathname.split(/(-|\/)/g).reverse()[0]),V,L=async()=>(V||(V=(await _e(M())).space_id),V),_e=async(e,t="block")=>(e=m(e),(await u.getJSON("https://www.notion.so/api/v3/getRecordValues",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requests:[{table:t,id:e}]}),method:"POST"})).results[0].value),wt=async(e="",t=20,n=L())=>(n=m(await n),await u.getJSON("https://www.notion.so/api/v3/search",{headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"BlocksInSpace",query:e,spaceId:n,limit:t,filters:{isDeletedOnly:!1,excludeTemplates:!1,isNavigableOnly:!1,requireEditPermissions:!1,ancestors:[],createdBy:[],editedBy:[],lastEditedTime:{},createdTime:{}},sort:"Relevance",source:"quick_find"}),method:"POST"})),bt=async({recordID:e,recordTable:t="block",spaceID:n=L(),path:r=[]},s={})=>{n=m(await n),e=m(e);let c=await u.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:n,operations:[{pointer:{table:t,id:e,spaceId:n},path:r,command:r.length?"set":"update",args:s}]}]}),method:"POST"});return c.errorId?c:!0},xt=async({recordValue:e={},recordTable:t="block"}={},{prepend:n=!1,siblingID:r=void 0,parentID:s=M(),parentTable:c="block",spaceID:a=L(),userID:i=xe()}={})=>{a=m(await a),s=m(s),r=m(r);let d=m(e?.id??h.uuidv4()),x=[],A={type:"text",id:d,version:0,created_time:new Date().getTime(),last_edited_time:new Date().getTime(),parent_id:s,parent_table:c,alive:!0,created_by_table:"notion_user",created_by_id:i,last_edited_by_table:"notion_user",last_edited_by_id:i,space_id:a,permissions:[{type:"user_permission",role:"editor",user_id:i}]};c==="space"?(s=a,A.parent_id=a,x.push("pages"),A.type="page"):c==="collection_view"?(x.push("page_sort"),A.type="page"):x.push("content");let ae=await u.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:a,operations:[{pointer:{table:c,id:s,spaceId:a},path:x,command:n?"listBefore":"listAfter",args:{...r?{after:r}:{},id:d}},{pointer:{table:t,id:d,spaceId:a},path:[],command:"set",args:{...A,...e}}]}]}),method:"POST"});return ae.errorId?ae:d},_t=async(e,{pageID:t=M(),spaceID:n=L()}={})=>{n=m(await n),t=m(t);let r=await u.getJSON("https://www.notion.so/api/v3/getUploadFileUrl",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({bucket:"secure",name:e.name,contentType:e.type,record:{table:"block",id:t,spaceId:n}})});return r.errorId?r:(fetch(r.signedPutUrl,{method:"PUT",headers:{"content-type":e.type},body:e}),r.url)},$t=(e,t,n="block")=>(e.startsWith("/")&&(e=`https://notion.so${e}`),e.includes("secure.notion-static.com")&&(e=new URL(e),e=`https://www.notion.so/signed/${encodeURIComponent(e.origin+e.pathname)}?table=${n}&id=${t}`),e);var h={};y(h,{is:()=>kt,rgbContrast:()=>Tt,rgbLogShade:()=>jt,slugger:()=>At,uuidv4:()=>Et});"use strict";var At=(e,t=new Set)=>{e=e.replace(/\s/g,"-").replace(/[^A-Za-z0-9-_]/g,"").toLowerCase();let n=0,r=e;for(;t.has(r);)n++,r=`${e}-${n}`;return r},Et=()=>crypto?.randomUUID?crypto.randomUUID():([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,e=>(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)),jt=(e,t)=>{var n=parseInt,r=Math.round,[s,c,t,a]=t.split(","),i=e<0,d=i?0:e*255**2,i=i?1+e:1-e;return"rgb"+(a?"a(":"(")+r((i*n(s[3]=="a"?s.slice(5):s.slice(4))**2+d)**.5)+","+r((i*n(c)**2+d)**.5)+","+r((i*n(t)**2+d)**.5)+(a?","+a:")")},Tt=(e,t,n)=>Math.sqrt(.299*(e*e)+.587*(t*t)+.114*(n*n))>165.75?"rgb(0,0,0)":"rgb(255,255,255)",Lt={alphanumeric:/^[\w\.-]+$/,uuid:/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,semver:/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i,email:/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i,url:/^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,64}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i,color:/^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i};function St(e,t){let n=e.match(t);return!!(n&&n.length)}var kt=async(e,t,{extension:n=""}={})=>{if(n=!e||!e.endsWith||e.endsWith(n),Array.isArray(t))return t.includes(e);switch(t){case"array":return Array.isArray(e);case"object":return e&&typeof e=="object"&&!Array.isArray(e);case"undefined":case"boolean":case"number":return typeof e===t&&n;case"string":return typeof e===t&&n;case"alphanumeric":case"uuid":case"semver":case"email":case"url":case"color":return typeof e=="string"&&St(e,Lt[t])&&n;case"file":return typeof e=="string"&&e&&await u.isFile(e)&&n}return!1};var g={};y(g,{core:()=>Ae,db:()=>Wt,enabled:()=>Ut,errors:()=>zt,get:()=>D,list:()=>Y,optionDefault:()=>je,optionTypes:()=>Jt,profileDB:()=>X,profileName:()=>Ee,supportedEnvs:()=>Mt});"use strict";var l=async(e,t,n,r,{extension:s="",error:c=`invalid ${t} (${s?`${s} `:""}${r}): ${JSON.stringify(n)}`,optional:a=!1}={})=>{let i;for(let d of Array.isArray(r)?[r]:r.split("|"))if(d==="file"?i=n&&!n.startsWith("http")?await h.is(`repo/${e._dir}/${n}`,d,{extension:s}):!1:i=await h.is(n,d,{extension:s}),i)break;return i||a&&await h.is(n,"undefined")?!0:(c&&e._err(c),!1)},Ct=async e=>(e.environments=e.environments??g.supportedEnvs,await l(e,"environments",e.environments,"array")?e.environments.map(n=>l(e,"environments.env",n,g.supportedEnvs)):!1),Pt=async e=>{if(!await l(e,"tags",e.tags,"array"))return!1;let n=["core","extension","theme","integration"];if(!e.tags.filter(i=>n.includes(i)).length)return e._err(`invalid tags (must contain at least one of 'core', 'extension', 'theme' or 'integration'): ${JSON.stringify(e.tags)}`),!1;let s=e.tags.includes("theme"),c=e.tags.includes("light")||e.tags.includes("dark"),a=e.tags.includes("light")&&e.tags.includes("dark");return s&&(!c||a)?(e._err(`invalid tags (themes must be either 'light' or 'dark', not neither or both): - ${JSON.stringify(e.tags)}`),!1):e.tags.map(i=>l(e,"tags.tag",i,"string"))},Pt=async e=>await l(e,"authors",e.authors,"array")?e.authors.map(n=>[l(e,"authors.author.name",n.name,"string"),l(e,"authors.author.email",n.email,"email",{optional:!0}),l(e,"authors.author.homepage",n.homepage,"url"),l(e,"authors.author.avatar",n.avatar,"url")]):!1,Ot=async e=>{if(!await l(e,"css",e.css,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.css[r])continue;let s=await l(e,`css.${r}`,e.css[r],"array");s&&(s=e.css[r].map(c=>l(e,`css.${r}.file`,c,"file",{extension:".css"}))),n.push(s)}return n},Rt=async e=>{if(!await l(e,"js",e.js,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.js[r])continue;let s=await l(e,`js.${r}`,e.js[r],"array");s&&(s=e.js[r].map(c=>l(e,`js.${r}.file`,c,"file",{extension:".mjs"}))),n.push(s)}if(e.js.electron)if(await l(e,"js.electron",e.js.electron,"array"))for(let s of e.js.electron){if(!await l(e,"js.electron.file",s,"object")){n.push(!1);continue}n.push([l(e,"js.electron.file.source",s.source,"file",{extension:".cjs"}),l(e,"js.electron.file.target",s.target,"string",{extension:".js"})])}else n.push(!1);return n},Nt=async e=>{if(!await l(e,"options",e.options,"array"))return!1;let n=[];for(let r of e.options){let s="options.option";if(!await l(e,`${s}.type`,r.type,y.optionTypes)){n.push(!1);continue}switch(r.environments=r.environments??y.supportedEnvs,n.push([l(e,`${s}.key`,r.key,"alphanumeric"),l(e,`${s}.label`,r.label,"string"),l(e,`${s}.tooltip`,r.tooltip,"string",{optional:!0}),l(e,`${s}.environments`,r.environments,"array").then(a=>a?r.environments.map(i=>l(e,`${s}.environments.env`,i,y.supportedEnvs)):!1)]),r.type){case"toggle":n.push(l(e,`${s}.value`,r.value,"boolean"));break;case"select":{let a=await l(e,`${s}.values`,r.values,"array");a&&(a=r.values.map(i=>l(e,`${s}.values.value`,i,"string"))),n.push(a);break}case"text":case"hotkey":n.push(l(e,`${s}.value`,r.value,"string"));break;case"number":case"color":n.push(l(e,`${s}.value`,r.value,r.type));break;case"file":{let a=await l(e,`${s}.extensions`,r.extensions,"array");a&&(a=r.extensions.map(i=>l(e,`${s}.extensions.extension`,i,"string"))),n.push(a);break}}}return n};async function Ae(e){let t=[l(e,"name",e.name,"string"),l(e,"id",e.id,"uuid"),l(e,"version",e.version,"semver"),kt(e),l(e,"description",e.description,"string"),l(e,"preview",e.preview,"file|url",{optional:!0}),Ct(e),Pt(e),Ot(e),Rt(e),Nt(e)];do t=await Promise.all(t.flat(1/0));while(t.some(n=>Array.isArray(n)));return t.every(n=>n)}"use strict";var je=["a6621988-551d-495a-97d8-3c568bca2e9e","0f0bf8b6-eae6-4273-b307-8fc43f2ee082","36a2ffc9-27ff-480e-84a7-c7700a7d232d"],qt=["linux","win32","darwin","extension"],Mt=["toggle","select","text","number","color","file","hotkey"],Ee=async()=>_.get(["currentprofile"],"default"),X=async()=>_.db(["profiles",await Ee()]),G,Q=[],Y=async(e=t=>!0)=>{G||(G=new Promise(async(n,r)=>{let s=[];for(let c of await u.getJSON("repo/registry.json"))try{let a={...await u.getJSON(`repo/${c}/mod.json`),_dir:c,_err:i=>Q.push({source:c,message:i})};await Ae(a)&&s.push(a)}catch{Q.push({source:c,message:"invalid mod.json"})}n(s)}));let t=[];for(let n of await G)await e(n)&&t.push(n);return t},Jt=async()=>(await Y(),Q),D=async e=>(await Y(t=>t.id===e))[0],zt=async e=>(await D(e)).environments.includes(E.name)?je.includes(e)?!0:(await X()).get(["_mods",e],!1):!1,Le=async(e,t)=>{let n=await D(e),r=n.options.find(s=>s.key===t);if(!!r)switch(r.type){case"toggle":case"text":case"number":case"color":case"hotkey":return r.value;case"select":return r.values[0];case"file":return}},Ut=async e=>{let t=await X();return _.db([e],async(n,r=void 0)=>(typeof n=="string"&&(n=[n]),n.length===2&&(r=await Le(e,n[1])??r),t.get(n,r)),t.set)};var o={};g(o,{addDocumentObserver:()=>Xt,addHotkeyListener:()=>It,copyToClipboard:()=>Ht,empty:()=>Wt,escape:()=>Te,html:()=>Ce,loadStylesheet:()=>Ft,queryParams:()=>Bt,raw:()=>ke,readFromClipboard:()=>Vt,removeDocumentObserver:()=>Gt,removeHotkeyListener:()=>Kt,render:()=>Pe,whenReady:()=>Zt});"use strict";var Se=!1,T=[],ee,J=[],te=[],Zt=(e=[])=>new Promise((t,n)=>{let r=()=>{let s=setInterval(c,100);function c(){!e.every(i=>document.querySelector(i))||(clearInterval(s),t(!0))}c()};document.readyState!=="complete"?document.addEventListener("readystatechange",s=>{document.readyState==="complete"&&r()}):r()}),Bt=()=>new URLSearchParams(window.location.search),Te=e=>e.replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\\/g,"\"),ke=(e,...t)=>{let n=e.map(r=>r+(["string","number"].includes(typeof t[0])?t.shift():Te(JSON.stringify(t.shift(),null,2)??""))).join("");return n.includes("r.trim()).filter(r=>r.length).join(" ")},Ce=(e,...t)=>{let n=document.createRange().createContextualFragment(ke(e,...t));return n.children.length===1?n.children[0]:n.children},Pe=(e,...t)=>(t=t.map(n=>n instanceof HTMLCollection?[...n]:n).flat(1/0).filter(n=>n),e.append(...t),e),Wt=e=>{for(;e.firstChild&&e.removeChild(e.firstChild););return e},Ft=e=>{let t=Ce`l(e,"tags.tag",i,"string"))},Ot=async e=>await l(e,"authors",e.authors,"array")?e.authors.map(n=>[l(e,"authors.author.name",n.name,"string"),l(e,"authors.author.email",n.email,"email",{optional:!0}),l(e,"authors.author.homepage",n.homepage,"url"),l(e,"authors.author.avatar",n.avatar,"url")]):!1,Nt=async e=>{if(!await l(e,"css",e.css,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.css[r])continue;let s=await l(e,`css.${r}`,e.css[r],"array");s&&(s=e.css[r].map(c=>l(e,`css.${r}.file`,c,"file",{extension:".css"}))),n.push(s)}return n},Rt=async e=>{if(!await l(e,"js",e.js,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.js[r])continue;let s=await l(e,`js.${r}`,e.js[r],"array");s&&(s=e.js[r].map(c=>l(e,`js.${r}.file`,c,"file",{extension:".mjs"}))),n.push(s)}if(e.js.electron)if(await l(e,"js.electron",e.js.electron,"array"))for(let s of e.js.electron){if(!await l(e,"js.electron.file",s,"object")){n.push(!1);continue}n.push([l(e,"js.electron.file.source",s.source,"file",{extension:".cjs"}),l(e,"js.electron.file.target",s.target,"string",{extension:".js"})])}else n.push(!1);return n},qt=async e=>{if(!await l(e,"options",e.options,"array"))return!1;let n=[];for(let r of e.options){let s="options.option";if(!await l(e,`${s}.type`,r.type,g.optionTypes)){n.push(!1);continue}switch(r.environments=r.environments??g.supportedEnvs,n.push([l(e,`${s}.key`,r.key,"alphanumeric"),l(e,`${s}.label`,r.label,"string"),l(e,`${s}.tooltip`,r.tooltip,"string",{optional:!0}),l(e,`${s}.environments`,r.environments,"array").then(a=>a?r.environments.map(i=>l(e,`${s}.environments.env`,i,g.supportedEnvs)):!1)]),r.type){case"toggle":n.push(l(e,`${s}.value`,r.value,"boolean"));break;case"select":{let a=await l(e,`${s}.values`,r.values,"array");a&&(a=r.values.map(i=>l(e,`${s}.values.value`,i,"string"))),n.push(a);break}case"text":case"hotkey":n.push(l(e,`${s}.value`,r.value,"string"));break;case"number":case"color":n.push(l(e,`${s}.value`,r.value,r.type));break;case"file":{let a=await l(e,`${s}.extensions`,r.extensions,"array");a&&(a=r.extensions.map(i=>l(e,`${s}.extensions.extension`,i,"string"))),n.push(a);break}}}return n};async function $e(e){let t=[l(e,"name",e.name,"string"),l(e,"id",e.id,"uuid"),l(e,"version",e.version,"semver"),Ct(e),l(e,"description",e.description,"string"),l(e,"preview",e.preview,"file|url",{optional:!0}),Pt(e),Ot(e),Nt(e),Rt(e),qt(e)];do t=await Promise.all(t.flat(1/0));while(t.some(n=>Array.isArray(n)));return t.every(n=>n)}"use strict";var Ae=["a6621988-551d-495a-97d8-3c568bca2e9e","0f0bf8b6-eae6-4273-b307-8fc43f2ee082","36a2ffc9-27ff-480e-84a7-c7700a7d232d"],Mt=["linux","win32","darwin","extension"],Jt=["toggle","select","text","number","color","file","hotkey"],Ee=async()=>_.get(["currentprofile"],"default"),X=async()=>_.db(["profiles",await Ee()]),G,Q=[],Y=async(e=t=>!0)=>{G||(G=new Promise(async(n,r)=>{let s=[];for(let c of await u.getJSON("repo/registry.json"))try{let a={...await u.getJSON(`repo/${c}/mod.json`),_dir:c,_err:i=>Q.push({source:c,message:i})};await $e(a)&&s.push(a)}catch{Q.push({source:c,message:"invalid mod.json"})}n(s)}));let t=[];for(let n of await G)await e(n)&&t.push(n);return t},zt=async()=>(await Y(),Q),D=async e=>(await Y(t=>t.id===e))[0],Ut=async e=>(await D(e)).environments.includes(j.name)?Ae.includes(e)?!0:(await X()).get(["_mods",e],!1):!1,je=async(e,t)=>{let n=await D(e),r=n.options.find(s=>s.key===t);if(!!r)switch(r.type){case"toggle":case"text":case"number":case"color":case"hotkey":return r.value;case"select":return r.values[0];case"file":return}},Wt=async e=>{let t=await X();return _.db([e],async(n,r=void 0)=>(typeof n=="string"&&(n=[n]),n.length===2&&(r=await je(e,n[1])??r),t.get(n,r)),t.set)};var o={};y(o,{addDocumentObserver:()=>Gt,addHotkeyListener:()=>Kt,copyToClipboard:()=>It,empty:()=>Ft,escape:()=>Le,html:()=>ke,loadStylesheet:()=>Ht,queryParams:()=>Bt,raw:()=>Se,readFromClipboard:()=>Vt,removeDocumentObserver:()=>Qt,removeHotkeyListener:()=>Xt,render:()=>Ce,whenReady:()=>Zt});"use strict";var Te=!1,S=[],ee,J=[],te=[],Zt=(e=[])=>new Promise((t,n)=>{let r=()=>{let s=setInterval(c,100);function c(){!e.every(i=>document.querySelector(i))||(clearInterval(s),t(!0))}c()};document.readyState!=="complete"?document.addEventListener("readystatechange",s=>{document.readyState==="complete"&&r()}):r()}),Bt=()=>new URLSearchParams(window.location.search),Le=e=>e.replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\\/g,"\"),Se=(e,...t)=>{let n=e.map(r=>r+(["string","number"].includes(typeof t[0])?t.shift():Le(JSON.stringify(t.shift(),null,2)??""))).join("");return n.includes("r.trim()).filter(r=>r.length).join(" ")},ke=(e,...t)=>{let n=document.createRange().createContextualFragment(Se(e,...t));return n.children.length===1?n.children[0]:n.children},Ce=(e,...t)=>(t=t.map(n=>n instanceof HTMLCollection?[...n]:n).flat(1/0).filter(n=>n),e.append(...t),e),Ft=e=>{for(;e.firstChild&&e.removeChild(e.firstChild););return e},Ht=e=>{let t=ke``;return Pe(document.head,t),t},Ht=async e=>{try{await navigator.clipboard.writeText(e)}catch{let t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}},Vt=()=>navigator.clipboard.readText(),Oe=(e,t)=>{if(document.activeElement.nodeName==="INPUT"&&!t.listenInInput)return;t.keys.every(s=>{s=s.toLowerCase();let c={metaKey:["meta","os","win","cmd","command"],ctrlKey:["ctrl","control"],shiftKey:["shift"],altKey:["alt"]};for(let a in c)if(c[a].includes(s)&&e[a])return!0;if(s==="space"&&(s=" "),s==="plus"&&(s="+"),s===e.key.toLowerCase())return!0})&&t.callback(e)},It=(e,t,{listenInInput:n=!1,keydown:r=!1}={})=>{typeof e=="string"&&(e=e.split("+")),T.push({keys:e,callback:t,listenInInput:n,keydown:r}),Se||(Se=!0,document.addEventListener("keyup",s=>{for(let c of T.filter(({keydown:a})=>!a))Oe(s,c)}),document.addEventListener("keydown",s=>{for(let c of T.filter(({keydown:a})=>a))Oe(s,c)}))},Kt=e=>{T=T.filter(t=>t.callback!==e)},Xt=(e,t=[])=>{if(!ee){let n=r=>{for(;r.length;){let s=r.shift(),c=(i,d)=>i instanceof Element&&(i.matches(d)||i.matches(`${d} *`)||i.querySelector(d)),a=i=>s.target.matches(i)||s.target.matches(`${i} *`)||[...s.addedNodes].some(d=>c(d,i));for(let i of J)(!i.selectors.length||i.selectors.some(a))&&i.callback(s)}};ee=new MutationObserver((r,s)=>{te.length||requestIdleCallback(()=>n(te)),te.push(...r)}),ee.observe(document.body,{childList:!0,subtree:!0,attributes:!0})}J.push({callback:e,selectors:t})},Gt=e=>{J=J.filter(t=>t.callback!==e)};var N={};g(N,{addCornerAction:()=>Xe,addPanelView:()=>Ie,addTooltip:()=>Ne,feather:()=>qe});"use strict";var Re,p,Qt=e=>[...e.getClientRects()].reduce((t,n)=>t.some(r=>r.y===n.y)?t:[...t,n],[]).length,Yt=async(e,t,n)=>{p.style.top="0px",p.style.left="0px";let r=e.getBoundingClientRect(),{offsetWidth:s,offsetHeight:c}=p,a=6,i=r.x,d=Math.floor(r.y);if(["top","bottom"].includes(t)){t==="top"&&(d-=c+a),t==="bottom"&&(d+=r.height+a),i-=s/2-r.width/2,p.style.left=`${i}px`,p.style.top=`${d}px`;let x=()=>Qt(p.firstElementChild)>n,A=x();for(;x();)p.style.left=`${window.innerWidth-i>i?i++:i--}px`;A&&(i+=window.innerWidth-i>i?a:-a,p.style.left=`${i}px`),p.style.textAlign="center"}return["left","right"].includes(t)&&(d-=c/2-r.height/2,t==="left"&&(i-=s+a),t==="right"&&(i+=r.width+a),p.style.left=`${i}px`,p.style.top=`${d}px`,p.style.textAlign="start"),!0},Ne=async(e,t,{delay:n=100,offsetDirection:r="bottom",maxLines:s=1}={})=>{Re||(Re=o.loadStylesheet("api/components/tooltip.css"),p=o.html`
`,o.render(document.body,p)),globalThis.markdownit||await import(u.localPath("dep/markdown-it.min.js"));let c=markdownit({linkify:!0});t instanceof Element||(t=o.html`
+ />`;return Ce(document.head,t),t},It=async e=>{try{await navigator.clipboard.writeText(e)}catch{let t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}},Vt=()=>navigator.clipboard.readText(),Pe=(e,t)=>{if(document.activeElement.nodeName==="INPUT"&&!t.listenInInput)return;let r={metaKey:["meta","os","win","cmd","command"],ctrlKey:["ctrl","control"],shiftKey:["shift"],altKey:["alt"]};if(!!t.keys.every(c=>{c=c.toLowerCase();for(let a in r)if(r[a].includes(c)&&e[a])return r[a]=[],!0;if(c==="space"&&(c=" "),c==="plus"&&(c="+"),c===e.key.toLowerCase())return!0})){for(let c in r){let a=e[c],i=r[c].length>0;if(a&&i)return}t.callback(e)}},Kt=(e,t,{listenInInput:n=!1,keydown:r=!1}={})=>{typeof e=="string"&&(e=e.split("+")),S.push({keys:e,callback:t,listenInInput:n,keydown:r}),Te||(Te=!0,document.addEventListener("keyup",s=>{for(let c of S.filter(({keydown:a})=>!a))Pe(s,c)}),document.addEventListener("keydown",s=>{for(let c of S.filter(({keydown:a})=>a))Pe(s,c)}))},Xt=e=>{S=S.filter(t=>t.callback!==e)},Gt=(e,t=[])=>{if(!ee){let n=r=>{for(;r.length;){let s=r.shift(),c=(i,d)=>i instanceof Element&&(i.matches(d)||i.matches(`${d} *`)||i.querySelector(d)),a=i=>s.target.matches(i)||s.target.matches(`${i} *`)||[...s.addedNodes].some(d=>c(d,i));for(let i of J)(!i.selectors.length||i.selectors.some(a))&&i.callback(s)}};ee=new MutationObserver((r,s)=>{te.length||requestIdleCallback(()=>n(te)),te.push(...r)}),ee.observe(document.body,{childList:!0,subtree:!0,attributes:!0})}J.push({callback:e,selectors:t})},Qt=e=>{J=J.filter(t=>t.callback!==e)};var R={};y(R,{addCornerAction:()=>Ke,addPanelView:()=>Ie,addTooltip:()=>Ne,feather:()=>Re});"use strict";var Oe,p,Yt=e=>[...e.getClientRects()].reduce((t,n)=>t.some(r=>r.y===n.y)?t:[...t,n],[]).length,Dt=async(e,t,n)=>{p.style.top="0px",p.style.left="0px";let r=e.getBoundingClientRect(),{offsetWidth:s,offsetHeight:c}=p,a=6,i=r.x,d=Math.floor(r.y);if(["top","bottom"].includes(t)){t==="top"&&(d-=c+a),t==="bottom"&&(d+=r.height+a),i-=s/2-r.width/2,p.style.left=`${i}px`,p.style.top=`${d}px`;let x=()=>Yt(p.firstElementChild)>n,A=x();for(;x();)p.style.left=`${window.innerWidth-i>i?i++:i--}px`;A&&(i+=window.innerWidth-i>i?a:-a,p.style.left=`${i}px`),p.style.textAlign="center"}return["left","right"].includes(t)&&(d-=c/2-r.height/2,t==="left"&&(i-=s+a),t==="right"&&(i+=r.width+a),p.style.left=`${i}px`,p.style.top=`${d}px`,p.style.textAlign="start"),!0},Ne=async(e,t,{delay:n=100,offsetDirection:r="bottom",maxLines:s=1}={})=>{Oe||(Oe=o.loadStylesheet("api/components/tooltip.css"),p=o.html`
`,o.render(document.body,p)),globalThis.markdownit||await import(u.localPath("dep/markdown-it.min.js"));let c=markdownit({linkify:!0});t instanceof Element||(t=o.html`
${t.split(` `).map(i=>c.renderInline(i)).join("
")} -
`);let a;e.addEventListener("mouseover",async i=>{a||(a=setTimeout(async()=>{e.matches(":hover")&&p.style.display!=="block"&&(p.style.display="block",o.render(o.empty(p),t),Yt(e,r,s),await p.animate([{opacity:0},{opacity:1}],{duration:65}).finished),a=void 0},n))}),e.addEventListener("mouseout",async i=>{a=void 0,p.style.display==="block"&&!e.matches(":hover")&&(await p.animate([{opacity:1},{opacity:0}],{duration:65}).finished,p.style.display="")})};"use strict";var ne,qe=async(e,t={})=>(ne||(ne=o.html`${await u.getText("dep/feather-sprite.svg")}`),t.style=((t.style?t.style+";":"")+"stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;fill:none;").trim(),t.viewBox="0 0 24 24",``${o.escape(n)}="${o.escape(r)}"`).join(" ")}>${ne.getElementById(e)?.innerHTML}`);"use strict";var k=[],Dt=o.raw` +
`);let a;e.addEventListener("mouseover",async i=>{a||(a=setTimeout(async()=>{e.matches(":hover")&&p.style.display!=="block"&&(p.style.display="block",o.render(o.empty(p),t),Dt(e,r,s),await p.animate([{opacity:0},{opacity:1}],{duration:65}).finished),a=void 0},n))}),e.addEventListener("mouseout",async i=>{a=void 0,p.style.display==="block"&&!e.matches(":hover")&&(await p.animate([{opacity:1},{opacity:0}],{duration:65}).finished,p.style.display="")})};"use strict";var ne,Re=async(e,t={})=>(ne||(ne=o.html`${await u.getText("dep/feather-sprite.svg")}`),t.style=((t.style?t.style+";":"")+"stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;fill:none;").trim(),t.viewBox="0 0 24 24",``${o.escape(n)}="${o.escape(r)}"`).join(" ")}>${ne.getElementById(e)?.innerHTML}`);"use strict";var k=[],en=o.raw` - `,Me,w,z,v,$,C,j,Je,ze,re,f,Ue,U,P,Z,se,b,oe,O,B="data-enhancer-panel-pinned",W=()=>$.hasAttribute(B),R=()=>{let e=[z,v,C,$].filter(t=>t);if(W()){ie();for(let t of e)t.removeAttribute(B)}else for(let t of e)t.setAttribute(B,"true");w.set(["panel.pinned"],W())},Ze=async()=>{document.documentElement.style.setProperty("--component--panel-width",f+"px"),w.set(["panel.width"],f)},Be=e=>{e.preventDefault(),re=!0,f=ze+(Je-e.clientX),f<190&&(f=190),f>480&&(f=480),$.style.width=f+"px",C.style.width=f+"px",z.style.paddingRight=f+"px",v&&(v.style.right=f+"px")},We=e=>{$.style.width="",C.style.width="",z.style.paddingRight="",v&&(v.style.right=""),Ze(),j.style.cursor="",document.body.removeEventListener("mousemove",Be),document.body.removeEventListener("mouseup",We)},en=e=>{Je=e.clientX,ze=f,j.style.cursor="auto",document.body.addEventListener("mousemove",Be),document.body.addEventListener("mouseup",We)},tn=()=>document.body.contains(b),Fe=()=>{if(!W())return R();o.render(Ue,O),o.empty(b);for(let t of k){let n=P.contains(t.$title),r=o.render(o.html`
`,o.render(o.html``,t.$icon.cloneNode(!0),t.$title.cloneNode(!0)));r.addEventListener("click",()=>{Ve(t),w.set(["panel.open"],t.id)}),o.render(b,r)}let e=Z.getBoundingClientRect();o.render(o.empty(O),o.render(o.html`
`,o.render(o.html`
`,b))),b.querySelector("[data-open]").focus(),b.animate([{opacity:0},{opacity:1}],{duration:200}),document.addEventListener("keydown",He)},ie=()=>{document.removeEventListener("keydown",He),b.animate([{opacity:1},{opacity:0}],{duration:200}).onfinish=()=>O.remove()},He=e=>{if(tn())switch(e.key){case"Escape":ie(),e.stopPropagation();break;case"Enter":document.activeElement.click(),e.stopPropagation();break;case"ArrowUp":(e.target.previousElementSibling||e.target.parentElement.lastElementChild).focus(),e.stopPropagation();break;case"ArrowDown":(e.target.nextElementSibling||e.target.parentElement.firstElementChild).focus(),e.stopPropagation();break}},Ve=e=>{let t=k.find(({$content:n})=>document.contains(n));o.render(o.empty(P),o.render(o.html``,e.$icon,e.$title)),e.onFocus(),o.render(o.empty(se),e.$content),t&&t.onBlur()};async function nn(){await o.whenReady([".notion-frame"]),z=document.querySelector(".notion-frame"),$=o.html`
`,C=o.html`
`,j=o.html`
`,P=o.html`
`,Z=o.render(o.html`
`,P),se=o.html`
`,b=o.html`
`,oe=o.html`
- ${Dt} -
`,O=o.html`
`;let e='.notion-cursor-listener > div[style*="flex-end"]',t=()=>{document.contains(v)||(v=document.querySelector(e),W()&&v&&v.setAttribute(B,"true"))};v=document.querySelector(e),o.addDocumentObserver(t,[e]),await w.get(["panel.pinned"])&&R(),o.addHotkeyListener(await w.get(["panel.hotkey"]),R),U.addEventListener("click",r=>{r.stopPropagation(),R()}),o.render($,o.render(Z,P,oe,U),se,j),await rn(),await sn();let n='.notion-cursor-listener > .notion-sidebar-container ~ [style^="position: absolute"]';await o.whenReady([n]),document.querySelector(n).before(C,$)}async function rn(){f=await w.get(["panel.width"],240),Ze(),j.addEventListener("mousedown",en),j.addEventListener("click",()=>{re?re=!1:R()})}async function sn(){Ue=document.querySelector(".notion-app-inner"),Z.addEventListener("click",Fe),oe.addEventListener("click",Fe),O.addEventListener("click",ie)}var Ie=async({id:e,icon:t,title:n,$content:r,onFocus:s=()=>{},onBlur:c=()=>{}})=>{Me||(Me=o.loadStylesheet("api/components/panel.css")),w||(w=await y.db("36a2ffc9-27ff-480e-84a7-c7700a7d232d")),U||(U=o.html`
- ${await N.feather("chevrons-right")} -
`);let a={id:e,$icon:o.render(o.html``,t instanceof Element?t:o.html`${t}`),$title:o.render(o.html``,n),$content:r,onFocus:s,onBlur:c};k.push(a),k.length===1&&await nn(),(k.length===1||await w.get(["panel.open"])===e)&&Ve(a)};"use strict";var Ke,q,Xe=async(e,t)=>{Ke||(Ke=o.loadStylesheet("api/components/corner-action.css"),q=o.html`
`),await o.whenReady([".notion-help-button"]);let n=document.querySelector(".notion-help-button"),r=document.querySelector(".onboarding-checklist-button");r&&q.prepend(r),q.prepend(n),o.render(document.querySelector(".notion-app-inner > .notion-cursor-listener"),q);let s=o.html`
${e}
`;return s.addEventListener("click",t),o.render(q,s),s};"use strict";"use strict"; + `,qe,w,z,v,$,C,E,Me,Je,re,f,ze,U,P,W,se,b,oe,O,Z="data-enhancer-panel-pinned",B=()=>$.hasAttribute(Z),N=()=>{let e=[z,v,C,$].filter(t=>t);if(B()){ie();for(let t of e)t.removeAttribute(Z)}else for(let t of e)t.setAttribute(Z,"true");w.set(["panel.pinned"],B())},Ue=async()=>{document.documentElement.style.setProperty("--component--panel-width",f+"px"),w.set(["panel.width"],f)},We=e=>{e.preventDefault(),re=!0,f=Je+(Me-e.clientX),f<190&&(f=190),f>480&&(f=480),$.style.width=f+"px",C.style.width=f+"px",z.style.paddingRight=f+"px",v&&(v.style.right=f+"px")},Ze=e=>{$.style.width="",C.style.width="",z.style.paddingRight="",v&&(v.style.right=""),Ue(),E.style.cursor="",document.body.removeEventListener("mousemove",We),document.body.removeEventListener("mouseup",Ze)},tn=e=>{Me=e.clientX,Je=f,E.style.cursor="auto",document.body.addEventListener("mousemove",We),document.body.addEventListener("mouseup",Ze)},nn=()=>document.body.contains(b),Be=()=>{if(!B())return N();o.render(ze,O),o.empty(b);for(let t of k){let n=P.contains(t.$title),r=o.render(o.html`
`,o.render(o.html``,t.$icon.cloneNode(!0),t.$title.cloneNode(!0)));r.addEventListener("click",()=>{He(t),w.set(["panel.open"],t.id)}),o.render(b,r)}let e=W.getBoundingClientRect();o.render(o.empty(O),o.render(o.html`
`,o.render(o.html`
`,b))),b.querySelector("[data-open]").focus(),b.animate([{opacity:0},{opacity:1}],{duration:200}),document.addEventListener("keydown",Fe)},ie=()=>{document.removeEventListener("keydown",Fe),b.animate([{opacity:1},{opacity:0}],{duration:200}).onfinish=()=>O.remove()},Fe=e=>{if(nn())switch(e.key){case"Escape":ie(),e.stopPropagation();break;case"Enter":document.activeElement.click(),e.stopPropagation();break;case"ArrowUp":(e.target.previousElementSibling||e.target.parentElement.lastElementChild).focus(),e.stopPropagation();break;case"ArrowDown":(e.target.nextElementSibling||e.target.parentElement.firstElementChild).focus(),e.stopPropagation();break}},He=e=>{let t=k.find(({$content:n})=>document.contains(n));o.render(o.empty(P),o.render(o.html``,e.$icon,e.$title)),e.onFocus(),o.render(o.empty(se),e.$content),t&&t.onBlur()};async function rn(){await o.whenReady([".notion-frame"]),z=document.querySelector(".notion-frame"),$=o.html`
`,C=o.html`
`,E=o.html`
`,P=o.html`
`,W=o.render(o.html`
`,P),se=o.html`
`,b=o.html`
`,oe=o.html`
+ ${en} +
`,O=o.html`
`;let e='.notion-cursor-listener > div[style*="flex-end"]',t=()=>{document.contains(v)||(v=document.querySelector(e),B()&&v&&v.setAttribute(Z,"true"))};v=document.querySelector(e),o.addDocumentObserver(t,[e]),await w.get(["panel.pinned"])&&N(),o.addHotkeyListener(await w.get(["panel.hotkey"]),N),U.addEventListener("click",r=>{r.stopPropagation(),N()}),o.render($,o.render(W,P,oe,U),se,E),await sn(),await on();let n='.notion-cursor-listener > .notion-sidebar-container ~ [style^="position: absolute"]';await o.whenReady([n]),document.querySelector(n).before(C,$)}async function sn(){f=await w.get(["panel.width"],240),Ue(),E.addEventListener("mousedown",tn),E.addEventListener("click",()=>{re?re=!1:N()})}async function on(){ze=document.querySelector(".notion-app-inner"),W.addEventListener("click",Be),oe.addEventListener("click",Be),O.addEventListener("click",ie)}var Ie=async({id:e,icon:t,title:n,$content:r,onFocus:s=()=>{},onBlur:c=()=>{}})=>{qe||(qe=o.loadStylesheet("api/components/panel.css")),w||(w=await g.db("36a2ffc9-27ff-480e-84a7-c7700a7d232d")),U||(U=o.html`
+ ${await R.feather("chevrons-right")} +
`);let a={id:e,$icon:o.render(o.html``,t instanceof Element?t:o.html`${t}`),$title:o.render(o.html``,n),$content:r,onFocus:s,onBlur:c};k.push(a),k.length===1&&await rn(),(k.length===1||await w.get(["panel.open"])===e)&&He(a)};"use strict";var Ve,q,Ke=async(e,t)=>{Ve||(Ve=o.loadStylesheet("api/components/corner-action.css"),q=o.html`
`),await o.whenReady([".notion-help-button"]);let n=document.querySelector(".notion-help-button"),r=document.querySelector(".onboarding-checklist-button");r&&q.prepend(r),q.prepend(n),o.render(document.querySelector(".notion-app-inner > .notion-cursor-listener"),q);let s=o.html`
${e}
`;return s.addEventListener("click",t),o.render(q,s),s};"use strict";"use strict"; diff --git a/api/web.mjs b/api/web.mjs index 42a0f25..b247f3c 100644 --- a/api/web.mjs +++ b/api/web.mjs @@ -169,23 +169,35 @@ export const readFromClipboard = () => { const triggerHotkeyListener = (event, hotkey) => { const inInput = document.activeElement.nodeName === 'INPUT' && !hotkey.listenInInput; if (inInput) return; - const pressed = hotkey.keys.every((key) => { - key = key.toLowerCase(); - const modifiers = { + const modifiers = { metaKey: ['meta', 'os', 'win', 'cmd', 'command'], ctrlKey: ['ctrl', 'control'], shiftKey: ['shift'], altKey: ['alt'], - }; - for (const modifier in modifiers) { - const pressed = modifiers[modifier].includes(key) && event[modifier]; - if (pressed) return true; - } - if (key === 'space') key = ' '; - if (key === 'plus') key = '+'; - if (key === event.key.toLowerCase()) return true; - }); - if (pressed) hotkey.callback(event); + }, + pressed = hotkey.keys.every((key) => { + key = key.toLowerCase(); + for (const modifier in modifiers) { + const pressed = modifiers[modifier].includes(key) && event[modifier]; + if (pressed) { + // mark modifier as part of hotkey + modifiers[modifier] = []; + return true; + } + } + if (key === 'space') key = ' '; + if (key === 'plus') key = '+'; + if (key === event.key.toLowerCase()) return true; + }); + if (!pressed) return; + // test for modifiers not in hotkey + // e.g. to differentiate ctrl+x from ctrl+shift+x + for (const modifier in modifiers) { + const modifierPressed = event[modifier], + modifierNotInHotkey = modifiers[modifier].length > 0; + if (modifierPressed && modifierNotInHotkey) return; + } + hotkey.callback(event); }; /** From 2775fab5d4ea91a07b14dd9126a289df2f3c067b Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Mon, 13 Dec 2021 13:34:39 +1100 Subject: [PATCH 48/52] make fs ops depend on __enhancerElectronApi for relative paths (update bundled index.cjs) --- api/fs.mjs | 14 +++++++------- api/index.cjs | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/api/fs.mjs b/api/fs.mjs index bf20afe..86dd027 100644 --- a/api/fs.mjs +++ b/api/fs.mjs @@ -13,6 +13,13 @@ import * as fs from '../env/fs.mjs'; +/** + * get an absolute path to files within notion + * @param {string} path - relative to the root notion/resources/app/ e.g. renderer/search.js + * @runtime electron + */ +export const notionPath = fs.notionPath; + /** * transform a path relative to the enhancer root directory into an absolute path * @type {function} @@ -46,10 +53,3 @@ export const getText = fs.getText; * @returns {boolean} whether or not the file exists */ export const isFile = fs.isFile; - -/** - * get an absolute path to files within notion - * @param {string} path - relative to the root notion/resources/app/ e.g. renderer/search.js - * @runtime electron - */ -export const notionPath = fs.notionPath; diff --git a/api/index.cjs b/api/index.cjs index ab42feb..9b330c6 100644 --- a/api/index.cjs +++ b/api/index.cjs @@ -1,21 +1,21 @@ -var ce=Object.defineProperty;var Xe=e=>ce(e,"__esModule",{value:!0});var y=(e,t)=>{Xe(e);for(var n in t)ce(e,n,{get:t[n],enumerable:!0})};y(exports,{components:()=>R,electron:()=>I,env:()=>j,fmt:()=>h,fs:()=>u,notion:()=>K,registry:()=>g,storage:()=>_,web:()=>o});var j={};y(j,{focusMenu:()=>Ye,focusNotion:()=>De,name:()=>Ge,reload:()=>et,version:()=>Qe});"use strict";var le=globalThis.__enhancerElectronApi.platform,de=globalThis.__enhancerElectronApi.version,pe=globalThis.__enhancerElectronApi.focusMenu,ue=globalThis.__enhancerElectronApi.focusNotion,he=globalThis.__enhancerElectronApi.reload;"use strict";var Ge=le,Qe=de,Ye=pe,De=ue,et=he;var u={};y(u,{getJSON:()=>nt,getText:()=>rt,isFile:()=>st,localPath:()=>tt,notionPath:()=>ot});"use strict";var T=e=>`notion://www.notion.so/__notion-enhancer/${e}`,fe=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.json());try{return globalThis.__enhancerElectronApi.nodeRequire(`notion-enhancer/${e}`)}catch{return fetch(T(e),t).then(r=>r.json())}},me=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.text());try{let n=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:r}=globalThis.__enhancerElectronApi.nodeRequire("path");return n.readFileSync(r(`${__dirname}/../../${e}`))}catch{return fetch(T(e),t).then(r=>r.text())}},ge=async e=>{try{let t=globalThis.__enhancerElectronApi.nodeRequire("fs"),{resolve:n}=globalThis.__enhancerElectronApi.nodeRequire("path");if(e.startsWith("http"))await fetch(e);else try{t.existsSync(n(`${__dirname}/../../${e}`))}catch{await fetch(T(e))}return!0}catch{return!1}},ye=globalThis.__enhancerElectronApi.notionPath;"use strict";var tt=T,nt=fe,rt=me,st=ge,ot=ye;var _={};y(_,{addChangeListener:()=>lt,db:()=>ct,get:()=>it,removeChangeListener:()=>dt,set:()=>at});"use strict";var F=(e,t=void 0)=>globalThis.__enhancerElectronApi.db.get(e,t),H=(e,t)=>globalThis.__enhancerElectronApi.db.set(e,t),ve=(e,t=F,n=H)=>(typeof e=="string"&&(e=[e]),{get:(r=[],s=void 0)=>t([...e,...r],s),set:(r,s)=>n([...e,...r],s)}),we=e=>globalThis.__enhancerElectronApi.db.addChangeListener(e),be=e=>globalThis.__enhancerElectronApi.db.removeChangeListener(e);"use strict";var it=F,at=H,ct=ve,lt=we,dt=be;var I={};y(I,{browser:()=>pt,getFocusedNotionWindow:()=>vt,getNotionWindows:()=>yt,notionRequire:()=>gt,onMessage:()=>mt,sendMessage:()=>ht,sendMessageToHost:()=>ft,webFrame:()=>ut});"use strict";var pt=globalThis.__enhancerElectronApi?.browser,ut=globalThis.__enhancerElectronApi?.webFrame,ht=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessage(e,t,n)},ft=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessageToHost(e,t,n)},mt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.onMessage(e,t,n)},gt=e=>globalThis.__enhancerElectronApi?globalThis.__enhancerElectronApi.notionRequire(e):null,yt=()=>globalThis.__enhancerElectronApi?globalThis.__enhancerElectronApi.getNotionWindows():null,vt=()=>globalThis.__enhancerElectronApi?globalThis.__enhancerElectronApi.getFocusedNotionWindow():null;var K={};y(K,{create:()=>xt,get:()=>_e,getPageID:()=>M,getSpaceID:()=>L,getUserID:()=>xe,search:()=>wt,set:()=>bt,sign:()=>$t,upload:()=>_t});"use strict";var m=e=>(e?.length===32&&!e.includes("-")&&(e=e.replace(/([\d\w]{8})([\d\w]{4})([\d\w]{4})([\d\w]{4})([\d\w]{12})/,"$1-$2-$3-$4-$5")),e),xe=()=>JSON.parse(localStorage["LRU:KeyValueStore2:current-user-id"]||{}).value,M=()=>m(o.queryParams().get("p")||location.pathname.split(/(-|\/)/g).reverse()[0]),V,L=async()=>(V||(V=(await _e(M())).space_id),V),_e=async(e,t="block")=>(e=m(e),(await u.getJSON("https://www.notion.so/api/v3/getRecordValues",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requests:[{table:t,id:e}]}),method:"POST"})).results[0].value),wt=async(e="",t=20,n=L())=>(n=m(await n),await u.getJSON("https://www.notion.so/api/v3/search",{headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"BlocksInSpace",query:e,spaceId:n,limit:t,filters:{isDeletedOnly:!1,excludeTemplates:!1,isNavigableOnly:!1,requireEditPermissions:!1,ancestors:[],createdBy:[],editedBy:[],lastEditedTime:{},createdTime:{}},sort:"Relevance",source:"quick_find"}),method:"POST"})),bt=async({recordID:e,recordTable:t="block",spaceID:n=L(),path:r=[]},s={})=>{n=m(await n),e=m(e);let c=await u.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:n,operations:[{pointer:{table:t,id:e,spaceId:n},path:r,command:r.length?"set":"update",args:s}]}]}),method:"POST"});return c.errorId?c:!0},xt=async({recordValue:e={},recordTable:t="block"}={},{prepend:n=!1,siblingID:r=void 0,parentID:s=M(),parentTable:c="block",spaceID:a=L(),userID:i=xe()}={})=>{a=m(await a),s=m(s),r=m(r);let d=m(e?.id??h.uuidv4()),x=[],A={type:"text",id:d,version:0,created_time:new Date().getTime(),last_edited_time:new Date().getTime(),parent_id:s,parent_table:c,alive:!0,created_by_table:"notion_user",created_by_id:i,last_edited_by_table:"notion_user",last_edited_by_id:i,space_id:a,permissions:[{type:"user_permission",role:"editor",user_id:i}]};c==="space"?(s=a,A.parent_id=a,x.push("pages"),A.type="page"):c==="collection_view"?(x.push("page_sort"),A.type="page"):x.push("content");let ae=await u.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:a,operations:[{pointer:{table:c,id:s,spaceId:a},path:x,command:n?"listBefore":"listAfter",args:{...r?{after:r}:{},id:d}},{pointer:{table:t,id:d,spaceId:a},path:[],command:"set",args:{...A,...e}}]}]}),method:"POST"});return ae.errorId?ae:d},_t=async(e,{pageID:t=M(),spaceID:n=L()}={})=>{n=m(await n),t=m(t);let r=await u.getJSON("https://www.notion.so/api/v3/getUploadFileUrl",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({bucket:"secure",name:e.name,contentType:e.type,record:{table:"block",id:t,spaceId:n}})});return r.errorId?r:(fetch(r.signedPutUrl,{method:"PUT",headers:{"content-type":e.type},body:e}),r.url)},$t=(e,t,n="block")=>(e.startsWith("/")&&(e=`https://notion.so${e}`),e.includes("secure.notion-static.com")&&(e=new URL(e),e=`https://www.notion.so/signed/${encodeURIComponent(e.origin+e.pathname)}?table=${n}&id=${t}`),e);var h={};y(h,{is:()=>kt,rgbContrast:()=>Tt,rgbLogShade:()=>jt,slugger:()=>At,uuidv4:()=>Et});"use strict";var At=(e,t=new Set)=>{e=e.replace(/\s/g,"-").replace(/[^A-Za-z0-9-_]/g,"").toLowerCase();let n=0,r=e;for(;t.has(r);)n++,r=`${e}-${n}`;return r},Et=()=>crypto?.randomUUID?crypto.randomUUID():([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,e=>(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)),jt=(e,t)=>{var n=parseInt,r=Math.round,[s,c,t,a]=t.split(","),i=e<0,d=i?0:e*255**2,i=i?1+e:1-e;return"rgb"+(a?"a(":"(")+r((i*n(s[3]=="a"?s.slice(5):s.slice(4))**2+d)**.5)+","+r((i*n(c)**2+d)**.5)+","+r((i*n(t)**2+d)**.5)+(a?","+a:")")},Tt=(e,t,n)=>Math.sqrt(.299*(e*e)+.587*(t*t)+.114*(n*n))>165.75?"rgb(0,0,0)":"rgb(255,255,255)",Lt={alphanumeric:/^[\w\.-]+$/,uuid:/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,semver:/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i,email:/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i,url:/^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,64}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i,color:/^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i};function St(e,t){let n=e.match(t);return!!(n&&n.length)}var kt=async(e,t,{extension:n=""}={})=>{if(n=!e||!e.endsWith||e.endsWith(n),Array.isArray(t))return t.includes(e);switch(t){case"array":return Array.isArray(e);case"object":return e&&typeof e=="object"&&!Array.isArray(e);case"undefined":case"boolean":case"number":return typeof e===t&&n;case"string":return typeof e===t&&n;case"alphanumeric":case"uuid":case"semver":case"email":case"url":case"color":return typeof e=="string"&&St(e,Lt[t])&&n;case"file":return typeof e=="string"&&e&&await u.isFile(e)&&n}return!1};var g={};y(g,{core:()=>Ae,db:()=>Wt,enabled:()=>Ut,errors:()=>zt,get:()=>D,list:()=>Y,optionDefault:()=>je,optionTypes:()=>Jt,profileDB:()=>X,profileName:()=>Ee,supportedEnvs:()=>Mt});"use strict";var l=async(e,t,n,r,{extension:s="",error:c=`invalid ${t} (${s?`${s} `:""}${r}): ${JSON.stringify(n)}`,optional:a=!1}={})=>{let i;for(let d of Array.isArray(r)?[r]:r.split("|"))if(d==="file"?i=n&&!n.startsWith("http")?await h.is(`repo/${e._dir}/${n}`,d,{extension:s}):!1:i=await h.is(n,d,{extension:s}),i)break;return i||a&&await h.is(n,"undefined")?!0:(c&&e._err(c),!1)},Ct=async e=>(e.environments=e.environments??g.supportedEnvs,await l(e,"environments",e.environments,"array")?e.environments.map(n=>l(e,"environments.env",n,g.supportedEnvs)):!1),Pt=async e=>{if(!await l(e,"tags",e.tags,"array"))return!1;let n=["core","extension","theme","integration"];if(!e.tags.filter(i=>n.includes(i)).length)return e._err(`invalid tags (must contain at least one of 'core', 'extension', 'theme' or 'integration'): +var le=Object.defineProperty;var Xe=e=>le(e,"__esModule",{value:!0});var y=(e,t)=>{Xe(e);for(var n in t)le(e,n,{get:t[n],enumerable:!0})};y(exports,{components:()=>R,electron:()=>V,env:()=>j,fmt:()=>h,fs:()=>u,notion:()=>X,registry:()=>g,storage:()=>_,web:()=>o});var j={};y(j,{focusMenu:()=>Ye,focusNotion:()=>De,name:()=>Ge,reload:()=>et,version:()=>Qe});"use strict";var de=globalThis.__enhancerElectronApi.platform,pe=globalThis.__enhancerElectronApi.version,ue=globalThis.__enhancerElectronApi.focusMenu,he=globalThis.__enhancerElectronApi.focusNotion,fe=globalThis.__enhancerElectronApi.reload;"use strict";var Ge=de,Qe=pe,Ye=ue,De=he,et=fe;var u={};y(u,{getJSON:()=>rt,getText:()=>st,isFile:()=>ot,localPath:()=>nt,notionPath:()=>tt});"use strict";var M=globalThis.__enhancerElectronApi.notionPath,L=e=>`notion://www.notion.so/__notion-enhancer/${e}`,me=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.json());try{return globalThis.__enhancerElectronApi.nodeRequire(`notion-enhancer/${e}`)}catch{return fetch(L(e),t).then(r=>r.json())}},ge=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.text());try{return globalThis.__enhancerElectronApi.nodeRequire("fs").readFileSync(M(`notion-enhancer/${e}`))}catch{return fetch(L(e),t).then(r=>r.text())}},ye=async e=>{try{let t=globalThis.__enhancerElectronApi.nodeRequire("fs");if(e.startsWith("http"))await fetch(e);else try{t.existsSync(M(`notion-enhancer/${e}`))}catch{await fetch(L(e))}return!0}catch{return!1}};"use strict";var tt=M,nt=L,rt=me,st=ge,ot=ye;var _={};y(_,{addChangeListener:()=>lt,db:()=>ct,get:()=>it,removeChangeListener:()=>dt,set:()=>at});"use strict";var H=(e,t=void 0)=>globalThis.__enhancerElectronApi.db.get(e,t),I=(e,t)=>globalThis.__enhancerElectronApi.db.set(e,t),ve=(e,t=H,n=I)=>(typeof e=="string"&&(e=[e]),{get:(r=[],s=void 0)=>t([...e,...r],s),set:(r,s)=>n([...e,...r],s)}),we=e=>globalThis.__enhancerElectronApi.db.addChangeListener(e),be=e=>globalThis.__enhancerElectronApi.db.removeChangeListener(e);"use strict";var it=H,at=I,ct=ve,lt=we,dt=be;var V={};y(V,{browser:()=>pt,getFocusedNotionWindow:()=>vt,getNotionWindows:()=>yt,notionRequire:()=>gt,onMessage:()=>mt,sendMessage:()=>ht,sendMessageToHost:()=>ft,webFrame:()=>ut});"use strict";var pt=globalThis.__enhancerElectronApi?.browser,ut=globalThis.__enhancerElectronApi?.webFrame,ht=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessage(e,t,n)},ft=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessageToHost(e,t,n)},mt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.onMessage(e,t,n)},gt=e=>globalThis.__enhancerElectronApi?globalThis.__enhancerElectronApi.notionRequire(e):null,yt=()=>globalThis.__enhancerElectronApi?globalThis.__enhancerElectronApi.getNotionWindows():null,vt=()=>globalThis.__enhancerElectronApi?globalThis.__enhancerElectronApi.getFocusedNotionWindow():null;var X={};y(X,{create:()=>xt,get:()=>_e,getPageID:()=>J,getSpaceID:()=>T,getUserID:()=>xe,search:()=>wt,set:()=>bt,sign:()=>$t,upload:()=>_t});"use strict";var m=e=>(e?.length===32&&!e.includes("-")&&(e=e.replace(/([\d\w]{8})([\d\w]{4})([\d\w]{4})([\d\w]{4})([\d\w]{12})/,"$1-$2-$3-$4-$5")),e),xe=()=>JSON.parse(localStorage["LRU:KeyValueStore2:current-user-id"]||{}).value,J=()=>m(o.queryParams().get("p")||location.pathname.split(/(-|\/)/g).reverse()[0]),K,T=async()=>(K||(K=(await _e(J())).space_id),K),_e=async(e,t="block")=>(e=m(e),(await u.getJSON("https://www.notion.so/api/v3/getRecordValues",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requests:[{table:t,id:e}]}),method:"POST"})).results[0].value),wt=async(e="",t=20,n=T())=>(n=m(await n),await u.getJSON("https://www.notion.so/api/v3/search",{headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"BlocksInSpace",query:e,spaceId:n,limit:t,filters:{isDeletedOnly:!1,excludeTemplates:!1,isNavigableOnly:!1,requireEditPermissions:!1,ancestors:[],createdBy:[],editedBy:[],lastEditedTime:{},createdTime:{}},sort:"Relevance",source:"quick_find"}),method:"POST"})),bt=async({recordID:e,recordTable:t="block",spaceID:n=T(),path:r=[]},s={})=>{n=m(await n),e=m(e);let c=await u.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:n,operations:[{pointer:{table:t,id:e,spaceId:n},path:r,command:r.length?"set":"update",args:s}]}]}),method:"POST"});return c.errorId?c:!0},xt=async({recordValue:e={},recordTable:t="block"}={},{prepend:n=!1,siblingID:r=void 0,parentID:s=J(),parentTable:c="block",spaceID:a=T(),userID:i=xe()}={})=>{a=m(await a),s=m(s),r=m(r);let d=m(e?.id??h.uuidv4()),x=[],A={type:"text",id:d,version:0,created_time:new Date().getTime(),last_edited_time:new Date().getTime(),parent_id:s,parent_table:c,alive:!0,created_by_table:"notion_user",created_by_id:i,last_edited_by_table:"notion_user",last_edited_by_id:i,space_id:a,permissions:[{type:"user_permission",role:"editor",user_id:i}]};c==="space"?(s=a,A.parent_id=a,x.push("pages"),A.type="page"):c==="collection_view"?(x.push("page_sort"),A.type="page"):x.push("content");let ce=await u.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:a,operations:[{pointer:{table:c,id:s,spaceId:a},path:x,command:n?"listBefore":"listAfter",args:{...r?{after:r}:{},id:d}},{pointer:{table:t,id:d,spaceId:a},path:[],command:"set",args:{...A,...e}}]}]}),method:"POST"});return ce.errorId?ce:d},_t=async(e,{pageID:t=J(),spaceID:n=T()}={})=>{n=m(await n),t=m(t);let r=await u.getJSON("https://www.notion.so/api/v3/getUploadFileUrl",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({bucket:"secure",name:e.name,contentType:e.type,record:{table:"block",id:t,spaceId:n}})});return r.errorId?r:(fetch(r.signedPutUrl,{method:"PUT",headers:{"content-type":e.type},body:e}),r.url)},$t=(e,t,n="block")=>(e.startsWith("/")&&(e=`https://notion.so${e}`),e.includes("secure.notion-static.com")&&(e=new URL(e),e=`https://www.notion.so/signed/${encodeURIComponent(e.origin+e.pathname)}?table=${n}&id=${t}`),e);var h={};y(h,{is:()=>kt,rgbContrast:()=>Lt,rgbLogShade:()=>jt,slugger:()=>At,uuidv4:()=>Et});"use strict";var At=(e,t=new Set)=>{e=e.replace(/\s/g,"-").replace(/[^A-Za-z0-9-_]/g,"").toLowerCase();let n=0,r=e;for(;t.has(r);)n++,r=`${e}-${n}`;return r},Et=()=>crypto?.randomUUID?crypto.randomUUID():([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,e=>(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)),jt=(e,t)=>{var n=parseInt,r=Math.round,[s,c,t,a]=t.split(","),i=e<0,d=i?0:e*255**2,i=i?1+e:1-e;return"rgb"+(a?"a(":"(")+r((i*n(s[3]=="a"?s.slice(5):s.slice(4))**2+d)**.5)+","+r((i*n(c)**2+d)**.5)+","+r((i*n(t)**2+d)**.5)+(a?","+a:")")},Lt=(e,t,n)=>Math.sqrt(.299*(e*e)+.587*(t*t)+.114*(n*n))>165.75?"rgb(0,0,0)":"rgb(255,255,255)",Tt={alphanumeric:/^[\w\.-]+$/,uuid:/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,semver:/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i,email:/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i,url:/^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,64}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i,color:/^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i};function St(e,t){let n=e.match(t);return!!(n&&n.length)}var kt=async(e,t,{extension:n=""}={})=>{if(n=!e||!e.endsWith||e.endsWith(n),Array.isArray(t))return t.includes(e);switch(t){case"array":return Array.isArray(e);case"object":return e&&typeof e=="object"&&!Array.isArray(e);case"undefined":case"boolean":case"number":return typeof e===t&&n;case"string":return typeof e===t&&n;case"alphanumeric":case"uuid":case"semver":case"email":case"url":case"color":return typeof e=="string"&&St(e,Tt[t])&&n;case"file":return typeof e=="string"&&e&&await u.isFile(e)&&n}return!1};var g={};y(g,{core:()=>Ae,db:()=>Wt,enabled:()=>Ut,errors:()=>zt,get:()=>ee,list:()=>D,optionDefault:()=>je,optionTypes:()=>Jt,profileDB:()=>G,profileName:()=>Ee,supportedEnvs:()=>Mt});"use strict";var l=async(e,t,n,r,{extension:s="",error:c=`invalid ${t} (${s?`${s} `:""}${r}): ${JSON.stringify(n)}`,optional:a=!1}={})=>{let i;for(let d of Array.isArray(r)?[r]:r.split("|"))if(d==="file"?i=n&&!n.startsWith("http")?await h.is(`repo/${e._dir}/${n}`,d,{extension:s}):!1:i=await h.is(n,d,{extension:s}),i)break;return i||a&&await h.is(n,"undefined")?!0:(c&&e._err(c),!1)},Ct=async e=>(e.environments=e.environments??g.supportedEnvs,await l(e,"environments",e.environments,"array")?e.environments.map(n=>l(e,"environments.env",n,g.supportedEnvs)):!1),Pt=async e=>{if(!await l(e,"tags",e.tags,"array"))return!1;let n=["core","extension","theme","integration"];if(!e.tags.filter(i=>n.includes(i)).length)return e._err(`invalid tags (must contain at least one of 'core', 'extension', 'theme' or 'integration'): ${JSON.stringify(e.tags)}`),!1;let s=e.tags.includes("theme"),c=e.tags.includes("light")||e.tags.includes("dark"),a=e.tags.includes("light")&&e.tags.includes("dark");return s&&(!c||a)?(e._err(`invalid tags (themes must be either 'light' or 'dark', not neither or both): - ${JSON.stringify(e.tags)}`),!1):e.tags.map(i=>l(e,"tags.tag",i,"string"))},Ot=async e=>await l(e,"authors",e.authors,"array")?e.authors.map(n=>[l(e,"authors.author.name",n.name,"string"),l(e,"authors.author.email",n.email,"email",{optional:!0}),l(e,"authors.author.homepage",n.homepage,"url"),l(e,"authors.author.avatar",n.avatar,"url")]):!1,Nt=async e=>{if(!await l(e,"css",e.css,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.css[r])continue;let s=await l(e,`css.${r}`,e.css[r],"array");s&&(s=e.css[r].map(c=>l(e,`css.${r}.file`,c,"file",{extension:".css"}))),n.push(s)}return n},Rt=async e=>{if(!await l(e,"js",e.js,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.js[r])continue;let s=await l(e,`js.${r}`,e.js[r],"array");s&&(s=e.js[r].map(c=>l(e,`js.${r}.file`,c,"file",{extension:".mjs"}))),n.push(s)}if(e.js.electron)if(await l(e,"js.electron",e.js.electron,"array"))for(let s of e.js.electron){if(!await l(e,"js.electron.file",s,"object")){n.push(!1);continue}n.push([l(e,"js.electron.file.source",s.source,"file",{extension:".cjs"}),l(e,"js.electron.file.target",s.target,"string",{extension:".js"})])}else n.push(!1);return n},qt=async e=>{if(!await l(e,"options",e.options,"array"))return!1;let n=[];for(let r of e.options){let s="options.option";if(!await l(e,`${s}.type`,r.type,g.optionTypes)){n.push(!1);continue}switch(r.environments=r.environments??g.supportedEnvs,n.push([l(e,`${s}.key`,r.key,"alphanumeric"),l(e,`${s}.label`,r.label,"string"),l(e,`${s}.tooltip`,r.tooltip,"string",{optional:!0}),l(e,`${s}.environments`,r.environments,"array").then(a=>a?r.environments.map(i=>l(e,`${s}.environments.env`,i,g.supportedEnvs)):!1)]),r.type){case"toggle":n.push(l(e,`${s}.value`,r.value,"boolean"));break;case"select":{let a=await l(e,`${s}.values`,r.values,"array");a&&(a=r.values.map(i=>l(e,`${s}.values.value`,i,"string"))),n.push(a);break}case"text":case"hotkey":n.push(l(e,`${s}.value`,r.value,"string"));break;case"number":case"color":n.push(l(e,`${s}.value`,r.value,r.type));break;case"file":{let a=await l(e,`${s}.extensions`,r.extensions,"array");a&&(a=r.extensions.map(i=>l(e,`${s}.extensions.extension`,i,"string"))),n.push(a);break}}}return n};async function $e(e){let t=[l(e,"name",e.name,"string"),l(e,"id",e.id,"uuid"),l(e,"version",e.version,"semver"),Ct(e),l(e,"description",e.description,"string"),l(e,"preview",e.preview,"file|url",{optional:!0}),Pt(e),Ot(e),Nt(e),Rt(e),qt(e)];do t=await Promise.all(t.flat(1/0));while(t.some(n=>Array.isArray(n)));return t.every(n=>n)}"use strict";var Ae=["a6621988-551d-495a-97d8-3c568bca2e9e","0f0bf8b6-eae6-4273-b307-8fc43f2ee082","36a2ffc9-27ff-480e-84a7-c7700a7d232d"],Mt=["linux","win32","darwin","extension"],Jt=["toggle","select","text","number","color","file","hotkey"],Ee=async()=>_.get(["currentprofile"],"default"),X=async()=>_.db(["profiles",await Ee()]),G,Q=[],Y=async(e=t=>!0)=>{G||(G=new Promise(async(n,r)=>{let s=[];for(let c of await u.getJSON("repo/registry.json"))try{let a={...await u.getJSON(`repo/${c}/mod.json`),_dir:c,_err:i=>Q.push({source:c,message:i})};await $e(a)&&s.push(a)}catch{Q.push({source:c,message:"invalid mod.json"})}n(s)}));let t=[];for(let n of await G)await e(n)&&t.push(n);return t},zt=async()=>(await Y(),Q),D=async e=>(await Y(t=>t.id===e))[0],Ut=async e=>(await D(e)).environments.includes(j.name)?Ae.includes(e)?!0:(await X()).get(["_mods",e],!1):!1,je=async(e,t)=>{let n=await D(e),r=n.options.find(s=>s.key===t);if(!!r)switch(r.type){case"toggle":case"text":case"number":case"color":case"hotkey":return r.value;case"select":return r.values[0];case"file":return}},Wt=async e=>{let t=await X();return _.db([e],async(n,r=void 0)=>(typeof n=="string"&&(n=[n]),n.length===2&&(r=await je(e,n[1])??r),t.get(n,r)),t.set)};var o={};y(o,{addDocumentObserver:()=>Gt,addHotkeyListener:()=>Kt,copyToClipboard:()=>It,empty:()=>Ft,escape:()=>Le,html:()=>ke,loadStylesheet:()=>Ht,queryParams:()=>Bt,raw:()=>Se,readFromClipboard:()=>Vt,removeDocumentObserver:()=>Qt,removeHotkeyListener:()=>Xt,render:()=>Ce,whenReady:()=>Zt});"use strict";var Te=!1,S=[],ee,J=[],te=[],Zt=(e=[])=>new Promise((t,n)=>{let r=()=>{let s=setInterval(c,100);function c(){!e.every(i=>document.querySelector(i))||(clearInterval(s),t(!0))}c()};document.readyState!=="complete"?document.addEventListener("readystatechange",s=>{document.readyState==="complete"&&r()}):r()}),Bt=()=>new URLSearchParams(window.location.search),Le=e=>e.replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\\/g,"\"),Se=(e,...t)=>{let n=e.map(r=>r+(["string","number"].includes(typeof t[0])?t.shift():Le(JSON.stringify(t.shift(),null,2)??""))).join("");return n.includes("r.trim()).filter(r=>r.length).join(" ")},ke=(e,...t)=>{let n=document.createRange().createContextualFragment(Se(e,...t));return n.children.length===1?n.children[0]:n.children},Ce=(e,...t)=>(t=t.map(n=>n instanceof HTMLCollection?[...n]:n).flat(1/0).filter(n=>n),e.append(...t),e),Ft=e=>{for(;e.firstChild&&e.removeChild(e.firstChild););return e},Ht=e=>{let t=ke`l(e,"tags.tag",i,"string"))},Ot=async e=>await l(e,"authors",e.authors,"array")?e.authors.map(n=>[l(e,"authors.author.name",n.name,"string"),l(e,"authors.author.email",n.email,"email",{optional:!0}),l(e,"authors.author.homepage",n.homepage,"url"),l(e,"authors.author.avatar",n.avatar,"url")]):!1,Nt=async e=>{if(!await l(e,"css",e.css,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.css[r])continue;let s=await l(e,`css.${r}`,e.css[r],"array");s&&(s=e.css[r].map(c=>l(e,`css.${r}.file`,c,"file",{extension:".css"}))),n.push(s)}return n},Rt=async e=>{if(!await l(e,"js",e.js,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.js[r])continue;let s=await l(e,`js.${r}`,e.js[r],"array");s&&(s=e.js[r].map(c=>l(e,`js.${r}.file`,c,"file",{extension:".mjs"}))),n.push(s)}if(e.js.electron)if(await l(e,"js.electron",e.js.electron,"array"))for(let s of e.js.electron){if(!await l(e,"js.electron.file",s,"object")){n.push(!1);continue}n.push([l(e,"js.electron.file.source",s.source,"file",{extension:".cjs"}),l(e,"js.electron.file.target",s.target,"string",{extension:".js"})])}else n.push(!1);return n},qt=async e=>{if(!await l(e,"options",e.options,"array"))return!1;let n=[];for(let r of e.options){let s="options.option";if(!await l(e,`${s}.type`,r.type,g.optionTypes)){n.push(!1);continue}switch(r.environments=r.environments??g.supportedEnvs,n.push([l(e,`${s}.key`,r.key,"alphanumeric"),l(e,`${s}.label`,r.label,"string"),l(e,`${s}.tooltip`,r.tooltip,"string",{optional:!0}),l(e,`${s}.environments`,r.environments,"array").then(a=>a?r.environments.map(i=>l(e,`${s}.environments.env`,i,g.supportedEnvs)):!1)]),r.type){case"toggle":n.push(l(e,`${s}.value`,r.value,"boolean"));break;case"select":{let a=await l(e,`${s}.values`,r.values,"array");a&&(a=r.values.map(i=>l(e,`${s}.values.value`,i,"string"))),n.push(a);break}case"text":case"hotkey":n.push(l(e,`${s}.value`,r.value,"string"));break;case"number":case"color":n.push(l(e,`${s}.value`,r.value,r.type));break;case"file":{let a=await l(e,`${s}.extensions`,r.extensions,"array");a&&(a=r.extensions.map(i=>l(e,`${s}.extensions.extension`,i,"string"))),n.push(a);break}}}return n};async function $e(e){let t=[l(e,"name",e.name,"string"),l(e,"id",e.id,"uuid"),l(e,"version",e.version,"semver"),Ct(e),l(e,"description",e.description,"string"),l(e,"preview",e.preview,"file|url",{optional:!0}),Pt(e),Ot(e),Nt(e),Rt(e),qt(e)];do t=await Promise.all(t.flat(1/0));while(t.some(n=>Array.isArray(n)));return t.every(n=>n)}"use strict";var Ae=["a6621988-551d-495a-97d8-3c568bca2e9e","0f0bf8b6-eae6-4273-b307-8fc43f2ee082","36a2ffc9-27ff-480e-84a7-c7700a7d232d"],Mt=["linux","win32","darwin","extension"],Jt=["toggle","select","text","number","color","file","hotkey"],Ee=async()=>_.get(["currentprofile"],"default"),G=async()=>_.db(["profiles",await Ee()]),Q,Y=[],D=async(e=t=>!0)=>{Q||(Q=new Promise(async(n,r)=>{let s=[];for(let c of await u.getJSON("repo/registry.json"))try{let a={...await u.getJSON(`repo/${c}/mod.json`),_dir:c,_err:i=>Y.push({source:c,message:i})};await $e(a)&&s.push(a)}catch{Y.push({source:c,message:"invalid mod.json"})}n(s)}));let t=[];for(let n of await Q)await e(n)&&t.push(n);return t},zt=async()=>(await D(),Y),ee=async e=>(await D(t=>t.id===e))[0],Ut=async e=>(await ee(e)).environments.includes(j.name)?Ae.includes(e)?!0:(await G()).get(["_mods",e],!1):!1,je=async(e,t)=>{let n=await ee(e),r=n.options.find(s=>s.key===t);if(!!r)switch(r.type){case"toggle":case"text":case"number":case"color":case"hotkey":return r.value;case"select":return r.values[0];case"file":return}},Wt=async e=>{let t=await G();return _.db([e],async(n,r=void 0)=>(typeof n=="string"&&(n=[n]),n.length===2&&(r=await je(e,n[1])??r),t.get(n,r)),t.set)};var o={};y(o,{addDocumentObserver:()=>Gt,addHotkeyListener:()=>Kt,copyToClipboard:()=>It,empty:()=>Ft,escape:()=>Te,html:()=>ke,loadStylesheet:()=>Ht,queryParams:()=>Bt,raw:()=>Se,readFromClipboard:()=>Vt,removeDocumentObserver:()=>Qt,removeHotkeyListener:()=>Xt,render:()=>Ce,whenReady:()=>Zt});"use strict";var Le=!1,S=[],te,z=[],ne=[],Zt=(e=[])=>new Promise((t,n)=>{let r=()=>{let s=setInterval(c,100);function c(){!e.every(i=>document.querySelector(i))||(clearInterval(s),t(!0))}c()};document.readyState!=="complete"?document.addEventListener("readystatechange",s=>{document.readyState==="complete"&&r()}):r()}),Bt=()=>new URLSearchParams(window.location.search),Te=e=>e.replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\\/g,"\"),Se=(e,...t)=>{let n=e.map(r=>r+(["string","number"].includes(typeof t[0])?t.shift():Te(JSON.stringify(t.shift(),null,2)??""))).join("");return n.includes("r.trim()).filter(r=>r.length).join(" ")},ke=(e,...t)=>{let n=document.createRange().createContextualFragment(Se(e,...t));return n.children.length===1?n.children[0]:n.children},Ce=(e,...t)=>(t=t.map(n=>n instanceof HTMLCollection?[...n]:n).flat(1/0).filter(n=>n),e.append(...t),e),Ft=e=>{for(;e.firstChild&&e.removeChild(e.firstChild););return e},Ht=e=>{let t=ke``;return Ce(document.head,t),t},It=async e=>{try{await navigator.clipboard.writeText(e)}catch{let t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}},Vt=()=>navigator.clipboard.readText(),Pe=(e,t)=>{if(document.activeElement.nodeName==="INPUT"&&!t.listenInInput)return;let r={metaKey:["meta","os","win","cmd","command"],ctrlKey:["ctrl","control"],shiftKey:["shift"],altKey:["alt"]};if(!!t.keys.every(c=>{c=c.toLowerCase();for(let a in r)if(r[a].includes(c)&&e[a])return r[a]=[],!0;if(c==="space"&&(c=" "),c==="plus"&&(c="+"),c===e.key.toLowerCase())return!0})){for(let c in r){let a=e[c],i=r[c].length>0;if(a&&i)return}t.callback(e)}},Kt=(e,t,{listenInInput:n=!1,keydown:r=!1}={})=>{typeof e=="string"&&(e=e.split("+")),S.push({keys:e,callback:t,listenInInput:n,keydown:r}),Te||(Te=!0,document.addEventListener("keyup",s=>{for(let c of S.filter(({keydown:a})=>!a))Pe(s,c)}),document.addEventListener("keydown",s=>{for(let c of S.filter(({keydown:a})=>a))Pe(s,c)}))},Xt=e=>{S=S.filter(t=>t.callback!==e)},Gt=(e,t=[])=>{if(!ee){let n=r=>{for(;r.length;){let s=r.shift(),c=(i,d)=>i instanceof Element&&(i.matches(d)||i.matches(`${d} *`)||i.querySelector(d)),a=i=>s.target.matches(i)||s.target.matches(`${i} *`)||[...s.addedNodes].some(d=>c(d,i));for(let i of J)(!i.selectors.length||i.selectors.some(a))&&i.callback(s)}};ee=new MutationObserver((r,s)=>{te.length||requestIdleCallback(()=>n(te)),te.push(...r)}),ee.observe(document.body,{childList:!0,subtree:!0,attributes:!0})}J.push({callback:e,selectors:t})},Qt=e=>{J=J.filter(t=>t.callback!==e)};var R={};y(R,{addCornerAction:()=>Ke,addPanelView:()=>Ie,addTooltip:()=>Ne,feather:()=>Re});"use strict";var Oe,p,Yt=e=>[...e.getClientRects()].reduce((t,n)=>t.some(r=>r.y===n.y)?t:[...t,n],[]).length,Dt=async(e,t,n)=>{p.style.top="0px",p.style.left="0px";let r=e.getBoundingClientRect(),{offsetWidth:s,offsetHeight:c}=p,a=6,i=r.x,d=Math.floor(r.y);if(["top","bottom"].includes(t)){t==="top"&&(d-=c+a),t==="bottom"&&(d+=r.height+a),i-=s/2-r.width/2,p.style.left=`${i}px`,p.style.top=`${d}px`;let x=()=>Yt(p.firstElementChild)>n,A=x();for(;x();)p.style.left=`${window.innerWidth-i>i?i++:i--}px`;A&&(i+=window.innerWidth-i>i?a:-a,p.style.left=`${i}px`),p.style.textAlign="center"}return["left","right"].includes(t)&&(d-=c/2-r.height/2,t==="left"&&(i-=s+a),t==="right"&&(i+=r.width+a),p.style.left=`${i}px`,p.style.top=`${d}px`,p.style.textAlign="start"),!0},Ne=async(e,t,{delay:n=100,offsetDirection:r="bottom",maxLines:s=1}={})=>{Oe||(Oe=o.loadStylesheet("api/components/tooltip.css"),p=o.html`
`,o.render(document.body,p)),globalThis.markdownit||await import(u.localPath("dep/markdown-it.min.js"));let c=markdownit({linkify:!0});t instanceof Element||(t=o.html`
+ />`;return Ce(document.head,t),t},It=async e=>{try{await navigator.clipboard.writeText(e)}catch{let t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}},Vt=()=>navigator.clipboard.readText(),Pe=(e,t)=>{if(document.activeElement.nodeName==="INPUT"&&!t.listenInInput)return;let r={metaKey:["meta","os","win","cmd","command"],ctrlKey:["ctrl","control"],shiftKey:["shift"],altKey:["alt"]};if(!!t.keys.every(c=>{c=c.toLowerCase();for(let a in r)if(r[a].includes(c)&&e[a])return r[a]=[],!0;if(c==="space"&&(c=" "),c==="plus"&&(c="+"),c===e.key.toLowerCase())return!0})){for(let c in r){let a=e[c],i=r[c].length>0;if(a&&i)return}t.callback(e)}},Kt=(e,t,{listenInInput:n=!1,keydown:r=!1}={})=>{typeof e=="string"&&(e=e.split("+")),S.push({keys:e,callback:t,listenInInput:n,keydown:r}),Le||(Le=!0,document.addEventListener("keyup",s=>{for(let c of S.filter(({keydown:a})=>!a))Pe(s,c)}),document.addEventListener("keydown",s=>{for(let c of S.filter(({keydown:a})=>a))Pe(s,c)}))},Xt=e=>{S=S.filter(t=>t.callback!==e)},Gt=(e,t=[])=>{if(!te){let n=r=>{for(;r.length;){let s=r.shift(),c=(i,d)=>i instanceof Element&&(i.matches(d)||i.matches(`${d} *`)||i.querySelector(d)),a=i=>s.target.matches(i)||s.target.matches(`${i} *`)||[...s.addedNodes].some(d=>c(d,i));for(let i of z)(!i.selectors.length||i.selectors.some(a))&&i.callback(s)}};te=new MutationObserver((r,s)=>{ne.length||requestIdleCallback(()=>n(ne)),ne.push(...r)}),te.observe(document.body,{childList:!0,subtree:!0,attributes:!0})}z.push({callback:e,selectors:t})},Qt=e=>{z=z.filter(t=>t.callback!==e)};var R={};y(R,{addCornerAction:()=>Ke,addPanelView:()=>Ie,addTooltip:()=>Ne,feather:()=>Re});"use strict";var Oe,p,Yt=e=>[...e.getClientRects()].reduce((t,n)=>t.some(r=>r.y===n.y)?t:[...t,n],[]).length,Dt=async(e,t,n)=>{p.style.top="0px",p.style.left="0px";let r=e.getBoundingClientRect(),{offsetWidth:s,offsetHeight:c}=p,a=6,i=r.x,d=Math.floor(r.y);if(["top","bottom"].includes(t)){t==="top"&&(d-=c+a),t==="bottom"&&(d+=r.height+a),i-=s/2-r.width/2,p.style.left=`${i}px`,p.style.top=`${d}px`;let x=()=>Yt(p.firstElementChild)>n,A=x();for(;x();)p.style.left=`${window.innerWidth-i>i?i++:i--}px`;A&&(i+=window.innerWidth-i>i?a:-a,p.style.left=`${i}px`),p.style.textAlign="center"}return["left","right"].includes(t)&&(d-=c/2-r.height/2,t==="left"&&(i-=s+a),t==="right"&&(i+=r.width+a),p.style.left=`${i}px`,p.style.top=`${d}px`,p.style.textAlign="start"),!0},Ne=async(e,t,{delay:n=100,offsetDirection:r="bottom",maxLines:s=1}={})=>{Oe||(Oe=o.loadStylesheet("api/components/tooltip.css"),p=o.html`
`,o.render(document.body,p)),globalThis.markdownit||await import(u.localPath("dep/markdown-it.min.js"));let c=markdownit({linkify:!0});t instanceof Element||(t=o.html`
${t.split(` `).map(i=>c.renderInline(i)).join("
")} -
`);let a;e.addEventListener("mouseover",async i=>{a||(a=setTimeout(async()=>{e.matches(":hover")&&p.style.display!=="block"&&(p.style.display="block",o.render(o.empty(p),t),Dt(e,r,s),await p.animate([{opacity:0},{opacity:1}],{duration:65}).finished),a=void 0},n))}),e.addEventListener("mouseout",async i=>{a=void 0,p.style.display==="block"&&!e.matches(":hover")&&(await p.animate([{opacity:1},{opacity:0}],{duration:65}).finished,p.style.display="")})};"use strict";var ne,Re=async(e,t={})=>(ne||(ne=o.html`${await u.getText("dep/feather-sprite.svg")}`),t.style=((t.style?t.style+";":"")+"stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;fill:none;").trim(),t.viewBox="0 0 24 24",``${o.escape(n)}="${o.escape(r)}"`).join(" ")}>${ne.getElementById(e)?.innerHTML}`);"use strict";var k=[],en=o.raw` +
`);let a;e.addEventListener("mouseover",async i=>{a||(a=setTimeout(async()=>{e.matches(":hover")&&p.style.display!=="block"&&(p.style.display="block",o.render(o.empty(p),t),Dt(e,r,s),await p.animate([{opacity:0},{opacity:1}],{duration:65}).finished),a=void 0},n))}),e.addEventListener("mouseout",async i=>{a=void 0,p.style.display==="block"&&!e.matches(":hover")&&(await p.animate([{opacity:1},{opacity:0}],{duration:65}).finished,p.style.display="")})};"use strict";var re,Re=async(e,t={})=>(re||(re=o.html`${await u.getText("dep/feather-sprite.svg")}`),t.style=((t.style?t.style+";":"")+"stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;fill:none;").trim(),t.viewBox="0 0 24 24",``${o.escape(n)}="${o.escape(r)}"`).join(" ")}>${re.getElementById(e)?.innerHTML}`);"use strict";var k=[],en=o.raw` - `,qe,w,z,v,$,C,E,Me,Je,re,f,ze,U,P,W,se,b,oe,O,Z="data-enhancer-panel-pinned",B=()=>$.hasAttribute(Z),N=()=>{let e=[z,v,C,$].filter(t=>t);if(B()){ie();for(let t of e)t.removeAttribute(Z)}else for(let t of e)t.setAttribute(Z,"true");w.set(["panel.pinned"],B())},Ue=async()=>{document.documentElement.style.setProperty("--component--panel-width",f+"px"),w.set(["panel.width"],f)},We=e=>{e.preventDefault(),re=!0,f=Je+(Me-e.clientX),f<190&&(f=190),f>480&&(f=480),$.style.width=f+"px",C.style.width=f+"px",z.style.paddingRight=f+"px",v&&(v.style.right=f+"px")},Ze=e=>{$.style.width="",C.style.width="",z.style.paddingRight="",v&&(v.style.right=""),Ue(),E.style.cursor="",document.body.removeEventListener("mousemove",We),document.body.removeEventListener("mouseup",Ze)},tn=e=>{Me=e.clientX,Je=f,E.style.cursor="auto",document.body.addEventListener("mousemove",We),document.body.addEventListener("mouseup",Ze)},nn=()=>document.body.contains(b),Be=()=>{if(!B())return N();o.render(ze,O),o.empty(b);for(let t of k){let n=P.contains(t.$title),r=o.render(o.html`
`,o.render(o.html``,t.$icon.cloneNode(!0),t.$title.cloneNode(!0)));r.addEventListener("click",()=>{He(t),w.set(["panel.open"],t.id)}),o.render(b,r)}let e=W.getBoundingClientRect();o.render(o.empty(O),o.render(o.html`
`,o.render(o.html`
`,b))),b.querySelector("[data-open]").focus(),b.animate([{opacity:0},{opacity:1}],{duration:200}),document.addEventListener("keydown",Fe)},ie=()=>{document.removeEventListener("keydown",Fe),b.animate([{opacity:1},{opacity:0}],{duration:200}).onfinish=()=>O.remove()},Fe=e=>{if(nn())switch(e.key){case"Escape":ie(),e.stopPropagation();break;case"Enter":document.activeElement.click(),e.stopPropagation();break;case"ArrowUp":(e.target.previousElementSibling||e.target.parentElement.lastElementChild).focus(),e.stopPropagation();break;case"ArrowDown":(e.target.nextElementSibling||e.target.parentElement.firstElementChild).focus(),e.stopPropagation();break}},He=e=>{let t=k.find(({$content:n})=>document.contains(n));o.render(o.empty(P),o.render(o.html``,e.$icon,e.$title)),e.onFocus(),o.render(o.empty(se),e.$content),t&&t.onBlur()};async function rn(){await o.whenReady([".notion-frame"]),z=document.querySelector(".notion-frame"),$=o.html`
`,C=o.html`
`,E=o.html`
`,P=o.html`
`,W=o.render(o.html`
`,P),se=o.html`
`,b=o.html`
`,oe=o.html`
+ `,qe,w,U,v,$,C,E,Me,Je,se,f,ze,W,P,Z,oe,b,ie,O,B="data-enhancer-panel-pinned",F=()=>$.hasAttribute(B),N=()=>{let e=[U,v,C,$].filter(t=>t);if(F()){ae();for(let t of e)t.removeAttribute(B)}else for(let t of e)t.setAttribute(B,"true");w.set(["panel.pinned"],F())},Ue=async()=>{document.documentElement.style.setProperty("--component--panel-width",f+"px"),w.set(["panel.width"],f)},We=e=>{e.preventDefault(),se=!0,f=Je+(Me-e.clientX),f<190&&(f=190),f>480&&(f=480),$.style.width=f+"px",C.style.width=f+"px",U.style.paddingRight=f+"px",v&&(v.style.right=f+"px")},Ze=e=>{$.style.width="",C.style.width="",U.style.paddingRight="",v&&(v.style.right=""),Ue(),E.style.cursor="",document.body.removeEventListener("mousemove",We),document.body.removeEventListener("mouseup",Ze)},tn=e=>{Me=e.clientX,Je=f,E.style.cursor="auto",document.body.addEventListener("mousemove",We),document.body.addEventListener("mouseup",Ze)},nn=()=>document.body.contains(b),Be=()=>{if(!F())return N();o.render(ze,O),o.empty(b);for(let t of k){let n=P.contains(t.$title),r=o.render(o.html`
`,o.render(o.html``,t.$icon.cloneNode(!0),t.$title.cloneNode(!0)));r.addEventListener("click",()=>{He(t),w.set(["panel.open"],t.id)}),o.render(b,r)}let e=Z.getBoundingClientRect();o.render(o.empty(O),o.render(o.html`
`,o.render(o.html`
`,b))),b.querySelector("[data-open]").focus(),b.animate([{opacity:0},{opacity:1}],{duration:200}),document.addEventListener("keydown",Fe)},ae=()=>{document.removeEventListener("keydown",Fe),b.animate([{opacity:1},{opacity:0}],{duration:200}).onfinish=()=>O.remove()},Fe=e=>{if(nn())switch(e.key){case"Escape":ae(),e.stopPropagation();break;case"Enter":document.activeElement.click(),e.stopPropagation();break;case"ArrowUp":(e.target.previousElementSibling||e.target.parentElement.lastElementChild).focus(),e.stopPropagation();break;case"ArrowDown":(e.target.nextElementSibling||e.target.parentElement.firstElementChild).focus(),e.stopPropagation();break}},He=e=>{let t=k.find(({$content:n})=>document.contains(n));o.render(o.empty(P),o.render(o.html``,e.$icon,e.$title)),e.onFocus(),o.render(o.empty(oe),e.$content),t&&t.onBlur()};async function rn(){await o.whenReady([".notion-frame"]),U=document.querySelector(".notion-frame"),$=o.html`
`,C=o.html`
`,E=o.html`
`,P=o.html`
`,Z=o.render(o.html`
`,P),oe=o.html`
`,b=o.html`
`,ie=o.html`
${en} -
`,O=o.html`
`;let e='.notion-cursor-listener > div[style*="flex-end"]',t=()=>{document.contains(v)||(v=document.querySelector(e),B()&&v&&v.setAttribute(Z,"true"))};v=document.querySelector(e),o.addDocumentObserver(t,[e]),await w.get(["panel.pinned"])&&N(),o.addHotkeyListener(await w.get(["panel.hotkey"]),N),U.addEventListener("click",r=>{r.stopPropagation(),N()}),o.render($,o.render(W,P,oe,U),se,E),await sn(),await on();let n='.notion-cursor-listener > .notion-sidebar-container ~ [style^="position: absolute"]';await o.whenReady([n]),document.querySelector(n).before(C,$)}async function sn(){f=await w.get(["panel.width"],240),Ue(),E.addEventListener("mousedown",tn),E.addEventListener("click",()=>{re?re=!1:N()})}async function on(){ze=document.querySelector(".notion-app-inner"),W.addEventListener("click",Be),oe.addEventListener("click",Be),O.addEventListener("click",ie)}var Ie=async({id:e,icon:t,title:n,$content:r,onFocus:s=()=>{},onBlur:c=()=>{}})=>{qe||(qe=o.loadStylesheet("api/components/panel.css")),w||(w=await g.db("36a2ffc9-27ff-480e-84a7-c7700a7d232d")),U||(U=o.html`
+
`,O=o.html`
`;let e='.notion-cursor-listener > div[style*="flex-end"]',t=()=>{document.contains(v)||(v=document.querySelector(e),F()&&v&&v.setAttribute(B,"true"))};v=document.querySelector(e),o.addDocumentObserver(t,[e]),await w.get(["panel.pinned"])&&N(),o.addHotkeyListener(await w.get(["panel.hotkey"]),N),W.addEventListener("click",r=>{r.stopPropagation(),N()}),o.render($,o.render(Z,P,ie,W),oe,E),await sn(),await on();let n='.notion-cursor-listener > .notion-sidebar-container ~ [style^="position: absolute"]';await o.whenReady([n]),document.querySelector(n).before(C,$)}async function sn(){f=await w.get(["panel.width"],240),Ue(),E.addEventListener("mousedown",tn),E.addEventListener("click",()=>{se?se=!1:N()})}async function on(){ze=document.querySelector(".notion-app-inner"),Z.addEventListener("click",Be),ie.addEventListener("click",Be),O.addEventListener("click",ae)}var Ie=async({id:e,icon:t,title:n,$content:r,onFocus:s=()=>{},onBlur:c=()=>{}})=>{qe||(qe=o.loadStylesheet("api/components/panel.css")),w||(w=await g.db("36a2ffc9-27ff-480e-84a7-c7700a7d232d")),W||(W=o.html`
${await R.feather("chevrons-right")}
`);let a={id:e,$icon:o.render(o.html``,t instanceof Element?t:o.html`${t}`),$title:o.render(o.html``,n),$content:r,onFocus:s,onBlur:c};k.push(a),k.length===1&&await rn(),(k.length===1||await w.get(["panel.open"])===e)&&He(a)};"use strict";var Ve,q,Ke=async(e,t)=>{Ve||(Ve=o.loadStylesheet("api/components/corner-action.css"),q=o.html`
`),await o.whenReady([".notion-help-button"]);let n=document.querySelector(".notion-help-button"),r=document.querySelector(".onboarding-checklist-button");r&&q.prepend(r),q.prepend(n),o.render(document.querySelector(".notion-app-inner > .notion-cursor-listener"),q);let s=o.html`
${e}
`;return s.addEventListener("click",t),o.render(q,s),s};"use strict";"use strict"; From caa01173187231c281def9d822ab75f2da024ba5 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Mon, 13 Dec 2021 18:39:41 +1100 Subject: [PATCH 49/52] electron fs: use notion:// for api reqs instead of http:// --- api/index.cjs | 14 +++++++------- api/notion.mjs | 48 ++++++++++++++++++++++++------------------------ 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/api/index.cjs b/api/index.cjs index 9b330c6..c130ac1 100644 --- a/api/index.cjs +++ b/api/index.cjs @@ -1,9 +1,9 @@ -var le=Object.defineProperty;var Xe=e=>le(e,"__esModule",{value:!0});var y=(e,t)=>{Xe(e);for(var n in t)le(e,n,{get:t[n],enumerable:!0})};y(exports,{components:()=>R,electron:()=>V,env:()=>j,fmt:()=>h,fs:()=>u,notion:()=>X,registry:()=>g,storage:()=>_,web:()=>o});var j={};y(j,{focusMenu:()=>Ye,focusNotion:()=>De,name:()=>Ge,reload:()=>et,version:()=>Qe});"use strict";var de=globalThis.__enhancerElectronApi.platform,pe=globalThis.__enhancerElectronApi.version,ue=globalThis.__enhancerElectronApi.focusMenu,he=globalThis.__enhancerElectronApi.focusNotion,fe=globalThis.__enhancerElectronApi.reload;"use strict";var Ge=de,Qe=pe,Ye=ue,De=he,et=fe;var u={};y(u,{getJSON:()=>rt,getText:()=>st,isFile:()=>ot,localPath:()=>nt,notionPath:()=>tt});"use strict";var M=globalThis.__enhancerElectronApi.notionPath,L=e=>`notion://www.notion.so/__notion-enhancer/${e}`,me=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.json());try{return globalThis.__enhancerElectronApi.nodeRequire(`notion-enhancer/${e}`)}catch{return fetch(L(e),t).then(r=>r.json())}},ge=(e,t={})=>{if(e.startsWith("http"))return fetch(e,t).then(n=>n.text());try{return globalThis.__enhancerElectronApi.nodeRequire("fs").readFileSync(M(`notion-enhancer/${e}`))}catch{return fetch(L(e),t).then(r=>r.text())}},ye=async e=>{try{let t=globalThis.__enhancerElectronApi.nodeRequire("fs");if(e.startsWith("http"))await fetch(e);else try{t.existsSync(M(`notion-enhancer/${e}`))}catch{await fetch(L(e))}return!0}catch{return!1}};"use strict";var tt=M,nt=L,rt=me,st=ge,ot=ye;var _={};y(_,{addChangeListener:()=>lt,db:()=>ct,get:()=>it,removeChangeListener:()=>dt,set:()=>at});"use strict";var H=(e,t=void 0)=>globalThis.__enhancerElectronApi.db.get(e,t),I=(e,t)=>globalThis.__enhancerElectronApi.db.set(e,t),ve=(e,t=H,n=I)=>(typeof e=="string"&&(e=[e]),{get:(r=[],s=void 0)=>t([...e,...r],s),set:(r,s)=>n([...e,...r],s)}),we=e=>globalThis.__enhancerElectronApi.db.addChangeListener(e),be=e=>globalThis.__enhancerElectronApi.db.removeChangeListener(e);"use strict";var it=H,at=I,ct=ve,lt=we,dt=be;var V={};y(V,{browser:()=>pt,getFocusedNotionWindow:()=>vt,getNotionWindows:()=>yt,notionRequire:()=>gt,onMessage:()=>mt,sendMessage:()=>ht,sendMessageToHost:()=>ft,webFrame:()=>ut});"use strict";var pt=globalThis.__enhancerElectronApi?.browser,ut=globalThis.__enhancerElectronApi?.webFrame,ht=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessage(e,t,n)},ft=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessageToHost(e,t,n)},mt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.onMessage(e,t,n)},gt=e=>globalThis.__enhancerElectronApi?globalThis.__enhancerElectronApi.notionRequire(e):null,yt=()=>globalThis.__enhancerElectronApi?globalThis.__enhancerElectronApi.getNotionWindows():null,vt=()=>globalThis.__enhancerElectronApi?globalThis.__enhancerElectronApi.getFocusedNotionWindow():null;var X={};y(X,{create:()=>xt,get:()=>_e,getPageID:()=>J,getSpaceID:()=>T,getUserID:()=>xe,search:()=>wt,set:()=>bt,sign:()=>$t,upload:()=>_t});"use strict";var m=e=>(e?.length===32&&!e.includes("-")&&(e=e.replace(/([\d\w]{8})([\d\w]{4})([\d\w]{4})([\d\w]{4})([\d\w]{12})/,"$1-$2-$3-$4-$5")),e),xe=()=>JSON.parse(localStorage["LRU:KeyValueStore2:current-user-id"]||{}).value,J=()=>m(o.queryParams().get("p")||location.pathname.split(/(-|\/)/g).reverse()[0]),K,T=async()=>(K||(K=(await _e(J())).space_id),K),_e=async(e,t="block")=>(e=m(e),(await u.getJSON("https://www.notion.so/api/v3/getRecordValues",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requests:[{table:t,id:e}]}),method:"POST"})).results[0].value),wt=async(e="",t=20,n=T())=>(n=m(await n),await u.getJSON("https://www.notion.so/api/v3/search",{headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"BlocksInSpace",query:e,spaceId:n,limit:t,filters:{isDeletedOnly:!1,excludeTemplates:!1,isNavigableOnly:!1,requireEditPermissions:!1,ancestors:[],createdBy:[],editedBy:[],lastEditedTime:{},createdTime:{}},sort:"Relevance",source:"quick_find"}),method:"POST"})),bt=async({recordID:e,recordTable:t="block",spaceID:n=T(),path:r=[]},s={})=>{n=m(await n),e=m(e);let c=await u.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:n,operations:[{pointer:{table:t,id:e,spaceId:n},path:r,command:r.length?"set":"update",args:s}]}]}),method:"POST"});return c.errorId?c:!0},xt=async({recordValue:e={},recordTable:t="block"}={},{prepend:n=!1,siblingID:r=void 0,parentID:s=J(),parentTable:c="block",spaceID:a=T(),userID:i=xe()}={})=>{a=m(await a),s=m(s),r=m(r);let d=m(e?.id??h.uuidv4()),x=[],A={type:"text",id:d,version:0,created_time:new Date().getTime(),last_edited_time:new Date().getTime(),parent_id:s,parent_table:c,alive:!0,created_by_table:"notion_user",created_by_id:i,last_edited_by_table:"notion_user",last_edited_by_id:i,space_id:a,permissions:[{type:"user_permission",role:"editor",user_id:i}]};c==="space"?(s=a,A.parent_id=a,x.push("pages"),A.type="page"):c==="collection_view"?(x.push("page_sort"),A.type="page"):x.push("content");let ce=await u.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:a,operations:[{pointer:{table:c,id:s,spaceId:a},path:x,command:n?"listBefore":"listAfter",args:{...r?{after:r}:{},id:d}},{pointer:{table:t,id:d,spaceId:a},path:[],command:"set",args:{...A,...e}}]}]}),method:"POST"});return ce.errorId?ce:d},_t=async(e,{pageID:t=J(),spaceID:n=T()}={})=>{n=m(await n),t=m(t);let r=await u.getJSON("https://www.notion.so/api/v3/getUploadFileUrl",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({bucket:"secure",name:e.name,contentType:e.type,record:{table:"block",id:t,spaceId:n}})});return r.errorId?r:(fetch(r.signedPutUrl,{method:"PUT",headers:{"content-type":e.type},body:e}),r.url)},$t=(e,t,n="block")=>(e.startsWith("/")&&(e=`https://notion.so${e}`),e.includes("secure.notion-static.com")&&(e=new URL(e),e=`https://www.notion.so/signed/${encodeURIComponent(e.origin+e.pathname)}?table=${n}&id=${t}`),e);var h={};y(h,{is:()=>kt,rgbContrast:()=>Lt,rgbLogShade:()=>jt,slugger:()=>At,uuidv4:()=>Et});"use strict";var At=(e,t=new Set)=>{e=e.replace(/\s/g,"-").replace(/[^A-Za-z0-9-_]/g,"").toLowerCase();let n=0,r=e;for(;t.has(r);)n++,r=`${e}-${n}`;return r},Et=()=>crypto?.randomUUID?crypto.randomUUID():([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,e=>(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)),jt=(e,t)=>{var n=parseInt,r=Math.round,[s,c,t,a]=t.split(","),i=e<0,d=i?0:e*255**2,i=i?1+e:1-e;return"rgb"+(a?"a(":"(")+r((i*n(s[3]=="a"?s.slice(5):s.slice(4))**2+d)**.5)+","+r((i*n(c)**2+d)**.5)+","+r((i*n(t)**2+d)**.5)+(a?","+a:")")},Lt=(e,t,n)=>Math.sqrt(.299*(e*e)+.587*(t*t)+.114*(n*n))>165.75?"rgb(0,0,0)":"rgb(255,255,255)",Tt={alphanumeric:/^[\w\.-]+$/,uuid:/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,semver:/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i,email:/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i,url:/^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,64}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i,color:/^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i};function St(e,t){let n=e.match(t);return!!(n&&n.length)}var kt=async(e,t,{extension:n=""}={})=>{if(n=!e||!e.endsWith||e.endsWith(n),Array.isArray(t))return t.includes(e);switch(t){case"array":return Array.isArray(e);case"object":return e&&typeof e=="object"&&!Array.isArray(e);case"undefined":case"boolean":case"number":return typeof e===t&&n;case"string":return typeof e===t&&n;case"alphanumeric":case"uuid":case"semver":case"email":case"url":case"color":return typeof e=="string"&&St(e,Tt[t])&&n;case"file":return typeof e=="string"&&e&&await u.isFile(e)&&n}return!1};var g={};y(g,{core:()=>Ae,db:()=>Wt,enabled:()=>Ut,errors:()=>zt,get:()=>ee,list:()=>D,optionDefault:()=>je,optionTypes:()=>Jt,profileDB:()=>G,profileName:()=>Ee,supportedEnvs:()=>Mt});"use strict";var l=async(e,t,n,r,{extension:s="",error:c=`invalid ${t} (${s?`${s} `:""}${r}): ${JSON.stringify(n)}`,optional:a=!1}={})=>{let i;for(let d of Array.isArray(r)?[r]:r.split("|"))if(d==="file"?i=n&&!n.startsWith("http")?await h.is(`repo/${e._dir}/${n}`,d,{extension:s}):!1:i=await h.is(n,d,{extension:s}),i)break;return i||a&&await h.is(n,"undefined")?!0:(c&&e._err(c),!1)},Ct=async e=>(e.environments=e.environments??g.supportedEnvs,await l(e,"environments",e.environments,"array")?e.environments.map(n=>l(e,"environments.env",n,g.supportedEnvs)):!1),Pt=async e=>{if(!await l(e,"tags",e.tags,"array"))return!1;let n=["core","extension","theme","integration"];if(!e.tags.filter(i=>n.includes(i)).length)return e._err(`invalid tags (must contain at least one of 'core', 'extension', 'theme' or 'integration'): +var le=Object.defineProperty;var Xe=e=>le(e,"__esModule",{value:!0});var y=(e,t)=>{Xe(e);for(var n in t)le(e,n,{get:t[n],enumerable:!0})};y(exports,{components:()=>R,electron:()=>V,env:()=>j,fmt:()=>h,fs:()=>u,notion:()=>X,registry:()=>g,storage:()=>_,web:()=>o});var j={};y(j,{focusMenu:()=>Ye,focusNotion:()=>De,name:()=>Ge,reload:()=>et,version:()=>Qe});"use strict";var de=globalThis.__enhancerElectronApi.platform,pe=globalThis.__enhancerElectronApi.version,ue=globalThis.__enhancerElectronApi.focusMenu,he=globalThis.__enhancerElectronApi.focusNotion,fe=globalThis.__enhancerElectronApi.reload;"use strict";var Ge=de,Qe=pe,Ye=ue,De=he,et=fe;var u={};y(u,{getJSON:()=>rt,getText:()=>st,isFile:()=>ot,localPath:()=>nt,notionPath:()=>tt});"use strict";var M=globalThis.__enhancerElectronApi.notionPath,L=e=>`notion://www.notion.so/__notion-enhancer/${e}`,me=(e,t={})=>{if(e=e.replace(/^https:\/\/www\.notion\.so\//,"notion://www.notion.so/"),e.startsWith("http")||e.startsWith("notion://"))return fetch(e,t).then(r=>r.json());try{return globalThis.__enhancerElectronApi.nodeRequire(`notion-enhancer/${e}`)}catch{return fetch(L(e),t).then(s=>s.json())}},ge=(e,t={})=>{if(e=e.replace(/^https:\/\/www\.notion\.so\//,"notion://www.notion.so/"),e.startsWith("http")||e.startsWith("notion://"))return fetch(e,t).then(r=>r.text());try{return globalThis.__enhancerElectronApi.nodeRequire("fs").readFileSync(M(`notion-enhancer/${e}`))}catch{return fetch(L(e),t).then(s=>s.text())}},ye=async e=>{try{let t=globalThis.__enhancerElectronApi.nodeRequire("fs");if(e.startsWith("http"))await fetch(e);else try{t.existsSync(M(`notion-enhancer/${e}`))}catch{await fetch(L(e))}return!0}catch{return!1}};"use strict";var tt=M,nt=L,rt=me,st=ge,ot=ye;var _={};y(_,{addChangeListener:()=>lt,db:()=>ct,get:()=>it,removeChangeListener:()=>dt,set:()=>at});"use strict";var H=(e,t=void 0)=>globalThis.__enhancerElectronApi.db.get(e,t),I=(e,t)=>globalThis.__enhancerElectronApi.db.set(e,t),we=(e,t=H,n=I)=>(typeof e=="string"&&(e=[e]),{get:(r=[],s=void 0)=>t([...e,...r],s),set:(r,s)=>n([...e,...r],s)}),ve=e=>globalThis.__enhancerElectronApi.db.addChangeListener(e),be=e=>globalThis.__enhancerElectronApi.db.removeChangeListener(e);"use strict";var it=H,at=I,ct=we,lt=ve,dt=be;var V={};y(V,{browser:()=>pt,getFocusedNotionWindow:()=>wt,getNotionWindows:()=>yt,notionRequire:()=>gt,onMessage:()=>mt,sendMessage:()=>ht,sendMessageToHost:()=>ft,webFrame:()=>ut});"use strict";var pt=globalThis.__enhancerElectronApi?.browser,ut=globalThis.__enhancerElectronApi?.webFrame,ht=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessage(e,t,n)},ft=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.sendMessageToHost(e,t,n)},mt=(e,t,n="notion-enhancer")=>{globalThis.__enhancerElectronApi&&globalThis.__enhancerElectronApi.ipcRenderer.onMessage(e,t,n)},gt=e=>globalThis.__enhancerElectronApi?globalThis.__enhancerElectronApi.notionRequire(e):null,yt=()=>globalThis.__enhancerElectronApi?globalThis.__enhancerElectronApi.getNotionWindows():null,wt=()=>globalThis.__enhancerElectronApi?globalThis.__enhancerElectronApi.getFocusedNotionWindow():null;var X={};y(X,{create:()=>xt,get:()=>xe,getPageID:()=>J,getSpaceID:()=>T,getUserID:()=>_e,search:()=>vt,set:()=>bt,sign:()=>$t,upload:()=>_t});"use strict";var m=e=>(e?.length===32&&!e.includes("-")&&(e=e.replace(/([\d\w]{8})([\d\w]{4})([\d\w]{4})([\d\w]{4})([\d\w]{12})/,"$1-$2-$3-$4-$5")),e),xe=async(e,t="block")=>{e=m(e);let n=await u.getJSON("https://www.notion.so/api/v3/getRecordValues",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requests:[{table:t,id:e}]}),method:"POST"});return n?.results?.[0]?.value||n},_e=()=>JSON.parse(localStorage["LRU:KeyValueStore2:current-user-id"]||{}).value,J=()=>m(o.queryParams().get("p")||location.pathname.split(/(-|\/)/g).reverse()[0]),K,T=async()=>(K||(K=(await xe(J())).space_id),K),vt=async(e="",t=20,n=T())=>(n=m(await n),await u.getJSON("https://www.notion.so/api/v3/search",{headers:{"Content-Type":"application/json"},body:JSON.stringify({type:"BlocksInSpace",query:e,spaceId:n,limit:t,filters:{isDeletedOnly:!1,excludeTemplates:!1,isNavigableOnly:!1,requireEditPermissions:!1,ancestors:[],createdBy:[],editedBy:[],lastEditedTime:{},createdTime:{}},sort:"Relevance",source:"quick_find"}),method:"POST"})),bt=async({recordID:e,recordTable:t="block",spaceID:n=T(),path:r=[]},s={})=>{n=m(await n),e=m(e);let c=await u.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:n,operations:[{pointer:{table:t,id:e,spaceId:n},path:r,command:r.length?"set":"update",args:s}]}]}),method:"POST"});return c.errorId?c:!0},xt=async({recordValue:e={},recordTable:t="block"}={},{prepend:n=!1,siblingID:r=void 0,parentID:s=J(),parentTable:c="block",spaceID:a=T(),userID:i=_e()}={})=>{a=m(await a),s=m(s),r=m(r);let d=m(e?.id??h.uuidv4()),x=[],A={type:"text",id:d,version:0,created_time:new Date().getTime(),last_edited_time:new Date().getTime(),parent_id:s,parent_table:c,alive:!0,created_by_table:"notion_user",created_by_id:i,last_edited_by_table:"notion_user",last_edited_by_id:i,space_id:a,permissions:[{type:"user_permission",role:"editor",user_id:i}]};c==="space"?(s=a,A.parent_id=a,x.push("pages"),A.type="page"):c==="collection_view"?(x.push("page_sort"),A.type="page"):x.push("content");let ce=await u.getJSON("https://www.notion.so/api/v3/saveTransactions",{headers:{"Content-Type":"application/json"},body:JSON.stringify({requestId:h.uuidv4(),transactions:[{id:h.uuidv4(),spaceId:a,operations:[{pointer:{table:c,id:s,spaceId:a},path:x,command:n?"listBefore":"listAfter",args:{...r?{after:r}:{},id:d}},{pointer:{table:t,id:d,spaceId:a},path:[],command:"set",args:{...A,...e}}]}]}),method:"POST"});return ce.errorId?ce:d},_t=async(e,{pageID:t=J(),spaceID:n=T()}={})=>{n=m(await n),t=m(t);let r=await u.getJSON("https://www.notion.so/api/v3/getUploadFileUrl",{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({bucket:"secure",name:e.name,contentType:e.type,record:{table:"block",id:t,spaceId:n}})});return r.errorId?r:(fetch(r.signedPutUrl,{method:"PUT",headers:{"content-type":e.type},body:e}),r.url)},$t=(e,t,n="block")=>(e.startsWith("/")&&(e=`https://notion.so${e}`),e.includes("secure.notion-static.com")&&(e=new URL(e),e=`https://www.notion.so/signed/${encodeURIComponent(e.origin+e.pathname)}?table=${n}&id=${t}`),e);var h={};y(h,{is:()=>kt,rgbContrast:()=>Lt,rgbLogShade:()=>jt,slugger:()=>At,uuidv4:()=>Et});"use strict";var At=(e,t=new Set)=>{e=e.replace(/\s/g,"-").replace(/[^A-Za-z0-9-_]/g,"").toLowerCase();let n=0,r=e;for(;t.has(r);)n++,r=`${e}-${n}`;return r},Et=()=>crypto?.randomUUID?crypto.randomUUID():([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,e=>(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16)),jt=(e,t)=>{var n=parseInt,r=Math.round,[s,c,t,a]=t.split(","),i=e<0,d=i?0:e*255**2,i=i?1+e:1-e;return"rgb"+(a?"a(":"(")+r((i*n(s[3]=="a"?s.slice(5):s.slice(4))**2+d)**.5)+","+r((i*n(c)**2+d)**.5)+","+r((i*n(t)**2+d)**.5)+(a?","+a:")")},Lt=(e,t,n)=>Math.sqrt(.299*(e*e)+.587*(t*t)+.114*(n*n))>165.75?"rgb(0,0,0)":"rgb(255,255,255)",Tt={alphanumeric:/^[\w\.-]+$/,uuid:/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,semver:/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/i,email:/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/i,url:/^[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,64}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)$/i,color:/^(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)$/i};function St(e,t){let n=e.match(t);return!!(n&&n.length)}var kt=async(e,t,{extension:n=""}={})=>{if(n=!e||!e.endsWith||e.endsWith(n),Array.isArray(t))return t.includes(e);switch(t){case"array":return Array.isArray(e);case"object":return e&&typeof e=="object"&&!Array.isArray(e);case"undefined":case"boolean":case"number":return typeof e===t&&n;case"string":return typeof e===t&&n;case"alphanumeric":case"uuid":case"semver":case"email":case"url":case"color":return typeof e=="string"&&St(e,Tt[t])&&n;case"file":return typeof e=="string"&&e&&await u.isFile(e)&&n}return!1};var g={};y(g,{core:()=>Ae,db:()=>Ut,enabled:()=>zt,errors:()=>Wt,get:()=>ee,list:()=>D,optionDefault:()=>je,optionTypes:()=>Jt,profileDB:()=>G,profileName:()=>Ee,supportedEnvs:()=>Mt});"use strict";var l=async(e,t,n,r,{extension:s="",error:c=`invalid ${t} (${s?`${s} `:""}${r}): ${JSON.stringify(n)}`,optional:a=!1}={})=>{let i;for(let d of Array.isArray(r)?[r]:r.split("|"))if(d==="file"?i=n&&!n.startsWith("http")?await h.is(`repo/${e._dir}/${n}`,d,{extension:s}):!1:i=await h.is(n,d,{extension:s}),i)break;return i||a&&await h.is(n,"undefined")?!0:(c&&e._err(c),!1)},Pt=async e=>(e.environments=e.environments??g.supportedEnvs,await l(e,"environments",e.environments,"array")?e.environments.map(n=>l(e,"environments.env",n,g.supportedEnvs)):!1),Ct=async e=>{if(!await l(e,"tags",e.tags,"array"))return!1;let n=["core","extension","theme","integration"];if(!e.tags.filter(i=>n.includes(i)).length)return e._err(`invalid tags (must contain at least one of 'core', 'extension', 'theme' or 'integration'): ${JSON.stringify(e.tags)}`),!1;let s=e.tags.includes("theme"),c=e.tags.includes("light")||e.tags.includes("dark"),a=e.tags.includes("light")&&e.tags.includes("dark");return s&&(!c||a)?(e._err(`invalid tags (themes must be either 'light' or 'dark', not neither or both): - ${JSON.stringify(e.tags)}`),!1):e.tags.map(i=>l(e,"tags.tag",i,"string"))},Ot=async e=>await l(e,"authors",e.authors,"array")?e.authors.map(n=>[l(e,"authors.author.name",n.name,"string"),l(e,"authors.author.email",n.email,"email",{optional:!0}),l(e,"authors.author.homepage",n.homepage,"url"),l(e,"authors.author.avatar",n.avatar,"url")]):!1,Nt=async e=>{if(!await l(e,"css",e.css,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.css[r])continue;let s=await l(e,`css.${r}`,e.css[r],"array");s&&(s=e.css[r].map(c=>l(e,`css.${r}.file`,c,"file",{extension:".css"}))),n.push(s)}return n},Rt=async e=>{if(!await l(e,"js",e.js,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.js[r])continue;let s=await l(e,`js.${r}`,e.js[r],"array");s&&(s=e.js[r].map(c=>l(e,`js.${r}.file`,c,"file",{extension:".mjs"}))),n.push(s)}if(e.js.electron)if(await l(e,"js.electron",e.js.electron,"array"))for(let s of e.js.electron){if(!await l(e,"js.electron.file",s,"object")){n.push(!1);continue}n.push([l(e,"js.electron.file.source",s.source,"file",{extension:".cjs"}),l(e,"js.electron.file.target",s.target,"string",{extension:".js"})])}else n.push(!1);return n},qt=async e=>{if(!await l(e,"options",e.options,"array"))return!1;let n=[];for(let r of e.options){let s="options.option";if(!await l(e,`${s}.type`,r.type,g.optionTypes)){n.push(!1);continue}switch(r.environments=r.environments??g.supportedEnvs,n.push([l(e,`${s}.key`,r.key,"alphanumeric"),l(e,`${s}.label`,r.label,"string"),l(e,`${s}.tooltip`,r.tooltip,"string",{optional:!0}),l(e,`${s}.environments`,r.environments,"array").then(a=>a?r.environments.map(i=>l(e,`${s}.environments.env`,i,g.supportedEnvs)):!1)]),r.type){case"toggle":n.push(l(e,`${s}.value`,r.value,"boolean"));break;case"select":{let a=await l(e,`${s}.values`,r.values,"array");a&&(a=r.values.map(i=>l(e,`${s}.values.value`,i,"string"))),n.push(a);break}case"text":case"hotkey":n.push(l(e,`${s}.value`,r.value,"string"));break;case"number":case"color":n.push(l(e,`${s}.value`,r.value,r.type));break;case"file":{let a=await l(e,`${s}.extensions`,r.extensions,"array");a&&(a=r.extensions.map(i=>l(e,`${s}.extensions.extension`,i,"string"))),n.push(a);break}}}return n};async function $e(e){let t=[l(e,"name",e.name,"string"),l(e,"id",e.id,"uuid"),l(e,"version",e.version,"semver"),Ct(e),l(e,"description",e.description,"string"),l(e,"preview",e.preview,"file|url",{optional:!0}),Pt(e),Ot(e),Nt(e),Rt(e),qt(e)];do t=await Promise.all(t.flat(1/0));while(t.some(n=>Array.isArray(n)));return t.every(n=>n)}"use strict";var Ae=["a6621988-551d-495a-97d8-3c568bca2e9e","0f0bf8b6-eae6-4273-b307-8fc43f2ee082","36a2ffc9-27ff-480e-84a7-c7700a7d232d"],Mt=["linux","win32","darwin","extension"],Jt=["toggle","select","text","number","color","file","hotkey"],Ee=async()=>_.get(["currentprofile"],"default"),G=async()=>_.db(["profiles",await Ee()]),Q,Y=[],D=async(e=t=>!0)=>{Q||(Q=new Promise(async(n,r)=>{let s=[];for(let c of await u.getJSON("repo/registry.json"))try{let a={...await u.getJSON(`repo/${c}/mod.json`),_dir:c,_err:i=>Y.push({source:c,message:i})};await $e(a)&&s.push(a)}catch{Y.push({source:c,message:"invalid mod.json"})}n(s)}));let t=[];for(let n of await Q)await e(n)&&t.push(n);return t},zt=async()=>(await D(),Y),ee=async e=>(await D(t=>t.id===e))[0],Ut=async e=>(await ee(e)).environments.includes(j.name)?Ae.includes(e)?!0:(await G()).get(["_mods",e],!1):!1,je=async(e,t)=>{let n=await ee(e),r=n.options.find(s=>s.key===t);if(!!r)switch(r.type){case"toggle":case"text":case"number":case"color":case"hotkey":return r.value;case"select":return r.values[0];case"file":return}},Wt=async e=>{let t=await G();return _.db([e],async(n,r=void 0)=>(typeof n=="string"&&(n=[n]),n.length===2&&(r=await je(e,n[1])??r),t.get(n,r)),t.set)};var o={};y(o,{addDocumentObserver:()=>Gt,addHotkeyListener:()=>Kt,copyToClipboard:()=>It,empty:()=>Ft,escape:()=>Te,html:()=>ke,loadStylesheet:()=>Ht,queryParams:()=>Bt,raw:()=>Se,readFromClipboard:()=>Vt,removeDocumentObserver:()=>Qt,removeHotkeyListener:()=>Xt,render:()=>Ce,whenReady:()=>Zt});"use strict";var Le=!1,S=[],te,z=[],ne=[],Zt=(e=[])=>new Promise((t,n)=>{let r=()=>{let s=setInterval(c,100);function c(){!e.every(i=>document.querySelector(i))||(clearInterval(s),t(!0))}c()};document.readyState!=="complete"?document.addEventListener("readystatechange",s=>{document.readyState==="complete"&&r()}):r()}),Bt=()=>new URLSearchParams(window.location.search),Te=e=>e.replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\\/g,"\"),Se=(e,...t)=>{let n=e.map(r=>r+(["string","number"].includes(typeof t[0])?t.shift():Te(JSON.stringify(t.shift(),null,2)??""))).join("");return n.includes("r.trim()).filter(r=>r.length).join(" ")},ke=(e,...t)=>{let n=document.createRange().createContextualFragment(Se(e,...t));return n.children.length===1?n.children[0]:n.children},Ce=(e,...t)=>(t=t.map(n=>n instanceof HTMLCollection?[...n]:n).flat(1/0).filter(n=>n),e.append(...t),e),Ft=e=>{for(;e.firstChild&&e.removeChild(e.firstChild););return e},Ht=e=>{let t=ke`l(e,"tags.tag",i,"string"))},Ot=async e=>await l(e,"authors",e.authors,"array")?e.authors.map(n=>[l(e,"authors.author.name",n.name,"string"),l(e,"authors.author.email",n.email,"email",{optional:!0}),l(e,"authors.author.homepage",n.homepage,"url"),l(e,"authors.author.avatar",n.avatar,"url")]):!1,Nt=async e=>{if(!await l(e,"css",e.css,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.css[r])continue;let s=await l(e,`css.${r}`,e.css[r],"array");s&&(s=e.css[r].map(c=>l(e,`css.${r}.file`,c,"file",{extension:".css"}))),n.push(s)}return n},Rt=async e=>{if(!await l(e,"js",e.js,"object"))return!1;let n=[];for(let r of["frame","client","menu"]){if(!e.js[r])continue;let s=await l(e,`js.${r}`,e.js[r],"array");s&&(s=e.js[r].map(c=>l(e,`js.${r}.file`,c,"file",{extension:".mjs"}))),n.push(s)}if(e.js.electron)if(await l(e,"js.electron",e.js.electron,"array"))for(let s of e.js.electron){if(!await l(e,"js.electron.file",s,"object")){n.push(!1);continue}n.push([l(e,"js.electron.file.source",s.source,"file",{extension:".cjs"}),l(e,"js.electron.file.target",s.target,"string",{extension:".js"})])}else n.push(!1);return n},qt=async e=>{if(!await l(e,"options",e.options,"array"))return!1;let n=[];for(let r of e.options){let s="options.option";if(!await l(e,`${s}.type`,r.type,g.optionTypes)){n.push(!1);continue}switch(r.environments=r.environments??g.supportedEnvs,n.push([l(e,`${s}.key`,r.key,"alphanumeric"),l(e,`${s}.label`,r.label,"string"),l(e,`${s}.tooltip`,r.tooltip,"string",{optional:!0}),l(e,`${s}.environments`,r.environments,"array").then(a=>a?r.environments.map(i=>l(e,`${s}.environments.env`,i,g.supportedEnvs)):!1)]),r.type){case"toggle":n.push(l(e,`${s}.value`,r.value,"boolean"));break;case"select":{let a=await l(e,`${s}.values`,r.values,"array");a&&(a=r.values.map(i=>l(e,`${s}.values.value`,i,"string"))),n.push(a);break}case"text":case"hotkey":n.push(l(e,`${s}.value`,r.value,"string"));break;case"number":case"color":n.push(l(e,`${s}.value`,r.value,r.type));break;case"file":{let a=await l(e,`${s}.extensions`,r.extensions,"array");a&&(a=r.extensions.map(i=>l(e,`${s}.extensions.extension`,i,"string"))),n.push(a);break}}}return n};async function $e(e){let t=[l(e,"name",e.name,"string"),l(e,"id",e.id,"uuid"),l(e,"version",e.version,"semver"),Pt(e),l(e,"description",e.description,"string"),l(e,"preview",e.preview,"file|url",{optional:!0}),Ct(e),Ot(e),Nt(e),Rt(e),qt(e)];do t=await Promise.all(t.flat(1/0));while(t.some(n=>Array.isArray(n)));return t.every(n=>n)}"use strict";var Ae=["a6621988-551d-495a-97d8-3c568bca2e9e","0f0bf8b6-eae6-4273-b307-8fc43f2ee082","36a2ffc9-27ff-480e-84a7-c7700a7d232d"],Mt=["linux","win32","darwin","extension"],Jt=["toggle","select","text","number","color","file","hotkey"],Ee=async()=>_.get(["currentprofile"],"default"),G=async()=>_.db(["profiles",await Ee()]),Q,Y=[],D=async(e=t=>!0)=>{Q||(Q=new Promise(async(n,r)=>{let s=[];for(let c of await u.getJSON("repo/registry.json"))try{let a={...await u.getJSON(`repo/${c}/mod.json`),_dir:c,_err:i=>Y.push({source:c,message:i})};await $e(a)&&s.push(a)}catch{Y.push({source:c,message:"invalid mod.json"})}n(s)}));let t=[];for(let n of await Q)await e(n)&&t.push(n);return t},Wt=async()=>(await D(),Y),ee=async e=>(await D(t=>t.id===e))[0],zt=async e=>(await ee(e)).environments.includes(j.name)?Ae.includes(e)?!0:(await G()).get(["_mods",e],!1):!1,je=async(e,t)=>{let n=await ee(e),r=n.options.find(s=>s.key===t);if(!!r)switch(r.type){case"toggle":case"text":case"number":case"color":case"hotkey":return r.value;case"select":return r.values[0];case"file":return}},Ut=async e=>{let t=await G();return _.db([e],async(n,r=void 0)=>(typeof n=="string"&&(n=[n]),n.length===2&&(r=await je(e,n[1])??r),t.get(n,r)),t.set)};var o={};y(o,{addDocumentObserver:()=>Gt,addHotkeyListener:()=>Kt,copyToClipboard:()=>It,empty:()=>Ft,escape:()=>Te,html:()=>ke,loadStylesheet:()=>Ht,queryParams:()=>Bt,raw:()=>Se,readFromClipboard:()=>Vt,removeDocumentObserver:()=>Qt,removeHotkeyListener:()=>Xt,render:()=>Pe,whenReady:()=>Zt});"use strict";var Le=!1,S=[],te,W=[],ne=[],Zt=(e=[])=>new Promise((t,n)=>{let r=()=>{let s=setInterval(c,100);function c(){!e.every(i=>document.querySelector(i))||(clearInterval(s),t(!0))}c()};document.readyState!=="complete"?document.addEventListener("readystatechange",s=>{document.readyState==="complete"&&r()}):r()}),Bt=()=>new URLSearchParams(window.location.search),Te=e=>e.replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\\/g,"\"),Se=(e,...t)=>{let n=e.map(r=>r+(["string","number"].includes(typeof t[0])?t.shift():Te(JSON.stringify(t.shift(),null,2)??""))).join("");return n.includes("r.trim()).filter(r=>r.length).join(" ")},ke=(e,...t)=>{let n=document.createRange().createContextualFragment(Se(e,...t));return n.children.length===1?n.children[0]:n.children},Pe=(e,...t)=>(t=t.map(n=>n instanceof HTMLCollection?[...n]:n).flat(1/0).filter(n=>n),e.append(...t),e),Ft=e=>{for(;e.firstChild&&e.removeChild(e.firstChild););return e},Ht=e=>{let t=ke``;return Ce(document.head,t),t},It=async e=>{try{await navigator.clipboard.writeText(e)}catch{let t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}},Vt=()=>navigator.clipboard.readText(),Pe=(e,t)=>{if(document.activeElement.nodeName==="INPUT"&&!t.listenInInput)return;let r={metaKey:["meta","os","win","cmd","command"],ctrlKey:["ctrl","control"],shiftKey:["shift"],altKey:["alt"]};if(!!t.keys.every(c=>{c=c.toLowerCase();for(let a in r)if(r[a].includes(c)&&e[a])return r[a]=[],!0;if(c==="space"&&(c=" "),c==="plus"&&(c="+"),c===e.key.toLowerCase())return!0})){for(let c in r){let a=e[c],i=r[c].length>0;if(a&&i)return}t.callback(e)}},Kt=(e,t,{listenInInput:n=!1,keydown:r=!1}={})=>{typeof e=="string"&&(e=e.split("+")),S.push({keys:e,callback:t,listenInInput:n,keydown:r}),Le||(Le=!0,document.addEventListener("keyup",s=>{for(let c of S.filter(({keydown:a})=>!a))Pe(s,c)}),document.addEventListener("keydown",s=>{for(let c of S.filter(({keydown:a})=>a))Pe(s,c)}))},Xt=e=>{S=S.filter(t=>t.callback!==e)},Gt=(e,t=[])=>{if(!te){let n=r=>{for(;r.length;){let s=r.shift(),c=(i,d)=>i instanceof Element&&(i.matches(d)||i.matches(`${d} *`)||i.querySelector(d)),a=i=>s.target.matches(i)||s.target.matches(`${i} *`)||[...s.addedNodes].some(d=>c(d,i));for(let i of z)(!i.selectors.length||i.selectors.some(a))&&i.callback(s)}};te=new MutationObserver((r,s)=>{ne.length||requestIdleCallback(()=>n(ne)),ne.push(...r)}),te.observe(document.body,{childList:!0,subtree:!0,attributes:!0})}z.push({callback:e,selectors:t})},Qt=e=>{z=z.filter(t=>t.callback!==e)};var R={};y(R,{addCornerAction:()=>Ke,addPanelView:()=>Ie,addTooltip:()=>Ne,feather:()=>Re});"use strict";var Oe,p,Yt=e=>[...e.getClientRects()].reduce((t,n)=>t.some(r=>r.y===n.y)?t:[...t,n],[]).length,Dt=async(e,t,n)=>{p.style.top="0px",p.style.left="0px";let r=e.getBoundingClientRect(),{offsetWidth:s,offsetHeight:c}=p,a=6,i=r.x,d=Math.floor(r.y);if(["top","bottom"].includes(t)){t==="top"&&(d-=c+a),t==="bottom"&&(d+=r.height+a),i-=s/2-r.width/2,p.style.left=`${i}px`,p.style.top=`${d}px`;let x=()=>Yt(p.firstElementChild)>n,A=x();for(;x();)p.style.left=`${window.innerWidth-i>i?i++:i--}px`;A&&(i+=window.innerWidth-i>i?a:-a,p.style.left=`${i}px`),p.style.textAlign="center"}return["left","right"].includes(t)&&(d-=c/2-r.height/2,t==="left"&&(i-=s+a),t==="right"&&(i+=r.width+a),p.style.left=`${i}px`,p.style.top=`${d}px`,p.style.textAlign="start"),!0},Ne=async(e,t,{delay:n=100,offsetDirection:r="bottom",maxLines:s=1}={})=>{Oe||(Oe=o.loadStylesheet("api/components/tooltip.css"),p=o.html`
`,o.render(document.body,p)),globalThis.markdownit||await import(u.localPath("dep/markdown-it.min.js"));let c=markdownit({linkify:!0});t instanceof Element||(t=o.html`
+ />`;return Pe(document.head,t),t},It=async e=>{try{await navigator.clipboard.writeText(e)}catch{let t=document.createElement("textarea");t.value=e,t.setAttribute("readonly",""),t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}},Vt=()=>navigator.clipboard.readText(),Ce=(e,t)=>{if(document.activeElement.nodeName==="INPUT"&&!t.listenInInput)return;let r={metaKey:["meta","os","win","cmd","command"],ctrlKey:["ctrl","control"],shiftKey:["shift"],altKey:["alt"]};if(!!t.keys.every(c=>{c=c.toLowerCase();for(let a in r)if(r[a].includes(c)&&e[a])return r[a]=[],!0;if(c==="space"&&(c=" "),c==="plus"&&(c="+"),c===e.key.toLowerCase())return!0})){for(let c in r){let a=e[c],i=r[c].length>0;if(a&&i)return}t.callback(e)}},Kt=(e,t,{listenInInput:n=!1,keydown:r=!1}={})=>{typeof e=="string"&&(e=e.split("+")),S.push({keys:e,callback:t,listenInInput:n,keydown:r}),Le||(Le=!0,document.addEventListener("keyup",s=>{for(let c of S.filter(({keydown:a})=>!a))Ce(s,c)}),document.addEventListener("keydown",s=>{for(let c of S.filter(({keydown:a})=>a))Ce(s,c)}))},Xt=e=>{S=S.filter(t=>t.callback!==e)},Gt=(e,t=[])=>{if(!te){let n=r=>{for(;r.length;){let s=r.shift(),c=(i,d)=>i instanceof Element&&(i.matches(d)||i.matches(`${d} *`)||i.querySelector(d)),a=i=>s.target.matches(i)||s.target.matches(`${i} *`)||[...s.addedNodes].some(d=>c(d,i));for(let i of W)(!i.selectors.length||i.selectors.some(a))&&i.callback(s)}};te=new MutationObserver((r,s)=>{ne.length||requestIdleCallback(()=>n(ne)),ne.push(...r)}),te.observe(document.body,{childList:!0,subtree:!0,attributes:!0})}W.push({callback:e,selectors:t})},Qt=e=>{W=W.filter(t=>t.callback!==e)};var R={};y(R,{addCornerAction:()=>Ke,addPanelView:()=>Ie,addTooltip:()=>Ne,feather:()=>Re});"use strict";var Oe,p,Yt=e=>[...e.getClientRects()].reduce((t,n)=>t.some(r=>r.y===n.y)?t:[...t,n],[]).length,Dt=async(e,t,n)=>{p.style.top="0px",p.style.left="0px";let r=e.getBoundingClientRect(),{offsetWidth:s,offsetHeight:c}=p,a=6,i=r.x,d=Math.floor(r.y);if(["top","bottom"].includes(t)){t==="top"&&(d-=c+a),t==="bottom"&&(d+=r.height+a),i-=s/2-r.width/2,p.style.left=`${i}px`,p.style.top=`${d}px`;let x=()=>Yt(p.firstElementChild)>n,A=x();for(;x();)p.style.left=`${window.innerWidth-i>i?i++:i--}px`;A&&(i+=window.innerWidth-i>i?a:-a,p.style.left=`${i}px`),p.style.textAlign="center"}return["left","right"].includes(t)&&(d-=c/2-r.height/2,t==="left"&&(i-=s+a),t==="right"&&(i+=r.width+a),p.style.left=`${i}px`,p.style.top=`${d}px`,p.style.textAlign="start"),!0},Ne=async(e,t,{delay:n=100,offsetDirection:r="bottom",maxLines:s=1}={})=>{Oe||(Oe=o.loadStylesheet("api/components/tooltip.css"),p=o.html`
`,o.render(document.body,p)),globalThis.markdownit||await import(u.localPath("dep/markdown-it.min.js"));let c=markdownit({linkify:!0});t instanceof Element||(t=o.html`
${t.split(` `).map(i=>c.renderInline(i)).join("
")}
`);let a;e.addEventListener("mouseover",async i=>{a||(a=setTimeout(async()=>{e.matches(":hover")&&p.style.display!=="block"&&(p.style.display="block",o.render(o.empty(p),t),Dt(e,r,s),await p.animate([{opacity:0},{opacity:1}],{duration:65}).finished),a=void 0},n))}),e.addEventListener("mouseout",async i=>{a=void 0,p.style.display==="block"&&!e.matches(":hover")&&(await p.animate([{opacity:1},{opacity:0}],{duration:65}).finished,p.style.display="")})};"use strict";var re,Re=async(e,t={})=>(re||(re=o.html`${await u.getText("dep/feather-sprite.svg")}`),t.style=((t.style?t.style+";":"")+"stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;fill:none;").trim(),t.viewBox="0 0 24 24",``${o.escape(n)}="${o.escape(r)}"`).join(" ")}>${re.getElementById(e)?.innerHTML}`);"use strict";var k=[],en=o.raw` @@ -13,9 +13,9 @@ var le=Object.defineProperty;var Xe=e=>le(e,"__esModule",{value:!0});var y=(e,t) 2.43056L 3.98809 -0.569442L 3.01191 0.569442ZM -0.488094 6.56944L 3.01191 9.56944L 3.98809 8.43056L 0.488094 5.43056L -0.488094 6.56944ZM 3.98809 9.56944L 7.48809 6.56944L 6.51191 5.43056L 3.01191 8.43056L 3.98809 9.56944Z"> - `,qe,w,U,v,$,C,E,Me,Je,se,f,ze,W,P,Z,oe,b,ie,O,B="data-enhancer-panel-pinned",F=()=>$.hasAttribute(B),N=()=>{let e=[U,v,C,$].filter(t=>t);if(F()){ae();for(let t of e)t.removeAttribute(B)}else for(let t of e)t.setAttribute(B,"true");w.set(["panel.pinned"],F())},Ue=async()=>{document.documentElement.style.setProperty("--component--panel-width",f+"px"),w.set(["panel.width"],f)},We=e=>{e.preventDefault(),se=!0,f=Je+(Me-e.clientX),f<190&&(f=190),f>480&&(f=480),$.style.width=f+"px",C.style.width=f+"px",U.style.paddingRight=f+"px",v&&(v.style.right=f+"px")},Ze=e=>{$.style.width="",C.style.width="",U.style.paddingRight="",v&&(v.style.right=""),Ue(),E.style.cursor="",document.body.removeEventListener("mousemove",We),document.body.removeEventListener("mouseup",Ze)},tn=e=>{Me=e.clientX,Je=f,E.style.cursor="auto",document.body.addEventListener("mousemove",We),document.body.addEventListener("mouseup",Ze)},nn=()=>document.body.contains(b),Be=()=>{if(!F())return N();o.render(ze,O),o.empty(b);for(let t of k){let n=P.contains(t.$title),r=o.render(o.html`
`,o.render(o.html``,t.$icon.cloneNode(!0),t.$title.cloneNode(!0)));r.addEventListener("click",()=>{He(t),w.set(["panel.open"],t.id)}),o.render(b,r)}let e=Z.getBoundingClientRect();o.render(o.empty(O),o.render(o.html`
`,o.render(o.html`
`,b))),b.querySelector("[data-open]").focus(),b.animate([{opacity:0},{opacity:1}],{duration:200}),document.addEventListener("keydown",Fe)},ae=()=>{document.removeEventListener("keydown",Fe),b.animate([{opacity:1},{opacity:0}],{duration:200}).onfinish=()=>O.remove()},Fe=e=>{if(nn())switch(e.key){case"Escape":ae(),e.stopPropagation();break;case"Enter":document.activeElement.click(),e.stopPropagation();break;case"ArrowUp":(e.target.previousElementSibling||e.target.parentElement.lastElementChild).focus(),e.stopPropagation();break;case"ArrowDown":(e.target.nextElementSibling||e.target.parentElement.firstElementChild).focus(),e.stopPropagation();break}},He=e=>{let t=k.find(({$content:n})=>document.contains(n));o.render(o.empty(P),o.render(o.html``,e.$icon,e.$title)),e.onFocus(),o.render(o.empty(oe),e.$content),t&&t.onBlur()};async function rn(){await o.whenReady([".notion-frame"]),U=document.querySelector(".notion-frame"),$=o.html`
`,C=o.html`
`,E=o.html`
`,P=o.html`
`,Z=o.render(o.html`
`,P),oe=o.html`
`,b=o.html`
`,ie=o.html`
+ `,qe,v,z,w,$,P,E,Me,Je,se,f,We,U,C,Z,oe,b,ie,O,B="data-enhancer-panel-pinned",F=()=>$.hasAttribute(B),N=()=>{let e=[z,w,P,$].filter(t=>t);if(F()){ae();for(let t of e)t.removeAttribute(B)}else for(let t of e)t.setAttribute(B,"true");v.set(["panel.pinned"],F())},ze=async()=>{document.documentElement.style.setProperty("--component--panel-width",f+"px"),v.set(["panel.width"],f)},Ue=e=>{e.preventDefault(),se=!0,f=Je+(Me-e.clientX),f<190&&(f=190),f>480&&(f=480),$.style.width=f+"px",P.style.width=f+"px",z.style.paddingRight=f+"px",w&&(w.style.right=f+"px")},Ze=e=>{$.style.width="",P.style.width="",z.style.paddingRight="",w&&(w.style.right=""),ze(),E.style.cursor="",document.body.removeEventListener("mousemove",Ue),document.body.removeEventListener("mouseup",Ze)},tn=e=>{Me=e.clientX,Je=f,E.style.cursor="auto",document.body.addEventListener("mousemove",Ue),document.body.addEventListener("mouseup",Ze)},nn=()=>document.body.contains(b),Be=()=>{if(!F())return N();o.render(We,O),o.empty(b);for(let t of k){let n=C.contains(t.$title),r=o.render(o.html`
`,o.render(o.html``,t.$icon.cloneNode(!0),t.$title.cloneNode(!0)));r.addEventListener("click",()=>{He(t),v.set(["panel.open"],t.id)}),o.render(b,r)}let e=Z.getBoundingClientRect();o.render(o.empty(O),o.render(o.html`
`,o.render(o.html`
`,b))),b.querySelector("[data-open]").focus(),b.animate([{opacity:0},{opacity:1}],{duration:200}),document.addEventListener("keydown",Fe)},ae=()=>{document.removeEventListener("keydown",Fe),b.animate([{opacity:1},{opacity:0}],{duration:200}).onfinish=()=>O.remove()},Fe=e=>{if(nn())switch(e.key){case"Escape":ae(),e.stopPropagation();break;case"Enter":document.activeElement.click(),e.stopPropagation();break;case"ArrowUp":(e.target.previousElementSibling||e.target.parentElement.lastElementChild).focus(),e.stopPropagation();break;case"ArrowDown":(e.target.nextElementSibling||e.target.parentElement.firstElementChild).focus(),e.stopPropagation();break}},He=e=>{let t=k.find(({$content:n})=>document.contains(n));o.render(o.empty(C),o.render(o.html``,e.$icon,e.$title)),e.onFocus(),o.render(o.empty(oe),e.$content),t&&t.onBlur()};async function rn(){await o.whenReady([".notion-frame"]),z=document.querySelector(".notion-frame"),$=o.html`
`,P=o.html`
`,E=o.html`
`,C=o.html`
`,Z=o.render(o.html`
`,C),oe=o.html`
`,b=o.html`
`,ie=o.html`
${en} -
`,O=o.html`
`;let e='.notion-cursor-listener > div[style*="flex-end"]',t=()=>{document.contains(v)||(v=document.querySelector(e),F()&&v&&v.setAttribute(B,"true"))};v=document.querySelector(e),o.addDocumentObserver(t,[e]),await w.get(["panel.pinned"])&&N(),o.addHotkeyListener(await w.get(["panel.hotkey"]),N),W.addEventListener("click",r=>{r.stopPropagation(),N()}),o.render($,o.render(Z,P,ie,W),oe,E),await sn(),await on();let n='.notion-cursor-listener > .notion-sidebar-container ~ [style^="position: absolute"]';await o.whenReady([n]),document.querySelector(n).before(C,$)}async function sn(){f=await w.get(["panel.width"],240),Ue(),E.addEventListener("mousedown",tn),E.addEventListener("click",()=>{se?se=!1:N()})}async function on(){ze=document.querySelector(".notion-app-inner"),Z.addEventListener("click",Be),ie.addEventListener("click",Be),O.addEventListener("click",ae)}var Ie=async({id:e,icon:t,title:n,$content:r,onFocus:s=()=>{},onBlur:c=()=>{}})=>{qe||(qe=o.loadStylesheet("api/components/panel.css")),w||(w=await g.db("36a2ffc9-27ff-480e-84a7-c7700a7d232d")),W||(W=o.html`
+
`,O=o.html`
`;let e='.notion-cursor-listener > div[style*="flex-end"]',t=()=>{document.contains(w)||(w=document.querySelector(e),F()&&w&&w.setAttribute(B,"true"))};w=document.querySelector(e),o.addDocumentObserver(t,[e]),await v.get(["panel.pinned"])&&N(),o.addHotkeyListener(await v.get(["panel.hotkey"]),N),U.addEventListener("click",r=>{r.stopPropagation(),N()}),o.render($,o.render(Z,C,ie,U),oe,E),await sn(),await on();let n='.notion-cursor-listener > .notion-sidebar-container ~ [style^="position: absolute"]';await o.whenReady([n]),document.querySelector(n).before(P,$)}async function sn(){f=await v.get(["panel.width"],240),ze(),E.addEventListener("mousedown",tn),E.addEventListener("click",()=>{se?se=!1:N()})}async function on(){We=document.querySelector(".notion-app-inner"),Z.addEventListener("click",Be),ie.addEventListener("click",Be),O.addEventListener("click",ae)}var Ie=async({id:e,icon:t,title:n,$content:r,onFocus:s=()=>{},onBlur:c=()=>{}})=>{qe||(qe=o.loadStylesheet("api/components/panel.css")),v||(v=await g.db("36a2ffc9-27ff-480e-84a7-c7700a7d232d")),U||(U=o.html`
${await R.feather("chevrons-right")} -
`);let a={id:e,$icon:o.render(o.html``,t instanceof Element?t:o.html`${t}`),$title:o.render(o.html``,n),$content:r,onFocus:s,onBlur:c};k.push(a),k.length===1&&await rn(),(k.length===1||await w.get(["panel.open"])===e)&&He(a)};"use strict";var Ve,q,Ke=async(e,t)=>{Ve||(Ve=o.loadStylesheet("api/components/corner-action.css"),q=o.html`
`),await o.whenReady([".notion-help-button"]);let n=document.querySelector(".notion-help-button"),r=document.querySelector(".onboarding-checklist-button");r&&q.prepend(r),q.prepend(n),o.render(document.querySelector(".notion-app-inner > .notion-cursor-listener"),q);let s=o.html`
${e}
`;return s.addEventListener("click",t),o.render(q,s),s};"use strict";"use strict"; +
`);let a={id:e,$icon:o.render(o.html``,t instanceof Element?t:o.html`${t}`),$title:o.render(o.html``,n),$content:r,onFocus:s,onBlur:c};k.push(a),k.length===1&&await rn(),(k.length===1||await v.get(["panel.open"])===e)&&He(a)};"use strict";var Ve,q,Ke=async(e,t)=>{Ve||(Ve=o.loadStylesheet("api/components/corner-action.css"),q=o.html`
`),await o.whenReady([".notion-help-button"]);let n=document.querySelector(".notion-help-button"),r=document.querySelector(".onboarding-checklist-button");r&&q.prepend(r),q.prepend(n),o.render(document.querySelector(".notion-app-inner > .notion-cursor-listener"),q);let s=o.html`
${e}
`;return s.addEventListener("click",t),o.render(q,s),s};"use strict";"use strict"; diff --git a/api/notion.mjs b/api/notion.mjs index 8c2ba8d..a6996eb 100644 --- a/api/notion.mjs +++ b/api/notion.mjs @@ -23,6 +23,30 @@ const standardiseUUID = (uuid) => { return uuid; }; +/** + * unofficial content api: get a block by id + * (requires user to be signed in or content to be public). + * why not use the official api? + * 1. cors blocking prevents use on the client + * 2. the majority of blocks are still 'unsupported' + * @param {string} id - uuidv4 record id + * @param {string} [table] - record type (default: 'block'). + * may also be 'collection', 'collection_view', 'space', 'notion_user', 'discussion', or 'comment' + * @returns {Promise} record data. type definitions can be found here: + * https://github.com/NotionX/react-notion-x/tree/master/packages/notion-types/src + */ +export const get = async (id, table = 'block') => { + id = standardiseUUID(id); + const json = await fs.getJSON('https://www.notion.so/api/v3/getRecordValues', { + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ requests: [{ table, id }] }), + method: 'POST', + }); + return json?.results?.[0]?.value || json; +}; + /** * get the id of the current user (requires user to be signed in) * @returns {string} uuidv4 user id @@ -49,30 +73,6 @@ export const getSpaceID = async () => { return _spaceID; }; -/** - * unofficial content api: get a block by id - * (requires user to be signed in or content to be public). - * why not use the official api? - * 1. cors blocking prevents use on the client - * 2. the majority of blocks are still 'unsupported' - * @param {string} id - uuidv4 record id - * @param {string} [table] - record type (default: 'block'). - * may also be 'collection', 'collection_view', 'space', 'notion_user', 'discussion', or 'comment' - * @returns {Promise} record data. type definitions can be found here: - * https://github.com/NotionX/react-notion-x/tree/master/packages/notion-types/src - */ -export const get = async (id, table = 'block') => { - id = standardiseUUID(id); - const json = await fs.getJSON('https://www.notion.so/api/v3/getRecordValues', { - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ requests: [{ table, id }] }), - method: 'POST', - }); - return json.results[0].value; -}; - /** * unofficial content api: search all blocks in a space * (requires user to be signed in or content to be public). From 081e59bb7869d640c68a9f7e7a810df23c63eff2 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Mon, 20 Dec 2021 12:14:03 +1100 Subject: [PATCH 50/52] var -> const --- api/fmt.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/fmt.mjs b/api/fmt.mjs index 1f10578..b19ec79 100644 --- a/api/fmt.mjs +++ b/api/fmt.mjs @@ -55,7 +55,7 @@ export const uuidv4 = () => { * @returns {string} the shaded color */ export const rgbLogShade = (p, c) => { - var i = parseInt, + const i = parseInt, r = Math.round, [a, b, c, d] = c.split(','), P = p < 0, From 0052fa4cdd1ccbcea491fc891c25f72935821b13 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Fri, 24 Dec 2021 23:28:33 +1100 Subject: [PATCH 51/52] minor cleanups, jsdoc changes --- api/components/corner-action.mjs | 5 +--- api/components/feather.mjs | 5 +--- api/components/index.mjs | 11 +++++---- api/components/panel.mjs | 17 ++++++------- api/components/tooltip.mjs | 19 ++++++--------- api/electron.mjs | 19 ++++++++------- api/env.mjs | 2 +- api/fmt.mjs | 33 ++++++++++++------------- api/fs.mjs | 10 ++++---- api/index.mjs | 2 -- api/notion.mjs | 42 ++++++++++++++++---------------- api/registry-validation.mjs | 4 +-- api/registry.mjs | 19 ++++++++------- api/storage.mjs | 20 +++++++-------- api/web.mjs | 24 +++++++++--------- 15 files changed, 111 insertions(+), 121 deletions(-) diff --git a/api/components/corner-action.mjs b/api/components/corner-action.mjs index 2ebf808..bf42f9b 100644 --- a/api/components/corner-action.mjs +++ b/api/components/corner-action.mjs @@ -7,10 +7,7 @@ 'use strict'; -/** - * shared notion-style elements - * @module notion-enhancer/api/components/corner-action - */ +/** shared notion-style elements */ import { web } from '../index.mjs'; diff --git a/api/components/feather.mjs b/api/components/feather.mjs index 6e2350f..9bd68fd 100644 --- a/api/components/feather.mjs +++ b/api/components/feather.mjs @@ -6,10 +6,7 @@ 'use strict'; -/** - * shared notion-style elements - * @module notion-enhancer/api/components/feather - */ +/** shared notion-style elements */ import { fs, web } from '../index.mjs'; diff --git a/api/components/index.mjs b/api/components/index.mjs index ab690bd..28c52b5 100644 --- a/api/components/index.mjs +++ b/api/components/index.mjs @@ -8,19 +8,20 @@ /** * shared notion-style elements - * @module notion-enhancer/api/components + * @namespace components */ +import * as _api from '../index.mjs'; // trick jsdoc /** * add a tooltip to show extra information on hover * @param {HTMLElement} $ref - the element that will trigger the tooltip when hovered * @param {string|HTMLElement} $content - markdown or element content of the tooltip - * @param {object=} [options] - configuration of how the tooltip should be displayed - * @param {number} [options.delay] - the amount of time in ms the element needs to be hovered over + * @param {object=} options - configuration of how the tooltip should be displayed + * @param {number=} options.delay - the amount of time in ms the element needs to be hovered over * for the tooltip to be shown (default: 100) - * @param {string} [options.offsetDirection] - which side of the element the tooltip + * @param {string=} options.offsetDirection - which side of the element the tooltip * should be shown on: 'top', 'bottom', 'left' or 'right' (default: 'bottom') - * @param {number} [options.maxLines] - the max number of lines that the content may be wrapped + * @param {number=} options.maxLines - the max number of lines that the content may be wrapped * to, used to position and size the tooltip correctly (default: 1) */ export { addTooltip } from './tooltip.mjs'; diff --git a/api/components/panel.mjs b/api/components/panel.mjs index 73225d6..8f82a58 100644 --- a/api/components/panel.mjs +++ b/api/components/panel.mjs @@ -7,10 +7,7 @@ 'use strict'; -/** - * shared notion-style elements - * @module notion-enhancer/api/components/side-panel - */ +/** shared notion-style elements */ import { web, components, registry } from '../index.mjs'; @@ -63,7 +60,7 @@ const panelPinnedAttr = 'data-enhancer-panel-pinned', db.set(['panel.pinned'], isPinned()); }, // resize - updateWidth = async () => { + updateWidth = () => { document.documentElement.style.setProperty('--component--panel-width', panelWidth + 'px'); db.set(['panel.width'], panelWidth); }, @@ -78,7 +75,7 @@ const panelPinnedAttr = 'data-enhancer-panel-pinned', $notionFrame.style.paddingRight = panelWidth + 'px'; if ($notionRightSidebar) $notionRightSidebar.style.right = panelWidth + 'px'; }, - resizeEnd = (event) => { + resizeEnd = (_event) => { $panel.style.width = ''; $hoverTrigger.style.width = ''; $notionFrame.style.paddingRight = ''; @@ -151,16 +148,18 @@ const panelPinnedAttr = 'data-enhancer-panel-pinned', document.activeElement.click(); event.stopPropagation(); break; - case 'ArrowUp': + case 'ArrowUp': { const $prev = event.target.previousElementSibling; ($prev || event.target.parentElement.lastElementChild).focus(); event.stopPropagation(); break; - case 'ArrowDown': + } + case 'ArrowDown': { const $next = event.target.nextElementSibling; ($next || event.target.parentElement.firstElementChild).focus(); event.stopPropagation(); break; + } } } }, @@ -240,7 +239,7 @@ async function enablePanelResize() { }); } -async function createViews() { +function createViews() { $notionApp = document.querySelector('.notion-app-inner'); $header.addEventListener('click', openSwitcher); $switcherTrigger.addEventListener('click', openSwitcher); diff --git a/api/components/tooltip.mjs b/api/components/tooltip.mjs index f78cdc7..80f1d03 100644 --- a/api/components/tooltip.mjs +++ b/api/components/tooltip.mjs @@ -6,10 +6,7 @@ 'use strict'; -/** - * shared notion-style elements - * @module notion-enhancer/api/components/tooltip - */ +/** shared notion-style elements */ import { fs, web } from '../index.mjs'; @@ -20,7 +17,7 @@ const countLines = ($el) => (prev, val) => (prev.some((p) => p.y === val.y) ? prev : [...prev, val]), [] ).length, - position = async ($ref, offsetDirection, maxLines) => { + position = ($ref, offsetDirection, maxLines) => { _$tooltip.style.top = `0px`; _$tooltip.style.left = `0px`; const rect = $ref.getBoundingClientRect(), @@ -63,12 +60,12 @@ const countLines = ($el) => * add a tooltip to show extra information on hover * @param {HTMLElement} $ref - the element that will trigger the tooltip when hovered * @param {string|HTMLElement} $content - markdown or element content of the tooltip - * @param {object=} [options] - configuration of how the tooltip should be displayed - * @param {number} [options.delay] - the amount of time in ms the element needs to be hovered over + * @param {object=} options - configuration of how the tooltip should be displayed + * @param {number=} options.delay - the amount of time in ms the element needs to be hovered over * for the tooltip to be shown (default: 100) - * @param {string} [options.offsetDirection] - which side of the element the tooltip + * @param {string=} options.offsetDirection - which side of the element the tooltip * should be shown on: 'top', 'bottom', 'left' or 'right' (default: 'bottom') - * @param {number} [options.maxLines] - the max number of lines that the content may be wrapped + * @param {number=} options.maxLines - the max number of lines that the content may be wrapped * to, used to position and size the tooltip correctly (default: 1) */ export const addTooltip = async ( @@ -94,7 +91,7 @@ export const addTooltip = async ( `; let displayDelay; - $ref.addEventListener('mouseover', async (event) => { + $ref.addEventListener('mouseover', (_event) => { if (!displayDelay) { displayDelay = setTimeout(async () => { if ($ref.matches(':hover')) { @@ -111,7 +108,7 @@ export const addTooltip = async ( } }); - $ref.addEventListener('mouseout', async (event) => { + $ref.addEventListener('mouseout', async (_event) => { displayDelay = undefined; if (_$tooltip.style.display === 'block' && !$ref.matches(':hover')) { await _$tooltip.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 65 }).finished; diff --git a/api/electron.mjs b/api/electron.mjs index f39be56..1492333 100644 --- a/api/electron.mjs +++ b/api/electron.mjs @@ -8,14 +8,15 @@ /** * access to electron renderer apis - * @module notion-enhancer/api/env + * @namespace electron */ +import * as _api from './index.mjs'; // trick jsdoc /** * access to the electron BrowserWindow instance for the current window * see https://www.electronjs.org/docs/latest/api/browser-window * @type {BrowserWindow} - * @runtime electron (renderer process) + * @process electron (renderer process) */ export const browser = globalThis.__enhancerElectronApi?.browser; @@ -23,7 +24,7 @@ export const browser = globalThis.__enhancerElectronApi?.browser; * access to the electron webFrame instance for the current page * see https://www.electronjs.org/docs/latest/api/web-frame * @type {webFrame} - * @runtime electron (renderer process) + * @process electron (renderer process) */ export const webFrame = globalThis.__enhancerElectronApi?.webFrame; @@ -34,7 +35,7 @@ export const webFrame = globalThis.__enhancerElectronApi?.webFrame; * @param {string=} namespace - a prefix for the message to categorise * it as e.g. enhancer-related. this should not be changed unless replicating * builtin ipc events. - * @runtime electron (renderer process) + * @process electron (renderer process) */ export const sendMessage = (channel, data, namespace = 'notion-enhancer') => { if (globalThis.__enhancerElectronApi) { @@ -49,7 +50,7 @@ export const sendMessage = (channel, data, namespace = 'notion-enhancer') => { * @param {string=} namespace - a prefix for the message to categorise * it as e.g. enhancer-related. this should not be changed unless replicating * builtin ipc events. - * @runtime electron (renderer process) + * @process electron (renderer process) */ export const sendMessageToHost = (channel, data, namespace = 'notion-enhancer') => { if (globalThis.__enhancerElectronApi) { @@ -65,7 +66,7 @@ export const sendMessageToHost = (channel, data, namespace = 'notion-enhancer') * @param {string=} namespace - a prefix for the message to categorise * it as e.g. enhancer-related. this should not be changed unless replicating * builtin ipc events. - * @runtime electron (renderer process) + * @process electron (renderer process) */ export const onMessage = (channel, callback, namespace = 'notion-enhancer') => { if (globalThis.__enhancerElectronApi) { @@ -76,7 +77,7 @@ export const onMessage = (channel, callback, namespace = 'notion-enhancer') => { /** * require() notion app files * @param {string} path - within notion/resources/app/ e.g. main/createWindow.js - * @runtime electron (main process) + * @process electron (main process) */ export const notionRequire = (path) => { return globalThis.__enhancerElectronApi @@ -86,7 +87,7 @@ export const notionRequire = (path) => { /** * get all available app windows excluding the menu - * @runtime electron (main process) + * @process electron (main process) */ export const getNotionWindows = () => { return globalThis.__enhancerElectronApi @@ -96,7 +97,7 @@ export const getNotionWindows = () => { /** * get the currently focused notion window - * @runtime electron (main process) + * @process electron (main process) */ export const getFocusedNotionWindow = () => { return globalThis.__enhancerElectronApi diff --git a/api/env.mjs b/api/env.mjs index 36598e6..27f3ac4 100644 --- a/api/env.mjs +++ b/api/env.mjs @@ -8,7 +8,7 @@ /** * environment-specific methods and constants - * @module notion-enhancer/api/env + * @namespace env */ import * as env from '../env/env.mjs'; diff --git a/api/fmt.mjs b/api/fmt.mjs index b19ec79..57660cb 100644 --- a/api/fmt.mjs +++ b/api/fmt.mjs @@ -8,16 +8,16 @@ /** * helpers for formatting or parsing text - * @module notion-enhancer/api/fmt + * @namespace fmt */ import { fs } from './index.mjs'; /** - * transform a heading into a slug (a lowercase alphanumeric string separated by dashes), + * transform a heading into a slug (a lowercase alphanumeric string separated by hyphens), * e.g. for use as an anchor id * @param {string} heading - the original heading to be slugified - * @param {Set} [slugs] - a list of pre-generated slugs to avoid duplicates + * @param {Set=} slugs - a list of pre-generated slugs to avoid duplicates * @returns {string} the generated slug */ export const slugger = (heading, slugs = new Set()) => { @@ -49,26 +49,25 @@ export const uuidv4 = () => { /** * log-based shading of an rgb color, from * https://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors - * @param {number} p - a decimal amount to shade the color. + * @param {number} shade - a decimal amount to shade the color. * 1 = white, 0 = the original color, -1 = black - * @param {string} c - the rgb color + * @param {string} color - the rgb color * @returns {string} the shaded color */ -export const rgbLogShade = (p, c) => { - const i = parseInt, - r = Math.round, - [a, b, c, d] = c.split(','), - P = p < 0, - t = P ? 0 : p * 255 ** 2, - P = P ? 1 + p : 1 - p; +export const rgbLogShade = (shade, color) => { + const int = parseInt, + round = Math.round, + [a, b, c, d] = color.split(','), + t = shade < 0 ? 0 : shade * 255 ** 2, + p = shade < 0 ? 1 + shade : 1 - shade; return ( 'rgb' + (d ? 'a(' : '(') + - r((P * i(a[3] == 'a' ? a.slice(5) : a.slice(4)) ** 2 + t) ** 0.5) + + round((p * int(a[3] == 'a' ? a.slice(5) : a.slice(4)) ** 2 + t) ** 0.5) + ',' + - r((P * i(b) ** 2 + t) ** 0.5) + + round((p * int(b) ** 2 + t) ** 0.5) + ',' + - r((P * i(c) ** 2 + t) ** 0.5) + + round((p * int(c) ** 2 + t) ** 0.5) + (d ? ',' + d : ')') ); }; @@ -104,8 +103,8 @@ function test(str, pattern) { /** * test the type of a value. unifies builtin, regex, and environment/api checks - * @param {*} value - the value to check - * @param {string|array} type - the type the value should be or a list of allowed values + * @param {unknown} value - the value to check + * @param {string|string[]} type - the type the value should be or a list of allowed values * @returns {boolean} whether or not the value matches the type */ export const is = async (value, type, { extension = '' } = {}) => { diff --git a/api/fs.mjs b/api/fs.mjs index 86dd027..fa18d7b 100644 --- a/api/fs.mjs +++ b/api/fs.mjs @@ -8,7 +8,7 @@ /** * environment-specific file reading - * @module notion-enhancer/api/fs + * @namespace fs */ import * as fs from '../env/fs.mjs'; @@ -16,7 +16,7 @@ import * as fs from '../env/fs.mjs'; /** * get an absolute path to files within notion * @param {string} path - relative to the root notion/resources/app/ e.g. renderer/search.js - * @runtime electron + * @process electron */ export const notionPath = fs.notionPath; @@ -32,8 +32,8 @@ export const localPath = fs.localPath; * fetch and parse a json file's contents * @type {function} * @param {string} path - a url or within-the-enhancer filepath - * @param {object} [opts] - the second argument of a fetch() request - * @returns {object} the json value of the requested file as a js object + * @param {FetchOptions=} opts - the second argument of a fetch() request + * @returns {unknown} the json value of the requested file as a js object */ export const getJSON = fs.getJSON; @@ -41,7 +41,7 @@ export const getJSON = fs.getJSON; * fetch a text file's contents * @type {function} * @param {string} path - a url or within-the-enhancer filepath - * @param {object} [opts] - the second argument of a fetch() request + * @param {FetchOptions=} opts - the second argument of a fetch() request * @returns {string} the text content of the requested file */ export const getText = fs.getText; diff --git a/api/index.mjs b/api/index.mjs index 8fdfab8..f3538f8 100644 --- a/api/index.mjs +++ b/api/index.mjs @@ -6,8 +6,6 @@ 'use strict'; -/** @module notion-enhancer/api */ - // compiles to .cjs for use in electron: // npx -y esbuild insert/api/index.mjs --minify --bundle --format=cjs --outfile=insert/api/index.cjs diff --git a/api/notion.mjs b/api/notion.mjs index a6996eb..8757ba4 100644 --- a/api/notion.mjs +++ b/api/notion.mjs @@ -8,7 +8,7 @@ /** * a basic wrapper around notion's content apis - * @module notion-enhancer/api/notion + * @namespace notion */ import { web, fs, fmt } from './index.mjs'; @@ -30,7 +30,7 @@ const standardiseUUID = (uuid) => { * 1. cors blocking prevents use on the client * 2. the majority of blocks are still 'unsupported' * @param {string} id - uuidv4 record id - * @param {string} [table] - record type (default: 'block'). + * @param {string=} table - record type (default: 'block'). * may also be 'collection', 'collection_view', 'space', 'notion_user', 'discussion', or 'comment' * @returns {Promise} record data. type definitions can be found here: * https://github.com/NotionX/react-notion-x/tree/master/packages/notion-types/src @@ -79,9 +79,9 @@ export const getSpaceID = async () => { * why not use the official api? * 1. cors blocking prevents use on the client * 2. the majority of blocks are still 'unsupported' - * @param {string} [query] - query to search blocks in the space for - * @param {number} [limit] - the max number of results to return (default: 20) - * @param {string} [spaceID] - uuidv4 workspace id + * @param {string=} query - query to search blocks in the space for + * @param {number=} limit - the max number of results to return (default: 20) + * @param {string=} spaceID - uuidv4 workspace id * @returns {object} the number of total results, the list of matches, and related record values. * type definitions can be found here: https://github.com/NotionX/react-notion-x/blob/master/packages/notion-types/src/api.ts */ @@ -142,14 +142,14 @@ export const search = async (query = '', limit = 20, spaceID = getSpaceID()) => * then find the value of blockRecord.properties.title using notion.get. * type definitions can be found here: https://github.com/NotionX/react-notion-x/blob/master/packages/notion-types/src/core.ts * @param {string} pointer.recordID - uuidv4 record id - * @param {string} [pointer.recordTable] - record type (default: 'block'). + * @param {string=} pointer.recordTable - record type (default: 'block'). * may also be 'collection', 'collection_view', 'space', 'notion_user', 'discussion', or 'comment' - * @param {string} [pointer.property] - the record property to update. + * @param {string=} pointer.property - the record property to update. * for record content, it will be the default: 'title'. * for page properties, it will be the property id (the key used in pageRecord.properties). * other possible values are unknown/untested - * @param {string} [pointer.spaceID] - uuidv4 workspace id - * @param {string} [pointer.path] - the path to the key to be set within the record + * @param {string=} pointer.spaceID - uuidv4 workspace id + * @param {string=} pointer.path - the path to the key to be set within the record * (default: [], the root of the record's values) * @returns {boolean|object} true if success, else an error object */ @@ -202,16 +202,16 @@ export const set = async ( * for examples, use notion.get to fetch an existing block record. * type definitions can be found here: https://github.com/NotionX/react-notion-x/blob/master/packages/notion-types/src/block.ts * may also be 'collection', 'collection_view', 'space', 'notion_user', 'discussion', or 'comment' - * @param {object} [insert.recordValue] - the new raw data values to set to the record. - * @param {object} [insert.recordTable] - record type (default: 'block'). + * @param {object=} insert.recordValue - the new raw data values to set to the record. + * @param {object=} insert.recordTable - record type (default: 'block'). * may also be 'collection', 'collection_view', 'space', 'notion_user', 'discussion', or 'comment' - * @param {string} [pointer.prepend] - insert before pointer.siblingID. if false, will be appended after - * @param {string} [pointer.siblingID] - uuidv4 sibling id. if unset, the record will be + * @param {string=} pointer.prepend - insert before pointer.siblingID. if false, will be appended after + * @param {string=} pointer.siblingID - uuidv4 sibling id. if unset, the record will be * inserted at the end of the page start (or the start if pointer.prepend is true) - * @param {string} [pointer.parentID] - uuidv4 parent id - * @param {string} [pointer.parentTable] - parent record type (default: 'block'). - * @param {string} [pointer.spaceID] - uuidv4 space id - * @param {string} [pointer.userID] - uuidv4 user id + * @param {string=} pointer.parentID - uuidv4 parent id + * @param {string=} pointer.parentTable - parent record type (default: 'block'). + * @param {string=} pointer.spaceID - uuidv4 space id + * @param {string=} pointer.userID - uuidv4 user id * instead of the end * @returns {string|object} error object or uuidv4 of the new record */ @@ -313,9 +313,9 @@ export const create = async ( * 1. cors blocking prevents use on the client * 2. the majority of blocks are still 'unsupported' * @param {File} file - the file to upload - * @param {object} [pointer] - where the file should be accessible from - * @param {string} [pointer.pageID] - uuidv4 page id - * @param {string} [pointer.spaceID] - uuidv4 space id + * @param {object=} pointer - where the file should be accessible from + * @param {string=} pointer.pageID - uuidv4 page id + * @param {string=} pointer.spaceID - uuidv4 space id * @returns {string|object} error object or the url of the uploaded file */ export const upload = async (file, { pageID = getPageID(), spaceID = getSpaceID() } = {}) => { @@ -351,7 +351,7 @@ export const upload = async (file, { pageID = getPageID(), spaceID = getSpaceID( * (requires user to be signed in or content to be public) * @param src source url for file * @param {string} recordID uuidv4 record/block/file id - * @param {string} [recordTable] record type (default: 'block'). + * @param {string=} recordTable record type (default: 'block'). * may also be 'collection', 'collection_view', 'space', 'notion_user', 'discussion', or 'comment' * @returns {string} url signed if necessary, else string as-is */ diff --git a/api/registry-validation.mjs b/api/registry-validation.mjs index d467e8a..1b032d2 100644 --- a/api/registry-validation.mjs +++ b/api/registry-validation.mjs @@ -85,7 +85,7 @@ const validateEnvironments = async (mod) => { const isArray = await check(mod, 'css', mod.css, 'object'); if (!isArray) return false; const tests = []; - for (let dest of ['frame', 'client', 'menu']) { + for (const dest of ['frame', 'client', 'menu']) { if (!mod.css[dest]) continue; let test = await check(mod, `css.${dest}`, mod.css[dest], 'array'); if (test) { @@ -101,7 +101,7 @@ const validateEnvironments = async (mod) => { const isArray = await check(mod, 'js', mod.js, 'object'); if (!isArray) return false; const tests = []; - for (let dest of ['frame', 'client', 'menu']) { + for (const dest of ['frame', 'client', 'menu']) { if (!mod.js[dest]) continue; let test = await check(mod, `js.${dest}`, mod.js[dest], 'array'); if (test) { diff --git a/api/registry.mjs b/api/registry.mjs index cc667e6..4c2682b 100644 --- a/api/registry.mjs +++ b/api/registry.mjs @@ -8,7 +8,7 @@ /** * interactions with the enhancer's repository of mods - * @module notion-enhancer/api/registry + * @namespace registry */ import { env, fs, storage } from './index.mjs'; @@ -17,7 +17,7 @@ import { validate } from './registry-validation.mjs'; /** * mod ids whitelisted as part of the enhancer's core, permanently enabled * @constant - * @type {array} + * @type {string[]} */ export const core = [ 'a6621988-551d-495a-97d8-3c568bca2e9e', @@ -28,14 +28,14 @@ export const core = [ /** * all environments/platforms currently supported by the enhancer * @constant - * @type {array} + * @type {string[]} */ export const supportedEnvs = ['linux', 'win32', 'darwin', 'extension']; /** * all available configuration types * @constant - * @type {array} + * @type {string[]} */ export const optionTypes = ['toggle', 'select', 'text', 'number', 'color', 'file', 'hotkey']; @@ -43,7 +43,7 @@ export const optionTypes = ['toggle', 'select', 'text', 'number', 'color', 'file * the name of the active configuration profile * @returns {string} */ -export const profileName = async () => storage.get(['currentprofile'], 'default'); +export const profileName = () => storage.get(['currentprofile'], 'default'); /** * the root database for the current profile @@ -51,8 +51,8 @@ export const profileName = async () => storage.get(['currentprofile'], 'default' */ export const profileDB = async () => storage.db(['profiles', await profileName()]); -let _list, - _errors = []; +let _list; +const _errors = []; /** * list all available mods in the repo * @param {function} filter - a function to filter out mods @@ -60,7 +60,8 @@ let _list, */ export const list = async (filter = (mod) => true) => { if (!_list) { - _list = new Promise(async (res, rej) => { + // deno-lint-ignore no-async-promise-executor + _list = new Promise(async (res, _rej) => { const passed = []; for (const dir of await fs.getJSON('repo/registry.json')) { try { @@ -84,7 +85,7 @@ export const list = async (filter = (mod) => true) => { /** * list validation errors encountered when loading the repo - * @returns {array} error objects with an error message and a source directory + * @returns {{ source: string, message: string }[]} error objects with an error message and a source directory */ export const errors = async () => { await list(); diff --git a/api/storage.mjs b/api/storage.mjs index 9d62c5b..8c7dc31 100644 --- a/api/storage.mjs +++ b/api/storage.mjs @@ -8,7 +8,7 @@ /** * environment-specific data persistence - * @module notion-enhancer/api/storage + * @namespace storage */ import * as storage from '../env/storage.mjs'; @@ -16,8 +16,8 @@ import * as storage from '../env/storage.mjs'; /** * get persisted data * @type {function} - * @param {array} path - the path of keys to the value being fetched - * @param {*} [fallback] - a default value if the path is not matched + * @param {string[]} path - the path of keys to the value being fetched + * @param {unknown=} fallback - a default value if the path is not matched * @returns {Promise} value ?? fallback */ export const get = storage.get; @@ -25,8 +25,8 @@ export const get = storage.get; /** * persist data * @type {function} - * @param {array} path - the path of keys to the value being set - * @param {*} value - the data to save + * @param {string[]} path - the path of keys to the value being set + * @param {unknown} value - the data to save * @returns {Promise} resolves when data has been saved */ export const set = storage.set; @@ -34,9 +34,9 @@ export const set = storage.set; /** * create a wrapper for accessing a partition of the storage * @type {function} - * @param {array} namespace - the path of keys to prefix all storage requests with - * @param {function} [get] - the storage get function to be wrapped - * @param {function} [set] - the storage set function to be wrapped + * @param {string[]} namespace - the path of keys to prefix all storage requests with + * @param {function=} get - the storage get function to be wrapped + * @param {function=} set - the storage set function to be wrapped * @returns {object} an object with the wrapped get/set functions */ export const db = storage.db; @@ -60,6 +60,6 @@ export const removeChangeListener = storage.removeChangeListener; * @callback onStorageChangeCallback * @param {object} event * @param {string} event.path- the path of keys to the changed value - * @param {string} [event.new] - the new value being persisted to the store - * @param {string} [event.old] - the previous value associated with the key + * @param {string=} event.new - the new value being persisted to the store + * @param {string=} event.old - the previous value associated with the key */ diff --git a/api/web.mjs b/api/web.mjs index b247f3c..8b201e0 100644 --- a/api/web.mjs +++ b/api/web.mjs @@ -8,7 +8,7 @@ /** * helpers for manipulation of a webpage - * @module notion-enhancer/api/web + * @namespace web */ import { fs } from './index.mjs'; @@ -16,16 +16,16 @@ import { fs } from './index.mjs'; let _hotkeyListenersActivated = false, _hotkeyEventListeners = [], _documentObserver, - _documentObserverListeners = [], - _documentObserverEvents = []; + _documentObserverListeners = []; +const _documentObserverEvents = []; /** * wait until a page is loaded and ready for modification - * @param {array} [selectors] - wait for the existence of elements that match these css selectors + * @param {array=} selectors - wait for the existence of elements that match these css selectors * @returns {Promise} a promise that will resolve when the page is ready */ export const whenReady = (selectors = []) => { - return new Promise((res, rej) => { + return new Promise((res, _rej) => { const onLoad = () => { const interval = setInterval(isReady, 100); function isReady() { @@ -37,7 +37,7 @@ export const whenReady = (selectors = []) => { isReady(); }; if (document.readyState !== 'complete') { - document.addEventListener('readystatechange', (event) => { + document.addEventListener('readystatechange', (_event) => { if (document.readyState === 'complete') onLoad(); }); } else onLoad(); @@ -46,7 +46,7 @@ export const whenReady = (selectors = []) => { /** * parse the current location search params into a usable form - * @returns {map} a map of the url search params + * @returns {Map} a map of the url search params */ export const queryParams = () => new URLSearchParams(window.location.search); @@ -207,10 +207,10 @@ const triggerHotkeyListener = (event, hotkey) => { * available modifiers are 'alt', 'ctrl', 'meta', and 'shift'. * can be provided as a + separated string. * @param {function} callback - called whenever the keys are pressed - * @param {object} [opts] - fine-tuned control over when the hotkey should be triggered - * @param {boolean} [opts.listenInInput] - whether the hotkey callback should be triggered + * @param {object=} opts - fine-tuned control over when the hotkey should be triggered + * @param {boolean=} opts.listenInInput - whether the hotkey callback should be triggered * when an input is focused - * @param {boolean} [opts.keydown] - whether to listen for the hotkey on keydown. + * @param {boolean=} opts.keydown - whether to listen for the hotkey on keydown. * by default, hotkeys are triggered by the keyup event. */ export const addHotkeyListener = ( @@ -248,7 +248,7 @@ export const removeHotkeyListener = (callback) => { /** * add a listener to watch for changes to the dom * @param {onDocumentObservedCallback} callback - * @param {array} [selectors] + * @param {string[]=} selectors */ export const addDocumentObserver = (callback, selectors = []) => { if (!_documentObserver) { @@ -271,7 +271,7 @@ export const addDocumentObserver = (callback, selectors = []) => { } } }; - _documentObserver = new MutationObserver((list, observer) => { + _documentObserver = new MutationObserver((list, _observer) => { if (!_documentObserverEvents.length) requestIdleCallback(() => handle(_documentObserverEvents)); _documentObserverEvents.push(...list); From 21d5660ec3fd30062d303835ad582c8e00e316e8 Mon Sep 17 00:00:00 2001 From: dragonwocky Date: Tue, 28 Dec 2021 16:21:29 +1100 Subject: [PATCH 52/52] update README with link to new website --- api/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/api/README.md b/api/README.md index 9f4674f..ab8ffc8 100644 --- a/api/README.md +++ b/api/README.md @@ -1,3 +1,5 @@ -# api +# notion-enhancer/api -the standard api used by the enhancer's mods and core +the standard api available within the notion-enhancer + +[read the docs online](https://notion-enhancer.github.io/documentation/api)