diff --git a/build.js b/build.js index 87307da..ff3324d 100644 --- a/build.js +++ b/build.js @@ -14,7 +14,7 @@ const DIST_DIR = "./dist"; const BROWSER_MANIFEST = "./platform-specific/extension/manifest.json"; const OBSIDIAN_MANIFEST = "./platform-specific/obsidian/manifest.json"; -const USERSCRIPT_HEADER = "./platform-specific/userscript/header.txt"; +const USERSCRIPT_HEADER = "./platform-specific/userscript/header.txt"; const OBSIDIAN_WRAPPER = "./platform-specific/obsidian/wrapper.js"; const VENCORD_WRAPPER = "./platform-specific/vencord/wrapper.js"; @@ -24,8 +24,13 @@ const OBSIDIAN_DIR = DIST_DIR + "/obsidian"; const VENCORD_DIR = DIST_DIR + "/vencord"; const STYLESHEET_PATH = SRC_DIR + "/stylesheet.css"; -// const APPLICATION_ENTRY = SRC_DIR + "/application.js"; -const APPLICATION_ENTRY = SRC_DIR + "/platforms/browser.js"; + +const WEB_ENTRY = SRC_DIR + "/platforms/web.js"; +const USERSCRIPT_ENTRY = SRC_DIR + "/platforms/userscript.js"; +const BROWSER_EXTENSION_ENTRY = SRC_DIR + "/platforms/extension.js"; +const OBSIDIAN_ENTRY = SRC_DIR + "/platforms/obsidian.js"; +const VENCORD_ENTRY = SRC_DIR + "/platforms/vencord.js"; + const BUNDLED_OUTPUT = DIST_DIR + "/birb.bundled.js"; const BIRB_OUTPUT = DIST_DIR + "/birb.js"; @@ -78,128 +83,268 @@ const version = `${versionDate}`; // Disable build number for now buildCache.version = version; writeFileSync(BUILD_CACHE_PATH, JSON.stringify(buildCache), 'utf8'); +/** + * @param {string} entryPoint + * @param {boolean} [embedFont] + * @returns {Promise} + */ +async function generateCode(entryPoint, embedFont = false) { + // Bundle with rollup + const bundle = await rollup({ + input: entryPoint, + }); + + await bundle.write({ + file: BUNDLED_OUTPUT, + format: 'iife', + }); + + await bundle.close(); + + let birbJs = readFileSync(BUNDLED_OUTPUT, 'utf8'); + + // Delete bundled file + unlinkSync(BUNDLED_OUTPUT); + + // Replace version placeholder + birbJs = birbJs.replaceAll(VERSION_KEY, version); + + // Compile and insert sprite sheets + for (const spriteSheet of spriteSheets) { + const dataUri = readFileSync(spriteSheet.path, 'base64'); + birbJs = birbJs.replaceAll(spriteSheet.key, `data:image/png;base64,${dataUri}`); + } + + // Insert stylesheet + const stylesheetContent = readFileSync(STYLESHEET_PATH, 'utf8'); + birbJs = birbJs.replace(STYLESHEET_KEY, stylesheetContent); + + if (embedFont) { + // Encode font to data URI + const monocraftFontData = readFileSync(FONTS_DIR + '/Monocraft.otf', 'base64'); + const monocraftDataUri = `data:font/otf;base64,${monocraftFontData}`; + birbJs = birbJs.replaceAll(MONOCRAFT_SRC_KEY, monocraftDataUri); + } else { + birbJs = birbJs.replaceAll(MONOCRAFT_SRC_KEY, MONOCRAFT_URL); + } + return birbJs; +} + // ============================================= // Build JavaScript function // ============================================= -// Bundle with rollup -const bundle = await rollup({ - input: APPLICATION_ENTRY, -}); - -await bundle.write({ - file: BUNDLED_OUTPUT, - format: 'iife', -}); - -await bundle.close(); - -let birbJs = readFileSync(BUNDLED_OUTPUT, 'utf8'); - -// Delete bundled file -unlinkSync(BUNDLED_OUTPUT); - -// Replace version placeholder -birbJs = birbJs.replaceAll(VERSION_KEY, version); - -// Compile and insert sprite sheets -for (const spriteSheet of spriteSheets) { - const dataUri = readFileSync(spriteSheet.path, 'base64'); - birbJs = birbJs.replaceAll(spriteSheet.key, `data:image/png;base64,${dataUri}`); +async function buildWeb() { + const birbJs = await generateCode(WEB_ENTRY); + writeFileSync(BIRB_OUTPUT, birbJs); } -// Insert stylesheet -const stylesheetContent = readFileSync(STYLESHEET_PATH, 'utf8'); -birbJs = birbJs.replace(STYLESHEET_KEY, stylesheetContent).replace(MONOCRAFT_SRC_KEY, MONOCRAFT_URL); +// Bundle with rollup +// const bundle = await rollup({ +// input: APPLICATION_ENTRY, +// }); + +// await bundle.write({ +// file: BUNDLED_OUTPUT, +// format: 'iife', +// }); + +// await bundle.close(); + +// let birbJs = readFileSync(BUNDLED_OUTPUT, 'utf8'); + +// // Delete bundled file +// unlinkSync(BUNDLED_OUTPUT); + +// // Replace version placeholder +// birbJs = birbJs.replaceAll(VERSION_KEY, version); + +// // Compile and insert sprite sheets +// for (const spriteSheet of spriteSheets) { +// const dataUri = readFileSync(spriteSheet.path, 'base64'); +// birbJs = birbJs.replaceAll(spriteSheet.key, `data:image/png;base64,${dataUri}`); +// } + +// // Insert stylesheet +// const stylesheetContent = readFileSync(STYLESHEET_PATH, 'utf8'); +// birbJs = birbJs.replace(STYLESHEET_KEY, stylesheetContent).replace(MONOCRAFT_SRC_KEY, MONOCRAFT_URL); -// Write bundled JavaScript function -writeFileSync(BIRB_OUTPUT, birbJs); +// // Write bundled JavaScript function +// writeFileSync(BIRB_OUTPUT, birbJs); // ============================================= // Build userscript // ============================================= // Get userscript header -const userScriptHeader = readFileSync(USERSCRIPT_HEADER, 'utf8').replaceAll(VERSION_KEY, version); +// const userScriptHeader = readFileSync(USERSCRIPT_HEADER, 'utf8').replaceAll(VERSION_KEY, version); -mkdirSync(USERSCRIPT_DIR, { recursive: true }); -const userScript = userScriptHeader + "\n" + birbJs; -writeFileSync(USERSCRIPT_DIR + '/birb.user.js', userScript); +// mkdirSync(USERSCRIPT_DIR, { recursive: true }); +// const userScript = userScriptHeader + "\n" + birbJs; +// writeFileSync(USERSCRIPT_DIR + '/birb.user.js', userScript); + +async function buildUserscript() { + const birbJs = await generateCode(USERSCRIPT_ENTRY); + // Get userscript header + const userScriptHeader = readFileSync(USERSCRIPT_HEADER, 'utf8').replaceAll(VERSION_KEY, version); + + mkdirSync(USERSCRIPT_DIR, { recursive: true }); + const userScript = userScriptHeader + "\n" + birbJs; + writeFileSync(USERSCRIPT_DIR + '/birb.user.js', userScript); +} // ============================================= // Build browser extension // ============================================= -mkdirSync(EXTENSION_DIR, { recursive: true }); +// mkdirSync(EXTENSION_DIR, { recursive: true }); -// Copy birb.js -writeFileSync(EXTENSION_DIR + '/birb.js', birbJs); +// // Copy birb.js +// writeFileSync(EXTENSION_DIR + '/birb.js', birbJs); -// Copy manifest.json -let browserManifest = readFileSync(BROWSER_MANIFEST, 'utf8'); -browserManifest = browserManifest.replace(VERSION_KEY, version); -writeFileSync(EXTENSION_DIR + '/manifest.json', browserManifest); +// // Copy manifest.json +// let browserManifest = readFileSync(BROWSER_MANIFEST, 'utf8'); +// browserManifest = browserManifest.replace(VERSION_KEY, version); +// writeFileSync(EXTENSION_DIR + '/manifest.json', browserManifest); -// Copy icons folder -mkdirSync(EXTENSION_DIR + '/images/icons', { recursive: true }); -cpSync(IMAGES_DIR + '/icons/transparent', EXTENSION_DIR + '/images/icons/transparent', { recursive: true }); +// // Copy icons folder +// mkdirSync(EXTENSION_DIR + '/images/icons', { recursive: true }); +// cpSync(IMAGES_DIR + '/icons/transparent', EXTENSION_DIR + '/images/icons/transparent', { recursive: true }); -// Copy fonts folder -mkdirSync(EXTENSION_DIR + '/fonts', { recursive: true }); -cpSync(FONTS_DIR, EXTENSION_DIR + '/fonts', { recursive: true }); +// // Copy fonts folder +// mkdirSync(EXTENSION_DIR + '/fonts', { recursive: true }); +// cpSync(FONTS_DIR, EXTENSION_DIR + '/fonts', { recursive: true }); -// Compress extension folder into zip -const output = createWriteStream(DIST_DIR + "/extension.zip"); -const archive = archiver('zip'); +// // Compress extension folder into zip +// const output = createWriteStream(DIST_DIR + "/extension.zip"); +// const archive = archiver('zip'); -output.on('close', () => { - console.log(`Created zip file: ${archive.pointer()} total bytes`); -}); +// output.on('close', () => { +// console.log(`Created zip file: ${archive.pointer()} total bytes`); +// }); -archive.on('error', (err) => { - throw err; -}); +// archive.on('error', (err) => { +// throw err; +// }); -archive.pipe(output); -archive.directory(EXTENSION_DIR + '/', false); -archive.finalize(); +// archive.pipe(output); +// archive.directory(EXTENSION_DIR + '/', false); +// archive.finalize(); + +async function buildExtension() { + const birbJs = await generateCode(BROWSER_EXTENSION_ENTRY); + + mkdirSync(EXTENSION_DIR, { recursive: true }); + + // Copy birb.js + writeFileSync(EXTENSION_DIR + '/birb.js', birbJs); + + // Copy manifest.json + let browserManifest = readFileSync(BROWSER_MANIFEST, 'utf8'); + browserManifest = browserManifest.replace(VERSION_KEY, version); + writeFileSync(EXTENSION_DIR + '/manifest.json', browserManifest); + + // Copy icons folder + mkdirSync(EXTENSION_DIR + '/images/icons', { recursive: true }); + cpSync(IMAGES_DIR + '/icons/transparent', EXTENSION_DIR + '/images/icons/transparent', { recursive: true }); + + // Copy fonts folder + mkdirSync(EXTENSION_DIR + '/fonts', { recursive: true }); + cpSync(FONTS_DIR, EXTENSION_DIR + '/fonts', { recursive: true }); + + // Compress extension folder into zip + const output = createWriteStream(DIST_DIR + "/extension.zip"); + const archive = archiver('zip'); + + output.on('close', () => { + console.log(`Created zip file: ${archive.pointer()} total bytes`); + }); + + archive.on('error', (err) => { + throw err; + }); + + archive.pipe(output); + archive.directory(EXTENSION_DIR + '/', false); + archive.finalize(); +} // ============================================= // Build Obsidian plugin // ============================================= -mkdirSync(OBSIDIAN_DIR, { recursive: true }); +// mkdirSync(OBSIDIAN_DIR, { recursive: true }); -// Wrap birb.js with plugin boilerplate -let obsidianPlugin = readFileSync(OBSIDIAN_WRAPPER, 'utf8').replace(VERSION_KEY, version).replace(CODE_KEY, birbJs); +// // Wrap birb.js with plugin boilerplate +// let obsidianPlugin = readFileSync(OBSIDIAN_WRAPPER, 'utf8').replace(VERSION_KEY, version).replace(CODE_KEY, birbJs); -// Encode font to data URI since Obsidian plugins can't have external font files -const monocraftFontData = readFileSync(FONTS_DIR + '/Monocraft.otf', 'base64'); -const monocraftDataUri = `data:font/otf;base64,${monocraftFontData}`; -obsidianPlugin = obsidianPlugin.replace(MONOCRAFT_URL, monocraftDataUri); +// // Encode font to data URI since Obsidian plugins can't have external font files +// const monocraftFontData = readFileSync(FONTS_DIR + '/Monocraft.otf', 'base64'); +// const monocraftDataUri = `data:font/otf;base64,${monocraftFontData}`; +// obsidianPlugin = obsidianPlugin.replace(MONOCRAFT_URL, monocraftDataUri); -// Create main.js with plugin code -writeFileSync(OBSIDIAN_DIR + '/main.js', obsidianPlugin); +// // Create main.js with plugin code +// writeFileSync(OBSIDIAN_DIR + '/main.js', obsidianPlugin); -// Copy manifest.json -let obsidianManifest = readFileSync(OBSIDIAN_MANIFEST, 'utf8'); -obsidianManifest = obsidianManifest.replace(/"version":\s*".*"/, `"version": "${version}"`); -writeFileSync(OBSIDIAN_DIR + '/manifest.json', obsidianManifest); +// // Copy manifest.json +// let obsidianManifest = readFileSync(OBSIDIAN_MANIFEST, 'utf8'); +// obsidianManifest = obsidianManifest.replace(/"version":\s*".*"/, `"version": "${version}"`); +// writeFileSync(OBSIDIAN_DIR + '/manifest.json', obsidianManifest); + +async function buildObsidian() { + const birbJs = await generateCode(OBSIDIAN_ENTRY, true); + + mkdirSync(OBSIDIAN_DIR, { recursive: true }); + + // Wrap birb.js with plugin boilerplate + let obsidianPlugin = readFileSync(OBSIDIAN_WRAPPER, 'utf8').replace(VERSION_KEY, version).replace(CODE_KEY, birbJs); + + // Create main.js with plugin code + writeFileSync(OBSIDIAN_DIR + '/main.js', obsidianPlugin); + + // Copy manifest.json + let obsidianManifest = readFileSync(OBSIDIAN_MANIFEST, 'utf8'); + obsidianManifest = obsidianManifest.replace(/"version":\s*".*"/, `"version": "${version}"`); + writeFileSync(OBSIDIAN_DIR + '/manifest.json', obsidianManifest); +} // ============================================= // Build Vencord plugin // ============================================= -mkdirSync(VENCORD_DIR, { recursive: true }); +// mkdirSync(VENCORD_DIR, { recursive: true }); -// Wrap birb.js with plugin boilerplate -let vencordPlugin = readFileSync(VENCORD_WRAPPER, 'utf8').replace(CODE_KEY, birbJs); +// // Wrap birb.js with plugin boilerplate +// let vencordPlugin = readFileSync(VENCORD_WRAPPER, 'utf8').replace(CODE_KEY, birbJs); -// Set context to "local" -vencordPlugin = vencordPlugin.replace(CONTEXT_KEY, "local"); +// // Set context to "local" +// vencordPlugin = vencordPlugin.replace(CONTEXT_KEY, "local"); -// Create exported birb function -writeFileSync(VENCORD_DIR + '/birb.export.js', vencordPlugin); +// // Create exported birb function +// writeFileSync(VENCORD_DIR + '/birb.export.js', vencordPlugin); -console.log(`Build complete: ${version}`); \ No newline at end of file +// console.log(`Build complete: ${version}`); + +async function buildVencord() { + const birbJs = await generateCode(VENCORD_ENTRY); + + mkdirSync(VENCORD_DIR, { recursive: true }); + + // Wrap birb.js with plugin boilerplate + let vencordPlugin = readFileSync(VENCORD_WRAPPER, 'utf8').replace(CODE_KEY, birbJs); + + // Set context to "local" + vencordPlugin = vencordPlugin.replace(CONTEXT_KEY, "local"); + + // Create exported birb function + writeFileSync(VENCORD_DIR + '/birb.export.js', vencordPlugin); +} + +console.log("Starting build..."); + +await buildWeb(); +await buildUserscript(); +await buildExtension(); +await buildObsidian(); +await buildVencord(); \ No newline at end of file diff --git a/dist/birb.js b/dist/birb.js index effd5d8..4af8e0b 100644 --- a/dist/birb.js +++ b/dist/birb.js @@ -1,4 +1,4 @@ -(function (exports) { +(function () { 'use strict'; const Directions = { @@ -226,139 +226,6 @@ return document.documentElement.clientHeight; } - const SAVE_KEY = "birbSaveData"; - - /** - * @typedef {import('./application.js').BirbSaveData} BirbSaveData - */ - - /** - * @abstract - */ - class Context { - - /** - * @abstract - * @returns {boolean} Whether this context is applicable - */ - // isContextActive() { - // throw new Error("Method not implemented"); - // } - - /** - * @abstract - * @returns {Promise} - */ - 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; - } - } - - /** - * Determines and returns the current context - * @returns {Context} - */ - // export function getContext() { - // if (CONTEXTS_BY_KEY[SET_CONTEXT]) { - // return new CONTEXTS_BY_KEY[SET_CONTEXT](); - // } - // for (const context of contextProcessingOrder) { - // if (context.isContextActive()) { - // return context; - // } - // } - // error("No applicable context found"); - // // return new LocalContext(); - // return null; - // } - - /** - * Parse URL parameters into a key-value map - * @param {string} url - * @returns {Record} - */ - 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 }; - }, {}); - } - /** Indicators for parts of the base bird sprite sheet */ const Sprite = { THEME_HIGHLIGHT: "theme-highlight", @@ -990,6 +857,140 @@ } } + const SAVE_KEY = "birbSaveData"; + + /** + * @typedef {import('./application.js').BirbSaveData} BirbSaveData + */ + + /** + * @abstract + */ + class Context { + + /** + * @abstract + * @returns {Promise} + */ + 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; + } + } + + class LocalContext extends Context { + + /** + * @override + * @returns {Promise} + */ + 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); + } + } + + /** + * Parse URL parameters into a key-value map + * @param {string} url + * @returns {Record} + */ + 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 }; + }, {}); + } + /** * @typedef {Object} SavedStickyNote * @property {string} id @@ -2614,41 +2615,6 @@ }); } - /** - * @typedef {import('../application.js').BirbSaveData} BirbSaveData - */ - - class LocalContext extends Context { - - /** - * @override - * @returns {Promise} - */ - 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); - } - } - initializeApplication(new LocalContext()); - exports.LocalContext = LocalContext; - - return exports; - -})({}); +})(); diff --git a/dist/extension.zip b/dist/extension.zip index 942b312..9acdc60 100644 Binary files a/dist/extension.zip and b/dist/extension.zip differ diff --git a/dist/extension/birb.js b/dist/extension/birb.js index effd5d8..98b5112 100644 --- a/dist/extension/birb.js +++ b/dist/extension/birb.js @@ -1,4 +1,4 @@ -(function (exports) { +(function () { 'use strict'; const Directions = { @@ -226,139 +226,6 @@ return document.documentElement.clientHeight; } - const SAVE_KEY = "birbSaveData"; - - /** - * @typedef {import('./application.js').BirbSaveData} BirbSaveData - */ - - /** - * @abstract - */ - class Context { - - /** - * @abstract - * @returns {boolean} Whether this context is applicable - */ - // isContextActive() { - // throw new Error("Method not implemented"); - // } - - /** - * @abstract - * @returns {Promise} - */ - 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; - } - } - - /** - * Determines and returns the current context - * @returns {Context} - */ - // export function getContext() { - // if (CONTEXTS_BY_KEY[SET_CONTEXT]) { - // return new CONTEXTS_BY_KEY[SET_CONTEXT](); - // } - // for (const context of contextProcessingOrder) { - // if (context.isContextActive()) { - // return context; - // } - // } - // error("No applicable context found"); - // // return new LocalContext(); - // return null; - // } - - /** - * Parse URL parameters into a key-value map - * @param {string} url - * @returns {Record} - */ - 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 }; - }, {}); - } - /** Indicators for parts of the base bird sprite sheet */ const Sprite = { THEME_HIGHLIGHT: "theme-highlight", @@ -990,6 +857,155 @@ } } + const SAVE_KEY = "birbSaveData"; + + /** + * @typedef {import('./application.js').BirbSaveData} BirbSaveData + */ + + /** + * @abstract + */ + class Context { + + /** + * @abstract + * @returns {Promise} + */ + 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; + } + } + + class BrowserExtensionContext extends Context { + + /** + * @override + * @returns {Promise} + */ + 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 + console.error(chrome.runtime.lastError); + } else { + console.log("Settings saved successfully"); + } + }); + } + + /** @override */ + resetSaveData() { + log("Resetting save data in browser extension storage"); + // @ts-expect-error + chrome.storage.sync.clear(); + } + } + + /** + * Parse URL parameters into a key-value map + * @param {string} url + * @returns {Record} + */ + 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 }; + }, {}); + } + /** * @typedef {Object} SavedStickyNote * @property {string} id @@ -2614,41 +2630,6 @@ }); } - /** - * @typedef {import('../application.js').BirbSaveData} BirbSaveData - */ + initializeApplication(new BrowserExtensionContext()); - class LocalContext extends Context { - - /** - * @override - * @returns {Promise} - */ - 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); - } - } - - initializeApplication(new LocalContext()); - - exports.LocalContext = LocalContext; - - return exports; - -})({}); +})(); diff --git a/dist/obsidian/main.js b/dist/obsidian/main.js index dcf8a32..21fbfde 100644 --- a/dist/obsidian/main.js +++ b/dist/obsidian/main.js @@ -3,7 +3,7 @@ module.exports = class PocketBird extends Plugin { onload() { console.log("Loading Pocket Bird version 2025.11.16..."); const OBSIDIAN_PLUGIN = this; - (function (exports) { + (function () { 'use strict'; const Directions = { @@ -231,139 +231,6 @@ module.exports = class PocketBird extends Plugin { return document.documentElement.clientHeight; } - const SAVE_KEY = "birbSaveData"; - - /** - * @typedef {import('./application.js').BirbSaveData} BirbSaveData - */ - - /** - * @abstract - */ - class Context { - - /** - * @abstract - * @returns {boolean} Whether this context is applicable - */ - // isContextActive() { - // throw new Error("Method not implemented"); - // } - - /** - * @abstract - * @returns {Promise} - */ - 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; - } - } - - /** - * Determines and returns the current context - * @returns {Context} - */ - // export function getContext() { - // if (CONTEXTS_BY_KEY[SET_CONTEXT]) { - // return new CONTEXTS_BY_KEY[SET_CONTEXT](); - // } - // for (const context of contextProcessingOrder) { - // if (context.isContextActive()) { - // return context; - // } - // } - // error("No applicable context found"); - // // return new LocalContext(); - // return null; - // } - - /** - * Parse URL parameters into a key-value map - * @param {string} url - * @returns {Record} - */ - 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 }; - }, {}); - } - /** Indicators for parts of the base bird sprite sheet */ const Sprite = { THEME_HIGHLIGHT: "theme-highlight", @@ -995,6 +862,193 @@ module.exports = class PocketBird extends Plugin { } } + const ROOT_PATH = ""; + + /** + * @typedef {import('./application.js').BirbSaveData} BirbSaveData + */ + + /** + * @abstract + */ + class Context { + + /** + * @abstract + * @returns {Promise} + */ + 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; + } + } + + class ObsidianContext extends Context { + + /** + * @override + * @returns {Promise} + */ + 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; + } + } + + /** + * Parse URL parameters into a key-value map + * @param {string} url + * @returns {Record} + */ + 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 }; + }, {}); + } + /** * @typedef {Object} SavedStickyNote * @property {string} id @@ -2619,44 +2673,9 @@ module.exports = class PocketBird extends Plugin { }); } - /** - * @typedef {import('../application.js').BirbSaveData} BirbSaveData - */ + initializeApplication(new ObsidianContext()); - class LocalContext extends Context { - - /** - * @override - * @returns {Promise} - */ - 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); - } - } - - initializeApplication(new LocalContext()); - - exports.LocalContext = LocalContext; - - return exports; - -})({}); +})(); console.log("Pocket Bird loaded!"); } diff --git a/dist/userscript/birb.user.js b/dist/userscript/birb.user.js index 285dc2e..80f2cf2 100644 --- a/dist/userscript/birb.user.js +++ b/dist/userscript/birb.user.js @@ -12,7 +12,7 @@ // @grant GM_deleteValue // ==/UserScript== -(function (exports) { +(function () { 'use strict'; const Directions = { @@ -240,139 +240,6 @@ return document.documentElement.clientHeight; } - const SAVE_KEY = "birbSaveData"; - - /** - * @typedef {import('./application.js').BirbSaveData} BirbSaveData - */ - - /** - * @abstract - */ - class Context { - - /** - * @abstract - * @returns {boolean} Whether this context is applicable - */ - // isContextActive() { - // throw new Error("Method not implemented"); - // } - - /** - * @abstract - * @returns {Promise} - */ - 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; - } - } - - /** - * Determines and returns the current context - * @returns {Context} - */ - // export function getContext() { - // if (CONTEXTS_BY_KEY[SET_CONTEXT]) { - // return new CONTEXTS_BY_KEY[SET_CONTEXT](); - // } - // for (const context of contextProcessingOrder) { - // if (context.isContextActive()) { - // return context; - // } - // } - // error("No applicable context found"); - // // return new LocalContext(); - // return null; - // } - - /** - * Parse URL parameters into a key-value map - * @param {string} url - * @returns {Record} - */ - 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 }; - }, {}); - } - /** Indicators for parts of the base bird sprite sheet */ const Sprite = { THEME_HIGHLIGHT: "theme-highlight", @@ -1004,6 +871,146 @@ } } + const SAVE_KEY = "birbSaveData"; + + /** + * @typedef {import('./application.js').BirbSaveData} BirbSaveData + */ + + /** + * @abstract + */ + class Context { + + /** + * @abstract + * @returns {Promise} + */ + 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; + } + } + + class UserScriptContext extends Context { + + /** + * @override + * @returns {Promise} + */ + 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); + } + } + + /** + * Parse URL parameters into a key-value map + * @param {string} url + * @returns {Record} + */ + 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 }; + }, {}); + } + /** * @typedef {Object} SavedStickyNote * @property {string} id @@ -2628,41 +2635,6 @@ }); } - /** - * @typedef {import('../application.js').BirbSaveData} BirbSaveData - */ + initializeApplication(new UserScriptContext()); - class LocalContext extends Context { - - /** - * @override - * @returns {Promise} - */ - 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); - } - } - - initializeApplication(new LocalContext()); - - exports.LocalContext = LocalContext; - - return exports; - -})({}); +})(); diff --git a/dist/vencord/birb.export.js b/dist/vencord/birb.export.js index fbf5b28..9680483 100644 --- a/dist/vencord/birb.export.js +++ b/dist/vencord/birb.export.js @@ -1,5 +1,5 @@ export const Birb = () => { -(function (exports) { +(function () { 'use strict'; const Directions = { @@ -227,139 +227,6 @@ export const Birb = () => { return document.documentElement.clientHeight; } - const SAVE_KEY = "birbSaveData"; - - /** - * @typedef {import('./application.js').BirbSaveData} BirbSaveData - */ - - /** - * @abstract - */ - class Context { - - /** - * @abstract - * @returns {boolean} Whether this context is applicable - */ - // isContextActive() { - // throw new Error("Method not implemented"); - // } - - /** - * @abstract - * @returns {Promise} - */ - 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; - } - } - - /** - * Determines and returns the current context - * @returns {Context} - */ - // export function getContext() { - // if (CONTEXTS_BY_KEY[SET_CONTEXT]) { - // return new CONTEXTS_BY_KEY[SET_CONTEXT](); - // } - // for (const context of contextProcessingOrder) { - // if (context.isContextActive()) { - // return context; - // } - // } - // error("No applicable context found"); - // // return new LocalContext(); - // return null; - // } - - /** - * Parse URL parameters into a key-value map - * @param {string} url - * @returns {Record} - */ - 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 }; - }, {}); - } - /** Indicators for parts of the base bird sprite sheet */ const Sprite = { THEME_HIGHLIGHT: "theme-highlight", @@ -991,6 +858,140 @@ export const Birb = () => { } } + const SAVE_KEY = "birbSaveData"; + + /** + * @typedef {import('./application.js').BirbSaveData} BirbSaveData + */ + + /** + * @abstract + */ + class Context { + + /** + * @abstract + * @returns {Promise} + */ + 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; + } + } + + class LocalContext extends Context { + + /** + * @override + * @returns {Promise} + */ + 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); + } + } + + /** + * Parse URL parameters into a key-value map + * @param {string} url + * @returns {Record} + */ + 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 }; + }, {}); + } + /** * @typedef {Object} SavedStickyNote * @property {string} id @@ -2615,43 +2616,8 @@ export const Birb = () => { }); } - /** - * @typedef {import('../application.js').BirbSaveData} BirbSaveData - */ - - class LocalContext extends Context { - - /** - * @override - * @returns {Promise} - */ - 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); - } - } - initializeApplication(new LocalContext()); - exports.LocalContext = LocalContext; - - return exports; - -})({}); +})(); }; \ No newline at end of file diff --git a/src/context.js b/src/context.js index 8187f5f..d52f166 100644 --- a/src/context.js +++ b/src/context.js @@ -13,14 +13,6 @@ const SET_CONTEXT = "__CONTEXT__" */ export class Context { - /** - * @abstract - * @returns {boolean} Whether this context is applicable - */ - // isContextActive() { - // throw new Error("Method not implemented"); - // } - /** * @abstract * @returns {Promise} @@ -102,6 +94,33 @@ export class Context { } } +export class LocalContext extends Context { + + /** + * @override + * @returns {Promise} + */ + 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 { /** @@ -135,7 +154,7 @@ export class UserScriptContext extends Context { } } -class BrowserExtensionContext extends Context { +export class BrowserExtensionContext extends Context { /** * @override @@ -257,37 +276,6 @@ export class ObsidianContext extends Context { } } -const contextProcessingOrder = [ - new UserScriptContext(), - new ObsidianContext(), - new BrowserExtensionContext(), -]; - -const CONTEXTS_BY_KEY = { - // "local": LocalContext, - "userscript": UserScriptContext, - "browser-extension": BrowserExtensionContext, - "obsidian": ObsidianContext -}; - -/** - * Determines and returns the current context - * @returns {Context} - */ -// export function getContext() { -// if (CONTEXTS_BY_KEY[SET_CONTEXT]) { -// return new CONTEXTS_BY_KEY[SET_CONTEXT](); -// } -// for (const context of contextProcessingOrder) { -// if (context.isContextActive()) { -// return context; -// } -// } -// error("No applicable context found"); -// // return new LocalContext(); -// return null; -// } - /** * Parse URL parameters into a key-value map * @param {string} url diff --git a/src/platforms/browser.js b/src/platforms/browser.js deleted file mode 100644 index 1c73403..0000000 --- a/src/platforms/browser.js +++ /dev/null @@ -1,36 +0,0 @@ -import { Context, SAVE_KEY } from "../context.js"; -import { log } from "../shared.js"; -import { initializeApplication } from "../application"; - -/** - * @typedef {import('../application.js').BirbSaveData} BirbSaveData - */ - -export class LocalContext extends Context { - - /** - * @override - * @returns {Promise} - */ - 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); - } -} - -initializeApplication(new LocalContext()); \ No newline at end of file diff --git a/src/platforms/extension.js b/src/platforms/extension.js new file mode 100644 index 0000000..e92e3a5 --- /dev/null +++ b/src/platforms/extension.js @@ -0,0 +1,4 @@ +import { initializeApplication } from "../application.js"; +import { BrowserExtensionContext } from "../context.js"; + +initializeApplication(new BrowserExtensionContext()); \ No newline at end of file diff --git a/src/platforms/obsidian.js b/src/platforms/obsidian.js new file mode 100644 index 0000000..26bd0a9 --- /dev/null +++ b/src/platforms/obsidian.js @@ -0,0 +1,4 @@ +import { initializeApplication } from "../application.js"; +import { ObsidianContext } from "../context.js"; + +initializeApplication(new ObsidianContext()); \ No newline at end of file diff --git a/src/platforms/userscript.js b/src/platforms/userscript.js new file mode 100644 index 0000000..9a757b7 --- /dev/null +++ b/src/platforms/userscript.js @@ -0,0 +1,4 @@ +import { initializeApplication } from "../application.js"; +import { UserScriptContext } from "../context.js"; + +initializeApplication(new UserScriptContext()); \ No newline at end of file diff --git a/src/platforms/vencord.js b/src/platforms/vencord.js new file mode 100644 index 0000000..c4813ae --- /dev/null +++ b/src/platforms/vencord.js @@ -0,0 +1,4 @@ +import { initializeApplication } from "../application.js"; +import { LocalContext } from "../context.js"; + +initializeApplication(new LocalContext()); \ No newline at end of file diff --git a/src/platforms/web.js b/src/platforms/web.js new file mode 100644 index 0000000..c4813ae --- /dev/null +++ b/src/platforms/web.js @@ -0,0 +1,4 @@ +import { initializeApplication } from "../application.js"; +import { LocalContext } from "../context.js"; + +initializeApplication(new LocalContext()); \ No newline at end of file