chore: refactor core/client.mjs, sync telemetry opt-in/out across profiles

This commit is contained in:
dragonwocky 2023-02-03 11:52:51 +11:00
parent 3cd8ed7703
commit ba98ed6412
Signed by: dragonwocky
GPG Key ID: 7998D08F7D7BD7A8
5 changed files with 246 additions and 208 deletions

View File

@ -72,10 +72,10 @@ const initDatabase = (namespace, fallbacks = {}) => {
// schema: // schema:
// - ("agreedToTerms") -> boolean // - ("agreedToTerms") -> boolean
// - ("telemetryEnabled") -> boolean
// - ("profileIds") -> $profileId[] // - ("profileIds") -> $profileId[]
// - ("activeProfile") -> $profileId // - ("activeProfile") -> $profileId
// - $profileId: ("profileName") -> string // - $profileId: ("profileName") -> string
// - $profileId: ("telemetryEnabled") -> boolean
// - $profileId__enabledMods: ($modId) -> boolean // - $profileId__enabledMods: ($modId) -> boolean
// - $profileId__$modId: ($optionKey) -> value // - $profileId__$modId: ($optionKey) -> value

View File

@ -5,186 +5,128 @@
*/ */
import { checkForUpdate } from "./update.mjs"; import { checkForUpdate } from "./update.mjs";
import { Frame, Modal, Button } from "./components.mjs";
const notionSidebar = `.notion-sidebar-container .notion-sidebar > :nth-child(3) > div > :nth-child(2)`; // prettier-ignore
const asyncFilter = async (arr, predicate) => Promise.all(arr.map(predicate))
.then((results) => arr.filter((_v, index) => results[index]));
function SidebarButton( const doThemeOverride = async (db) => {
{ icon, notifications, themeOverridesLoaded, ...props }, const { getMods, isEnabled } = globalThis.__enhancerApi,
...children enabledFilter = (theme) => isEnabled(theme.id),
) { overrideThemes = await db.get("loadThemeOverrides"),
const { html } = globalThis.__enhancerApi; enabledThemes = await asyncFilter(await getMods("themes"), enabledFilter);
return html`<div return (
tabindex="0" overrideThemes === "Enabled" ||
role="button" (overrideThemes === "Auto" && enabledThemes.length)
class="notion-enhancer--menu-button );
flex select-none cursor-pointer rounded-[3px] },
text-[14px] my-px mx-[4px] py-[2px] px-[10px] overrideThemes = async (db) => {
transition hover:bg-[color:var(--theme--bg-hover)]" const { html, enhancerUrl } = globalThis.__enhancerApi;
...${props} if (!(await doThemeOverride(db))) return;
>
<div class="flex items-center justify-center w-[22px] h-[22px] mr-[8px]">
<i class="i-${icon}"></i>
</div>
<div>${children}</div>
<div class="ml-auto my-auto${notifications > 0 ? "" : " hidden"}">
<!-- accents are squashed into one variable for theming:
use rgb to match notion if overrides not loaded -->
<div
class="flex justify-center w-[16px] h-[16px] font-semibold
text-([10px] [color:var(--theme--accent-secondary\\_contrast)])
bg-[color:var(--theme--accent-secondary)] rounded-[3px] mb-[2px]
dark:bg-[color:${themeOverridesLoaded
? "var(--theme--accent-secondary)"
: "rgb(180,65,60)"}]"
>
<span class="ml-[-0.5px]">${notifications}</span>
</div>
</div>
</div>`;
}
export default async (api, db) => {
const {
html,
platform,
version,
getMods,
isEnabled,
enhancerUrl,
onMessage,
sendMessage,
addMutationListener,
addKeyListener,
initDatabase,
} = api,
openMenuHotkey = await db.get("openMenuHotkey"),
menuButtonIconStyle = await db.get("menuButtonIconStyle"),
loadThemeOverrides = await db.get("loadThemeOverrides"),
customStyles = (await db.get("customStyles"))?.content;
// appearance
const enabledThemes = (await getMods("themes")).map((theme) =>
isEnabled(theme.id)
),
forceLoadOverrides = loadThemeOverrides === "Enabled",
autoLoadOverrides =
loadThemeOverrides === "Auto" &&
(await Promise.all(enabledThemes)).some((enabled) => enabled);
if (forceLoadOverrides || autoLoadOverrides) {
document.head.append(html`<link document.head.append(html`<link
rel="stylesheet" rel="stylesheet"
href=${enhancerUrl("core/theme.css")} href=${enhancerUrl("core/theme.css")}
/>`); />`);
} },
insertCustomStyles = async (db) => {
if (customStyles) { const { html } = globalThis.__enhancerApi,
const $customStyles = html`<style> customStyles = (await db.get("customStyles"))?.content;
if (!customStyles) return;
return document.head.append(html`<style>
${customStyles} ${customStyles}
</style>`; </style>`);
document.head.append($customStyles);
}
// menu
let $menuModal, $menuFrame, _notionTheme;
const updateTheme = (force = false) => {
const darkMode = document.body.classList.contains("dark"),
notionTheme = darkMode ? "dark" : "light";
if (notionTheme !== _notionTheme || force) {
_notionTheme = notionTheme;
const msg = {
namespace: "notion-enhancer",
hotkey: openMenuHotkey,
theme: notionTheme,
icon: menuButtonIconStyle,
};
$menuFrame?.contentWindow.postMessage(msg, "*");
}
}; };
const openMenu = () => { const insertMenu = async (db) => {
updateTheme(true); const notionSidebar = `.notion-sidebar-container .notion-sidebar > :nth-child(3) > div > :nth-child(2)`,
$menuModal?.setAttribute("open", true); { html, addKeyListener, addMutationListener } = globalThis.__enhancerApi,
$menuFrame?.contentWindow.focus(); { platform, enhancerUrl, onMessage } = globalThis.__enhancerApi,
}, menuButtonIconStyle = await db.get("menuButtonIconStyle"),
closeMenu = () => $menuModal?.removeAttribute("open"); openMenuHotkey = await db.get("openMenuHotkey"),
renderPing = {
namespace: "notion-enhancer",
hotkey: openMenuHotkey,
icon: menuButtonIconStyle,
};
$menuFrame = html`<iframe let _contentWindow;
title="notion-enhancer menu" const sendThemePing = () => {
src="${enhancerUrl("core/menu/index.html")}" if (!_contentWindow) return;
class="rounded-[5px] w-[1150px] h-[calc(100vh-100px)] const darkMode = document.body.classList.contains("dark"),
max-w-[calc(100vw-100px)] max-h-[715px] overflow-hidden notionTheme = darkMode ? "dark" : "light";
bg-[color:var(--theme--bg-primary)] drop-shadow-xl if (renderPing.theme === notionTheme) return;
group-open:(pointer-events-auto opacity-100 scale-100) renderPing.theme = notionTheme;
transition opacity-0 scale-95" _contentWindow.postMessage(renderPing, "*");
onload=${() => { },
// pass notion-enhancer api to electron menu process sendRenderPing = (contentWindow) => {
if (platform !== "browser") { _contentWindow ??= contentWindow;
$menuFrame.contentWindow.__enhancerApi = api; if (!$modal.hasAttribute("open")) return;
} delete renderPing.theme;
// menu relies on updateTheme for render trigger _contentWindow.focus();
updateTheme(true); sendThemePing();
}} };
></iframe>`;
$menuModal = html`<div
class="notion-enhancer--menu-modal group
z-[999] fixed inset-0 w-screen h-screen
transition pointer-events-none opacity-0
open:(pointer-events-auto opacity-100)"
>
<div
class="fixed inset-0 bg-[color:var(--theme--bg-overlay)]"
onclick=${closeMenu}
></div>
<div
class="fixed inset-0 flex w-screen h-screen
items-center justify-center pointer-events-none"
>
${$menuFrame}
</div>
</div>`;
document.body.append($menuModal);
const $menuButton = html`<${SidebarButton} const $modal = html`<${Modal} onopen=${sendRenderPing}>
onclick=${openMenu} <${Frame}
notifications=${(await checkForUpdate()) ? 1 : 0} title="notion-enhancer menu"
icon="notion-enhancer${menuButtonIconStyle === "Monochrome" src="${enhancerUrl("core/menu/index.html")}"
? "?mask" onload=${function () {
: " text-[16px]"}" // pass notion-enhancer api to electron menu process
>notion-enhancer if (platform !== "browser") {
<//>`; const apiKey = "__enhancerApi";
addMutationListener(notionSidebar, () => { this.contentWindow[apiKey] = globalThis[apiKey];
if (document.contains($menuButton)) return; }
document.querySelector(notionSidebar)?.append($menuButton); sendRenderPing(this.contentWindow);
}); }}
document.querySelector(notionSidebar)?.append($menuButton); />
<//>`,
$button = html`<${Button}
onclick=${$modal.open}
notifications=${(await checkForUpdate()) ? 1 : 0}
themeOverridesLoaded=${await doThemeOverride(db)}
icon="notion-enhancer${menuButtonIconStyle === "Monochrome"
? "?mask"
: " text-[16px]"}"
>notion-enhancer
<//>`;
document.body.append($modal);
addMutationListener(notionSidebar, () => {
if (document.contains($button)) return;
document.querySelector(notionSidebar)?.append($button);
});
document.querySelector(notionSidebar)?.append($button);
addMutationListener("body", sendThemePing);
window.addEventListener("focus", sendRenderPing);
window.addEventListener("focus", () => updateTheme(true)); addKeyListener(openMenuHotkey, (event) => {
window.addEventListener("message", (event) => { event.preventDefault();
if (event.data?.namespace !== "notion-enhancer") return; $modal.open();
if (event.data?.action === "close-menu") closeMenu(); });
if (event.data?.action === "open-menu") openMenu(); window.addEventListener("message", (event) => {
}); if (event.data?.namespace !== "notion-enhancer") return;
addMutationListener("body", () => { if (event.data?.action === "close-menu") $modal.close();
if ($menuModal?.hasAttribute("open")) updateTheme(); if (event.data?.action === "open-menu") $modal.open();
}); });
onMessage("notion-enhancer", (message) => { onMessage("notion-enhancer", (message) => {
if (message === "open-menu") openMenu(); if (message === "open-menu") $modal.open();
}); });
addKeyListener(openMenuHotkey, (event) => { },
event.preventDefault(); sendTelemetryPing = async () => {
openMenu(); const { version } = globalThis.__enhancerApi,
}); db = globalThis.__enhancerApi.initDatabase(),
addKeyListener("Escape", () => { agreedToTerms = await db.get("agreedToTerms"),
if (document.activeElement?.nodeName === "INPUT") return; telemetryEnabled = await db.get("telemetryEnabled");
closeMenu(); if (!telemetryEnabled || agreedToTerms !== version) return;
});
sendMessage("notion-enhancer", "load-complete");
if ((await initDatabase().get("agreedToTerms")) === version) {
// telemetry // telemetry
} };
export default async (api, db) => {
await Promise.all([
overrideThemes(db),
insertCustomStyles(db),
insertMenu(db),
sendTelemetryPing(),
]);
api.sendMessage("notion-enhancer", "load-complete");
}; };

