mirror of
https://github.com/NohamR/Pocket-Bird.git
synced 2026-05-24 19:59:36 +00:00
318 lines
6.5 KiB
JavaScript
318 lines
6.5 KiB
JavaScript
import { debug, log, error } from "./shared.js";
|
|
|
|
export const SAVE_KEY = "birbSaveData";
|
|
const ROOT_PATH = "";
|
|
const SET_CONTEXT = "__CONTEXT__"
|
|
const MONOCRAFT_URL = "__MONOCRAFT_URL__";
|
|
|
|
/**
|
|
* @typedef {import('./application.js').BirbSaveData} BirbSaveData
|
|
*/
|
|
|
|
/**
|
|
* @abstract
|
|
*/
|
|
export class Context {
|
|
|
|
/**
|
|
* @abstract
|
|
* @returns {Promise<BirbSaveData|{}>}
|
|
*/
|
|
async getSaveData() {
|
|
throw new Error("Method not implemented");
|
|
}
|
|
|
|
/**
|
|
* @abstract
|
|
* @param {BirbSaveData} saveData
|
|
*/
|
|
async putSaveData(saveData) {
|
|
throw new Error("Method not implemented");
|
|
}
|
|
|
|
/**
|
|
* @abstract
|
|
*/
|
|
resetSaveData() {
|
|
throw new Error("Method not implemented");
|
|
}
|
|
|
|
/**
|
|
* @returns {string[]} A list of CSS selectors for focusable elements
|
|
*/
|
|
getFocusableElements() {
|
|
return ["img", "video", ".birb-sticky-note"];
|
|
}
|
|
|
|
getFocusElementTopMargin() {
|
|
return 80;
|
|
}
|
|
|
|
/**
|
|
* @returns {string} The current path of the active page in this context
|
|
*/
|
|
getPath() {
|
|
// Default to website URL
|
|
return window.location.href;
|
|
}
|
|
|
|
/**
|
|
* @returns {HTMLElement} The current active page element where sticky notes can be applied
|
|
*/
|
|
getActivePage() {
|
|
// Default to root element
|
|
return document.documentElement;
|
|
}
|
|
|
|
/**
|
|
* Checks if a path is applicable given the context
|
|
* @param {string} path Can be a site URL or another context-specific path
|
|
* @returns {boolean} Whether the path matches the current context state
|
|
*/
|
|
isPathApplicable(path) {
|
|
// Default to website URL matching
|
|
const currentUrl = window.location.href;
|
|
const stickyNoteWebsite = path.split("?")[0];
|
|
const currentWebsite = currentUrl.split("?")[0];
|
|
|
|
if (stickyNoteWebsite !== currentWebsite) {
|
|
return false;
|
|
}
|
|
|
|
const pathParams = parseUrlParams(path);
|
|
const currentParams = parseUrlParams(currentUrl);
|
|
|
|
if (window.location.hostname === "www.youtube.com") {
|
|
if (currentParams.v !== undefined && currentParams.v !== pathParams.v) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
areStickyNotesEnabled() {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @returns {string}
|
|
*/
|
|
getFontStyles() {
|
|
return getFontFaceImport(MONOCRAFT_URL);
|
|
}
|
|
}
|
|
|
|
export class LocalContext extends Context {
|
|
|
|
/**
|
|
* @override
|
|
* @returns {Promise<BirbSaveData|{}>}
|
|
*/
|
|
async getSaveData() {
|
|
log("Loading save data from localStorage");
|
|
return JSON.parse(localStorage.getItem(SAVE_KEY) ?? "{}");
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {BirbSaveData} saveData
|
|
*/
|
|
async putSaveData(saveData) {
|
|
log("Saving data to localStorage");
|
|
localStorage.setItem(SAVE_KEY, JSON.stringify(saveData));
|
|
}
|
|
|
|
/** @override */
|
|
resetSaveData() {
|
|
log("Resetting save data in localStorage");
|
|
localStorage.removeItem(SAVE_KEY);
|
|
}
|
|
}
|
|
|
|
export class UserScriptContext extends Context {
|
|
|
|
/**
|
|
* @override
|
|
* @returns {Promise<BirbSaveData|{}>}
|
|
*/
|
|
async getSaveData() {
|
|
log("Loading save data from UserScript storage");
|
|
/** @type {BirbSaveData|{}} */
|
|
let saveData = {};
|
|
// @ts-expect-error
|
|
saveData = GM_getValue(SAVE_KEY, {}) ?? {};
|
|
return saveData;
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {BirbSaveData} saveData
|
|
*/
|
|
async putSaveData(saveData) {
|
|
log("Saving data to UserScript storage");
|
|
// @ts-expect-error
|
|
GM_setValue(SAVE_KEY, saveData);
|
|
}
|
|
|
|
/** @override */
|
|
resetSaveData() {
|
|
log("Resetting save data in UserScript storage");
|
|
// @ts-expect-error
|
|
GM_deleteValue(SAVE_KEY);
|
|
}
|
|
}
|
|
|
|
export class BrowserExtensionContext extends Context {
|
|
|
|
/**
|
|
* @override
|
|
* @returns {Promise<BirbSaveData|{}>}
|
|
*/
|
|
async getSaveData() {
|
|
log("Loading save data from browser extension storage");
|
|
return new Promise((resolve) => {
|
|
// @ts-expect-error
|
|
chrome.storage.sync.get([SAVE_KEY], (result) => {
|
|
resolve(result[SAVE_KEY] ?? {});
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {BirbSaveData} saveData
|
|
*/
|
|
async putSaveData(saveData) {
|
|
log("Saving data to browser extension storage");
|
|
// @ts-expect-error
|
|
chrome.storage.sync.set({ [SAVE_KEY]: saveData }, function () {
|
|
// @ts-expect-error
|
|
if (chrome.runtime.lastError) {
|
|
// @ts-expect-error
|
|
error(chrome.runtime.lastError);
|
|
} else {
|
|
log("Settings saved successfully");
|
|
}
|
|
});
|
|
}
|
|
|
|
/** @override */
|
|
resetSaveData() {
|
|
log("Resetting save data in browser extension storage");
|
|
// @ts-expect-error
|
|
chrome.storage.sync.clear();
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @returns {string}
|
|
*/
|
|
getFontStyles() {
|
|
// Use extension bundled font file
|
|
// @ts-expect-error
|
|
return getFontFaceImport(chrome.runtime.getURL('fonts/Monocraft.otf'));
|
|
}
|
|
}
|
|
|
|
export class ObsidianContext extends Context {
|
|
|
|
/**
|
|
* @override
|
|
* @returns {Promise<BirbSaveData|{}>}
|
|
*/
|
|
async getSaveData() {
|
|
return new Promise((resolve) => {
|
|
// @ts-expect-error
|
|
OBSIDIAN_PLUGIN.loadData().then((data) => {
|
|
resolve(data ?? {});
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
* @param {BirbSaveData|{}} saveData
|
|
*/
|
|
async putSaveData(saveData) {
|
|
// @ts-expect-error
|
|
await OBSIDIAN_PLUGIN.saveData(saveData);
|
|
}
|
|
|
|
/** @override */
|
|
resetSaveData() {
|
|
this.putSaveData({});
|
|
}
|
|
|
|
/** @override */
|
|
getFocusableElements() {
|
|
const elements = [
|
|
".workspace-leaf",
|
|
".cm-callout",
|
|
".HyperMD-codeblock-begin",
|
|
".status-bar",
|
|
".mobile-navbar"
|
|
];
|
|
return super.getFocusableElements().concat(elements);
|
|
}
|
|
|
|
/** @override */
|
|
getPath() {
|
|
// @ts-expect-error
|
|
const file = app.workspace.getActiveFile();
|
|
if (file && this.getActiveEditorElement()) {
|
|
return file.path;
|
|
} else {
|
|
return ROOT_PATH;
|
|
}
|
|
}
|
|
|
|
/** @override */
|
|
getActivePage() {
|
|
if (this.getPath() === ROOT_PATH) {
|
|
// Root page, use document element
|
|
return document.documentElement
|
|
}
|
|
return this.getActiveEditorElement() ?? document.documentElement;
|
|
}
|
|
|
|
/** @override */
|
|
isPathApplicable(path) {
|
|
return path === this.getPath();
|
|
}
|
|
|
|
/** @override */
|
|
areStickyNotesEnabled() {
|
|
return this.getPath() !== ROOT_PATH;
|
|
}
|
|
|
|
/** @returns {HTMLElement|null} */
|
|
getActiveEditorElement() {
|
|
// @ts-expect-error
|
|
const activeLeaf = app.workspace.activeLeaf;
|
|
const leafElement = activeLeaf?.view?.containerEl;
|
|
return leafElement?.querySelector(".cm-scroller") ?? null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} src
|
|
* @returns {string}
|
|
*/
|
|
function getFontFaceImport(src) {
|
|
return `@font-face { font-family: 'Monocraft'; src: url("${src}") format('opentype'); font-weight: normal; font-style: normal; }`;
|
|
}
|
|
|
|
/**
|
|
* Parse URL parameters into a key-value map
|
|
* @param {string} url
|
|
* @returns {Record<string, string>}
|
|
*/
|
|
function parseUrlParams(url) {
|
|
const queryString = url.split("?")[1];
|
|
if (!queryString) return {};
|
|
|
|
return queryString.split("&").reduce((params, param) => {
|
|
const [key, value] = param.split("=");
|
|
return { ...params, [key]: value };
|
|
}, {});
|
|
} |