mirror of
https://github.com/NohamR/Pocket-Bird.git
synced 2026-05-25 19:59:38 +00:00
Merge pull request #5 from IdreesInc/vencord
Add support for Vencord and clean up build and context process
This commit is contained in:
216
build.js
216
build.js
@@ -12,19 +12,27 @@ const IMAGES_DIR = "./images";
|
|||||||
const FONTS_DIR = "./fonts";
|
const FONTS_DIR = "./fonts";
|
||||||
const DIST_DIR = "./dist";
|
const DIST_DIR = "./dist";
|
||||||
|
|
||||||
const BROWSER_MANIFEST = "./platform-specific/extension/manifest.json";
|
const WEB_DIR = DIST_DIR + "/web";
|
||||||
const OBSIDIAN_MANIFEST = "./platform-specific/obsidian/manifest.json";
|
|
||||||
const USERSCRIPT_HEADER = "./platform-specific/userscript/header.txt";
|
|
||||||
const OBSIDIAN_WRAPPER = "./platform-specific/obsidian/wrapper.js";
|
|
||||||
|
|
||||||
const USERSCRIPT_DIR = DIST_DIR + "/userscript";
|
const USERSCRIPT_DIR = DIST_DIR + "/userscript";
|
||||||
const EXTENSION_DIR = DIST_DIR + "/extension";
|
const EXTENSION_DIR = DIST_DIR + "/extension";
|
||||||
const OBSIDIAN_DIR = DIST_DIR + "/obsidian";
|
const OBSIDIAN_DIR = DIST_DIR + "/obsidian";
|
||||||
|
const VENCORD_DIR = DIST_DIR + "/vencord";
|
||||||
|
|
||||||
const STYLESHEET_PATH = SRC_DIR + "/stylesheet.css";
|
const STYLESHEET_PATH = SRC_DIR + "/stylesheet.css";
|
||||||
const APPLICATION_ENTRY = SRC_DIR + "/application.js";
|
|
||||||
const BUNDLED_OUTPUT = DIST_DIR + "/birb.bundled.js";
|
const WEB_ENTRY = SRC_DIR + "/platforms/web/web.js";
|
||||||
const BIRB_OUTPUT = DIST_DIR + "/birb.js";
|
const USERSCRIPT_ENTRY = SRC_DIR + "/platforms/userscript/userscript.js";
|
||||||
|
const BROWSER_EXTENSION_ENTRY = SRC_DIR + "/platforms/extension/extension.js";
|
||||||
|
const OBSIDIAN_ENTRY = SRC_DIR + "/platforms/obsidian/obsidian.js";
|
||||||
|
const VENCORD_ENTRY = SRC_DIR + "/platforms/vencord/vencord.js";
|
||||||
|
|
||||||
|
const BROWSER_MANIFEST = SRC_DIR + "/platforms/extension/manifest.json";
|
||||||
|
const OBSIDIAN_MANIFEST = SRC_DIR + "/platforms/obsidian/manifest.json";
|
||||||
|
const USERSCRIPT_HEADER = SRC_DIR + "/platforms/userscript/header.txt";
|
||||||
|
const OBSIDIAN_WRAPPER = SRC_DIR + "/platforms/obsidian/wrapper.js";
|
||||||
|
const VENCORD_WRAPPER = SRC_DIR + "/platforms/vencord/wrapper.js";
|
||||||
|
|
||||||
|
const TEMP_BUNDLED_OUTPUT = DIST_DIR + "/birb.bundled.js";
|
||||||
|
|
||||||
const MONOCRAFT_URL = "https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf";
|
const MONOCRAFT_URL = "https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf";
|
||||||
|
|
||||||
@@ -32,6 +40,7 @@ const VERSION_KEY = "__VERSION__";
|
|||||||
const STYLESHEET_KEY = "___STYLESHEET___";
|
const STYLESHEET_KEY = "___STYLESHEET___";
|
||||||
const MONOCRAFT_SRC_KEY = "__MONOCRAFT_SRC__";
|
const MONOCRAFT_SRC_KEY = "__MONOCRAFT_SRC__";
|
||||||
const CODE_KEY = "__CODE__";
|
const CODE_KEY = "__CODE__";
|
||||||
|
const CONTEXT_KEY = "__CONTEXT__";
|
||||||
|
|
||||||
const spriteSheets = [
|
const spriteSheets = [
|
||||||
{
|
{
|
||||||
@@ -74,113 +83,146 @@ const version = `${versionDate}`; // Disable build number for now
|
|||||||
buildCache.version = version;
|
buildCache.version = version;
|
||||||
writeFileSync(BUILD_CACHE_PATH, JSON.stringify(buildCache), 'utf8');
|
writeFileSync(BUILD_CACHE_PATH, JSON.stringify(buildCache), 'utf8');
|
||||||
|
|
||||||
// =============================================
|
/**
|
||||||
// Build JavaScript function
|
* @param {string} entryPoint
|
||||||
// =============================================
|
* @param {boolean} [embedFont]
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
async function generateCode(entryPoint, embedFont = false) {
|
||||||
|
// Bundle with rollup
|
||||||
|
const bundle = await rollup({
|
||||||
|
input: entryPoint,
|
||||||
|
});
|
||||||
|
|
||||||
// Bundle with rollup
|
await bundle.write({
|
||||||
const bundle = await rollup({
|
file: TEMP_BUNDLED_OUTPUT,
|
||||||
input: APPLICATION_ENTRY,
|
format: 'iife',
|
||||||
});
|
});
|
||||||
|
|
||||||
await bundle.write({
|
await bundle.close();
|
||||||
file: BUNDLED_OUTPUT,
|
|
||||||
format: 'iife',
|
|
||||||
});
|
|
||||||
|
|
||||||
await bundle.close();
|
let birbJs = readFileSync(TEMP_BUNDLED_OUTPUT, 'utf8');
|
||||||
|
|
||||||
let birbJs = readFileSync(BUNDLED_OUTPUT, 'utf8');
|
// Delete bundled file
|
||||||
|
unlinkSync(TEMP_BUNDLED_OUTPUT);
|
||||||
|
|
||||||
// Delete bundled file
|
// Replace version placeholder
|
||||||
unlinkSync(BUNDLED_OUTPUT);
|
birbJs = birbJs.replaceAll(VERSION_KEY, version);
|
||||||
|
|
||||||
// Replace version placeholder
|
// Compile and insert sprite sheets
|
||||||
birbJs = birbJs.replaceAll(VERSION_KEY, version);
|
for (const spriteSheet of spriteSheets) {
|
||||||
|
const dataUri = readFileSync(spriteSheet.path, 'base64');
|
||||||
|
birbJs = birbJs.replaceAll(spriteSheet.key, `data:image/png;base64,${dataUri}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Compile and insert sprite sheets
|
// Insert stylesheet
|
||||||
for (const spriteSheet of spriteSheets) {
|
const stylesheetContent = readFileSync(STYLESHEET_PATH, 'utf8');
|
||||||
const dataUri = readFileSync(spriteSheet.path, 'base64');
|
birbJs = birbJs.replace(STYLESHEET_KEY, stylesheetContent);
|
||||||
birbJs = birbJs.replaceAll(spriteSheet.key, `data:image/png;base64,${dataUri}`);
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert stylesheet
|
async function buildWeb() {
|
||||||
const stylesheetContent = readFileSync(STYLESHEET_PATH, 'utf8');
|
const birbJs = await generateCode(WEB_ENTRY);
|
||||||
birbJs = birbJs.replace(STYLESHEET_KEY, stylesheetContent).replace(MONOCRAFT_SRC_KEY, MONOCRAFT_URL);
|
mkdirSync(WEB_DIR, { recursive: true });
|
||||||
|
writeFileSync(WEB_DIR + '/birb.js', birbJs);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildUserscript() {
|
||||||
|
const birbJs = await generateCode(USERSCRIPT_ENTRY);
|
||||||
|
|
||||||
// Write bundled JavaScript function
|
// Get userscript header
|
||||||
writeFileSync(BIRB_OUTPUT, birbJs);
|
const userScriptHeader = readFileSync(USERSCRIPT_HEADER, 'utf8').replaceAll(VERSION_KEY, version);
|
||||||
|
|
||||||
// =============================================
|
mkdirSync(USERSCRIPT_DIR, { recursive: true });
|
||||||
// Build userscript
|
const userScript = userScriptHeader + "\n" + birbJs;
|
||||||
// =============================================
|
writeFileSync(USERSCRIPT_DIR + '/birb.user.js', userScript);
|
||||||
|
}
|
||||||
|
|
||||||
// Get userscript header
|
async function buildExtension() {
|
||||||
const userScriptHeader = readFileSync(USERSCRIPT_HEADER, 'utf8').replaceAll(VERSION_KEY, version);
|
const birbJs = await generateCode(BROWSER_EXTENSION_ENTRY);
|
||||||
|
|
||||||
mkdirSync(USERSCRIPT_DIR, { recursive: true });
|
mkdirSync(EXTENSION_DIR, { recursive: true });
|
||||||
const userScript = userScriptHeader + "\n" + birbJs;
|
|
||||||
writeFileSync(USERSCRIPT_DIR + '/birb.user.js', userScript);
|
|
||||||
|
|
||||||
// =============================================
|
// Copy birb.js
|
||||||
// Build browser extension
|
writeFileSync(EXTENSION_DIR + '/birb.js', birbJs);
|
||||||
// =============================================
|
|
||||||
|
|
||||||
mkdirSync(EXTENSION_DIR, { recursive: true });
|
// Copy manifest.json
|
||||||
|
let browserManifest = readFileSync(BROWSER_MANIFEST, 'utf8');
|
||||||
|
browserManifest = browserManifest.replace(VERSION_KEY, version);
|
||||||
|
writeFileSync(EXTENSION_DIR + '/manifest.json', browserManifest);
|
||||||
|
|
||||||
// Copy birb.js
|
// Copy icons folder
|
||||||
writeFileSync(EXTENSION_DIR + '/birb.js', birbJs);
|
mkdirSync(EXTENSION_DIR + '/images/icons', { recursive: true });
|
||||||
|
cpSync(IMAGES_DIR + '/icons/transparent', EXTENSION_DIR + '/images/icons/transparent', { recursive: true });
|
||||||
|
|
||||||
// Copy manifest.json
|
// Copy fonts folder
|
||||||
let browserManifest = readFileSync(BROWSER_MANIFEST, 'utf8');
|
mkdirSync(EXTENSION_DIR + '/fonts', { recursive: true });
|
||||||
browserManifest = browserManifest.replace(VERSION_KEY, version);
|
cpSync(FONTS_DIR, EXTENSION_DIR + '/fonts', { recursive: true });
|
||||||
writeFileSync(EXTENSION_DIR + '/manifest.json', browserManifest);
|
|
||||||
|
|
||||||
// Copy icons folder
|
// Compress extension folder into zip
|
||||||
mkdirSync(EXTENSION_DIR + '/images/icons', { recursive: true });
|
const output = createWriteStream(DIST_DIR + "/extension.zip");
|
||||||
cpSync(IMAGES_DIR + '/icons/transparent', EXTENSION_DIR + '/images/icons/transparent', { recursive: true });
|
const archive = archiver('zip');
|
||||||
|
|
||||||
// Copy fonts folder
|
output.on('close', () => {
|
||||||
mkdirSync(EXTENSION_DIR + '/fonts', { recursive: true });
|
console.log(`Created zip file: ${archive.pointer()} total bytes`);
|
||||||
cpSync(FONTS_DIR, EXTENSION_DIR + '/fonts', { recursive: true });
|
});
|
||||||
|
|
||||||
// Compress extension folder into zip
|
archive.on('error', (err) => {
|
||||||
const output = createWriteStream(DIST_DIR + "/extension.zip");
|
throw err;
|
||||||
const archive = archiver('zip');
|
});
|
||||||
|
|
||||||
output.on('close', () => {
|
archive.pipe(output);
|
||||||
console.log(`Created zip file: ${archive.pointer()} total bytes`);
|
archive.directory(EXTENSION_DIR + '/', false);
|
||||||
});
|
archive.finalize();
|
||||||
|
}
|
||||||
|
|
||||||
archive.on('error', (err) => {
|
async function buildObsidian() {
|
||||||
throw err;
|
const birbJs = await generateCode(OBSIDIAN_ENTRY, true);
|
||||||
});
|
|
||||||
|
|
||||||
archive.pipe(output);
|
mkdirSync(OBSIDIAN_DIR, { recursive: true });
|
||||||
archive.directory(EXTENSION_DIR + '/', false);
|
|
||||||
archive.finalize();
|
|
||||||
|
|
||||||
// =============================================
|
// Wrap birb.js with plugin boilerplate
|
||||||
// Build Obsidian plugin
|
let obsidianPlugin = readFileSync(OBSIDIAN_WRAPPER, 'utf8').replace(VERSION_KEY, version).replace(CODE_KEY, birbJs);
|
||||||
// =============================================
|
|
||||||
|
|
||||||
mkdirSync(OBSIDIAN_DIR, { recursive: true });
|
// Create main.js with plugin code
|
||||||
|
writeFileSync(OBSIDIAN_DIR + '/main.js', obsidianPlugin);
|
||||||
|
|
||||||
// Wrap birb.js with plugin boilerplate
|
// Copy manifest.json
|
||||||
let obsidianPlugin = readFileSync(OBSIDIAN_WRAPPER, 'utf8').replace(VERSION_KEY, version).replace(CODE_KEY, birbJs);
|
let obsidianManifest = readFileSync(OBSIDIAN_MANIFEST, 'utf8');
|
||||||
|
obsidianManifest = obsidianManifest.replace(/"version":\s*".*"/, `"version": "${version}"`);
|
||||||
|
writeFileSync(OBSIDIAN_DIR + '/manifest.json', obsidianManifest);
|
||||||
|
}
|
||||||
|
|
||||||
// Encode font to data URI since Obsidian plugins can't have external font files
|
async function buildVencord() {
|
||||||
const monocraftFontData = readFileSync(FONTS_DIR + '/Monocraft.otf', 'base64');
|
const birbJs = await generateCode(VENCORD_ENTRY);
|
||||||
const monocraftDataUri = `data:font/otf;base64,${monocraftFontData}`;
|
|
||||||
obsidianPlugin = obsidianPlugin.replace(MONOCRAFT_URL, monocraftDataUri);
|
|
||||||
|
|
||||||
// Create main.js with plugin code
|
mkdirSync(VENCORD_DIR, { recursive: true });
|
||||||
writeFileSync(OBSIDIAN_DIR + '/main.js', obsidianPlugin);
|
|
||||||
|
|
||||||
// Copy manifest.json
|
// Wrap birb.js with plugin boilerplate
|
||||||
let obsidianManifest = readFileSync(OBSIDIAN_MANIFEST, 'utf8');
|
let vencordPlugin = readFileSync(VENCORD_WRAPPER, 'utf8').replace(CODE_KEY, birbJs);
|
||||||
obsidianManifest = obsidianManifest.replace(/"version":\s*".*"/, `"version": "${version}"`);
|
|
||||||
writeFileSync(OBSIDIAN_DIR + '/manifest.json', obsidianManifest);
|
|
||||||
|
|
||||||
console.log(`Build complete: ${version}`);
|
// Set context to "local"
|
||||||
|
vencordPlugin = vencordPlugin.replace(CONTEXT_KEY, "local");
|
||||||
|
|
||||||
|
// Create exported birb function
|
||||||
|
writeFileSync(VENCORD_DIR + '/birb.js', vencordPlugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Starting build...");
|
||||||
|
|
||||||
|
await buildWeb();
|
||||||
|
await buildUserscript();
|
||||||
|
await buildExtension();
|
||||||
|
await buildObsidian();
|
||||||
|
await buildVencord();
|
||||||
|
|
||||||
|
console.log("Build completed successfully!");
|
||||||
BIN
dist/extension.zip
vendored
BIN
dist/extension.zip
vendored
Binary file not shown.
368
dist/extension/birb.js
vendored
368
dist/extension/birb.js
vendored
@@ -7,6 +7,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
let debugMode = location.hostname === "127.0.0.1";
|
let debugMode = location.hostname === "127.0.0.1";
|
||||||
|
let context = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {boolean} Whether debug mode is enabled
|
* @returns {boolean} Whether debug mode is enabled
|
||||||
@@ -22,6 +23,17 @@
|
|||||||
debugMode = value;
|
debugMode = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getContext() {
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("Context requested before being set");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setContext(newContext) {
|
||||||
|
context = newContext;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an HTML element with the specified parameters
|
* Create an HTML element with the specified parameters
|
||||||
* @param {string} className
|
* @param {string} className
|
||||||
@@ -846,7 +858,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SAVE_KEY = "birbSaveData";
|
const SAVE_KEY = "birbSaveData";
|
||||||
const ROOT_PATH = "";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('./application.js').BirbSaveData} BirbSaveData
|
* @typedef {import('./application.js').BirbSaveData} BirbSaveData
|
||||||
@@ -857,14 +868,6 @@
|
|||||||
*/
|
*/
|
||||||
class Context {
|
class Context {
|
||||||
|
|
||||||
/**
|
|
||||||
* @abstract
|
|
||||||
* @returns {boolean} Whether this context is applicable
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
throw new Error("Method not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract
|
* @abstract
|
||||||
* @returns {Promise<BirbSaveData|{}>}
|
* @returns {Promise<BirbSaveData|{}>}
|
||||||
@@ -946,96 +949,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LocalContext extends Context {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
return window.location.hostname === "127.0.0.1"
|
|
||||||
|| window.location.hostname === "localhost"
|
|
||||||
|| window.location.hostname.startsWith("192.168.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class UserScriptContext extends Context {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
// @ts-expect-error
|
|
||||||
return typeof GM_getValue === "function";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BrowserExtensionContext extends Context {
|
class BrowserExtensionContext extends Context {
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
// @ts-expect-error
|
|
||||||
return typeof chrome !== "undefined";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
* @returns {Promise<BirbSaveData|{}>}
|
* @returns {Promise<BirbSaveData|{}>}
|
||||||
@@ -1061,9 +976,9 @@
|
|||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
if (chrome.runtime.lastError) {
|
if (chrome.runtime.lastError) {
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
console.error(chrome.runtime.lastError);
|
error(chrome.runtime.lastError);
|
||||||
} else {
|
} else {
|
||||||
console.log("Settings saved successfully");
|
log("Settings saved successfully");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1076,111 +991,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ObsidianContext extends Context {
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
// @ts-expect-error
|
|
||||||
return typeof app !== "undefined" && typeof app.vault !== "undefined";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const CONTEXTS = [
|
|
||||||
new UserScriptContext(),
|
|
||||||
new ObsidianContext(),
|
|
||||||
new BrowserExtensionContext(),
|
|
||||||
new LocalContext()
|
|
||||||
];
|
|
||||||
|
|
||||||
function getContext() {
|
|
||||||
for (const context of CONTEXTS) {
|
|
||||||
if (context.isContextActive()) {
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
error("No applicable context found, defaulting to LocalContext");
|
|
||||||
return new LocalContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse URL parameters into a key-value map
|
* Parse URL parameters into a key-value map
|
||||||
* @param {string} url
|
* @param {string} url
|
||||||
@@ -1883,6 +1693,13 @@
|
|||||||
border: none !important;
|
border: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.birb-sticky-note-input::placeholder {
|
||||||
|
font-family: "Monocraft", monospace !important;
|
||||||
|
font-size: 14px !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
color: rgba(0, 0, 0, 0.35) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.birb-sticky-note-input:focus {
|
.birb-sticky-note-input:focus {
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
@@ -1924,68 +1741,24 @@
|
|||||||
/** @type {Partial<Settings>} */
|
/** @type {Partial<Settings>} */
|
||||||
let userSettings = {};
|
let userSettings = {};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the sprite sheet and return the pixel-map template
|
* @param {Context} context
|
||||||
* @param {string} dataUri
|
|
||||||
* @param {boolean} [templateColors]
|
|
||||||
* @returns {Promise<string[][]>}
|
|
||||||
*/
|
*/
|
||||||
function loadSpriteSheetPixels(dataUri, templateColors = true) {
|
async function initializeApplication(context) {
|
||||||
return new Promise((resolve, reject) => {
|
log("birbOS booting up...");
|
||||||
const img = new Image();
|
setContext(context);
|
||||||
img.src = dataUri;
|
log("Loading sprite sheets...");
|
||||||
img.onload = () => {
|
const birbPixels = await loadSpriteSheetPixels(SPRITE_SHEET);
|
||||||
const canvas = document.createElement('canvas');
|
const featherPixels = await loadSpriteSheetPixels(FEATHER_SPRITE_SHEET);
|
||||||
canvas.width = img.width;
|
startApplication(birbPixels, featherPixels);
|
||||||
canvas.height = img.height;
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
if (!ctx) {
|
|
||||||
reject(new Error('Failed to get canvas context'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ctx.drawImage(img, 0, 0);
|
|
||||||
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
|
||||||
const pixels = imageData.data;
|
|
||||||
const hexArray = [];
|
|
||||||
for (let y = 0; y < img.height; y++) {
|
|
||||||
const row = [];
|
|
||||||
for (let x = 0; x < img.width; x++) {
|
|
||||||
const index = (y * img.width + x) * 4;
|
|
||||||
const r = pixels[index];
|
|
||||||
const g = pixels[index + 1];
|
|
||||||
const b = pixels[index + 2];
|
|
||||||
const a = pixels[index + 3];
|
|
||||||
if (a === 0) {
|
|
||||||
row.push(Sprite.TRANSPARENT);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
|
||||||
if (!templateColors) {
|
|
||||||
row.push(hex);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) {
|
|
||||||
error(`Unknown color: ${hex}`);
|
|
||||||
row.push(Sprite.TRANSPARENT);
|
|
||||||
}
|
|
||||||
row.push(SPRITE_SHEET_COLOR_MAP[hex]);
|
|
||||||
}
|
|
||||||
hexArray.push(row);
|
|
||||||
}
|
|
||||||
resolve(hexArray);
|
|
||||||
};
|
|
||||||
img.onerror = (err) => {
|
|
||||||
reject(err);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log("Loading sprite sheets...");
|
/**
|
||||||
|
* @param {string[][]} birbPixels
|
||||||
Promise.all([
|
* @param {string[][]} featherPixels
|
||||||
loadSpriteSheetPixels(SPRITE_SHEET),
|
*/
|
||||||
loadSpriteSheetPixels(FEATHER_SPRITE_SHEET)
|
function startApplication(birbPixels, featherPixels) {
|
||||||
]).then(([birbPixels, featherPixels]) => {
|
|
||||||
|
|
||||||
const SPRITE_SHEET = birbPixels;
|
const SPRITE_SHEET = birbPixels;
|
||||||
const FEATHER_SPRITE_SHEET = featherPixels;
|
const FEATHER_SPRITE_SHEET = featherPixels;
|
||||||
@@ -2046,7 +1819,7 @@
|
|||||||
insertModal(`${birdBirb()} Mode`, message);
|
insertModal(`${birdBirb()} Mode`, message);
|
||||||
}),
|
}),
|
||||||
new Separator(),
|
new Separator(),
|
||||||
new MenuItem("2025.11.15", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.11.15"); }, false),
|
new MenuItem("2025.11.16", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.11.16"); }, false),
|
||||||
];
|
];
|
||||||
|
|
||||||
const styleElement = document.createElement("style");
|
const styleElement = document.createElement("style");
|
||||||
@@ -2662,16 +2435,11 @@
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
/** @type {HTMLElement[]} */
|
/** @type {HTMLElement[]} */
|
||||||
// @ts-expect-error
|
|
||||||
const largeElements = 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
|
|
||||||
const fixedAllowed = getContext() instanceof ObsidianContext;
|
|
||||||
const nonFixedElements = largeElements.filter((el) => {
|
const nonFixedElements = largeElements.filter((el) => {
|
||||||
if (fixedAllowed) {
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const style = window.getComputedStyle(el);
|
|
||||||
return style.position !== "fixed" && style.position !== "sticky";
|
|
||||||
});
|
});
|
||||||
if (nonFixedElements.length === 0) {
|
if (nonFixedElements.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
@@ -2811,8 +2579,64 @@
|
|||||||
// Run the birb
|
// Run the birb
|
||||||
init();
|
init();
|
||||||
draw();
|
draw();
|
||||||
}).catch((e) => {
|
}
|
||||||
error("Error while loading sprite sheets: ", e);
|
|
||||||
});
|
/**
|
||||||
|
* Load the sprite sheet and return the pixel-map template
|
||||||
|
* @param {string} dataUri
|
||||||
|
* @param {boolean} [templateColors]
|
||||||
|
* @returns {Promise<string[][]>}
|
||||||
|
*/
|
||||||
|
function loadSpriteSheetPixels(dataUri, templateColors = true) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.src = dataUri;
|
||||||
|
img.onload = () => {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = img.width;
|
||||||
|
canvas.height = img.height;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx) {
|
||||||
|
reject(new Error('Failed to get canvas context'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
||||||
|
const pixels = imageData.data;
|
||||||
|
const hexArray = [];
|
||||||
|
for (let y = 0; y < img.height; y++) {
|
||||||
|
const row = [];
|
||||||
|
for (let x = 0; x < img.width; x++) {
|
||||||
|
const index = (y * img.width + x) * 4;
|
||||||
|
const r = pixels[index];
|
||||||
|
const g = pixels[index + 1];
|
||||||
|
const b = pixels[index + 2];
|
||||||
|
const a = pixels[index + 3];
|
||||||
|
if (a === 0) {
|
||||||
|
row.push(Sprite.TRANSPARENT);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
||||||
|
if (!templateColors) {
|
||||||
|
row.push(hex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) {
|
||||||
|
error(`Unknown color: ${hex}`);
|
||||||
|
row.push(Sprite.TRANSPARENT);
|
||||||
|
}
|
||||||
|
row.push(SPRITE_SHEET_COLOR_MAP[hex]);
|
||||||
|
}
|
||||||
|
hexArray.push(row);
|
||||||
|
}
|
||||||
|
resolve(hexArray);
|
||||||
|
};
|
||||||
|
img.onerror = (err) => {
|
||||||
|
reject(err);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeApplication(new BrowserExtensionContext());
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
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": "2025.11.15",
|
"version": "2025.11.16",
|
||||||
"homepage_url": "https://idreesinc.com",
|
"homepage_url": "https://idreesinc.com",
|
||||||
"icons": {
|
"icons": {
|
||||||
"48": "images/icons/transparent/48x48x1.png",
|
"48": "images/icons/transparent/48x48x1.png",
|
||||||
|
|||||||
328
dist/obsidian/main.js
vendored
328
dist/obsidian/main.js
vendored
@@ -1,7 +1,7 @@
|
|||||||
const { Plugin, Notice } = require('obsidian');
|
const { Plugin, Notice } = require('obsidian');
|
||||||
module.exports = class PocketBird extends Plugin {
|
module.exports = class PocketBird extends Plugin {
|
||||||
onload() {
|
onload() {
|
||||||
console.log("Loading Pocket Bird version 2025.11.15...");
|
console.log("Loading Pocket Bird version 2025.11.16...");
|
||||||
const OBSIDIAN_PLUGIN = this;
|
const OBSIDIAN_PLUGIN = this;
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
@@ -12,6 +12,7 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let debugMode = location.hostname === "127.0.0.1";
|
let debugMode = location.hostname === "127.0.0.1";
|
||||||
|
let context = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {boolean} Whether debug mode is enabled
|
* @returns {boolean} Whether debug mode is enabled
|
||||||
@@ -27,6 +28,17 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
debugMode = value;
|
debugMode = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getContext() {
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("Context requested before being set");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setContext(newContext) {
|
||||||
|
context = newContext;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an HTML element with the specified parameters
|
* Create an HTML element with the specified parameters
|
||||||
* @param {string} className
|
* @param {string} className
|
||||||
@@ -850,7 +862,6 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const SAVE_KEY = "birbSaveData";
|
|
||||||
const ROOT_PATH = "";
|
const ROOT_PATH = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -862,14 +873,6 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
*/
|
*/
|
||||||
class Context {
|
class Context {
|
||||||
|
|
||||||
/**
|
|
||||||
* @abstract
|
|
||||||
* @returns {boolean} Whether this context is applicable
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
throw new Error("Method not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract
|
* @abstract
|
||||||
* @returns {Promise<BirbSaveData|{}>}
|
* @returns {Promise<BirbSaveData|{}>}
|
||||||
@@ -951,145 +954,7 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LocalContext extends Context {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
return window.location.hostname === "127.0.0.1"
|
|
||||||
|| window.location.hostname === "localhost"
|
|
||||||
|| window.location.hostname.startsWith("192.168.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class UserScriptContext extends Context {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
// @ts-expect-error
|
|
||||||
return typeof GM_getValue === "function";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BrowserExtensionContext extends Context {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
// @ts-expect-error
|
|
||||||
return typeof chrome !== "undefined";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ObsidianContext extends Context {
|
class ObsidianContext extends Context {
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
// @ts-expect-error
|
|
||||||
return typeof app !== "undefined" && typeof app.vault !== "undefined";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
@@ -1169,23 +1034,6 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const CONTEXTS = [
|
|
||||||
new UserScriptContext(),
|
|
||||||
new ObsidianContext(),
|
|
||||||
new BrowserExtensionContext(),
|
|
||||||
new LocalContext()
|
|
||||||
];
|
|
||||||
|
|
||||||
function getContext() {
|
|
||||||
for (const context of CONTEXTS) {
|
|
||||||
if (context.isContextActive()) {
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
error("No applicable context found, defaulting to LocalContext");
|
|
||||||
return new LocalContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse URL parameters into a key-value map
|
* Parse URL parameters into a key-value map
|
||||||
* @param {string} url
|
* @param {string} url
|
||||||
@@ -1888,6 +1736,13 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
border: none !important;
|
border: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.birb-sticky-note-input::placeholder {
|
||||||
|
font-family: "Monocraft", monospace !important;
|
||||||
|
font-size: 14px !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
color: rgba(0, 0, 0, 0.35) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.birb-sticky-note-input:focus {
|
.birb-sticky-note-input:focus {
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
@@ -1929,68 +1784,24 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
/** @type {Partial<Settings>} */
|
/** @type {Partial<Settings>} */
|
||||||
let userSettings = {};
|
let userSettings = {};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the sprite sheet and return the pixel-map template
|
* @param {Context} context
|
||||||
* @param {string} dataUri
|
|
||||||
* @param {boolean} [templateColors]
|
|
||||||
* @returns {Promise<string[][]>}
|
|
||||||
*/
|
*/
|
||||||
function loadSpriteSheetPixels(dataUri, templateColors = true) {
|
async function initializeApplication(context) {
|
||||||
return new Promise((resolve, reject) => {
|
log("birbOS booting up...");
|
||||||
const img = new Image();
|
setContext(context);
|
||||||
img.src = dataUri;
|
log("Loading sprite sheets...");
|
||||||
img.onload = () => {
|
const birbPixels = await loadSpriteSheetPixels(SPRITE_SHEET);
|
||||||
const canvas = document.createElement('canvas');
|
const featherPixels = await loadSpriteSheetPixels(FEATHER_SPRITE_SHEET);
|
||||||
canvas.width = img.width;
|
startApplication(birbPixels, featherPixels);
|
||||||
canvas.height = img.height;
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
if (!ctx) {
|
|
||||||
reject(new Error('Failed to get canvas context'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ctx.drawImage(img, 0, 0);
|
|
||||||
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
|
||||||
const pixels = imageData.data;
|
|
||||||
const hexArray = [];
|
|
||||||
for (let y = 0; y < img.height; y++) {
|
|
||||||
const row = [];
|
|
||||||
for (let x = 0; x < img.width; x++) {
|
|
||||||
const index = (y * img.width + x) * 4;
|
|
||||||
const r = pixels[index];
|
|
||||||
const g = pixels[index + 1];
|
|
||||||
const b = pixels[index + 2];
|
|
||||||
const a = pixels[index + 3];
|
|
||||||
if (a === 0) {
|
|
||||||
row.push(Sprite.TRANSPARENT);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
|
||||||
if (!templateColors) {
|
|
||||||
row.push(hex);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) {
|
|
||||||
error(`Unknown color: ${hex}`);
|
|
||||||
row.push(Sprite.TRANSPARENT);
|
|
||||||
}
|
|
||||||
row.push(SPRITE_SHEET_COLOR_MAP[hex]);
|
|
||||||
}
|
|
||||||
hexArray.push(row);
|
|
||||||
}
|
|
||||||
resolve(hexArray);
|
|
||||||
};
|
|
||||||
img.onerror = (err) => {
|
|
||||||
reject(err);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log("Loading sprite sheets...");
|
/**
|
||||||
|
* @param {string[][]} birbPixels
|
||||||
Promise.all([
|
* @param {string[][]} featherPixels
|
||||||
loadSpriteSheetPixels(SPRITE_SHEET),
|
*/
|
||||||
loadSpriteSheetPixels(FEATHER_SPRITE_SHEET)
|
function startApplication(birbPixels, featherPixels) {
|
||||||
]).then(([birbPixels, featherPixels]) => {
|
|
||||||
|
|
||||||
const SPRITE_SHEET = birbPixels;
|
const SPRITE_SHEET = birbPixels;
|
||||||
const FEATHER_SPRITE_SHEET = featherPixels;
|
const FEATHER_SPRITE_SHEET = featherPixels;
|
||||||
@@ -2051,7 +1862,7 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
insertModal(`${birdBirb()} Mode`, message);
|
insertModal(`${birdBirb()} Mode`, message);
|
||||||
}),
|
}),
|
||||||
new Separator(),
|
new Separator(),
|
||||||
new MenuItem("2025.11.15", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.11.15"); }, false),
|
new MenuItem("2025.11.16", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.11.16"); }, false),
|
||||||
];
|
];
|
||||||
|
|
||||||
const styleElement = document.createElement("style");
|
const styleElement = document.createElement("style");
|
||||||
@@ -2667,16 +2478,11 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
/** @type {HTMLElement[]} */
|
/** @type {HTMLElement[]} */
|
||||||
// @ts-expect-error
|
|
||||||
const largeElements = 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
|
|
||||||
const fixedAllowed = getContext() instanceof ObsidianContext;
|
|
||||||
const nonFixedElements = largeElements.filter((el) => {
|
const nonFixedElements = largeElements.filter((el) => {
|
||||||
if (fixedAllowed) {
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const style = window.getComputedStyle(el);
|
|
||||||
return style.position !== "fixed" && style.position !== "sticky";
|
|
||||||
});
|
});
|
||||||
if (nonFixedElements.length === 0) {
|
if (nonFixedElements.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
@@ -2816,9 +2622,65 @@ module.exports = class PocketBird extends Plugin {
|
|||||||
// Run the birb
|
// Run the birb
|
||||||
init();
|
init();
|
||||||
draw();
|
draw();
|
||||||
}).catch((e) => {
|
}
|
||||||
error("Error while loading sprite sheets: ", e);
|
|
||||||
});
|
/**
|
||||||
|
* Load the sprite sheet and return the pixel-map template
|
||||||
|
* @param {string} dataUri
|
||||||
|
* @param {boolean} [templateColors]
|
||||||
|
* @returns {Promise<string[][]>}
|
||||||
|
*/
|
||||||
|
function loadSpriteSheetPixels(dataUri, templateColors = true) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.src = dataUri;
|
||||||
|
img.onload = () => {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = img.width;
|
||||||
|
canvas.height = img.height;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx) {
|
||||||
|
reject(new Error('Failed to get canvas context'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
||||||
|
const pixels = imageData.data;
|
||||||
|
const hexArray = [];
|
||||||
|
for (let y = 0; y < img.height; y++) {
|
||||||
|
const row = [];
|
||||||
|
for (let x = 0; x < img.width; x++) {
|
||||||
|
const index = (y * img.width + x) * 4;
|
||||||
|
const r = pixels[index];
|
||||||
|
const g = pixels[index + 1];
|
||||||
|
const b = pixels[index + 2];
|
||||||
|
const a = pixels[index + 3];
|
||||||
|
if (a === 0) {
|
||||||
|
row.push(Sprite.TRANSPARENT);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
||||||
|
if (!templateColors) {
|
||||||
|
row.push(hex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) {
|
||||||
|
error(`Unknown color: ${hex}`);
|
||||||
|
row.push(Sprite.TRANSPARENT);
|
||||||
|
}
|
||||||
|
row.push(SPRITE_SHEET_COLOR_MAP[hex]);
|
||||||
|
}
|
||||||
|
hexArray.push(row);
|
||||||
|
}
|
||||||
|
resolve(hexArray);
|
||||||
|
};
|
||||||
|
img.onerror = (err) => {
|
||||||
|
reject(err);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeApplication(new ObsidianContext());
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|||||||
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": "2025.11.15",
|
"version": "2025.11.16",
|
||||||
"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",
|
||||||
|
|||||||
375
dist/userscript/birb.user.js
vendored
375
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 2025.11.15
|
// @version 2025.11.16
|
||||||
// @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
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
let debugMode = location.hostname === "127.0.0.1";
|
let debugMode = location.hostname === "127.0.0.1";
|
||||||
|
let context = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {boolean} Whether debug mode is enabled
|
* @returns {boolean} Whether debug mode is enabled
|
||||||
@@ -36,6 +37,17 @@
|
|||||||
debugMode = value;
|
debugMode = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getContext() {
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("Context requested before being set");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setContext(newContext) {
|
||||||
|
context = newContext;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an HTML element with the specified parameters
|
* Create an HTML element with the specified parameters
|
||||||
* @param {string} className
|
* @param {string} className
|
||||||
@@ -860,7 +872,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SAVE_KEY = "birbSaveData";
|
const SAVE_KEY = "birbSaveData";
|
||||||
const ROOT_PATH = "";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('./application.js').BirbSaveData} BirbSaveData
|
* @typedef {import('./application.js').BirbSaveData} BirbSaveData
|
||||||
@@ -871,14 +882,6 @@
|
|||||||
*/
|
*/
|
||||||
class Context {
|
class Context {
|
||||||
|
|
||||||
/**
|
|
||||||
* @abstract
|
|
||||||
* @returns {boolean} Whether this context is applicable
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
throw new Error("Method not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract
|
* @abstract
|
||||||
* @returns {Promise<BirbSaveData|{}>}
|
* @returns {Promise<BirbSaveData|{}>}
|
||||||
@@ -960,54 +963,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LocalContext extends Context {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
return window.location.hostname === "127.0.0.1"
|
|
||||||
|| window.location.hostname === "localhost"
|
|
||||||
|| window.location.hostname.startsWith("192.168.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class UserScriptContext extends Context {
|
class UserScriptContext extends Context {
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
// @ts-expect-error
|
|
||||||
return typeof GM_getValue === "function";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
* @returns {Promise<BirbSaveData|{}>}
|
* @returns {Promise<BirbSaveData|{}>}
|
||||||
@@ -1039,162 +996,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BrowserExtensionContext extends Context {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
// @ts-expect-error
|
|
||||||
return typeof chrome !== "undefined";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ObsidianContext extends Context {
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
// @ts-expect-error
|
|
||||||
return typeof app !== "undefined" && typeof app.vault !== "undefined";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const CONTEXTS = [
|
|
||||||
new UserScriptContext(),
|
|
||||||
new ObsidianContext(),
|
|
||||||
new BrowserExtensionContext(),
|
|
||||||
new LocalContext()
|
|
||||||
];
|
|
||||||
|
|
||||||
function getContext() {
|
|
||||||
for (const context of CONTEXTS) {
|
|
||||||
if (context.isContextActive()) {
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
error("No applicable context found, defaulting to LocalContext");
|
|
||||||
return new LocalContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse URL parameters into a key-value map
|
* Parse URL parameters into a key-value map
|
||||||
* @param {string} url
|
* @param {string} url
|
||||||
@@ -1897,6 +1698,13 @@
|
|||||||
border: none !important;
|
border: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.birb-sticky-note-input::placeholder {
|
||||||
|
font-family: "Monocraft", monospace !important;
|
||||||
|
font-size: 14px !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
color: rgba(0, 0, 0, 0.35) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.birb-sticky-note-input:focus {
|
.birb-sticky-note-input:focus {
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
@@ -1938,68 +1746,24 @@
|
|||||||
/** @type {Partial<Settings>} */
|
/** @type {Partial<Settings>} */
|
||||||
let userSettings = {};
|
let userSettings = {};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the sprite sheet and return the pixel-map template
|
* @param {Context} context
|
||||||
* @param {string} dataUri
|
|
||||||
* @param {boolean} [templateColors]
|
|
||||||
* @returns {Promise<string[][]>}
|
|
||||||
*/
|
*/
|
||||||
function loadSpriteSheetPixels(dataUri, templateColors = true) {
|
async function initializeApplication(context) {
|
||||||
return new Promise((resolve, reject) => {
|
log("birbOS booting up...");
|
||||||
const img = new Image();
|
setContext(context);
|
||||||
img.src = dataUri;
|
log("Loading sprite sheets...");
|
||||||
img.onload = () => {
|
const birbPixels = await loadSpriteSheetPixels(SPRITE_SHEET);
|
||||||
const canvas = document.createElement('canvas');
|
const featherPixels = await loadSpriteSheetPixels(FEATHER_SPRITE_SHEET);
|
||||||
canvas.width = img.width;
|
startApplication(birbPixels, featherPixels);
|
||||||
canvas.height = img.height;
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
if (!ctx) {
|
|
||||||
reject(new Error('Failed to get canvas context'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ctx.drawImage(img, 0, 0);
|
|
||||||
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
|
||||||
const pixels = imageData.data;
|
|
||||||
const hexArray = [];
|
|
||||||
for (let y = 0; y < img.height; y++) {
|
|
||||||
const row = [];
|
|
||||||
for (let x = 0; x < img.width; x++) {
|
|
||||||
const index = (y * img.width + x) * 4;
|
|
||||||
const r = pixels[index];
|
|
||||||
const g = pixels[index + 1];
|
|
||||||
const b = pixels[index + 2];
|
|
||||||
const a = pixels[index + 3];
|
|
||||||
if (a === 0) {
|
|
||||||
row.push(Sprite.TRANSPARENT);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
|
||||||
if (!templateColors) {
|
|
||||||
row.push(hex);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) {
|
|
||||||
error(`Unknown color: ${hex}`);
|
|
||||||
row.push(Sprite.TRANSPARENT);
|
|
||||||
}
|
|
||||||
row.push(SPRITE_SHEET_COLOR_MAP[hex]);
|
|
||||||
}
|
|
||||||
hexArray.push(row);
|
|
||||||
}
|
|
||||||
resolve(hexArray);
|
|
||||||
};
|
|
||||||
img.onerror = (err) => {
|
|
||||||
reject(err);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log("Loading sprite sheets...");
|
/**
|
||||||
|
* @param {string[][]} birbPixels
|
||||||
Promise.all([
|
* @param {string[][]} featherPixels
|
||||||
loadSpriteSheetPixels(SPRITE_SHEET),
|
*/
|
||||||
loadSpriteSheetPixels(FEATHER_SPRITE_SHEET)
|
function startApplication(birbPixels, featherPixels) {
|
||||||
]).then(([birbPixels, featherPixels]) => {
|
|
||||||
|
|
||||||
const SPRITE_SHEET = birbPixels;
|
const SPRITE_SHEET = birbPixels;
|
||||||
const FEATHER_SPRITE_SHEET = featherPixels;
|
const FEATHER_SPRITE_SHEET = featherPixels;
|
||||||
@@ -2060,7 +1824,7 @@
|
|||||||
insertModal(`${birdBirb()} Mode`, message);
|
insertModal(`${birdBirb()} Mode`, message);
|
||||||
}),
|
}),
|
||||||
new Separator(),
|
new Separator(),
|
||||||
new MenuItem("2025.11.15", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.11.15"); }, false),
|
new MenuItem("2025.11.16", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.11.16"); }, false),
|
||||||
];
|
];
|
||||||
|
|
||||||
const styleElement = document.createElement("style");
|
const styleElement = document.createElement("style");
|
||||||
@@ -2676,16 +2440,11 @@
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
/** @type {HTMLElement[]} */
|
/** @type {HTMLElement[]} */
|
||||||
// @ts-expect-error
|
|
||||||
const largeElements = 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
|
|
||||||
const fixedAllowed = getContext() instanceof ObsidianContext;
|
|
||||||
const nonFixedElements = largeElements.filter((el) => {
|
const nonFixedElements = largeElements.filter((el) => {
|
||||||
if (fixedAllowed) {
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const style = window.getComputedStyle(el);
|
|
||||||
return style.position !== "fixed" && style.position !== "sticky";
|
|
||||||
});
|
});
|
||||||
if (nonFixedElements.length === 0) {
|
if (nonFixedElements.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
@@ -2825,8 +2584,64 @@
|
|||||||
// Run the birb
|
// Run the birb
|
||||||
init();
|
init();
|
||||||
draw();
|
draw();
|
||||||
}).catch((e) => {
|
}
|
||||||
error("Error while loading sprite sheets: ", e);
|
|
||||||
});
|
/**
|
||||||
|
* Load the sprite sheet and return the pixel-map template
|
||||||
|
* @param {string} dataUri
|
||||||
|
* @param {boolean} [templateColors]
|
||||||
|
* @returns {Promise<string[][]>}
|
||||||
|
*/
|
||||||
|
function loadSpriteSheetPixels(dataUri, templateColors = true) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.src = dataUri;
|
||||||
|
img.onload = () => {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = img.width;
|
||||||
|
canvas.height = img.height;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx) {
|
||||||
|
reject(new Error('Failed to get canvas context'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
||||||
|
const pixels = imageData.data;
|
||||||
|
const hexArray = [];
|
||||||
|
for (let y = 0; y < img.height; y++) {
|
||||||
|
const row = [];
|
||||||
|
for (let x = 0; x < img.width; x++) {
|
||||||
|
const index = (y * img.width + x) * 4;
|
||||||
|
const r = pixels[index];
|
||||||
|
const g = pixels[index + 1];
|
||||||
|
const b = pixels[index + 2];
|
||||||
|
const a = pixels[index + 3];
|
||||||
|
if (a === 0) {
|
||||||
|
row.push(Sprite.TRANSPARENT);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
||||||
|
if (!templateColors) {
|
||||||
|
row.push(hex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) {
|
||||||
|
error(`Unknown color: ${hex}`);
|
||||||
|
row.push(Sprite.TRANSPARENT);
|
||||||
|
}
|
||||||
|
row.push(SPRITE_SHEET_COLOR_MAP[hex]);
|
||||||
|
}
|
||||||
|
hexArray.push(row);
|
||||||
|
}
|
||||||
|
resolve(hexArray);
|
||||||
|
};
|
||||||
|
img.onerror = (err) => {
|
||||||
|
reject(err);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeApplication(new UserScriptContext());
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
2630
dist/vencord/birb.js
vendored
Normal file
2630
dist/vencord/birb.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
379
dist/birb.js → dist/web/birb.js
vendored
379
dist/birb.js → dist/web/birb.js
vendored
@@ -7,6 +7,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
let debugMode = location.hostname === "127.0.0.1";
|
let debugMode = location.hostname === "127.0.0.1";
|
||||||
|
let context = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {boolean} Whether debug mode is enabled
|
* @returns {boolean} Whether debug mode is enabled
|
||||||
@@ -22,6 +23,17 @@
|
|||||||
debugMode = value;
|
debugMode = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getContext() {
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("Context requested before being set");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setContext(newContext) {
|
||||||
|
context = newContext;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an HTML element with the specified parameters
|
* Create an HTML element with the specified parameters
|
||||||
* @param {string} className
|
* @param {string} className
|
||||||
@@ -846,7 +858,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SAVE_KEY = "birbSaveData";
|
const SAVE_KEY = "birbSaveData";
|
||||||
const ROOT_PATH = "";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('./application.js').BirbSaveData} BirbSaveData
|
* @typedef {import('./application.js').BirbSaveData} BirbSaveData
|
||||||
@@ -857,14 +868,6 @@
|
|||||||
*/
|
*/
|
||||||
class Context {
|
class Context {
|
||||||
|
|
||||||
/**
|
|
||||||
* @abstract
|
|
||||||
* @returns {boolean} Whether this context is applicable
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
throw new Error("Method not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract
|
* @abstract
|
||||||
* @returns {Promise<BirbSaveData|{}>}
|
* @returns {Promise<BirbSaveData|{}>}
|
||||||
@@ -948,16 +951,6 @@
|
|||||||
|
|
||||||
class LocalContext extends Context {
|
class LocalContext extends Context {
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
return window.location.hostname === "127.0.0.1"
|
|
||||||
|| window.location.hostname === "localhost"
|
|
||||||
|| window.location.hostname.startsWith("192.168.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
* @returns {Promise<BirbSaveData|{}>}
|
* @returns {Promise<BirbSaveData|{}>}
|
||||||
@@ -983,204 +976,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UserScriptContext extends Context {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
// @ts-expect-error
|
|
||||||
return typeof GM_getValue === "function";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class BrowserExtensionContext extends Context {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
// @ts-expect-error
|
|
||||||
return typeof chrome !== "undefined";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ObsidianContext extends Context {
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
// @ts-expect-error
|
|
||||||
return typeof app !== "undefined" && typeof app.vault !== "undefined";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const CONTEXTS = [
|
|
||||||
new UserScriptContext(),
|
|
||||||
new ObsidianContext(),
|
|
||||||
new BrowserExtensionContext(),
|
|
||||||
new LocalContext()
|
|
||||||
];
|
|
||||||
|
|
||||||
function getContext() {
|
|
||||||
for (const context of CONTEXTS) {
|
|
||||||
if (context.isContextActive()) {
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
error("No applicable context found, defaulting to LocalContext");
|
|
||||||
return new LocalContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse URL parameters into a key-value map
|
* Parse URL parameters into a key-value map
|
||||||
* @param {string} url
|
* @param {string} url
|
||||||
@@ -1883,6 +1678,13 @@
|
|||||||
border: none !important;
|
border: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.birb-sticky-note-input::placeholder {
|
||||||
|
font-family: "Monocraft", monospace !important;
|
||||||
|
font-size: 14px !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
color: rgba(0, 0, 0, 0.35) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.birb-sticky-note-input:focus {
|
.birb-sticky-note-input:focus {
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
@@ -1924,68 +1726,24 @@
|
|||||||
/** @type {Partial<Settings>} */
|
/** @type {Partial<Settings>} */
|
||||||
let userSettings = {};
|
let userSettings = {};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the sprite sheet and return the pixel-map template
|
* @param {Context} context
|
||||||
* @param {string} dataUri
|
|
||||||
* @param {boolean} [templateColors]
|
|
||||||
* @returns {Promise<string[][]>}
|
|
||||||
*/
|
*/
|
||||||
function loadSpriteSheetPixels(dataUri, templateColors = true) {
|
async function initializeApplication(context) {
|
||||||
return new Promise((resolve, reject) => {
|
log("birbOS booting up...");
|
||||||
const img = new Image();
|
setContext(context);
|
||||||
img.src = dataUri;
|
log("Loading sprite sheets...");
|
||||||
img.onload = () => {
|
const birbPixels = await loadSpriteSheetPixels(SPRITE_SHEET);
|
||||||
const canvas = document.createElement('canvas');
|
const featherPixels = await loadSpriteSheetPixels(FEATHER_SPRITE_SHEET);
|
||||||
canvas.width = img.width;
|
startApplication(birbPixels, featherPixels);
|
||||||
canvas.height = img.height;
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
if (!ctx) {
|
|
||||||
reject(new Error('Failed to get canvas context'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ctx.drawImage(img, 0, 0);
|
|
||||||
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
|
||||||
const pixels = imageData.data;
|
|
||||||
const hexArray = [];
|
|
||||||
for (let y = 0; y < img.height; y++) {
|
|
||||||
const row = [];
|
|
||||||
for (let x = 0; x < img.width; x++) {
|
|
||||||
const index = (y * img.width + x) * 4;
|
|
||||||
const r = pixels[index];
|
|
||||||
const g = pixels[index + 1];
|
|
||||||
const b = pixels[index + 2];
|
|
||||||
const a = pixels[index + 3];
|
|
||||||
if (a === 0) {
|
|
||||||
row.push(Sprite.TRANSPARENT);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
|
||||||
if (!templateColors) {
|
|
||||||
row.push(hex);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) {
|
|
||||||
error(`Unknown color: ${hex}`);
|
|
||||||
row.push(Sprite.TRANSPARENT);
|
|
||||||
}
|
|
||||||
row.push(SPRITE_SHEET_COLOR_MAP[hex]);
|
|
||||||
}
|
|
||||||
hexArray.push(row);
|
|
||||||
}
|
|
||||||
resolve(hexArray);
|
|
||||||
};
|
|
||||||
img.onerror = (err) => {
|
|
||||||
reject(err);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log("Loading sprite sheets...");
|
/**
|
||||||
|
* @param {string[][]} birbPixels
|
||||||
Promise.all([
|
* @param {string[][]} featherPixels
|
||||||
loadSpriteSheetPixels(SPRITE_SHEET),
|
*/
|
||||||
loadSpriteSheetPixels(FEATHER_SPRITE_SHEET)
|
function startApplication(birbPixels, featherPixels) {
|
||||||
]).then(([birbPixels, featherPixels]) => {
|
|
||||||
|
|
||||||
const SPRITE_SHEET = birbPixels;
|
const SPRITE_SHEET = birbPixels;
|
||||||
const FEATHER_SPRITE_SHEET = featherPixels;
|
const FEATHER_SPRITE_SHEET = featherPixels;
|
||||||
@@ -2046,7 +1804,7 @@
|
|||||||
insertModal(`${birdBirb()} Mode`, message);
|
insertModal(`${birdBirb()} Mode`, message);
|
||||||
}),
|
}),
|
||||||
new Separator(),
|
new Separator(),
|
||||||
new MenuItem("2025.11.15", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.11.15"); }, false),
|
new MenuItem("2025.11.16", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.11.16"); }, false),
|
||||||
];
|
];
|
||||||
|
|
||||||
const styleElement = document.createElement("style");
|
const styleElement = document.createElement("style");
|
||||||
@@ -2662,16 +2420,11 @@
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
/** @type {HTMLElement[]} */
|
/** @type {HTMLElement[]} */
|
||||||
// @ts-expect-error
|
|
||||||
const largeElements = 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
|
|
||||||
const fixedAllowed = getContext() instanceof ObsidianContext;
|
|
||||||
const nonFixedElements = largeElements.filter((el) => {
|
const nonFixedElements = largeElements.filter((el) => {
|
||||||
if (fixedAllowed) {
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const style = window.getComputedStyle(el);
|
|
||||||
return style.position !== "fixed" && style.position !== "sticky";
|
|
||||||
});
|
});
|
||||||
if (nonFixedElements.length === 0) {
|
if (nonFixedElements.length === 0) {
|
||||||
return false;
|
return false;
|
||||||
@@ -2811,8 +2564,64 @@
|
|||||||
// Run the birb
|
// Run the birb
|
||||||
init();
|
init();
|
||||||
draw();
|
draw();
|
||||||
}).catch((e) => {
|
}
|
||||||
error("Error while loading sprite sheets: ", e);
|
|
||||||
});
|
/**
|
||||||
|
* Load the sprite sheet and return the pixel-map template
|
||||||
|
* @param {string} dataUri
|
||||||
|
* @param {boolean} [templateColors]
|
||||||
|
* @returns {Promise<string[][]>}
|
||||||
|
*/
|
||||||
|
function loadSpriteSheetPixels(dataUri, templateColors = true) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.src = dataUri;
|
||||||
|
img.onload = () => {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = img.width;
|
||||||
|
canvas.height = img.height;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx) {
|
||||||
|
reject(new Error('Failed to get canvas context'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
||||||
|
const pixels = imageData.data;
|
||||||
|
const hexArray = [];
|
||||||
|
for (let y = 0; y < img.height; y++) {
|
||||||
|
const row = [];
|
||||||
|
for (let x = 0; x < img.width; x++) {
|
||||||
|
const index = (y * img.width + x) * 4;
|
||||||
|
const r = pixels[index];
|
||||||
|
const g = pixels[index + 1];
|
||||||
|
const b = pixels[index + 2];
|
||||||
|
const a = pixels[index + 3];
|
||||||
|
if (a === 0) {
|
||||||
|
row.push(Sprite.TRANSPARENT);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
||||||
|
if (!templateColors) {
|
||||||
|
row.push(hex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) {
|
||||||
|
error(`Unknown color: ${hex}`);
|
||||||
|
row.push(Sprite.TRANSPARENT);
|
||||||
|
}
|
||||||
|
row.push(SPRITE_SHEET_COLOR_MAP[hex]);
|
||||||
|
}
|
||||||
|
hexArray.push(row);
|
||||||
|
}
|
||||||
|
resolve(hexArray);
|
||||||
|
};
|
||||||
|
img.onerror = (err) => {
|
||||||
|
reject(err);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeApplication(new LocalContext());
|
||||||
|
|
||||||
})();
|
})();
|
||||||
@@ -26,6 +26,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="spacer"></div>
|
<div id="spacer"></div>
|
||||||
<!-- <script type="module" src="spritesheet-compiler.js"></script> -->
|
<!-- <script type="module" src="spritesheet-compiler.js"></script> -->
|
||||||
<script src="../dist/birb.js"></script>
|
<script src="../dist/web/birb.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -2,9 +2,11 @@ import Frame from './frame.js';
|
|||||||
import Layer from './layer.js';
|
import Layer from './layer.js';
|
||||||
import Anim from './anim.js';
|
import Anim from './anim.js';
|
||||||
import { Birb, Animations } from './birb.js';
|
import { Birb, Animations } from './birb.js';
|
||||||
import { getContext, ObsidianContext } from './context.js';
|
import { Context, ObsidianContext } from './context.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
getContext,
|
||||||
|
setContext,
|
||||||
Directions,
|
Directions,
|
||||||
isDebug,
|
isDebug,
|
||||||
setDebug,
|
setDebug,
|
||||||
@@ -109,68 +111,24 @@ const MIN_FOCUS_ELEMENT_WIDTH = 100;
|
|||||||
/** @type {Partial<Settings>} */
|
/** @type {Partial<Settings>} */
|
||||||
let userSettings = {};
|
let userSettings = {};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the sprite sheet and return the pixel-map template
|
* @param {Context} context
|
||||||
* @param {string} dataUri
|
|
||||||
* @param {boolean} [templateColors]
|
|
||||||
* @returns {Promise<string[][]>}
|
|
||||||
*/
|
*/
|
||||||
function loadSpriteSheetPixels(dataUri, templateColors = true) {
|
export async function initializeApplication(context) {
|
||||||
return new Promise((resolve, reject) => {
|
log("birbOS booting up...");
|
||||||
const img = new Image();
|
setContext(context);
|
||||||
img.src = dataUri;
|
log("Loading sprite sheets...");
|
||||||
img.onload = () => {
|
const birbPixels = await loadSpriteSheetPixels(SPRITE_SHEET);
|
||||||
const canvas = document.createElement('canvas');
|
const featherPixels = await loadSpriteSheetPixels(FEATHER_SPRITE_SHEET);
|
||||||
canvas.width = img.width;
|
startApplication(birbPixels, featherPixels);
|
||||||
canvas.height = img.height;
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
if (!ctx) {
|
|
||||||
reject(new Error('Failed to get canvas context'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ctx.drawImage(img, 0, 0);
|
|
||||||
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
|
||||||
const pixels = imageData.data;
|
|
||||||
const hexArray = [];
|
|
||||||
for (let y = 0; y < img.height; y++) {
|
|
||||||
const row = [];
|
|
||||||
for (let x = 0; x < img.width; x++) {
|
|
||||||
const index = (y * img.width + x) * 4;
|
|
||||||
const r = pixels[index];
|
|
||||||
const g = pixels[index + 1];
|
|
||||||
const b = pixels[index + 2];
|
|
||||||
const a = pixels[index + 3];
|
|
||||||
if (a === 0) {
|
|
||||||
row.push(Sprite.TRANSPARENT);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
|
||||||
if (!templateColors) {
|
|
||||||
row.push(hex);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) {
|
|
||||||
error(`Unknown color: ${hex}`);
|
|
||||||
row.push(Sprite.TRANSPARENT);
|
|
||||||
}
|
|
||||||
row.push(SPRITE_SHEET_COLOR_MAP[hex]);
|
|
||||||
}
|
|
||||||
hexArray.push(row);
|
|
||||||
}
|
|
||||||
resolve(hexArray);
|
|
||||||
};
|
|
||||||
img.onerror = (err) => {
|
|
||||||
reject(err);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log("Loading sprite sheets...");
|
/**
|
||||||
|
* @param {string[][]} birbPixels
|
||||||
Promise.all([
|
* @param {string[][]} featherPixels
|
||||||
loadSpriteSheetPixels(SPRITE_SHEET),
|
*/
|
||||||
loadSpriteSheetPixels(FEATHER_SPRITE_SHEET)
|
function startApplication(birbPixels, featherPixels) {
|
||||||
]).then(([birbPixels, featherPixels]) => {
|
|
||||||
|
|
||||||
const SPRITE_SHEET = birbPixels;
|
const SPRITE_SHEET = birbPixels;
|
||||||
const FEATHER_SPRITE_SHEET = featherPixels;
|
const FEATHER_SPRITE_SHEET = featherPixels;
|
||||||
@@ -851,10 +809,11 @@ Promise.all([
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
/** @type {HTMLElement[]} */
|
/** @type {HTMLElement[]} */
|
||||||
// @ts-expect-error
|
|
||||||
const largeElements = 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
|
||||||
|
const fixedAllowed = true;
|
||||||
const nonFixedElements = largeElements.filter((el) => {
|
const nonFixedElements = largeElements.filter((el) => {
|
||||||
if (fixedAllowed) {
|
if (fixedAllowed) {
|
||||||
return true;
|
return true;
|
||||||
@@ -1008,6 +967,60 @@ Promise.all([
|
|||||||
// Run the birb
|
// Run the birb
|
||||||
init();
|
init();
|
||||||
draw();
|
draw();
|
||||||
}).catch((e) => {
|
}
|
||||||
error("Error while loading sprite sheets: ", e);
|
|
||||||
});
|
/**
|
||||||
|
* Load the sprite sheet and return the pixel-map template
|
||||||
|
* @param {string} dataUri
|
||||||
|
* @param {boolean} [templateColors]
|
||||||
|
* @returns {Promise<string[][]>}
|
||||||
|
*/
|
||||||
|
function loadSpriteSheetPixels(dataUri, templateColors = true) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.src = dataUri;
|
||||||
|
img.onload = () => {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = img.width;
|
||||||
|
canvas.height = img.height;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx) {
|
||||||
|
reject(new Error('Failed to get canvas context'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
||||||
|
const pixels = imageData.data;
|
||||||
|
const hexArray = [];
|
||||||
|
for (let y = 0; y < img.height; y++) {
|
||||||
|
const row = [];
|
||||||
|
for (let x = 0; x < img.width; x++) {
|
||||||
|
const index = (y * img.width + x) * 4;
|
||||||
|
const r = pixels[index];
|
||||||
|
const g = pixels[index + 1];
|
||||||
|
const b = pixels[index + 2];
|
||||||
|
const a = pixels[index + 3];
|
||||||
|
if (a === 0) {
|
||||||
|
row.push(Sprite.TRANSPARENT);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const hex = `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
||||||
|
if (!templateColors) {
|
||||||
|
row.push(hex);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (SPRITE_SHEET_COLOR_MAP[hex] === undefined) {
|
||||||
|
error(`Unknown color: ${hex}`);
|
||||||
|
row.push(Sprite.TRANSPARENT);
|
||||||
|
}
|
||||||
|
row.push(SPRITE_SHEET_COLOR_MAP[hex]);
|
||||||
|
}
|
||||||
|
hexArray.push(row);
|
||||||
|
}
|
||||||
|
resolve(hexArray);
|
||||||
|
};
|
||||||
|
img.onerror = (err) => {
|
||||||
|
reject(err);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { debug, log, error } from "./shared.js";
|
import { debug, log, error } from "./shared.js";
|
||||||
|
|
||||||
const SAVE_KEY = "birbSaveData";
|
export const SAVE_KEY = "birbSaveData";
|
||||||
const ROOT_PATH = "";
|
const ROOT_PATH = "";
|
||||||
|
const SET_CONTEXT = "__CONTEXT__"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('./application.js').BirbSaveData} BirbSaveData
|
* @typedef {import('./application.js').BirbSaveData} BirbSaveData
|
||||||
@@ -12,14 +13,6 @@ const ROOT_PATH = "";
|
|||||||
*/
|
*/
|
||||||
export class Context {
|
export class Context {
|
||||||
|
|
||||||
/**
|
|
||||||
* @abstract
|
|
||||||
* @returns {boolean} Whether this context is applicable
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
throw new Error("Method not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @abstract
|
* @abstract
|
||||||
* @returns {Promise<BirbSaveData|{}>}
|
* @returns {Promise<BirbSaveData|{}>}
|
||||||
@@ -103,16 +96,6 @@ export class Context {
|
|||||||
|
|
||||||
export class LocalContext extends Context {
|
export class LocalContext extends Context {
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
return window.location.hostname === "127.0.0.1"
|
|
||||||
|| window.location.hostname === "localhost"
|
|
||||||
|| window.location.hostname.startsWith("192.168.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
* @returns {Promise<BirbSaveData|{}>}
|
* @returns {Promise<BirbSaveData|{}>}
|
||||||
@@ -140,15 +123,6 @@ export class LocalContext extends Context {
|
|||||||
|
|
||||||
export class UserScriptContext extends Context {
|
export class UserScriptContext extends Context {
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
// @ts-expect-error
|
|
||||||
return typeof GM_getValue === "function";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
* @returns {Promise<BirbSaveData|{}>}
|
* @returns {Promise<BirbSaveData|{}>}
|
||||||
@@ -180,16 +154,7 @@ export class UserScriptContext extends Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BrowserExtensionContext extends Context {
|
export class BrowserExtensionContext extends Context {
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
// @ts-expect-error
|
|
||||||
return typeof chrome !== "undefined";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
@@ -216,9 +181,9 @@ class BrowserExtensionContext extends Context {
|
|||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
if (chrome.runtime.lastError) {
|
if (chrome.runtime.lastError) {
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
console.error(chrome.runtime.lastError);
|
error(chrome.runtime.lastError);
|
||||||
} else {
|
} else {
|
||||||
console.log("Settings saved successfully");
|
log("Settings saved successfully");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -232,14 +197,6 @@ class BrowserExtensionContext extends Context {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ObsidianContext extends Context {
|
export class ObsidianContext extends Context {
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isContextActive() {
|
|
||||||
// @ts-expect-error
|
|
||||||
return typeof app !== "undefined" && typeof app.vault !== "undefined";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
@@ -319,21 +276,31 @@ export class ObsidianContext extends Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const CONTEXTS = [
|
export class VencordContext extends Context {
|
||||||
new UserScriptContext(),
|
|
||||||
new ObsidianContext(),
|
|
||||||
new BrowserExtensionContext(),
|
|
||||||
new LocalContext()
|
|
||||||
];
|
|
||||||
|
|
||||||
export function getContext() {
|
/**
|
||||||
for (const context of CONTEXTS) {
|
* @override
|
||||||
if (context.isContextActive()) {
|
* @returns {Promise<BirbSaveData|{}>}
|
||||||
return context;
|
*/
|
||||||
}
|
async getSaveData() {
|
||||||
|
// @ts-expect-error
|
||||||
|
return await Vencord.Api.DataStore.get(SAVE_KEY) ?? {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
* @param {BirbSaveData} saveData
|
||||||
|
*/
|
||||||
|
async putSaveData(saveData) {
|
||||||
|
// @ts-expect-error
|
||||||
|
await Vencord.Api.DataStore.set(SAVE_KEY, saveData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
resetSaveData() {
|
||||||
|
// @ts-expect-error
|
||||||
|
Vencord.Api.DataStore.del(SAVE_KEY);
|
||||||
}
|
}
|
||||||
error("No applicable context found, defaulting to LocalContext");
|
|
||||||
return new LocalContext();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
4
src/platforms/extension/extension.js
Normal file
4
src/platforms/extension/extension.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { initializeApplication } from "../../application.js";
|
||||||
|
import { BrowserExtensionContext } from "../../context.js";
|
||||||
|
|
||||||
|
initializeApplication(new BrowserExtensionContext());
|
||||||
4
src/platforms/obsidian/obsidian.js
Normal file
4
src/platforms/obsidian/obsidian.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { initializeApplication } from "../../application.js";
|
||||||
|
import { ObsidianContext } from "../../context.js";
|
||||||
|
|
||||||
|
initializeApplication(new ObsidianContext());
|
||||||
4
src/platforms/userscript/userscript.js
Normal file
4
src/platforms/userscript/userscript.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { initializeApplication } from "../../application.js";
|
||||||
|
import { UserScriptContext } from "../../context.js";
|
||||||
|
|
||||||
|
initializeApplication(new UserScriptContext());
|
||||||
4
src/platforms/vencord/vencord.js
Normal file
4
src/platforms/vencord/vencord.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { initializeApplication } from "../../application.js";
|
||||||
|
import { VencordContext } from "../../context.js";
|
||||||
|
|
||||||
|
initializeApplication(new VencordContext());
|
||||||
3
src/platforms/vencord/wrapper.js
Normal file
3
src/platforms/vencord/wrapper.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export const Birb = () => {
|
||||||
|
__CODE__
|
||||||
|
};
|
||||||
4
src/platforms/web/web.js
Normal file
4
src/platforms/web/web.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { initializeApplication } from "../../application.js";
|
||||||
|
import { LocalContext } from "../../context.js";
|
||||||
|
|
||||||
|
initializeApplication(new LocalContext());
|
||||||
@@ -4,6 +4,7 @@ export const Directions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let debugMode = location.hostname === "127.0.0.1";
|
let debugMode = location.hostname === "127.0.0.1";
|
||||||
|
let context = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {boolean} Whether debug mode is enabled
|
* @returns {boolean} Whether debug mode is enabled
|
||||||
@@ -19,6 +20,17 @@ export function setDebug(value) {
|
|||||||
debugMode = value;
|
debugMode = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getContext() {
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("Context requested before being set");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setContext(newContext) {
|
||||||
|
context = newContext;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an HTML element with the specified parameters
|
* Create an HTML element with the specified parameters
|
||||||
* @param {string} className
|
* @param {string} className
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
|
getContext,
|
||||||
makeElement,
|
makeElement,
|
||||||
makeDraggable,
|
makeDraggable,
|
||||||
makeClosable
|
makeClosable
|
||||||
} from './shared.js';
|
} from './shared.js';
|
||||||
import { getContext } from './context.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} SavedStickyNote
|
* @typedef {Object} SavedStickyNote
|
||||||
|
|||||||
@@ -357,6 +357,13 @@
|
|||||||
border: none !important;
|
border: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.birb-sticky-note-input::placeholder {
|
||||||
|
font-family: "Monocraft", monospace !important;
|
||||||
|
font-size: 14px !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
color: rgba(0, 0, 0, 0.35) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.birb-sticky-note-input:focus {
|
.birb-sticky-note-input:focus {
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
|
|||||||
Reference in New Issue
Block a user