mirror of
https://github.com/NohamR/Pocket-Bird.git
synced 2026-05-25 12:17:22 +00:00
Compare commits
25 Commits
2025.11.14
...
win98
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8de14bb94 | ||
|
|
e0ab21d608 | ||
|
|
86c254b2cb | ||
|
|
14ef2713d8 | ||
|
|
bc3f5cce02 | ||
|
|
c3ff3b39dc | ||
|
|
8b8aa50cae | ||
|
|
f4b598d2dc | ||
|
|
bccaa0aa15 | ||
|
|
37a30ea509 | ||
|
|
e7be2b7661 | ||
|
|
c750bf5560 | ||
|
|
c927ce23e4 | ||
|
|
6ee9efd5a8 | ||
|
|
a5e81e4265 | ||
|
|
76e55a3caa | ||
|
|
224fe4aaec | ||
|
|
764e8311fa | ||
|
|
b66521b7a2 | ||
|
|
72f2805fa0 | ||
|
|
1ca5094536 | ||
|
|
2ddf10bb01 | ||
|
|
9eba0c13ed | ||
|
|
45626fda25 | ||
|
|
322b0d0b90 |
29
README.md
29
README.md
@@ -13,6 +13,10 @@ It's a pet bird that hops around your computer, what more could you want?
|
|||||||
|
|
||||||
### Get it for [Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/pocket-bird/)
|
### Get it for [Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/pocket-bird/)
|
||||||
|
|
||||||
|
### Get it for [Obsidian (beta)](https://github.com/IdreesInc/Pocket-Bird#Obsidian)
|
||||||
|
|
||||||
|
### Get it for [TamperMonkey](https://github.com/IdreesInc/Pocket-Bird#Userscript)
|
||||||
|
|
||||||
#### Join the [Discord](https://discord.gg/6yxE9prcNc) to help me beta test new features and suggest ideas!
|
#### Join the [Discord](https://discord.gg/6yxE9prcNc) to help me beta test new features and suggest ideas!
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@@ -35,6 +39,14 @@ It's a pet bird that hops around your computer, what more could you want?
|
|||||||
2. Click "Add to Firefox"
|
2. Click "Add to Firefox"
|
||||||
3. Confirm any permission prompts that appear
|
3. Confirm any permission prompts that appear
|
||||||
|
|
||||||
|
### Obsidian
|
||||||
|
|
||||||
|
1. Install the [Beta Plugin Manager (BRAT)](https://obsidian.md/plugins?id=obsidian42-brat) plugin for Obsidian
|
||||||
|
2. Enable the BRAT plugin and open its settings
|
||||||
|
3. In the BRAT settings, click "Add Beta Plugin" and enter the following URL: `https://github.com/IdreesInc/PB-Obsidian-Releases`
|
||||||
|
4. Select "Latest version" and click "Add Plugin"
|
||||||
|
5. Enjoy a pet bird in your Obsidian notes!
|
||||||
|
|
||||||
### Userscript
|
### Userscript
|
||||||
|
|
||||||
*Note that this is mainly used for beta testing new features, installation via browser extension is recommended for the best experience.*
|
*Note that this is mainly used for beta testing new features, installation via browser extension is recommended for the best experience.*
|
||||||
@@ -44,6 +56,14 @@ It's a pet bird that hops around your computer, what more could you want?
|
|||||||
3. Install my Pocket Bird script by going to this link and clicking install: [https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/userscript/birb.user.js](https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/userscript/birb.user.js)
|
3. Install my Pocket Bird script by going to this link and clicking install: [https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/userscript/birb.user.js](https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/userscript/birb.user.js)
|
||||||
4. Now any websites you visit will have a little bird hopping around!
|
4. Now any websites you visit will have a little bird hopping around!
|
||||||
|
|
||||||
|
### Your Own Website
|
||||||
|
|
||||||
|
Pocket Bird can also be embedded directly into your own website! Just include the following code snippet anywhere in your HTML:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script src="https://cdn.jsdelivr.net/gh/IdreesInc/Pocket-Bird@main/dist/web/birb.embed.js"></script>
|
||||||
|
```
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
### How do I pet the bird?
|
### How do I pet the bird?
|
||||||
@@ -70,6 +90,15 @@ Open the Pocket Bird menu by clicking the bird and select "Settings". From there
|
|||||||
|
|
||||||
If you are running Pocket bird on a browser, the extension needs these permissions in order to insert the bird and sticky notes into your webpages. Pocket Bird does not collect any of your data or browsing history and all data is stored locally on your device!
|
If you are running Pocket bird on a browser, the extension needs these permissions in order to insert the bird and sticky notes into your webpages. Pocket Bird does not collect any of your data or browsing history and all data is stored locally on your device!
|
||||||
|
|
||||||
|
## Sites With Pocket Bird
|
||||||
|
|
||||||
|
Here are some websites where you can find Pocket Bird hopping around:
|
||||||
|
|
||||||
|
- [https://grepjason.sh](https://grepjason.sh)
|
||||||
|
|
||||||
|
*If you've added Pocket Bird to your website, let me know and I'll add it to this list!*
|
||||||
|
|
||||||
|
|
||||||
## Getting in Touch
|
## Getting in Touch
|
||||||
|
|
||||||
If you'd like to get in touch, check out the [Discord](https://discord.gg/6yxE9prcNc) to suggest features, report bugs, and stay updated on development!
|
If you'd like to get in touch, check out the [Discord](https://discord.gg/6yxE9prcNc) to suggest features, report bugs, and stay updated on development!
|
||||||
|
|||||||
94
build.js
94
build.js
@@ -12,19 +12,24 @@ 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 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 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 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";
|
||||||
|
|
||||||
@@ -67,32 +72,35 @@ if (buildCache.version && buildCache.version.startsWith(versionDate)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const version = `${versionDate}.${buildNumber}`;
|
// const version = `${versionDate}.${buildNumber}`;
|
||||||
|
const version = `${versionDate}`; // Disable build number for now
|
||||||
|
|
||||||
// Update build cache
|
// Update build cache
|
||||||
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
|
// Bundle with rollup
|
||||||
const bundle = await rollup({
|
const bundle = await rollup({
|
||||||
input: APPLICATION_ENTRY,
|
input: entryPoint,
|
||||||
});
|
});
|
||||||
|
|
||||||
await bundle.write({
|
await bundle.write({
|
||||||
file: BUNDLED_OUTPUT,
|
file: TEMP_BUNDLED_OUTPUT,
|
||||||
format: 'iife',
|
format: 'iife',
|
||||||
});
|
});
|
||||||
|
|
||||||
await bundle.close();
|
await bundle.close();
|
||||||
|
|
||||||
let birbJs = readFileSync(BUNDLED_OUTPUT, 'utf8');
|
let birbJs = readFileSync(TEMP_BUNDLED_OUTPUT, 'utf8');
|
||||||
|
|
||||||
// Delete bundled file
|
// Delete bundled file
|
||||||
unlinkSync(BUNDLED_OUTPUT);
|
unlinkSync(TEMP_BUNDLED_OUTPUT);
|
||||||
|
|
||||||
// Replace version placeholder
|
// Replace version placeholder
|
||||||
birbJs = birbJs.replaceAll(VERSION_KEY, version);
|
birbJs = birbJs.replaceAll(VERSION_KEY, version);
|
||||||
@@ -105,15 +113,28 @@ for (const spriteSheet of spriteSheets) {
|
|||||||
|
|
||||||
// Insert stylesheet
|
// Insert stylesheet
|
||||||
const stylesheetContent = readFileSync(STYLESHEET_PATH, 'utf8');
|
const stylesheetContent = readFileSync(STYLESHEET_PATH, 'utf8');
|
||||||
birbJs = birbJs.replace(STYLESHEET_KEY, stylesheetContent).replace(MONOCRAFT_SRC_KEY, MONOCRAFT_URL);
|
birbJs = birbJs.replace(STYLESHEET_KEY, stylesheetContent);
|
||||||
|
|
||||||
|
if (embedFont) {
|
||||||
|
// Encode font to data URI
|
||||||
|
const monocraftFontData = readFileSync(FONTS_DIR + '/Monocraft.otf', 'base64');
|
||||||
|
const monocraftDataUri = `data:font/otf;base64,${monocraftFontData}`;
|
||||||
|
birbJs = birbJs.replaceAll(MONOCRAFT_SRC_KEY, monocraftDataUri);
|
||||||
|
} else {
|
||||||
|
birbJs = birbJs.replaceAll(MONOCRAFT_SRC_KEY, MONOCRAFT_URL);
|
||||||
|
}
|
||||||
|
return birbJs;
|
||||||
|
}
|
||||||
|
|
||||||
// Write bundled JavaScript function
|
async function buildWeb() {
|
||||||
writeFileSync(BIRB_OUTPUT, birbJs);
|
const birbJs = await generateCode(WEB_ENTRY);
|
||||||
|
mkdirSync(WEB_DIR, { recursive: true });
|
||||||
|
writeFileSync(WEB_DIR + '/birb.js', birbJs);
|
||||||
|
writeFileSync(WEB_DIR + '/birb.embed.js', birbJs);
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================
|
async function buildUserscript() {
|
||||||
// Build userscript
|
const birbJs = await generateCode(USERSCRIPT_ENTRY);
|
||||||
// =============================================
|
|
||||||
|
|
||||||
// Get userscript header
|
// Get userscript header
|
||||||
const userScriptHeader = readFileSync(USERSCRIPT_HEADER, 'utf8').replaceAll(VERSION_KEY, version);
|
const userScriptHeader = readFileSync(USERSCRIPT_HEADER, 'utf8').replaceAll(VERSION_KEY, version);
|
||||||
@@ -121,10 +142,10 @@ const userScriptHeader = readFileSync(USERSCRIPT_HEADER, 'utf8').replaceAll(VERS
|
|||||||
mkdirSync(USERSCRIPT_DIR, { recursive: true });
|
mkdirSync(USERSCRIPT_DIR, { recursive: true });
|
||||||
const userScript = userScriptHeader + "\n" + birbJs;
|
const userScript = userScriptHeader + "\n" + birbJs;
|
||||||
writeFileSync(USERSCRIPT_DIR + '/birb.user.js', userScript);
|
writeFileSync(USERSCRIPT_DIR + '/birb.user.js', userScript);
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================
|
async function buildExtension() {
|
||||||
// Build browser extension
|
const birbJs = await generateCode(BROWSER_EXTENSION_ENTRY);
|
||||||
// =============================================
|
|
||||||
|
|
||||||
mkdirSync(EXTENSION_DIR, { recursive: true });
|
mkdirSync(EXTENSION_DIR, { recursive: true });
|
||||||
|
|
||||||
@@ -159,21 +180,16 @@ archive.on('error', (err) => {
|
|||||||
archive.pipe(output);
|
archive.pipe(output);
|
||||||
archive.directory(EXTENSION_DIR + '/', false);
|
archive.directory(EXTENSION_DIR + '/', false);
|
||||||
archive.finalize();
|
archive.finalize();
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================
|
async function buildObsidian() {
|
||||||
// Build Obsidian plugin
|
const birbJs = await generateCode(OBSIDIAN_ENTRY, true);
|
||||||
// =============================================
|
|
||||||
|
|
||||||
mkdirSync(OBSIDIAN_DIR, { recursive: true });
|
mkdirSync(OBSIDIAN_DIR, { recursive: true });
|
||||||
|
|
||||||
// Wrap birb.js with plugin boilerplate
|
// Wrap birb.js with plugin boilerplate
|
||||||
let obsidianPlugin = readFileSync(OBSIDIAN_WRAPPER, 'utf8').replace(VERSION_KEY, version).replace(CODE_KEY, birbJs);
|
let obsidianPlugin = readFileSync(OBSIDIAN_WRAPPER, 'utf8').replace(VERSION_KEY, version).replace(CODE_KEY, birbJs);
|
||||||
|
|
||||||
// Encode font to data URI since Obsidian plugins can't have external font files
|
|
||||||
const monocraftFontData = readFileSync(FONTS_DIR + '/Monocraft.otf', 'base64');
|
|
||||||
const monocraftDataUri = `data:font/otf;base64,${monocraftFontData}`;
|
|
||||||
obsidianPlugin = obsidianPlugin.replace(MONOCRAFT_URL, monocraftDataUri);
|
|
||||||
|
|
||||||
// Create main.js with plugin code
|
// Create main.js with plugin code
|
||||||
writeFileSync(OBSIDIAN_DIR + '/main.js', obsidianPlugin);
|
writeFileSync(OBSIDIAN_DIR + '/main.js', obsidianPlugin);
|
||||||
|
|
||||||
@@ -181,5 +197,13 @@ writeFileSync(OBSIDIAN_DIR + '/main.js', obsidianPlugin);
|
|||||||
let obsidianManifest = readFileSync(OBSIDIAN_MANIFEST, 'utf8');
|
let obsidianManifest = readFileSync(OBSIDIAN_MANIFEST, 'utf8');
|
||||||
obsidianManifest = obsidianManifest.replace(/"version":\s*".*"/, `"version": "${version}"`);
|
obsidianManifest = obsidianManifest.replace(/"version":\s*".*"/, `"version": "${version}"`);
|
||||||
writeFileSync(OBSIDIAN_DIR + '/manifest.json', obsidianManifest);
|
writeFileSync(OBSIDIAN_DIR + '/manifest.json', obsidianManifest);
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`Build complete: ${version}`);
|
console.log("Starting build...");
|
||||||
|
|
||||||
|
await buildWeb();
|
||||||
|
await buildUserscript();
|
||||||
|
await buildExtension();
|
||||||
|
await buildObsidian();
|
||||||
|
|
||||||
|
console.log("Build completed successfully!");
|
||||||
BIN
dist/extension.zip
vendored
BIN
dist/extension.zip
vendored
Binary file not shown.
366
dist/extension/birb.js
vendored
366
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;
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
log("Loading sprite sheets...");
|
log("Loading sprite sheets...");
|
||||||
|
const birbPixels = await loadSpriteSheetPixels(SPRITE_SHEET);
|
||||||
|
const featherPixels = await loadSpriteSheetPixels(FEATHER_SPRITE_SHEET);
|
||||||
|
startApplication(birbPixels, featherPixels);
|
||||||
|
}
|
||||||
|
|
||||||
Promise.all([
|
/**
|
||||||
loadSpriteSheetPixels(SPRITE_SHEET),
|
* @param {string[][]} birbPixels
|
||||||
loadSpriteSheetPixels(FEATHER_SPRITE_SHEET)
|
* @param {string[][]} featherPixels
|
||||||
]).then(([birbPixels, featherPixels]) => {
|
*/
|
||||||
|
function startApplication(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.14.205", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.11.14.205"); }, false),
|
new MenuItem("2026.1.1", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.1.1"); }, 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.14.205",
|
"version": "2026.1.1",
|
||||||
"homepage_url": "https://idreesinc.com",
|
"homepage_url": "https://idreesinc.com",
|
||||||
"icons": {
|
"icons": {
|
||||||
"48": "images/icons/transparent/48x48x1.png",
|
"48": "images/icons/transparent/48x48x1.png",
|
||||||
|
|||||||
326
dist/obsidian/main.js
vendored
326
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.14.205...");
|
console.log("Loading Pocket Bird version 2026.1.1...");
|
||||||
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;
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
log("Loading sprite sheets...");
|
log("Loading sprite sheets...");
|
||||||
|
const birbPixels = await loadSpriteSheetPixels(SPRITE_SHEET);
|
||||||
|
const featherPixels = await loadSpriteSheetPixels(FEATHER_SPRITE_SHEET);
|
||||||
|
startApplication(birbPixels, featherPixels);
|
||||||
|
}
|
||||||
|
|
||||||
Promise.all([
|
/**
|
||||||
loadSpriteSheetPixels(SPRITE_SHEET),
|
* @param {string[][]} birbPixels
|
||||||
loadSpriteSheetPixels(FEATHER_SPRITE_SHEET)
|
* @param {string[][]} featherPixels
|
||||||
]).then(([birbPixels, featherPixels]) => {
|
*/
|
||||||
|
function startApplication(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.14.205", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.11.14.205"); }, false),
|
new MenuItem("2026.1.1", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.1.1"); }, 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());
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|||||||
4
dist/obsidian/manifest.json
vendored
4
dist/obsidian/manifest.json
vendored
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"id": "pocket-bird",
|
"id": "pocket-bird",
|
||||||
"name": "Pocket Bird",
|
"name": "Pocket Bird",
|
||||||
"version": "2025.11.14.205",
|
"version": "2026.1.1",
|
||||||
"minAppVersion": "0.15.0",
|
"minAppVersion": "0.15.0",
|
||||||
"description": "It's a pet bird in your Obsidian, what more could you want?",
|
"description": "Add a pet bird to fly around your notes and keep you company!",
|
||||||
"author": "Idrees Hassan",
|
"author": "Idrees Hassan",
|
||||||
"authorUrl": "https://idreesinc.com",
|
"authorUrl": "https://idreesinc.com",
|
||||||
"isDesktopOnly": false
|
"isDesktopOnly": false
|
||||||
|
|||||||
373
dist/userscript/birb.user.js
vendored
373
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.14.205
|
// @version 2026.1.1
|
||||||
// @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;
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
log("Loading sprite sheets...");
|
log("Loading sprite sheets...");
|
||||||
|
const birbPixels = await loadSpriteSheetPixels(SPRITE_SHEET);
|
||||||
|
const featherPixels = await loadSpriteSheetPixels(FEATHER_SPRITE_SHEET);
|
||||||
|
startApplication(birbPixels, featherPixels);
|
||||||
|
}
|
||||||
|
|
||||||
Promise.all([
|
/**
|
||||||
loadSpriteSheetPixels(SPRITE_SHEET),
|
* @param {string[][]} birbPixels
|
||||||
loadSpriteSheetPixels(FEATHER_SPRITE_SHEET)
|
* @param {string[][]} featherPixels
|
||||||
]).then(([birbPixels, featherPixels]) => {
|
*/
|
||||||
|
function startApplication(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.14.205", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.11.14.205"); }, false),
|
new MenuItem("2026.1.1", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.1.1"); }, 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());
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
2627
dist/web/birb.embed.js
vendored
Normal file
2627
dist/web/birb.embed.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
377
dist/birb.js → dist/web/birb.js
vendored
377
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;
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
log("Loading sprite sheets...");
|
log("Loading sprite sheets...");
|
||||||
|
const birbPixels = await loadSpriteSheetPixels(SPRITE_SHEET);
|
||||||
|
const featherPixels = await loadSpriteSheetPixels(FEATHER_SPRITE_SHEET);
|
||||||
|
startApplication(birbPixels, featherPixels);
|
||||||
|
}
|
||||||
|
|
||||||
Promise.all([
|
/**
|
||||||
loadSpriteSheetPixels(SPRITE_SHEET),
|
* @param {string[][]} birbPixels
|
||||||
loadSpriteSheetPixels(FEATHER_SPRITE_SHEET)
|
* @param {string[][]} featherPixels
|
||||||
]).then(([birbPixels, featherPixels]) => {
|
*/
|
||||||
|
function startApplication(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.14.205", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.11.14.205"); }, false),
|
new MenuItem("2026.1.1", () => { alert("Thank you for using Pocket Bird! You are on version: 2026.1.1"); }, 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;
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
log("Loading sprite sheets...");
|
log("Loading sprite sheets...");
|
||||||
|
const birbPixels = await loadSpriteSheetPixels(SPRITE_SHEET);
|
||||||
|
const featherPixels = await loadSpriteSheetPixels(FEATHER_SPRITE_SHEET);
|
||||||
|
startApplication(birbPixels, featherPixels);
|
||||||
|
}
|
||||||
|
|
||||||
Promise.all([
|
/**
|
||||||
loadSpriteSheetPixels(SPRITE_SHEET),
|
* @param {string[][]} birbPixels
|
||||||
loadSpriteSheetPixels(FEATHER_SPRITE_SHEET)
|
* @param {string[][]} featherPixels
|
||||||
]).then(([birbPixels, featherPixels]) => {
|
*/
|
||||||
|
function startApplication(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,23 +276,6 @@ export class ObsidianContext extends Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const CONTEXTS = [
|
|
||||||
new UserScriptContext(),
|
|
||||||
new ObsidianContext(),
|
|
||||||
new BrowserExtensionContext(),
|
|
||||||
new LocalContext()
|
|
||||||
];
|
|
||||||
|
|
||||||
export 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
|
||||||
|
|||||||
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());
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
"name": "Pocket Bird",
|
"name": "Pocket Bird",
|
||||||
"version": "__VERSION__",
|
"version": "__VERSION__",
|
||||||
"minAppVersion": "0.15.0",
|
"minAppVersion": "0.15.0",
|
||||||
"description": "It's a pet bird in your Obsidian, what more could you want?",
|
"description": "Add a pet bird to fly around your notes and keep you company!",
|
||||||
"author": "Idrees Hassan",
|
"author": "Idrees Hassan",
|
||||||
"authorUrl": "https://idreesinc.com",
|
"authorUrl": "https://idreesinc.com",
|
||||||
"isDesktopOnly": false
|
"isDesktopOnly": false
|
||||||
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/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