Add separate entry points

This commit is contained in:
Idrees Hassan
2025-11-16 10:25:57 -05:00
parent 6ee9efd5a8
commit c927ce23e4
14 changed files with 1032 additions and 1011 deletions

309
build.js
View File

@@ -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<string>}
*/
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}`);
// 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();

306
dist/birb.js vendored
View File

@@ -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<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;
}
}
/**
* 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<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 };
}, {});
}
/** 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<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;
}
}
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);
}
}
/**
* 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 };
}, {});
}
/**
* @typedef {Object} SavedStickyNote
* @property {string} id
@@ -2614,41 +2615,6 @@
});
}
/**
* @typedef {import('../application.js').BirbSaveData} BirbSaveData
*/
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);
}
}
initializeApplication(new LocalContext());
exports.LocalContext = LocalContext;
return exports;
})({});
})();

BIN
dist/extension.zip vendored

Binary file not shown.

323
dist/extension/birb.js vendored
View File

@@ -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<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;
}
}
/**
* 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<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 };
}, {});
}
/** 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<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;
}
}
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
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<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 };
}, {});
}
/**
* @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<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);
}
}
initializeApplication(new LocalContext());
exports.LocalContext = LocalContext;
return exports;
})({});
})();

361
dist/obsidian/main.js vendored
View File

@@ -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<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;
}
}
/**
* 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<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 };
}, {});
}
/** 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<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;
}
}
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;
}
}
/**
* 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 };
}, {});
}
/**
* @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<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);
}
}
initializeApplication(new LocalContext());
exports.LocalContext = LocalContext;
return exports;
})({});
})();
console.log("Pocket Bird loaded!");
}

View File

@@ -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<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;
}
}
/**
* 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<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 };
}, {});
}
/** 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<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;
}
}
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);
}
}
/**
* 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 };
}, {});
}
/**
* @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<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);
}
}
initializeApplication(new LocalContext());
exports.LocalContext = LocalContext;
return exports;
})({});
})();

View File

@@ -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<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;
}
}
/**
* 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<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 };
}, {});
}
/** 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<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;
}
}
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);
}
}
/**
* 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 };
}, {});
}
/**
* @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<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);
}
}
initializeApplication(new LocalContext());
exports.LocalContext = LocalContext;
return exports;
})({});
})();
};

View File

@@ -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<BirbSaveData|{}>}
@@ -102,6 +94,33 @@ export class Context {
}
}
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 {
/**
@@ -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

View File

@@ -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<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);
}
}
initializeApplication(new LocalContext());

View File

@@ -0,0 +1,4 @@
import { initializeApplication } from "../application.js";
import { BrowserExtensionContext } from "../context.js";
initializeApplication(new BrowserExtensionContext());

View File

@@ -0,0 +1,4 @@
import { initializeApplication } from "../application.js";
import { ObsidianContext } from "../context.js";
initializeApplication(new ObsidianContext());

View File

@@ -0,0 +1,4 @@
import { initializeApplication } from "../application.js";
import { UserScriptContext } from "../context.js";
initializeApplication(new UserScriptContext());

4
src/platforms/vencord.js Normal file
View File

@@ -0,0 +1,4 @@
import { initializeApplication } from "../application.js";
import { LocalContext } from "../context.js";
initializeApplication(new LocalContext());

4
src/platforms/web.js Normal file
View File

@@ -0,0 +1,4 @@
import { initializeApplication } from "../application.js";
import { LocalContext } from "../context.js";
initializeApplication(new LocalContext());