mirror of
https://github.com/NohamR/Pocket-Bird.git
synced 2026-05-24 19:59:36 +00:00
Update font handling to better bundle fonts
This commit is contained in:
24
build.js
24
build.js
@@ -35,7 +35,7 @@ const MONOCRAFT_URL = "https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40
|
|||||||
|
|
||||||
const VERSION_KEY = "__VERSION__";
|
const VERSION_KEY = "__VERSION__";
|
||||||
const STYLESHEET_KEY = "___STYLESHEET___";
|
const STYLESHEET_KEY = "___STYLESHEET___";
|
||||||
const MONOCRAFT_SRC_KEY = "__MONOCRAFT_SRC__";
|
const MONOCRAFT_URL_KEY = "__MONOCRAFT_URL__";
|
||||||
const CODE_KEY = "__CODE__";
|
const CODE_KEY = "__CODE__";
|
||||||
|
|
||||||
const spriteSheets = [
|
const spriteSheets = [
|
||||||
@@ -85,7 +85,9 @@ writeFileSync(BUILD_CACHE_PATH, JSON.stringify(buildCache), 'utf8');
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} entryPoint
|
* @param {string} entryPoint
|
||||||
* @param {boolean} [embedFont]
|
* @param {boolean} [embedFont] When true, the Monocraft font is base64-encoded
|
||||||
|
* and substituted into the __MONOCRAFT_FONT_FACE__ placeholder so the
|
||||||
|
* build is fully self-contained (used for Obsidian).
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string>}
|
||||||
*/
|
*/
|
||||||
async function generateCode(entryPoint, embedFont = false) {
|
async function generateCode(entryPoint, embedFont = false) {
|
||||||
@@ -109,6 +111,15 @@ async function generateCode(entryPoint, embedFont = false) {
|
|||||||
// Replace version placeholder
|
// Replace version placeholder
|
||||||
birbJs = birbJs.replaceAll(VERSION_KEY, version);
|
birbJs = birbJs.replaceAll(VERSION_KEY, version);
|
||||||
|
|
||||||
|
// Replace CDN font URL placeholder
|
||||||
|
if (embedFont) {
|
||||||
|
// Embed as a base64 data URI so the build works fully offline.
|
||||||
|
const monocraftFontData = readFileSync(FONTS_DIR + '/Monocraft.otf', 'base64');
|
||||||
|
birbJs = birbJs.replaceAll(MONOCRAFT_URL_KEY, `data:font/otf;base64,${monocraftFontData}`);
|
||||||
|
} else {
|
||||||
|
birbJs = birbJs.replaceAll(MONOCRAFT_URL_KEY, MONOCRAFT_URL);
|
||||||
|
}
|
||||||
|
|
||||||
// Compile and insert sprite sheets
|
// Compile and insert sprite sheets
|
||||||
for (const spriteSheet of spriteSheets) {
|
for (const spriteSheet of spriteSheets) {
|
||||||
const dataUri = readFileSync(spriteSheet.path, 'base64');
|
const dataUri = readFileSync(spriteSheet.path, 'base64');
|
||||||
@@ -119,14 +130,6 @@ async function generateCode(entryPoint, embedFont = false) {
|
|||||||
const stylesheetContent = readFileSync(STYLESHEET_PATH, 'utf8');
|
const stylesheetContent = readFileSync(STYLESHEET_PATH, 'utf8');
|
||||||
birbJs = birbJs.replace(STYLESHEET_KEY, stylesheetContent);
|
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;
|
return birbJs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,6 +190,7 @@ async function buildExtension() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function buildObsidian() {
|
async function buildObsidian() {
|
||||||
|
// embedFont=true: bakes the font as base64 so the plugin works fully offline.
|
||||||
const birbJs = await generateCode(OBSIDIAN_ENTRY, true);
|
const birbJs = await generateCode(OBSIDIAN_ENTRY, true);
|
||||||
|
|
||||||
mkdirSync(OBSIDIAN_DIR, { recursive: true });
|
mkdirSync(OBSIDIAN_DIR, { recursive: true });
|
||||||
|
|||||||
BIN
dist/extension.zip
vendored
BIN
dist/extension.zip
vendored
Binary file not shown.
363
dist/extension/birb.js
vendored
363
dist/extension/birb.js
vendored
@@ -1,12 +1,188 @@
|
|||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const SAVE_KEY = "birbSaveData";
|
||||||
|
const MONOCRAFT_URL = "https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getFontStyles() {
|
||||||
|
return getFontFaceImport(MONOCRAFT_URL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BrowserExtensionContext extends Context {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
* @returns {Promise<BirbSaveData|{}>}
|
||||||
|
*/
|
||||||
|
async getSaveData() {
|
||||||
|
log("Loading save data from browser extension storage");
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
// @ts-expect-error
|
||||||
|
chrome.storage.sync.get([SAVE_KEY], (result) => {
|
||||||
|
resolve(result[SAVE_KEY] ?? {});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
* @param {BirbSaveData} saveData
|
||||||
|
*/
|
||||||
|
async putSaveData(saveData) {
|
||||||
|
log("Saving data to browser extension storage");
|
||||||
|
// @ts-expect-error
|
||||||
|
chrome.storage.sync.set({ [SAVE_KEY]: saveData }, function () {
|
||||||
|
// @ts-expect-error
|
||||||
|
if (chrome.runtime.lastError) {
|
||||||
|
// @ts-expect-error
|
||||||
|
error(chrome.runtime.lastError);
|
||||||
|
} else {
|
||||||
|
log("Settings saved successfully");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
resetSaveData() {
|
||||||
|
log("Resetting save data in browser extension storage");
|
||||||
|
// @ts-expect-error
|
||||||
|
chrome.storage.sync.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getFontStyles() {
|
||||||
|
// Use extension bundled font file
|
||||||
|
// @ts-expect-error
|
||||||
|
return getFontFaceImport(chrome.runtime.getURL('fonts/Monocraft.otf'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} src
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function getFontFaceImport(src) {
|
||||||
|
return `@font-face { font-family: 'Monocraft'; src: url("${src}") format('opentype'); font-weight: normal; font-style: normal; }`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse URL parameters into a key-value map
|
||||||
|
* @param {string} url
|
||||||
|
* @returns {Record<string, string>}
|
||||||
|
*/
|
||||||
|
function parseUrlParams(url) {
|
||||||
|
const queryString = url.split("?")[1];
|
||||||
|
if (!queryString) return {};
|
||||||
|
|
||||||
|
return queryString.split("&").reduce((params, param) => {
|
||||||
|
const [key, value] = param.split("=");
|
||||||
|
return { ...params, [key]: value };
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
const Directions = {
|
const Directions = {
|
||||||
LEFT: -1,
|
LEFT: -1,
|
||||||
RIGHT: 1,
|
RIGHT: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let debugMode = location.hostname === "127.0.0.1";
|
let debugMode = location.hostname === "127.0.0.1";
|
||||||
|
/** @type {Context|null} */
|
||||||
let context = null;
|
let context = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,6 +199,9 @@
|
|||||||
debugMode = value;
|
debugMode = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Context} The specific context for this platform
|
||||||
|
*/
|
||||||
function getContext() {
|
function getContext() {
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error("Context requested before being set");
|
throw new Error("Context requested before being set");
|
||||||
@@ -30,6 +209,9 @@
|
|||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Context} newContext
|
||||||
|
*/
|
||||||
function setContext(newContext) {
|
function setContext(newContext) {
|
||||||
context = newContext;
|
context = newContext;
|
||||||
}
|
}
|
||||||
@@ -1163,155 +1345,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
error(chrome.runtime.lastError);
|
|
||||||
} else {
|
|
||||||
log("Settings saved successfully");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
resetSaveData() {
|
|
||||||
log("Resetting save data in browser extension storage");
|
|
||||||
// @ts-expect-error
|
|
||||||
chrome.storage.sync.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* @typedef {Object} SavedStickyNote
|
||||||
* @property {string} id
|
* @property {string} id
|
||||||
@@ -1643,14 +1676,7 @@
|
|||||||
const WINDOW_PIXEL_SIZE = CANVAS_PIXEL_SIZE * BIRB_CSS_SCALE;
|
const WINDOW_PIXEL_SIZE = CANVAS_PIXEL_SIZE * BIRB_CSS_SCALE;
|
||||||
|
|
||||||
// Build-time assets
|
// Build-time assets
|
||||||
const STYLESHEET = `@font-face {
|
const STYLESHEET = `:root {
|
||||||
font-family: 'Monocraft';
|
|
||||||
src: url("https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf") format('opentype');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--birb-border-size: 2px;
|
--birb-border-size: 2px;
|
||||||
--birb-neg-border-size: calc(var(--birb-border-size) * -1);
|
--birb-neg-border-size: calc(var(--birb-border-size) * -1);
|
||||||
--birb-double-border-size: calc(var(--birb-border-size) * 2);
|
--birb-double-border-size: calc(var(--birb-border-size) * 2);
|
||||||
@@ -2174,11 +2200,9 @@
|
|||||||
}),
|
}),
|
||||||
new Separator(),
|
new Separator(),
|
||||||
new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }),
|
new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }),
|
||||||
new MenuItem("2026.1.25", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.1.25"); }, false),
|
new MenuItem("2026.3.8", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.8"); }, false),
|
||||||
];
|
];
|
||||||
|
|
||||||
const styleElement = document.createElement("style");
|
|
||||||
|
|
||||||
/** @type {Birb} */
|
/** @type {Birb} */
|
||||||
let birb;
|
let birb;
|
||||||
|
|
||||||
@@ -2304,8 +2328,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onLoad() {
|
function onLoad() {
|
||||||
styleElement.textContent = STYLESHEET;
|
injectStyleElement(getContext().getFontStyles());
|
||||||
document.head.appendChild(styleElement);
|
injectStyleElement(STYLESHEET);
|
||||||
|
|
||||||
birb = new Birb(BIRB_CSS_SCALE, CANVAS_PIXEL_SIZE, SPRITE_SHEET, SPRITE_WIDTH, SPRITE_HEIGHT, HATS_SPRITE_SHEET);
|
birb = new Birb(BIRB_CSS_SCALE, CANVAS_PIXEL_SIZE, SPRITE_SHEET, SPRITE_WIDTH, SPRITE_HEIGHT, HATS_SPRITE_SHEET);
|
||||||
birb.setAnimation(Animations.BOB);
|
birb.setAnimation(Animations.BOB);
|
||||||
@@ -2459,6 +2483,18 @@
|
|||||||
birb.setY(birdY);
|
birb.setY(birdY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string|null} stylesheetContents
|
||||||
|
*/
|
||||||
|
function injectStyleElement(stylesheetContents) {
|
||||||
|
if (!stylesheetContents) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const element = document.createElement("style");
|
||||||
|
element.textContent = stylesheetContents;
|
||||||
|
document.head.appendChild(element);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {StickyNote} stickyNote
|
* @param {StickyNote} stickyNote
|
||||||
*/
|
*/
|
||||||
@@ -2968,8 +3004,7 @@
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
/** @type {HTMLElement[]} */
|
const largeElements = /** @type {HTMLElement[]} */ (Array.from(visible).filter((img) => img instanceof HTMLElement && img !== focusedElement && img.offsetWidth >= MIN_FOCUS_ELEMENT_WIDTH));
|
||||||
const largeElements = Array.from(visible).filter((img) => img instanceof HTMLElement && img !== focusedElement && img.offsetWidth >= MIN_FOCUS_ELEMENT_WIDTH);
|
|
||||||
const nonFixedElements = largeElements.filter((el) => {
|
const nonFixedElements = largeElements.filter((el) => {
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
2
dist/extension/manifest.json
vendored
2
dist/extension/manifest.json
vendored
@@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "Pocket Bird",
|
"name": "Pocket Bird",
|
||||||
"description": "It's a pet bird in your browser, what more could you want?",
|
"description": "It's a pet bird in your browser, what more could you want?",
|
||||||
"version": "2026.1.25",
|
"version": "2026.3.8",
|
||||||
"homepage_url": "https://idreesinc.com",
|
"homepage_url": "https://idreesinc.com",
|
||||||
"icons": {
|
"icons": {
|
||||||
"48": "images/icons/transparent/48x48x1.png",
|
"48": "images/icons/transparent/48x48x1.png",
|
||||||
|
|||||||
431
dist/obsidian/main.js
vendored
431
dist/obsidian/main.js
vendored
File diff suppressed because one or more lines are too long
2
dist/obsidian/manifest.json
vendored
2
dist/obsidian/manifest.json
vendored
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "pocket-bird",
|
"id": "pocket-bird",
|
||||||
"name": "Pocket Bird",
|
"name": "Pocket Bird",
|
||||||
"version": "2026.1.25",
|
"version": "2026.3.8",
|
||||||
"minAppVersion": "0.15.0",
|
"minAppVersion": "0.15.0",
|
||||||
"description": "Add a pet bird to fly around your notes and keep you company!",
|
"description": "Add a pet bird to fly around your notes and keep you company!",
|
||||||
"author": "Idrees Hassan",
|
"author": "Idrees Hassan",
|
||||||
|
|||||||
337
dist/userscript/birb.user.js
vendored
337
dist/userscript/birb.user.js
vendored
@@ -1,7 +1,7 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name Pocket Bird
|
// @name Pocket Bird
|
||||||
// @namespace https://idreesinc.com
|
// @namespace https://idreesinc.com
|
||||||
// @version 2026.1.25
|
// @version 2026.3.8
|
||||||
// @description It's a pet bird in your browser, what more could you want?
|
// @description It's a pet bird in your browser, what more could you want?
|
||||||
// @author Idrees
|
// @author Idrees
|
||||||
// @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/userscript/birb.user.js
|
// @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/userscript/birb.user.js
|
||||||
@@ -15,12 +15,169 @@
|
|||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const SAVE_KEY = "birbSaveData";
|
||||||
|
const MONOCRAFT_URL = "https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getFontStyles() {
|
||||||
|
return getFontFaceImport(MONOCRAFT_URL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} src
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function getFontFaceImport(src) {
|
||||||
|
return `@font-face { font-family: 'Monocraft'; src: url("${src}") format('opentype'); font-weight: normal; font-style: normal; }`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse URL parameters into a key-value map
|
||||||
|
* @param {string} url
|
||||||
|
* @returns {Record<string, string>}
|
||||||
|
*/
|
||||||
|
function parseUrlParams(url) {
|
||||||
|
const queryString = url.split("?")[1];
|
||||||
|
if (!queryString) return {};
|
||||||
|
|
||||||
|
return queryString.split("&").reduce((params, param) => {
|
||||||
|
const [key, value] = param.split("=");
|
||||||
|
return { ...params, [key]: value };
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
const Directions = {
|
const Directions = {
|
||||||
LEFT: -1,
|
LEFT: -1,
|
||||||
RIGHT: 1,
|
RIGHT: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let debugMode = location.hostname === "127.0.0.1";
|
let debugMode = location.hostname === "127.0.0.1";
|
||||||
|
/** @type {Context|null} */
|
||||||
let context = null;
|
let context = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,6 +194,9 @@
|
|||||||
debugMode = value;
|
debugMode = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Context} The specific context for this platform
|
||||||
|
*/
|
||||||
function getContext() {
|
function getContext() {
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error("Context requested before being set");
|
throw new Error("Context requested before being set");
|
||||||
@@ -44,6 +204,9 @@
|
|||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Context} newContext
|
||||||
|
*/
|
||||||
function setContext(newContext) {
|
function setContext(newContext) {
|
||||||
context = newContext;
|
context = newContext;
|
||||||
}
|
}
|
||||||
@@ -1177,146 +1340,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
* @typedef {Object} SavedStickyNote
|
||||||
* @property {string} id
|
* @property {string} id
|
||||||
@@ -1648,14 +1671,7 @@
|
|||||||
const WINDOW_PIXEL_SIZE = CANVAS_PIXEL_SIZE * BIRB_CSS_SCALE;
|
const WINDOW_PIXEL_SIZE = CANVAS_PIXEL_SIZE * BIRB_CSS_SCALE;
|
||||||
|
|
||||||
// Build-time assets
|
// Build-time assets
|
||||||
const STYLESHEET = `@font-face {
|
const STYLESHEET = `:root {
|
||||||
font-family: 'Monocraft';
|
|
||||||
src: url("https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf") format('opentype');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--birb-border-size: 2px;
|
--birb-border-size: 2px;
|
||||||
--birb-neg-border-size: calc(var(--birb-border-size) * -1);
|
--birb-neg-border-size: calc(var(--birb-border-size) * -1);
|
||||||
--birb-double-border-size: calc(var(--birb-border-size) * 2);
|
--birb-double-border-size: calc(var(--birb-border-size) * 2);
|
||||||
@@ -2179,11 +2195,9 @@
|
|||||||
}),
|
}),
|
||||||
new Separator(),
|
new Separator(),
|
||||||
new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }),
|
new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }),
|
||||||
new MenuItem("2026.1.25", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.1.25"); }, false),
|
new MenuItem("2026.3.8", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.8"); }, false),
|
||||||
];
|
];
|
||||||
|
|
||||||
const styleElement = document.createElement("style");
|
|
||||||
|
|
||||||
/** @type {Birb} */
|
/** @type {Birb} */
|
||||||
let birb;
|
let birb;
|
||||||
|
|
||||||
@@ -2309,8 +2323,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onLoad() {
|
function onLoad() {
|
||||||
styleElement.textContent = STYLESHEET;
|
injectStyleElement(getContext().getFontStyles());
|
||||||
document.head.appendChild(styleElement);
|
injectStyleElement(STYLESHEET);
|
||||||
|
|
||||||
birb = new Birb(BIRB_CSS_SCALE, CANVAS_PIXEL_SIZE, SPRITE_SHEET, SPRITE_WIDTH, SPRITE_HEIGHT, HATS_SPRITE_SHEET);
|
birb = new Birb(BIRB_CSS_SCALE, CANVAS_PIXEL_SIZE, SPRITE_SHEET, SPRITE_WIDTH, SPRITE_HEIGHT, HATS_SPRITE_SHEET);
|
||||||
birb.setAnimation(Animations.BOB);
|
birb.setAnimation(Animations.BOB);
|
||||||
@@ -2464,6 +2478,18 @@
|
|||||||
birb.setY(birdY);
|
birb.setY(birdY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string|null} stylesheetContents
|
||||||
|
*/
|
||||||
|
function injectStyleElement(stylesheetContents) {
|
||||||
|
if (!stylesheetContents) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const element = document.createElement("style");
|
||||||
|
element.textContent = stylesheetContents;
|
||||||
|
document.head.appendChild(element);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {StickyNote} stickyNote
|
* @param {StickyNote} stickyNote
|
||||||
*/
|
*/
|
||||||
@@ -2973,8 +2999,7 @@
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
/** @type {HTMLElement[]} */
|
const largeElements = /** @type {HTMLElement[]} */ (Array.from(visible).filter((img) => img instanceof HTMLElement && img !== focusedElement && img.offsetWidth >= MIN_FOCUS_ELEMENT_WIDTH));
|
||||||
const largeElements = Array.from(visible).filter((img) => img instanceof HTMLElement && img !== focusedElement && img.offsetWidth >= MIN_FOCUS_ELEMENT_WIDTH);
|
|
||||||
const nonFixedElements = largeElements.filter((el) => {
|
const nonFixedElements = largeElements.filter((el) => {
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
323
dist/web/birb.embed.js
vendored
323
dist/web/birb.embed.js
vendored
@@ -1,12 +1,163 @@
|
|||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const SAVE_KEY = "birbSaveData";
|
||||||
|
const MONOCRAFT_URL = "https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getFontStyles() {
|
||||||
|
return getFontFaceImport(MONOCRAFT_URL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} src
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function getFontFaceImport(src) {
|
||||||
|
return `@font-face { font-family: 'Monocraft'; src: url("${src}") format('opentype'); font-weight: normal; font-style: normal; }`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse URL parameters into a key-value map
|
||||||
|
* @param {string} url
|
||||||
|
* @returns {Record<string, string>}
|
||||||
|
*/
|
||||||
|
function parseUrlParams(url) {
|
||||||
|
const queryString = url.split("?")[1];
|
||||||
|
if (!queryString) return {};
|
||||||
|
|
||||||
|
return queryString.split("&").reduce((params, param) => {
|
||||||
|
const [key, value] = param.split("=");
|
||||||
|
return { ...params, [key]: value };
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
const Directions = {
|
const Directions = {
|
||||||
LEFT: -1,
|
LEFT: -1,
|
||||||
RIGHT: 1,
|
RIGHT: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let debugMode = location.hostname === "127.0.0.1";
|
let debugMode = location.hostname === "127.0.0.1";
|
||||||
|
/** @type {Context|null} */
|
||||||
let context = null;
|
let context = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,6 +174,9 @@
|
|||||||
debugMode = value;
|
debugMode = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Context} The specific context for this platform
|
||||||
|
*/
|
||||||
function getContext() {
|
function getContext() {
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error("Context requested before being set");
|
throw new Error("Context requested before being set");
|
||||||
@@ -30,6 +184,9 @@
|
|||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Context} newContext
|
||||||
|
*/
|
||||||
function setContext(newContext) {
|
function setContext(newContext) {
|
||||||
context = newContext;
|
context = newContext;
|
||||||
}
|
}
|
||||||
@@ -1163,140 +1320,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
* @typedef {Object} SavedStickyNote
|
||||||
* @property {string} id
|
* @property {string} id
|
||||||
@@ -1628,14 +1651,7 @@
|
|||||||
const WINDOW_PIXEL_SIZE = CANVAS_PIXEL_SIZE * BIRB_CSS_SCALE;
|
const WINDOW_PIXEL_SIZE = CANVAS_PIXEL_SIZE * BIRB_CSS_SCALE;
|
||||||
|
|
||||||
// Build-time assets
|
// Build-time assets
|
||||||
const STYLESHEET = `@font-face {
|
const STYLESHEET = `:root {
|
||||||
font-family: 'Monocraft';
|
|
||||||
src: url("https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf") format('opentype');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--birb-border-size: 2px;
|
--birb-border-size: 2px;
|
||||||
--birb-neg-border-size: calc(var(--birb-border-size) * -1);
|
--birb-neg-border-size: calc(var(--birb-border-size) * -1);
|
||||||
--birb-double-border-size: calc(var(--birb-border-size) * 2);
|
--birb-double-border-size: calc(var(--birb-border-size) * 2);
|
||||||
@@ -2159,11 +2175,9 @@
|
|||||||
}),
|
}),
|
||||||
new Separator(),
|
new Separator(),
|
||||||
new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }),
|
new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }),
|
||||||
new MenuItem("2026.1.25", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.1.25"); }, false),
|
new MenuItem("2026.3.8", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.8"); }, false),
|
||||||
];
|
];
|
||||||
|
|
||||||
const styleElement = document.createElement("style");
|
|
||||||
|
|
||||||
/** @type {Birb} */
|
/** @type {Birb} */
|
||||||
let birb;
|
let birb;
|
||||||
|
|
||||||
@@ -2289,8 +2303,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onLoad() {
|
function onLoad() {
|
||||||
styleElement.textContent = STYLESHEET;
|
injectStyleElement(getContext().getFontStyles());
|
||||||
document.head.appendChild(styleElement);
|
injectStyleElement(STYLESHEET);
|
||||||
|
|
||||||
birb = new Birb(BIRB_CSS_SCALE, CANVAS_PIXEL_SIZE, SPRITE_SHEET, SPRITE_WIDTH, SPRITE_HEIGHT, HATS_SPRITE_SHEET);
|
birb = new Birb(BIRB_CSS_SCALE, CANVAS_PIXEL_SIZE, SPRITE_SHEET, SPRITE_WIDTH, SPRITE_HEIGHT, HATS_SPRITE_SHEET);
|
||||||
birb.setAnimation(Animations.BOB);
|
birb.setAnimation(Animations.BOB);
|
||||||
@@ -2444,6 +2458,18 @@
|
|||||||
birb.setY(birdY);
|
birb.setY(birdY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string|null} stylesheetContents
|
||||||
|
*/
|
||||||
|
function injectStyleElement(stylesheetContents) {
|
||||||
|
if (!stylesheetContents) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const element = document.createElement("style");
|
||||||
|
element.textContent = stylesheetContents;
|
||||||
|
document.head.appendChild(element);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {StickyNote} stickyNote
|
* @param {StickyNote} stickyNote
|
||||||
*/
|
*/
|
||||||
@@ -2953,8 +2979,7 @@
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
/** @type {HTMLElement[]} */
|
const largeElements = /** @type {HTMLElement[]} */ (Array.from(visible).filter((img) => img instanceof HTMLElement && img !== focusedElement && img.offsetWidth >= MIN_FOCUS_ELEMENT_WIDTH));
|
||||||
const largeElements = Array.from(visible).filter((img) => img instanceof HTMLElement && img !== focusedElement && img.offsetWidth >= MIN_FOCUS_ELEMENT_WIDTH);
|
|
||||||
const nonFixedElements = largeElements.filter((el) => {
|
const nonFixedElements = largeElements.filter((el) => {
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
323
dist/web/birb.js
vendored
323
dist/web/birb.js
vendored
@@ -1,12 +1,163 @@
|
|||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const SAVE_KEY = "birbSaveData";
|
||||||
|
const MONOCRAFT_URL = "https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getFontStyles() {
|
||||||
|
return getFontFaceImport(MONOCRAFT_URL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} src
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function getFontFaceImport(src) {
|
||||||
|
return `@font-face { font-family: 'Monocraft'; src: url("${src}") format('opentype'); font-weight: normal; font-style: normal; }`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse URL parameters into a key-value map
|
||||||
|
* @param {string} url
|
||||||
|
* @returns {Record<string, string>}
|
||||||
|
*/
|
||||||
|
function parseUrlParams(url) {
|
||||||
|
const queryString = url.split("?")[1];
|
||||||
|
if (!queryString) return {};
|
||||||
|
|
||||||
|
return queryString.split("&").reduce((params, param) => {
|
||||||
|
const [key, value] = param.split("=");
|
||||||
|
return { ...params, [key]: value };
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
const Directions = {
|
const Directions = {
|
||||||
LEFT: -1,
|
LEFT: -1,
|
||||||
RIGHT: 1,
|
RIGHT: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let debugMode = location.hostname === "127.0.0.1";
|
let debugMode = location.hostname === "127.0.0.1";
|
||||||
|
/** @type {Context|null} */
|
||||||
let context = null;
|
let context = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,6 +174,9 @@
|
|||||||
debugMode = value;
|
debugMode = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Context} The specific context for this platform
|
||||||
|
*/
|
||||||
function getContext() {
|
function getContext() {
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error("Context requested before being set");
|
throw new Error("Context requested before being set");
|
||||||
@@ -30,6 +184,9 @@
|
|||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Context} newContext
|
||||||
|
*/
|
||||||
function setContext(newContext) {
|
function setContext(newContext) {
|
||||||
context = newContext;
|
context = newContext;
|
||||||
}
|
}
|
||||||
@@ -1163,140 +1320,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
* @typedef {Object} SavedStickyNote
|
||||||
* @property {string} id
|
* @property {string} id
|
||||||
@@ -1628,14 +1651,7 @@
|
|||||||
const WINDOW_PIXEL_SIZE = CANVAS_PIXEL_SIZE * BIRB_CSS_SCALE;
|
const WINDOW_PIXEL_SIZE = CANVAS_PIXEL_SIZE * BIRB_CSS_SCALE;
|
||||||
|
|
||||||
// Build-time assets
|
// Build-time assets
|
||||||
const STYLESHEET = `@font-face {
|
const STYLESHEET = `:root {
|
||||||
font-family: 'Monocraft';
|
|
||||||
src: url("https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf") format('opentype');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--birb-border-size: 2px;
|
--birb-border-size: 2px;
|
||||||
--birb-neg-border-size: calc(var(--birb-border-size) * -1);
|
--birb-neg-border-size: calc(var(--birb-border-size) * -1);
|
||||||
--birb-double-border-size: calc(var(--birb-border-size) * 2);
|
--birb-double-border-size: calc(var(--birb-border-size) * 2);
|
||||||
@@ -2159,11 +2175,9 @@
|
|||||||
}),
|
}),
|
||||||
new Separator(),
|
new Separator(),
|
||||||
new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }),
|
new MenuItem(() => `Source Code ${isPetBoostActive() ? " ❤" : ""}`, () => { window.open("https://github.com/IdreesInc/Pocket-Bird"); }),
|
||||||
new MenuItem("2026.1.25", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.1.25"); }, false),
|
new MenuItem("2026.3.8", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.3.8"); }, false),
|
||||||
];
|
];
|
||||||
|
|
||||||
const styleElement = document.createElement("style");
|
|
||||||
|
|
||||||
/** @type {Birb} */
|
/** @type {Birb} */
|
||||||
let birb;
|
let birb;
|
||||||
|
|
||||||
@@ -2289,8 +2303,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onLoad() {
|
function onLoad() {
|
||||||
styleElement.textContent = STYLESHEET;
|
injectStyleElement(getContext().getFontStyles());
|
||||||
document.head.appendChild(styleElement);
|
injectStyleElement(STYLESHEET);
|
||||||
|
|
||||||
birb = new Birb(BIRB_CSS_SCALE, CANVAS_PIXEL_SIZE, SPRITE_SHEET, SPRITE_WIDTH, SPRITE_HEIGHT, HATS_SPRITE_SHEET);
|
birb = new Birb(BIRB_CSS_SCALE, CANVAS_PIXEL_SIZE, SPRITE_SHEET, SPRITE_WIDTH, SPRITE_HEIGHT, HATS_SPRITE_SHEET);
|
||||||
birb.setAnimation(Animations.BOB);
|
birb.setAnimation(Animations.BOB);
|
||||||
@@ -2444,6 +2458,18 @@
|
|||||||
birb.setY(birdY);
|
birb.setY(birdY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string|null} stylesheetContents
|
||||||
|
*/
|
||||||
|
function injectStyleElement(stylesheetContents) {
|
||||||
|
if (!stylesheetContents) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const element = document.createElement("style");
|
||||||
|
element.textContent = stylesheetContents;
|
||||||
|
document.head.appendChild(element);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {StickyNote} stickyNote
|
* @param {StickyNote} stickyNote
|
||||||
*/
|
*/
|
||||||
@@ -2953,8 +2979,7 @@
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
/** @type {HTMLElement[]} */
|
const largeElements = /** @type {HTMLElement[]} */ (Array.from(visible).filter((img) => img instanceof HTMLElement && img !== focusedElement && img.offsetWidth >= MIN_FOCUS_ELEMENT_WIDTH));
|
||||||
const largeElements = Array.from(visible).filter((img) => img instanceof HTMLElement && img !== focusedElement && img.offsetWidth >= MIN_FOCUS_ELEMENT_WIDTH);
|
|
||||||
const nonFixedElements = largeElements.filter((el) => {
|
const nonFixedElements = largeElements.filter((el) => {
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -218,8 +218,6 @@ function startApplication(birbPixels, featherPixels, hatsPixels) {
|
|||||||
new MenuItem("__VERSION__", () => { alert("Thank you for using Pocket Bird! You are on version: __VERSION__") }, false),
|
new MenuItem("__VERSION__", () => { alert("Thank you for using Pocket Bird! You are on version: __VERSION__") }, false),
|
||||||
];
|
];
|
||||||
|
|
||||||
const styleElement = document.createElement("style");
|
|
||||||
|
|
||||||
/** @type {Birb} */
|
/** @type {Birb} */
|
||||||
let birb;
|
let birb;
|
||||||
|
|
||||||
@@ -345,8 +343,8 @@ function startApplication(birbPixels, featherPixels, hatsPixels) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onLoad() {
|
function onLoad() {
|
||||||
styleElement.textContent = STYLESHEET;
|
injectStyleElement(getContext().getFontStyles());
|
||||||
document.head.appendChild(styleElement);
|
injectStyleElement(STYLESHEET);
|
||||||
|
|
||||||
birb = new Birb(BIRB_CSS_SCALE, CANVAS_PIXEL_SIZE, SPRITE_SHEET, SPRITE_WIDTH, SPRITE_HEIGHT, HATS_SPRITE_SHEET);
|
birb = new Birb(BIRB_CSS_SCALE, CANVAS_PIXEL_SIZE, SPRITE_SHEET, SPRITE_WIDTH, SPRITE_HEIGHT, HATS_SPRITE_SHEET);
|
||||||
birb.setAnimation(Animations.BOB);
|
birb.setAnimation(Animations.BOB);
|
||||||
@@ -500,6 +498,18 @@ function startApplication(birbPixels, featherPixels, hatsPixels) {
|
|||||||
birb.setY(birdY);
|
birb.setY(birdY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string|null} stylesheetContents
|
||||||
|
*/
|
||||||
|
function injectStyleElement(stylesheetContents) {
|
||||||
|
if (!stylesheetContents) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const element = document.createElement("style");
|
||||||
|
element.textContent = stylesheetContents;
|
||||||
|
document.head.appendChild(element);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {StickyNote} stickyNote
|
* @param {StickyNote} stickyNote
|
||||||
*/
|
*/
|
||||||
@@ -1013,8 +1023,7 @@ function startApplication(birbPixels, featherPixels, hatsPixels) {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
/** @type {HTMLElement[]} */
|
const largeElements = /** @type {HTMLElement[]} */ (Array.from(visible).filter((img) => img instanceof HTMLElement && img !== focusedElement && img.offsetWidth >= MIN_FOCUS_ELEMENT_WIDTH));
|
||||||
const largeElements = Array.from(visible).filter((img) => img instanceof HTMLElement && img !== focusedElement && img.offsetWidth >= MIN_FOCUS_ELEMENT_WIDTH);
|
|
||||||
// Ensure the bird doesn't land on fixed or sticky elements
|
// Ensure the bird doesn't land on fixed or sticky elements
|
||||||
// const fixedAllowed = getContext() instanceof ObsidianContext;
|
// const fixedAllowed = getContext() instanceof ObsidianContext;
|
||||||
// TODO: FIX
|
// TODO: FIX
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { debug, log, error } from "./shared.js";
|
|||||||
export const SAVE_KEY = "birbSaveData";
|
export const SAVE_KEY = "birbSaveData";
|
||||||
const ROOT_PATH = "";
|
const ROOT_PATH = "";
|
||||||
const SET_CONTEXT = "__CONTEXT__"
|
const SET_CONTEXT = "__CONTEXT__"
|
||||||
|
const MONOCRAFT_URL = "__MONOCRAFT_URL__";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('./application.js').BirbSaveData} BirbSaveData
|
* @typedef {import('./application.js').BirbSaveData} BirbSaveData
|
||||||
@@ -92,6 +93,13 @@ export class Context {
|
|||||||
areStickyNotesEnabled() {
|
areStickyNotesEnabled() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getFontStyles() {
|
||||||
|
return getFontFaceImport(MONOCRAFT_URL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LocalContext extends Context {
|
export class LocalContext extends Context {
|
||||||
@@ -194,6 +202,16 @@ export class BrowserExtensionContext extends Context {
|
|||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
chrome.storage.sync.clear();
|
chrome.storage.sync.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getFontStyles() {
|
||||||
|
// Use extension bundled font file
|
||||||
|
// @ts-expect-error
|
||||||
|
return getFontFaceImport(chrome.runtime.getURL('fonts/Monocraft.otf'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ObsidianContext extends Context {
|
export class ObsidianContext extends Context {
|
||||||
@@ -276,6 +294,14 @@ export class ObsidianContext extends Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} src
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function getFontFaceImport(src) {
|
||||||
|
return `@font-face { font-family: 'Monocraft'; src: url("${src}") format('opentype'); font-weight: normal; font-style: normal; }`;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse URL parameters into a key-value map
|
* Parse URL parameters into a key-value map
|
||||||
* @param {string} url
|
* @param {string} url
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
import { Context } from "./context";
|
||||||
|
|
||||||
export const Directions = {
|
export const Directions = {
|
||||||
LEFT: -1,
|
LEFT: -1,
|
||||||
RIGHT: 1,
|
RIGHT: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let debugMode = location.hostname === "127.0.0.1";
|
let debugMode = location.hostname === "127.0.0.1";
|
||||||
|
/** @type {Context|null} */
|
||||||
let context = null;
|
let context = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,6 +23,9 @@ export function setDebug(value) {
|
|||||||
debugMode = value;
|
debugMode = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Context} The specific context for this platform
|
||||||
|
*/
|
||||||
export function getContext() {
|
export function getContext() {
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error("Context requested before being set");
|
throw new Error("Context requested before being set");
|
||||||
@@ -27,6 +33,9 @@ export function getContext() {
|
|||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Context} newContext
|
||||||
|
*/
|
||||||
export function setContext(newContext) {
|
export function setContext(newContext) {
|
||||||
context = newContext;
|
context = newContext;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,3 @@
|
|||||||
@font-face {
|
|
||||||
font-family: 'Monocraft';
|
|
||||||
src: url("__MONOCRAFT_SRC__") format('opentype');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--birb-border-size: 2px;
|
--birb-border-size: 2px;
|
||||||
--birb-neg-border-size: calc(var(--birb-border-size) * -1);
|
--birb-neg-border-size: calc(var(--birb-border-size) * -1);
|
||||||
|
|||||||
Reference in New Issue
Block a user