mirror of
https://github.com/notion-enhancer/notion-enhancer.git
synced 2025-04-04 12:49:03 +00:00
chore: refactor core/client.mjs, sync telemetry opt-in/out across profiles
This commit is contained in:
parent
3cd8ed7703
commit
ba98ed6412
@ -72,10 +72,10 @@ const initDatabase = (namespace, fallbacks = {}) => {
|
||||
|
||||
// schema:
|
||||
// - ("agreedToTerms") -> boolean
|
||||
// - ("telemetryEnabled") -> boolean
|
||||
// - ("profileIds") -> $profileId[]
|
||||
// - ("activeProfile") -> $profileId
|
||||
// - $profileId: ("profileName") -> string
|
||||
// - $profileId: ("telemetryEnabled") -> boolean
|
||||
// - $profileId__enabledMods: ($modId) -> boolean
|
||||
// - $profileId__$modId: ($optionKey) -> value
|
||||
|
||||
|
@ -5,186 +5,128 @@
|
||||
*/
|
||||
|
||||
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(
|
||||
{ icon, notifications, themeOverridesLoaded, ...props },
|
||||
...children
|
||||
) {
|
||||
const { html } = globalThis.__enhancerApi;
|
||||
return html`<div
|
||||
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)]"
|
||||
...${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 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) {
|
||||
const doThemeOverride = async (db) => {
|
||||
const { getMods, isEnabled } = globalThis.__enhancerApi,
|
||||
enabledFilter = (theme) => isEnabled(theme.id),
|
||||
overrideThemes = await db.get("loadThemeOverrides"),
|
||||
enabledThemes = await asyncFilter(await getMods("themes"), enabledFilter);
|
||||
return (
|
||||
overrideThemes === "Enabled" ||
|
||||
(overrideThemes === "Auto" && enabledThemes.length)
|
||||
);
|
||||
},
|
||||
overrideThemes = async (db) => {
|
||||
const { html, enhancerUrl } = globalThis.__enhancerApi;
|
||||
if (!(await doThemeOverride(db))) return;
|
||||
document.head.append(html`<link
|
||||
rel="stylesheet"
|
||||
href=${enhancerUrl("core/theme.css")}
|
||||
/>`);
|
||||
}
|
||||
|
||||
if (customStyles) {
|
||||
const $customStyles = html`<style>
|
||||
},
|
||||
insertCustomStyles = async (db) => {
|
||||
const { html } = globalThis.__enhancerApi,
|
||||
customStyles = (await db.get("customStyles"))?.content;
|
||||
if (!customStyles) return;
|
||||
return document.head.append(html`<style>
|
||||
${customStyles}
|
||||
</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, "*");
|
||||
}
|
||||
</style>`);
|
||||
};
|
||||
|
||||
const openMenu = () => {
|
||||
updateTheme(true);
|
||||
$menuModal?.setAttribute("open", true);
|
||||
$menuFrame?.contentWindow.focus();
|
||||
},
|
||||
closeMenu = () => $menuModal?.removeAttribute("open");
|
||||
const insertMenu = async (db) => {
|
||||
const notionSidebar = `.notion-sidebar-container .notion-sidebar > :nth-child(3) > div > :nth-child(2)`,
|
||||
{ html, addKeyListener, addMutationListener } = globalThis.__enhancerApi,
|
||||
{ platform, enhancerUrl, onMessage } = globalThis.__enhancerApi,
|
||||
menuButtonIconStyle = await db.get("menuButtonIconStyle"),
|
||||
openMenuHotkey = await db.get("openMenuHotkey"),
|
||||
renderPing = {
|
||||
namespace: "notion-enhancer",
|
||||
hotkey: openMenuHotkey,
|
||||
icon: menuButtonIconStyle,
|
||||
};
|
||||
|
||||
$menuFrame = html`<iframe
|
||||
title="notion-enhancer menu"
|
||||
src="${enhancerUrl("core/menu/index.html")}"
|
||||
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"
|
||||
onload=${() => {
|
||||
// pass notion-enhancer api to electron menu process
|
||||
if (platform !== "browser") {
|
||||
$menuFrame.contentWindow.__enhancerApi = api;
|
||||
}
|
||||
// menu relies on updateTheme for render trigger
|
||||
updateTheme(true);
|
||||
}}
|
||||
></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);
|
||||
let _contentWindow;
|
||||
const sendThemePing = () => {
|
||||
if (!_contentWindow) return;
|
||||
const darkMode = document.body.classList.contains("dark"),
|
||||
notionTheme = darkMode ? "dark" : "light";
|
||||
if (renderPing.theme === notionTheme) return;
|
||||
renderPing.theme = notionTheme;
|
||||
_contentWindow.postMessage(renderPing, "*");
|
||||
},
|
||||
sendRenderPing = (contentWindow) => {
|
||||
_contentWindow ??= contentWindow;
|
||||
if (!$modal.hasAttribute("open")) return;
|
||||
delete renderPing.theme;
|
||||
_contentWindow.focus();
|
||||
sendThemePing();
|
||||
};
|
||||
|
||||
const $menuButton = html`<${SidebarButton}
|
||||
onclick=${openMenu}
|
||||
notifications=${(await checkForUpdate()) ? 1 : 0}
|
||||
icon="notion-enhancer${menuButtonIconStyle === "Monochrome"
|
||||
? "?mask"
|
||||
: " text-[16px]"}"
|
||||
>notion-enhancer
|
||||
<//>`;
|
||||
addMutationListener(notionSidebar, () => {
|
||||
if (document.contains($menuButton)) return;
|
||||
document.querySelector(notionSidebar)?.append($menuButton);
|
||||
});
|
||||
document.querySelector(notionSidebar)?.append($menuButton);
|
||||
const $modal = html`<${Modal} onopen=${sendRenderPing}>
|
||||
<${Frame}
|
||||
title="notion-enhancer menu"
|
||||
src="${enhancerUrl("core/menu/index.html")}"
|
||||
onload=${function () {
|
||||
// pass notion-enhancer api to electron menu process
|
||||
if (platform !== "browser") {
|
||||
const apiKey = "__enhancerApi";
|
||||
this.contentWindow[apiKey] = globalThis[apiKey];
|
||||
}
|
||||
sendRenderPing(this.contentWindow);
|
||||
}}
|
||||
/>
|
||||
<//>`,
|
||||
$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));
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.data?.namespace !== "notion-enhancer") return;
|
||||
if (event.data?.action === "close-menu") closeMenu();
|
||||
if (event.data?.action === "open-menu") openMenu();
|
||||
});
|
||||
addMutationListener("body", () => {
|
||||
if ($menuModal?.hasAttribute("open")) updateTheme();
|
||||
});
|
||||
onMessage("notion-enhancer", (message) => {
|
||||
if (message === "open-menu") openMenu();
|
||||
});
|
||||
addKeyListener(openMenuHotkey, (event) => {
|
||||
event.preventDefault();
|
||||
openMenu();
|
||||
});
|
||||
addKeyListener("Escape", () => {
|
||||
if (document.activeElement?.nodeName === "INPUT") return;
|
||||
closeMenu();
|
||||
});
|
||||
|
||||
sendMessage("notion-enhancer", "load-complete");
|
||||
|
||||
if ((await initDatabase().get("agreedToTerms")) === version) {
|
||||
addKeyListener(openMenuHotkey, (event) => {
|
||||
event.preventDefault();
|
||||
$modal.open();
|
||||
});
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.data?.namespace !== "notion-enhancer") return;
|
||||
if (event.data?.action === "close-menu") $modal.close();
|
||||
if (event.data?.action === "open-menu") $modal.open();
|
||||
});
|
||||
onMessage("notion-enhancer", (message) => {
|
||||
if (message === "open-menu") $modal.open();
|
||||
});
|
||||
},
|
||||
sendTelemetryPing = async () => {
|
||||
const { version } = globalThis.__enhancerApi,
|
||||
db = globalThis.__enhancerApi.initDatabase(),
|
||||
agreedToTerms = await db.get("agreedToTerms"),
|
||||
telemetryEnabled = await db.get("telemetryEnabled");
|
||||
if (!telemetryEnabled || agreedToTerms !== version) return;
|
||||
// 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
95
src/core/components.mjs
Normal 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 };
|
@ -6,57 +6,47 @@
|
||||
|
||||
import { setState, useState } from "../state.mjs";
|
||||
|
||||
function Popup({ trigger, onopen, onclose, onbeforeclose }, ...children) {
|
||||
const { html, extendProps } = globalThis.__enhancerApi,
|
||||
$popup = html`<div
|
||||
class="notion-enhancer--menu-popup
|
||||
group absolute top-0 left-0 w-full h-full
|
||||
flex-(& col) justify-center items-end z-20
|
||||
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>`;
|
||||
function Popup({ trigger, ...props }, ...children) {
|
||||
const { html, extendProps } = globalThis.__enhancerApi;
|
||||
extendProps(props, {
|
||||
class: `notion-enhancer--menu-popup
|
||||
group absolute top-0 left-0 w-full h-full
|
||||
flex-(& col) justify-center items-end z-20
|
||||
pointer-events-none font-normal text-left`,
|
||||
});
|
||||
|
||||
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.setAttribute("open", true);
|
||||
$popup.querySelectorAll("[tabindex]").forEach(($el) => ($el.tabIndex = 0));
|
||||
setState({ popupOpen: true });
|
||||
onopen?.();
|
||||
$popup.onopen?.();
|
||||
};
|
||||
$popup.hide = () => {
|
||||
onbeforeclose?.();
|
||||
$popup.onbeforeclose?.();
|
||||
$popup.removeAttribute("open");
|
||||
$popup.style.pointerEvents = "auto";
|
||||
$popup.querySelectorAll("[tabindex]").forEach(($el) => ($el.tabIndex = -1));
|
||||
setTimeout(() => {
|
||||
$popup.style.pointerEvents = "";
|
||||
setState({ popupOpen: false });
|
||||
onclose?.();
|
||||
$popup.onclose?.();
|
||||
}, 200);
|
||||
};
|
||||
$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) => {
|
||||
if (!$popup.hasAttribute("open")) 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 (!trigger) return $popup;
|
||||
extendProps(trigger, {
|
||||
onclick: $popup.show,
|
||||
onkeydown(event) {
|
||||
if ([" ", "Enter"].includes(event.key)) {
|
||||
event.preventDefault();
|
||||
$popup.show();
|
||||
}
|
||||
},
|
||||
});
|
||||
return $popup;
|
||||
}
|
||||
|
||||
|
@ -25,10 +25,10 @@ function Telemetry() {
|
||||
|
||||
const _get = async () => {
|
||||
// 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) => {
|
||||
await initDatabase([await getProfile()]).set("telemetryEnabled", value);
|
||||
await initDatabase().set("telemetryEnabled", value);
|
||||
setState({ rerender: true, databaseUpdated: true });
|
||||
};
|
||||
|
||||
@ -42,8 +42,9 @@ function Telemetry() {
|
||||
platform (<code>"${platform}"</code>), timezone
|
||||
(<code>"${timezone}"</code>), notion-enhancer version
|
||||
(<code>"${version}"</code>), and enabled mods (${$enabledMods}). You can
|
||||
opt in or out of telemetry at any time. For more information, read the
|
||||
notion-enhancer's <a href=${privacyPolicy}>privacy policy</a>.`}
|
||||
opt in or out of telemetry at any time. This setting syncs across
|
||||
configuration profiles. For more information, read the notion-enhancer's
|
||||
<a href=${privacyPolicy}>privacy policy</a>.`}
|
||||
...${{ _get, _set }}
|
||||
/>`;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user