95
src/core/components.mjs Normal file
View File

@ -0,0 +1,95 @@
/**
* notion-enhancer
* (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
* (https://notion-enhancer.github.io/) under the MIT license
*/
function Frame(props) {
const { html, extendProps } = globalThis.__enhancerApi;
extendProps(props, {
class: `rounded-[5px] w-[1150px] h-[calc(100vh-100px)]
max-w-[calc(100vw-100px)] max-h-[715px] overflow-hidden
bg-[color:var(--theme--bg-primary)] drop-shadow-xl
group-open:(pointer-events-auto opacity-100 scale-100)
transition opacity-0 scale-95`,
});
return html`<iframe ...${props}></iframe>`;
}
function Modal(props, ...children) {
const { html, extendProps, addKeyListener } = globalThis.__enhancerApi;
extendProps(props, {
class: `notion-enhancer--menu-modal group
z-[999] fixed inset-0 w-screen h-screen
transition pointer-events-none opacity-0
open:(pointer-events-auto opacity-100)`,
});
const $modal = html`<div ...${props}>
<div
class="fixed inset-0 bg-[color:var(--theme--bg-overlay)]"
onclick=${() => $modal.close()}
></div>
<div
class="fixed inset-0 flex w-screen h-screen
items-center justify-center pointer-events-none"
>
${children}
</div>
</div>`;
$modal.open = () => {
$modal.setAttribute("open", "");
$modal.onopen?.();
};
$modal.close = () => {
$modal.onbeforeclose?.();
$modal.removeAttribute("open");
$modal.style.pointerEvents = "auto";
setTimeout(() => {
$modal.style.pointerEvents = "";
$modal.onclose?.();
}, 200);
};
addKeyListener("Escape", () => {
if (document.activeElement?.nodeName === "INPUT") return;
$modal.close();
});
return $modal;
}
function Button(
{ icon, notifications, themeOverridesLoaded, ...props },
...children
) {
const { html, extendProps } = globalThis.__enhancerApi;
extendProps(props, {
tabindex: 0,
role: "button",
class: `notion-enhancer--menu-button
flex select-none cursor-pointer rounded-[3px]
text-[14px] my-px mx-[4px] py-[2px] px-[10px]
transition hover:bg-[color:var(--theme--bg-hover)]`,
});
return html`<div ...${props}>
<div class="flex items-center justify-center w-[22px] h-[22px] mr-[8px]">
<i class="i-${icon}"></i>
</div>
<div>${children}</div>
<div class="ml-auto my-auto${notifications > 0 ? "" : " hidden"}">
<!-- accents are squashed into one variable for theming:
use rgb to match notion if overrides not loaded -->
<div
class="flex justify-center w-[16px] h-[16px] font-semibold
text-([10px] [color:var(--theme--accent-secondary\\_contrast)])
bg-[color:var(--theme--accent-secondary)] rounded-[3px] mb-[2px]
dark:bg-[color:${themeOverridesLoaded
? "var(--theme--accent-secondary)"
: "rgb(180,65,60)"}]"
>
<span class="ml-[-0.5px]">${notifications}</span>
</div>
</div>
</div>`;
}
export { Frame, Modal, Button };

View File

@ -6,57 +6,47 @@
import { setState, useState } from "../state.mjs"; import { setState, useState } from "../state.mjs";
function Popup({ trigger, onopen, onclose, onbeforeclose }, ...children) { function Popup({ trigger, ...props }, ...children) {
const { html, extendProps } = globalThis.__enhancerApi, const { html, extendProps } = globalThis.__enhancerApi;
$popup = html`<div extendProps(props, {
class="notion-enhancer--menu-popup class: `notion-enhancer--menu-popup
group absolute top-0 left-0 w-full h-full group absolute top-0 left-0 w-full h-full
flex-(& col) justify-center items-end z-20 flex-(& col) justify-center items-end z-20
pointer-events-none font-normal text-left" pointer-events-none font-normal text-left`,
> });
<div class="relative right-[100%]">
<div
class="bg-[color:var(--theme--bg-secondary)]
w-[250px] max-w-[calc(100vw-24px)] max-h-[70vh]
py-[6px] px-[4px] drop-shadow-xl overflow-y-auto
transition duration-[200ms] opacity-0 scale-95 rounded-[4px]
group-open:(pointer-events-auto opacity-100 scale-100)"
>
${children}
</div>
</div>
</div>`;
const $popup = html`<div ...${props}>
<div class="relative right-[100%]">
<div
class="bg-[color:var(--theme--bg-secondary)]
w-[250px] max-w-[calc(100vw-24px)] max-h-[70vh]
py-[6px] px-[4px] drop-shadow-xl overflow-y-auto
transition duration-[200ms] opacity-0 scale-95 rounded-[4px]
group-open:(pointer-events-auto opacity-100 scale-100)"
>
${children}
</div>
</div>
</div>`;
$popup.show = () => { $popup.show = () => {
$popup.setAttribute("open", true); $popup.setAttribute("open", true);
$popup.querySelectorAll("[tabindex]").forEach(($el) => ($el.tabIndex = 0)); $popup.querySelectorAll("[tabindex]").forEach(($el) => ($el.tabIndex = 0));
setState({ popupOpen: true }); setState({ popupOpen: true });
onopen?.(); $popup.onopen?.();
}; };
$popup.hide = () => { $popup.hide = () => {
onbeforeclose?.(); $popup.onbeforeclose?.();
$popup.removeAttribute("open"); $popup.removeAttribute("open");
$popup.style.pointerEvents = "auto"; $popup.style.pointerEvents = "auto";
$popup.querySelectorAll("[tabindex]").forEach(($el) => ($el.tabIndex = -1)); $popup.querySelectorAll("[tabindex]").forEach(($el) => ($el.tabIndex = -1));
setTimeout(() => { setTimeout(() => {
$popup.style.pointerEvents = ""; $popup.style.pointerEvents = "";
setState({ popupOpen: false }); setState({ popupOpen: false });
onclose?.(); $popup.onclose?.();
}, 200); }, 200);
}; };
$popup.querySelectorAll("[tabindex]").forEach(($el) => ($el.tabIndex = -1)); $popup.querySelectorAll("[tabindex]").forEach(($el) => ($el.tabIndex = -1));
if (trigger) {
extendProps(trigger, {
onclick: $popup.show,
onkeydown(event) {
if ([" ", "Enter"].includes(event.key)) {
event.preventDefault();
$popup.show();
}
},
});
}
document.addEventListener("click", (event) => { document.addEventListener("click", (event) => {
if (!$popup.hasAttribute("open")) return; if (!$popup.hasAttribute("open")) return;
if ($popup.contains(event.target) || $popup === event.target) return; if ($popup.contains(event.target) || $popup === event.target) return;
@ -67,6 +57,16 @@ function Popup({ trigger, onopen, onclose, onbeforeclose }, ...children) {
if ($popup.hasAttribute("open")) $popup.hide(); if ($popup.hasAttribute("open")) $popup.hide();
}); });
if (!trigger) return $popup;
extendProps(trigger, {
onclick: $popup.show,
onkeydown(event) {
if ([" ", "Enter"].includes(event.key)) {
event.preventDefault();
$popup.show();
}
},
});
return $popup; return $popup;
} }

View File

@ -25,10 +25,10 @@ function Telemetry() {
const _get = async () => { const _get = async () => {
// defaults to true, must be explicitly set to false to disable // defaults to true, must be explicitly set to false to disable
return initDatabase([await getProfile()]).get("telemetryEnabled") ?? true; return initDatabase().get("telemetryEnabled") ?? true;
}, },
_set = async (value) => { _set = async (value) => {
await initDatabase([await getProfile()]).set("telemetryEnabled", value); await initDatabase().set("telemetryEnabled", value);
setState({ rerender: true, databaseUpdated: true }); setState({ rerender: true, databaseUpdated: true });
}; };
@ -42,8 +42,9 @@ function Telemetry() {
platform (<code>"${platform}"</code>), timezone platform (<code>"${platform}"</code>), timezone
(<code>"${timezone}"</code>), notion-enhancer version (<code>"${timezone}"</code>), notion-enhancer version
(<code>"${version}"</code>), and enabled mods (${$enabledMods}). You can (<code>"${version}"</code>), and enabled mods (${$enabledMods}). You can
opt in or out of telemetry at any time. For more information, read the opt in or out of telemetry at any time. This setting syncs across
notion-enhancer's <a href=${privacyPolicy}>privacy policy</a>.`} configuration profiles. For more information, read the notion-enhancer's
<a href=${privacyPolicy}>privacy policy</a>.`}
...${{ _get, _set }} ...${{ _get, _set }}
/>`; />`;
} }