mirror of
https://github.com/NohamR/Pocket-Bird.git
synced 2026-05-25 19:59:38 +00:00
@@ -3,10 +3,11 @@
|
|||||||

|

|
||||||
[](https://chromewebstore.google.com/detail/pocket-bird/lbbdngkbbgaecefacpnhnhleggabghak)
|
[](https://chromewebstore.google.com/detail/pocket-bird/lbbdngkbbgaecefacpnhnhleggabghak)
|
||||||
[](https://addons.mozilla.org/en-US/firefox/addon/pocket-bird/)
|
[](https://addons.mozilla.org/en-US/firefox/addon/pocket-bird/)
|
||||||
|
[](https://discord.gg/6yxE9prcNc)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
It's a pet bird that hops around your browser, what more could you want?
|
It's a pet bird that hops around your computer, what more could you want?
|
||||||
|
|
||||||
### Get it for [Google Chrome](https://chromewebstore.google.com/detail/pocket-bird/lbbdngkbbgaecefacpnhnhleggabghak)
|
### Get it for [Google Chrome](https://chromewebstore.google.com/detail/pocket-bird/lbbdngkbbgaecefacpnhnhleggabghak)
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ It's a pet bird that hops around your browser, what more could you want?
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- A cute little pixel art bird hops around your webpages as you browse
|
- A cute little pixel art bird hops around your apps and websites
|
||||||
- Collect rare falling feathers to unlock over 10+ different species of birds
|
- Collect rare falling feathers to unlock over 10+ different species of birds
|
||||||
- Add sticky notes that stay on the page even after you refresh
|
- Add sticky notes that stay on the page even after you refresh
|
||||||
- And most importantly, you can pet the bird!
|
- And most importantly, you can pet the bird!
|
||||||
@@ -51,7 +52,7 @@ Simply move your cursor back and forth over your bird until a heart appears! You
|
|||||||
|
|
||||||
### How do I collect feathers?
|
### How do I collect feathers?
|
||||||
|
|
||||||
Feathers will occasionally fall from the top of your browser window. Clicking on a feather will add a new species to your field guide, allowing you to change the appearance of your pet!
|
Feathers will occasionally fall from the top of your window. Clicking on a feather will add a new species to your field guide, allowing you to change the appearance of your pet!
|
||||||
|
|
||||||
### How do I change my bird's appearance?
|
### How do I change my bird's appearance?
|
||||||
|
|
||||||
@@ -67,7 +68,7 @@ Open the Pocket Bird menu by clicking the bird and select "Settings". From there
|
|||||||
|
|
||||||
### Why does Pocket Bird need permission to read and change my data on websites I visit?
|
### Why does Pocket Bird need permission to read and change my data on websites I visit?
|
||||||
|
|
||||||
Pocket Bird 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!
|
||||||
|
|
||||||
## Getting in Touch
|
## Getting in Touch
|
||||||
|
|
||||||
|
|||||||
70
build.js
70
build.js
@@ -12,8 +12,11 @@ const IMAGES_DIR = "./images";
|
|||||||
const FONTS_DIR = "./fonts";
|
const FONTS_DIR = "./fonts";
|
||||||
const DIST_DIR = "./dist";
|
const DIST_DIR = "./dist";
|
||||||
|
|
||||||
const BROWSER_MANIFEST = "./browser-manifest.json";
|
const BROWSER_MANIFEST = "./platform-specific/extension/manifest.json";
|
||||||
const OBSIDIAN_MANIFEST = "./obsidian-manifest.json";
|
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";
|
||||||
@@ -23,8 +26,12 @@ const APPLICATION_ENTRY = SRC_DIR + "/application.js";
|
|||||||
const BUNDLED_OUTPUT = DIST_DIR + "/birb.bundled.js";
|
const BUNDLED_OUTPUT = DIST_DIR + "/birb.bundled.js";
|
||||||
const BIRB_OUTPUT = DIST_DIR + "/birb.js";
|
const BIRB_OUTPUT = DIST_DIR + "/birb.js";
|
||||||
|
|
||||||
|
const MONOCRAFT_URL = "https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf";
|
||||||
|
|
||||||
const VERSION_KEY = "__VERSION__";
|
const VERSION_KEY = "__VERSION__";
|
||||||
const STYLESHEET_KEY = "___STYLESHEET___";
|
const STYLESHEET_KEY = "___STYLESHEET___";
|
||||||
|
const MONOCRAFT_SRC_KEY = "__MONOCRAFT_SRC__";
|
||||||
|
const CODE_KEY = "__CODE__";
|
||||||
|
|
||||||
const spriteSheets = [
|
const spriteSheets = [
|
||||||
{
|
{
|
||||||
@@ -66,6 +73,10 @@ const version = `${versionDate}.${buildNumber}`;
|
|||||||
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
|
||||||
|
// =============================================
|
||||||
|
|
||||||
// Bundle with rollup
|
// Bundle with rollup
|
||||||
const bundle = await rollup({
|
const bundle = await rollup({
|
||||||
input: APPLICATION_ENTRY,
|
input: APPLICATION_ENTRY,
|
||||||
@@ -94,33 +105,27 @@ 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);
|
birbJs = birbJs.replace(STYLESHEET_KEY, stylesheetContent).replace(MONOCRAFT_SRC_KEY, MONOCRAFT_URL);
|
||||||
|
|
||||||
// Build standard javascript file
|
|
||||||
|
// Write bundled JavaScript function
|
||||||
writeFileSync(BIRB_OUTPUT, birbJs);
|
writeFileSync(BIRB_OUTPUT, birbJs);
|
||||||
|
|
||||||
// Build user script
|
// =============================================
|
||||||
const userScriptHeader =
|
// Build userscript
|
||||||
`// ==UserScript==
|
// =============================================
|
||||||
// @name Pocket Bird
|
|
||||||
// @namespace https://idreesinc.com
|
// Get userscript header
|
||||||
// @version ${version}
|
const userScriptHeader = readFileSync(USERSCRIPT_HEADER, 'utf8').replaceAll(VERSION_KEY, version);
|
||||||
// @description It's a bird that hops around your web browser, the future is here
|
|
||||||
// @author Idrees
|
|
||||||
// @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/userscript/birb.user.js
|
|
||||||
// @updateURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/userscript/birb.user.js
|
|
||||||
// @match *://*/*
|
|
||||||
// @grant GM_setValue
|
|
||||||
// @grant GM_getValue
|
|
||||||
// @grant GM_deleteValue
|
|
||||||
// ==/UserScript==
|
|
||||||
|
|
||||||
`;
|
|
||||||
mkdirSync(USERSCRIPT_DIR, { recursive: true });
|
mkdirSync(USERSCRIPT_DIR, { recursive: true });
|
||||||
const userScript = userScriptHeader + birbJs;
|
const userScript = userScriptHeader + "\n" + birbJs;
|
||||||
writeFileSync(USERSCRIPT_DIR + '/birb.user.js', userScript);
|
writeFileSync(USERSCRIPT_DIR + '/birb.user.js', userScript);
|
||||||
|
|
||||||
|
// =============================================
|
||||||
// Build browser extension
|
// Build browser extension
|
||||||
|
// =============================================
|
||||||
|
|
||||||
mkdirSync(EXTENSION_DIR, { recursive: true });
|
mkdirSync(EXTENSION_DIR, { recursive: true });
|
||||||
|
|
||||||
// Copy birb.js
|
// Copy birb.js
|
||||||
@@ -155,26 +160,19 @@ archive.pipe(output);
|
|||||||
archive.directory(EXTENSION_DIR + '/', false);
|
archive.directory(EXTENSION_DIR + '/', false);
|
||||||
archive.finalize();
|
archive.finalize();
|
||||||
|
|
||||||
|
// =============================================
|
||||||
// Build Obsidian plugin
|
// Build Obsidian plugin
|
||||||
|
// =============================================
|
||||||
|
|
||||||
mkdirSync(OBSIDIAN_DIR, { recursive: true });
|
mkdirSync(OBSIDIAN_DIR, { recursive: true });
|
||||||
|
|
||||||
// Wrap birb.js with plugin boilerplate
|
// Wrap birb.js with plugin boilerplate
|
||||||
const obsidianPlugin = `
|
let obsidianPlugin = readFileSync(OBSIDIAN_WRAPPER, 'utf8').replace(VERSION_KEY, version).replace(CODE_KEY, birbJs);
|
||||||
const { Plugin, Notice } = require('obsidian');
|
|
||||||
module.exports = class PocketBird extends Plugin {
|
|
||||||
onload() {
|
|
||||||
console.log("Loading Pocket Bird version ${version}...");
|
|
||||||
const OBSIDIAN_PLUGIN = this;
|
|
||||||
${birbJs}
|
|
||||||
console.log("Pocket Bird loaded!");
|
|
||||||
}
|
|
||||||
|
|
||||||
onunload() {
|
// Encode font to data URI since Obsidian plugins can't have external font files
|
||||||
// Remove the birb when the plugin is unloaded
|
const monocraftFontData = readFileSync(FONTS_DIR + '/Monocraft.otf', 'base64');
|
||||||
document.getElementById('birb')?.remove();
|
const monocraftDataUri = `data:font/otf;base64,${monocraftFontData}`;
|
||||||
console.log('Pocket Bird unloaded!');
|
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);
|
||||||
|
|||||||
204
dist/birb.js
vendored
204
dist/birb.js
vendored
@@ -70,8 +70,9 @@
|
|||||||
* @param {HTMLElement|null} element The element to detect drag events on
|
* @param {HTMLElement|null} element The element to detect drag events on
|
||||||
* @param {boolean} [parent] Whether to move the parent element when the child is dragged
|
* @param {boolean} [parent] Whether to move the parent element when the child is dragged
|
||||||
* @param {(top: number, left: number) => void} [callback] Callback for when element is moved
|
* @param {(top: number, left: number) => void} [callback] Callback for when element is moved
|
||||||
|
* @param {HTMLElement} [pageElement] The page element to constrain movement within
|
||||||
*/
|
*/
|
||||||
function makeDraggable(element, parent = true, callback = () => { }) {
|
function makeDraggable(element, parent = true, callback = () => { }, pageElement) {
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -98,6 +99,7 @@
|
|||||||
offsetX = touch.clientX - elementToMove.offsetLeft;
|
offsetX = touch.clientX - elementToMove.offsetLeft;
|
||||||
offsetY = touch.clientY - elementToMove.offsetTop;
|
offsetY = touch.clientY - elementToMove.offsetTop;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("mouseup", (e) => {
|
document.addEventListener("mouseup", (e) => {
|
||||||
@@ -117,9 +119,12 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("mousemove", (e) => {
|
document.addEventListener("mousemove", (e) => {
|
||||||
|
const page = pageElement || document.documentElement;
|
||||||
|
const maxX = page.scrollWidth - elementToMove.clientWidth;
|
||||||
|
const maxY = page.scrollHeight - elementToMove.clientHeight;
|
||||||
if (isMouseDown) {
|
if (isMouseDown) {
|
||||||
elementToMove.style.left = `${Math.max(0, e.clientX - offsetX)}px`;
|
elementToMove.style.left = `${Math.max(0, Math.min(maxX, e.clientX - offsetX))}px`;
|
||||||
elementToMove.style.top = `${Math.max(0, e.clientY - offsetY)}px`;
|
elementToMove.style.top = `${Math.max(0, Math.min(maxY, e.clientY - offsetY))}px`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -135,8 +140,9 @@
|
|||||||
/**
|
/**
|
||||||
* @param {() => void} func
|
* @param {() => void} func
|
||||||
* @param {Element} [closeButton]
|
* @param {Element} [closeButton]
|
||||||
|
* @param {boolean} [allowEscape] Whether to allow closing with the Escape key
|
||||||
*/
|
*/
|
||||||
function makeClosable(func, closeButton) {
|
function makeClosable(func, closeButton, allowEscape = true) {
|
||||||
if (closeButton) {
|
if (closeButton) {
|
||||||
onClick(closeButton, func);
|
onClick(closeButton, func);
|
||||||
}
|
}
|
||||||
@@ -144,7 +150,7 @@
|
|||||||
if (closeButton && !document.body.contains(closeButton)) {
|
if (closeButton && !document.body.contains(closeButton)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (e.key === "Escape") {
|
if (allowEscape && e.key === "Escape") {
|
||||||
func();
|
func();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -840,6 +846,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SAVE_KEY = "birbSaveData";
|
const SAVE_KEY = "birbSaveData";
|
||||||
|
const ROOT_PATH = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('./application.js').BirbSaveData} BirbSaveData
|
* @typedef {import('./application.js').BirbSaveData} BirbSaveData
|
||||||
@@ -900,6 +907,14 @@
|
|||||||
return window.location.href;
|
return window.location.href;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {HTMLElement} The current active page element where sticky notes can be applied
|
||||||
|
*/
|
||||||
|
getActivePage() {
|
||||||
|
// Default to root element
|
||||||
|
return document.documentElement;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a path is applicable given the context
|
* Checks if a path is applicable given the context
|
||||||
* @param {string} path Can be a site URL or another context-specific path
|
* @param {string} path Can be a site URL or another context-specific path
|
||||||
@@ -1062,7 +1077,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ObsidianContext extends Context {
|
class ObsidianContext extends Context {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
@@ -1077,8 +1091,12 @@
|
|||||||
* @returns {Promise<BirbSaveData|{}>}
|
* @returns {Promise<BirbSaveData|{}>}
|
||||||
*/
|
*/
|
||||||
async getSaveData() {
|
async getSaveData() {
|
||||||
// @ts-expect-error
|
return new Promise((resolve) => {
|
||||||
return await OBSIDIAN_PLUGIN.loadData() ?? {};
|
// @ts-expect-error
|
||||||
|
OBSIDIAN_PLUGIN.loadData().then((data) => {
|
||||||
|
resolve(data ?? {});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1087,7 +1105,7 @@
|
|||||||
*/
|
*/
|
||||||
async putSaveData(saveData) {
|
async putSaveData(saveData) {
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
return await OBSIDIAN_PLUGIN.saveData(saveData);
|
await OBSIDIAN_PLUGIN.saveData(saveData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
@@ -1095,24 +1113,54 @@
|
|||||||
this.putSaveData({});
|
this.putSaveData({});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
|
||||||
getFocusElementTopMargin() {
|
|
||||||
return 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
getFocusableElements() {
|
getFocusableElements() {
|
||||||
const elements = [
|
const elements = [
|
||||||
".workspace-leaf",
|
".workspace-leaf",
|
||||||
".cm-callout",
|
".cm-callout",
|
||||||
".HyperMD-codeblock-begin"
|
".HyperMD-codeblock-begin",
|
||||||
|
".status-bar",
|
||||||
|
".mobile-navbar"
|
||||||
];
|
];
|
||||||
return super.getFocusableElements().concat(elements);
|
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 */
|
/** @override */
|
||||||
areStickyNotesEnabled() {
|
areStickyNotesEnabled() {
|
||||||
return false;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1176,13 +1224,17 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {StickyNote} stickyNote
|
* @param {StickyNote} stickyNote
|
||||||
|
* @param {HTMLElement} page
|
||||||
* @param {() => void} onSave
|
* @param {() => void} onSave
|
||||||
* @param {() => void} onDelete
|
* @param {() => void} onDelete
|
||||||
* @returns {HTMLElement}
|
* @returns {HTMLElement}
|
||||||
*/
|
*/
|
||||||
function renderStickyNote(stickyNote, onSave, onDelete) {
|
function renderStickyNote(stickyNote, page, onSave, onDelete) {
|
||||||
const noteElement = makeElement("birb-window");
|
const noteElement = makeElement("birb-window");
|
||||||
noteElement.classList.add("birb-sticky-note");
|
noteElement.classList.add("birb-sticky-note");
|
||||||
|
const color = getColor(stickyNote.id);
|
||||||
|
noteElement.style.setProperty("--birb-highlight", color);
|
||||||
|
noteElement.style.setProperty("--birb-border-color", color);
|
||||||
|
|
||||||
// Create header
|
// Create header
|
||||||
const header = makeElement("birb-window-header");
|
const header = makeElement("birb-window-header");
|
||||||
@@ -1205,13 +1257,13 @@
|
|||||||
|
|
||||||
noteElement.style.top = `${stickyNote.top}px`;
|
noteElement.style.top = `${stickyNote.top}px`;
|
||||||
noteElement.style.left = `${stickyNote.left}px`;
|
noteElement.style.left = `${stickyNote.left}px`;
|
||||||
document.body.appendChild(noteElement);
|
page.appendChild(noteElement);
|
||||||
|
|
||||||
makeDraggable(header, true, (top, left) => {
|
makeDraggable(header, true, (top, left) => {
|
||||||
stickyNote.top = top;
|
stickyNote.top = top;
|
||||||
stickyNote.left = left;
|
stickyNote.left = left;
|
||||||
onSave();
|
onSave();
|
||||||
});
|
}, page);
|
||||||
|
|
||||||
if (closeButton) {
|
if (closeButton) {
|
||||||
makeClosable(() => {
|
makeClosable(() => {
|
||||||
@@ -1219,7 +1271,7 @@
|
|||||||
onDelete();
|
onDelete();
|
||||||
noteElement.remove();
|
noteElement.remove();
|
||||||
}
|
}
|
||||||
}, closeButton);
|
}, closeButton, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (textarea && textarea instanceof HTMLTextAreaElement) {
|
if (textarea && textarea instanceof HTMLTextAreaElement) {
|
||||||
@@ -1256,10 +1308,11 @@
|
|||||||
const existingNotes = document.querySelectorAll(".birb-sticky-note");
|
const existingNotes = document.querySelectorAll(".birb-sticky-note");
|
||||||
existingNotes.forEach(note => note.remove());
|
existingNotes.forEach(note => note.remove());
|
||||||
// Render all sticky notes
|
// Render all sticky notes
|
||||||
|
const pageElement = getContext().getActivePage();
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
for (let stickyNote of stickyNotes) {
|
for (let stickyNote of stickyNotes) {
|
||||||
if (context.isPathApplicable(stickyNote.site)) {
|
if (context.isPathApplicable(stickyNote.site)) {
|
||||||
renderStickyNote(stickyNote, onSave, () => onDelete(stickyNote));
|
renderStickyNote(stickyNote, pageElement, onSave, () => onDelete(stickyNote));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1270,18 +1323,33 @@
|
|||||||
* @param {(note: StickyNote) => void} onDelete
|
* @param {(note: StickyNote) => void} onDelete
|
||||||
*/
|
*/
|
||||||
function createNewStickyNote(stickyNotes, onSave, onDelete) {
|
function createNewStickyNote(stickyNotes, onSave, onDelete) {
|
||||||
|
if (getContext().areStickyNotesEnabled() === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const id = Date.now().toString();
|
const id = Date.now().toString();
|
||||||
const site = getContext().getPath();
|
const site = getContext().getPath();
|
||||||
const stickyNote = new StickyNote(id, site, "");
|
const stickyNote = new StickyNote(id, site, "");
|
||||||
const element = renderStickyNote(stickyNote, onSave, () => onDelete(stickyNote));
|
const page = getContext().getActivePage();
|
||||||
element.style.left = `${window.innerWidth / 2 - element.offsetWidth / 2}px`;
|
const element = renderStickyNote(stickyNote, page, onSave, () => onDelete(stickyNote));
|
||||||
element.style.top = `${window.scrollY + window.innerHeight / 2 - element.offsetHeight / 2}px`;
|
element.style.left = `${page.clientWidth / 2 - element.offsetWidth / 2}px`;
|
||||||
|
element.style.top = `${page.scrollTop + page.clientHeight / 2 - element.offsetHeight / 2}px`;
|
||||||
stickyNote.top = parseInt(element.style.top, 10);
|
stickyNote.top = parseInt(element.style.top, 10);
|
||||||
stickyNote.left = parseInt(element.style.left, 10);
|
stickyNote.left = parseInt(element.style.left, 10);
|
||||||
stickyNotes.push(stickyNote);
|
stickyNotes.push(stickyNote);
|
||||||
onSave();
|
onSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a color based on the mod of the sticky note ID
|
||||||
|
* @param {string} id
|
||||||
|
* @returns {string} A color hex code
|
||||||
|
*/
|
||||||
|
function getColor(id) {
|
||||||
|
const colors = ["#ff8baa", "#79bcff", "#d18bff", "#6de192", "#ffd17c", "#ffb37c", "#ff7c7c"];
|
||||||
|
const index = parseInt(id, 10) % colors.length;
|
||||||
|
return colors[index];
|
||||||
|
}
|
||||||
|
|
||||||
const MENU_ID = "birb-menu";
|
const MENU_ID = "birb-menu";
|
||||||
const MENU_EXIT_ID = "birb-menu-exit";
|
const MENU_EXIT_ID = "birb-menu-exit";
|
||||||
|
|
||||||
@@ -1290,23 +1358,34 @@
|
|||||||
* @param {string} text
|
* @param {string} text
|
||||||
* @param {() => void} action
|
* @param {() => void} action
|
||||||
* @param {boolean} [removeMenu]
|
* @param {boolean} [removeMenu]
|
||||||
* @param {boolean} [isDebug]
|
|
||||||
*/
|
*/
|
||||||
constructor(text, action, removeMenu = true, isDebug = false) {
|
constructor(text, action, removeMenu = true) {
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.removeMenu = removeMenu;
|
this.removeMenu = removeMenu;
|
||||||
this.isDebug = isDebug;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DebugMenuItem extends MenuItem {
|
class ConditionalMenuItem extends MenuItem {
|
||||||
|
/**
|
||||||
|
* @param {string} text
|
||||||
|
* @param {() => void} action
|
||||||
|
* @param {() => boolean} condition
|
||||||
|
* @param {boolean} [removeMenu]
|
||||||
|
*/
|
||||||
|
constructor(text, action, condition, removeMenu = true) {
|
||||||
|
super(text, action, removeMenu);
|
||||||
|
this.condition = condition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DebugMenuItem extends ConditionalMenuItem {
|
||||||
/**
|
/**
|
||||||
* @param {string} text
|
* @param {string} text
|
||||||
* @param {() => void} action
|
* @param {() => void} action
|
||||||
*/
|
*/
|
||||||
constructor(text, action, removeMenu = true) {
|
constructor(text, action, removeMenu = true) {
|
||||||
super(text, action, removeMenu, true);
|
super(text, action, () => isDebug(), removeMenu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1352,7 +1431,7 @@
|
|||||||
let content = makeElement("birb-window-content");
|
let content = makeElement("birb-window-content");
|
||||||
const removeCallback = () => removeMenu();
|
const removeCallback = () => removeMenu();
|
||||||
for (const item of menuItems) {
|
for (const item of menuItems) {
|
||||||
if (!item.isDebug || isDebug()) {
|
if (!(item instanceof ConditionalMenuItem) || item.condition()) {
|
||||||
content.appendChild(makeMenuItem(item, removeCallback));
|
content.appendChild(makeMenuItem(item, removeCallback));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1409,7 +1488,7 @@
|
|||||||
}
|
}
|
||||||
const removeCallback = () => removeMenu();
|
const removeCallback = () => removeMenu();
|
||||||
for (const item of menuItems) {
|
for (const item of menuItems) {
|
||||||
if (!item.isDebug || isDebug()) {
|
if (!(item instanceof ConditionalMenuItem) || item.condition()) {
|
||||||
content.appendChild(makeMenuItem(item, removeCallback));
|
content.appendChild(makeMenuItem(item, removeCallback));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1445,7 +1524,14 @@
|
|||||||
const WINDOW_PIXEL_SIZE = CANVAS_PIXEL_SIZE * BIRB_CSS_SCALE;
|
const WINDOW_PIXEL_SIZE = CANVAS_PIXEL_SIZE * BIRB_CSS_SCALE;
|
||||||
|
|
||||||
// Build-time assets
|
// Build-time assets
|
||||||
const STYLESHEET = `:root {
|
const STYLESHEET = `@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url("https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf") format('opentype');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
--birb-border-size: 2px;
|
--birb-border-size: 2px;
|
||||||
--birb-neg-border-size: calc(var(--birb-border-size) * -1);
|
--birb-neg-border-size: calc(var(--birb-border-size) * -1);
|
||||||
--birb-double-border-size: calc(var(--birb-border-size) * 2);
|
--birb-double-border-size: calc(var(--birb-border-size) * 2);
|
||||||
@@ -1566,6 +1652,7 @@
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
color: var(--birb-background-color);
|
color: var(--birb-background-color);
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.birb-window-close {
|
.birb-window-close {
|
||||||
@@ -1764,6 +1851,17 @@
|
|||||||
.birb-sticky-note {
|
.birb-sticky-note {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
animation: fade-in 0.15s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.birb-sticky-note > .birb-window-content {
|
.birb-sticky-note > .birb-window-content {
|
||||||
@@ -1786,7 +1884,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.birb-sticky-note-input:focus {
|
.birb-sticky-note-input:focus {
|
||||||
outline: none;
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
}`;
|
}`;
|
||||||
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABD5JREFUeJztnTFrFEEYht9JLAJidwju2YpdBAvzAyIWaXJXpRS0MBCwEBTJDwghhaAgGLTSyupMY2UqG9PYWQRb7yJyYJEIacxnkZ11bm5n9+7Y3Zm9ex8Imezd7Te7O9+zM7N7G4AQQgghhBBCCJkJlO8KkPAREXG9ppRiGyK1hY23BvgUkI7dbjYBAJ1ud6BcRR0IITOKxLSiSFpRNFTOkmNR8VtRJF8WF0U2NobKZccnpEzmfFeA5NNuNvG00UCn3R4qV8nB58942mgkZULqDgVYI3wJqNPtYrvfH1i23e8nQ2BCCCkFcwj8ZXEx+alqCJxWhypjE0ICQFKoOrZPAZl1oPwImTFE5Hzy3/hddXzfAvIhf0LK5ILvCtSNgxs3vMRVSikREZ+3nvB2F0JmFN3z0b0/9oKqx9cUBJleeEYfAzPp2BuqFr3v9W4XkcqPgS1dtoEZIe0CAM/AxAOy220JAG/zn3HsoNs/83R0cu8DNM+85g9yvqJVJBQwAYDdbksXvcx/KqWSOoTW+7Pzwkee1pHMiyDmzjQaH/QyETHfU0qDsIc+xnKIiITWEEl5PGh+8HqsfQp4FMxUWNvpJcvoPzdOAZriOVy7DzwCdm6/SV7f7bYH5mPKkFEIAiZE41vAGYhSKpHetHNlXsnRXynkWDhXIiIydzEaWHbveQ8f1+ew8uoMAHDy+wgA8P5JNHCWKUJGQwLGoIBvrbTxoPlBv7ewuITUDHGJ7/uPY3x9cd3LBaOyuDKvZOXVGT6uz6EICWYKELGA7r9O70JrASKWIAwZpQYb4yD4FjAJm7Wdnrx/Es36cc6VX6jD9VBwDoH1jbeu1035wZpzSGOSYfLZn96QgLX87Nj2cNy1TaPGJuFwurcsC6v7SpcBYGHVr/x8C3htp+d1Ys8VP+4I1SbPMisaCwune8vY+PUJAPDy8m0AwN3DdyMF+P7jGAAm6orr+Gk9UFvAGt0TTVkXQAnWlv/i26/8+KULuPp6mLgEZOZbySJy9j7rJMGRBWizsLqPmw8Pce3qpdTPWgdiIgH5FjAhmlDEpzndWxYzB+x8q0BA4sr/mRAgDAmmYYsPE/S+fAuYkJDpby3JxoUOMDjyqap9OwWIGkkwV4CI5/VsCZ18OwEANDYPXJ/9H2RC6fgWMCGh099aShr4nZ9vgfO2712C5oXJkPMut2JpEtLyS6OxeVDYhvsWMCEkF9GdEFuEWoIh599Ij8OKNwL9raXM9xUpP2RciTYFbNep6DoQQjJRX19cP084hwhDJleAWkJ5EixTPDo2UoRXVR0IIU4UzofeAyKcKsynYXSePU6eiqHLZT6gwPqid2r8sutACMnHfmJO6Pk41n+FU0qh8+xx8rdZRom9Lr3erPjs+RESBvGXEYAa5ONYj8Q3h6J2uQry4oe+swmZduqWg2Pfl+dcUQUb7js+IWS6+Ac8zd6eLzTjoQAAAABJRU5ErkJggg==";
|
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABD5JREFUeJztnTFrFEEYht9JLAJidwju2YpdBAvzAyIWaXJXpRS0MBCwEBTJDwghhaAgGLTSyupMY2UqG9PYWQRb7yJyYJEIacxnkZ11bm5n9+7Y3Zm9ex8Imezd7Te7O9+zM7N7G4AQQgghhBBCCJkJlO8KkPAREXG9ppRiGyK1hY23BvgUkI7dbjYBAJ1ud6BcRR0IITOKxLSiSFpRNFTOkmNR8VtRJF8WF0U2NobKZccnpEzmfFeA5NNuNvG00UCn3R4qV8nB58942mgkZULqDgVYI3wJqNPtYrvfH1i23e8nQ2BCCCkFcwj8ZXEx+alqCJxWhypjE0ICQFKoOrZPAZl1oPwImTFE5Hzy3/hddXzfAvIhf0LK5ILvCtSNgxs3vMRVSikREZ+3nvB2F0JmFN3z0b0/9oKqx9cUBJleeEYfAzPp2BuqFr3v9W4XkcqPgS1dtoEZIe0CAM/AxAOy220JAG/zn3HsoNs/83R0cu8DNM+85g9yvqJVJBQwAYDdbksXvcx/KqWSOoTW+7Pzwkee1pHMiyDmzjQaH/QyETHfU0qDsIc+xnKIiITWEEl5PGh+8HqsfQp4FMxUWNvpJcvoPzdOAZriOVy7DzwCdm6/SV7f7bYH5mPKkFEIAiZE41vAGYhSKpHetHNlXsnRXynkWDhXIiIydzEaWHbveQ8f1+ew8uoMAHDy+wgA8P5JNHCWKUJGQwLGoIBvrbTxoPlBv7ewuITUDHGJ7/uPY3x9cd3LBaOyuDKvZOXVGT6uz6EICWYKELGA7r9O70JrASKWIAwZpQYb4yD4FjAJm7Wdnrx/Es36cc6VX6jD9VBwDoH1jbeu1035wZpzSGOSYfLZn96QgLX87Nj2cNy1TaPGJuFwurcsC6v7SpcBYGHVr/x8C3htp+d1Ys8VP+4I1SbPMisaCwune8vY+PUJAPDy8m0AwN3DdyMF+P7jGAAm6orr+Gk9UFvAGt0TTVkXQAnWlv/i26/8+KULuPp6mLgEZOZbySJy9j7rJMGRBWizsLqPmw8Pce3qpdTPWgdiIgH5FjAhmlDEpzndWxYzB+x8q0BA4sr/mRAgDAmmYYsPE/S+fAuYkJDpby3JxoUOMDjyqap9OwWIGkkwV4CI5/VsCZ18OwEANDYPXJ/9H2RC6fgWMCGh099aShr4nZ9vgfO2712C5oXJkPMut2JpEtLyS6OxeVDYhvsWMCEkF9GdEFuEWoIh599Ij8OKNwL9raXM9xUpP2RciTYFbNep6DoQQjJRX19cP084hwhDJleAWkJ5EixTPDo2UoRXVR0IIU4UzofeAyKcKsynYXSePU6eiqHLZT6gwPqid2r8sutACMnHfmJO6Pk41n+FU0qh8+xx8rdZRom9Lr3erPjs+RESBvGXEYAa5ONYj8Q3h6J2uQry4oe+swmZduqWg2Pfl+dcUQUb7js+IWS6+Ac8zd6eLzTjoQAAAABJRU5ErkJggg==";
|
||||||
const FEATHER_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAARhJREFUWIXtlbENwjAQRf8hSiZIRQ+9WQNRUFIAKzACBSsAA1Ag1mAABqCCBomG3hQQ9OMEx4ZDNH5SikSJ3/fZ5wCJRCKRSPwZ0RzMWmtLAhGvQyUAi9mXP/aFaGjJRQQiguHihMvcFMJUVUYlAMuHixPGy4en1WmVQqgHYHkuZjiEj6a2/LjtYzTY0eiZbgC37Mxh1UN3sn/dr6cCz/LHB/DJj9s+2oMdbtdz6TtfFwQHcMvOInfmQNjsgchNWLXmdfK6gyioAu/6uKrsm1kWLAciKuCuey5nYuXAh234bdmZ6INIUw4E/Ix49xtjCmXfzLL8nY/ktdgnAKwxxgIoXIyqmAOwvIqfiN0ALNd21HYBO9XXGMAdnZTYyHWzWjQAAAAASUVORK5CYII=";
|
const FEATHER_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAARhJREFUWIXtlbENwjAQRf8hSiZIRQ+9WQNRUFIAKzACBSsAA1Ag1mAABqCCBomG3hQQ9OMEx4ZDNH5SikSJ3/fZ5wCJRCKRSPwZ0RzMWmtLAhGvQyUAi9mXP/aFaGjJRQQiguHihMvcFMJUVUYlAMuHixPGy4en1WmVQqgHYHkuZjiEj6a2/LjtYzTY0eiZbgC37Mxh1UN3sn/dr6cCz/LHB/DJj9s+2oMdbtdz6TtfFwQHcMvOInfmQNjsgchNWLXmdfK6gyioAu/6uKrsm1kWLAciKuCuey5nYuXAh234bdmZ6INIUw4E/Ix49xtjCmXfzLL8nY/ktdgnAKwxxgIoXIyqmAOwvIqfiN0ALNd21HYBO9XXGMAdnZTYyHWzWjQAAAAASUVORK5CYII=";
|
||||||
@@ -1807,7 +1906,7 @@
|
|||||||
const AFK_TIME = isDebug() ? 0 : 1000 * 5;
|
const AFK_TIME = isDebug() ? 0 : 1000 * 5;
|
||||||
const PET_BOOST_DURATION = 1000 * 60 * 5;
|
const PET_BOOST_DURATION = 1000 * 60 * 5;
|
||||||
const PET_MENU_COOLDOWN = 1000;
|
const PET_MENU_COOLDOWN = 1000;
|
||||||
const URL_CHECK_INTERVAL = 500;
|
const URL_CHECK_INTERVAL = 150;
|
||||||
const HOP_DELAY = 500;
|
const HOP_DELAY = 500;
|
||||||
|
|
||||||
// Random event chances per tick
|
// Random event chances per tick
|
||||||
@@ -1910,9 +2009,7 @@
|
|||||||
const menuItems = [
|
const menuItems = [
|
||||||
new MenuItem(`Pet ${birdBirb()}`, pet),
|
new MenuItem(`Pet ${birdBirb()}`, pet),
|
||||||
new MenuItem("Field Guide", insertFieldGuide),
|
new MenuItem("Field Guide", insertFieldGuide),
|
||||||
...(getContext().areStickyNotesEnabled() ? [
|
new ConditionalMenuItem("Sticky Note", () => createNewStickyNote(stickyNotes, save, deleteStickyNote), () => getContext().areStickyNotesEnabled()),
|
||||||
new MenuItem("Sticky Note", () => createNewStickyNote(stickyNotes, save, deleteStickyNote))
|
|
||||||
] : []),
|
|
||||||
new MenuItem(`Hide ${birdBirb()}`, () => birb.setVisible(false)),
|
new MenuItem(`Hide ${birdBirb()}`, () => birb.setVisible(false)),
|
||||||
new DebugMenuItem("Freeze/Unfreeze", () => {
|
new DebugMenuItem("Freeze/Unfreeze", () => {
|
||||||
frozen = !frozen;
|
frozen = !frozen;
|
||||||
@@ -1949,7 +2046,7 @@
|
|||||||
insertModal(`${birdBirb()} Mode`, message);
|
insertModal(`${birdBirb()} Mode`, message);
|
||||||
}),
|
}),
|
||||||
new Separator(),
|
new Separator(),
|
||||||
new MenuItem("2025.11.13.27", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.11.13.27"); }, false),
|
new MenuItem("2025.11.14.205", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.11.14.205"); }, false),
|
||||||
];
|
];
|
||||||
|
|
||||||
const styleElement = document.createElement("style");
|
const styleElement = document.createElement("style");
|
||||||
@@ -2066,31 +2163,6 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preload font
|
|
||||||
const MONOCRAFT_SRC = "https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf";
|
|
||||||
const fontLink = document.createElement("link");
|
|
||||||
fontLink.rel = "stylesheet";
|
|
||||||
fontLink.href = `url(${MONOCRAFT_SRC}) format('opentype')`;
|
|
||||||
document.head.appendChild(fontLink);
|
|
||||||
|
|
||||||
// Add stylesheet font-face
|
|
||||||
const fontFace = `
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Monocraft';
|
|
||||||
src: url(${MONOCRAFT_SRC}) format('opentype');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const fontStyle = document.createElement("style");
|
|
||||||
fontStyle.textContent = fontFace;
|
|
||||||
document.head.appendChild(fontStyle);
|
|
||||||
} catch (e) {
|
|
||||||
error("Failed to load font: " + e);
|
|
||||||
}
|
|
||||||
|
|
||||||
load().then(onLoad);
|
load().then(onLoad);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2148,7 +2220,7 @@
|
|||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
const currentPath = getContext().getPath().split("?")[0];
|
const currentPath = getContext().getPath().split("?")[0];
|
||||||
if (currentPath !== lastPath) {
|
if (currentPath !== lastPath) {
|
||||||
log("Path changed, updating sticky notes");
|
log("Path changed, updating sticky notes: " + currentPath);
|
||||||
lastPath = currentPath;
|
lastPath = currentPath;
|
||||||
drawStickyNotes(stickyNotes, save, deleteStickyNote);
|
drawStickyNotes(stickyNotes, save, deleteStickyNote);
|
||||||
}
|
}
|
||||||
@@ -2593,7 +2665,11 @@
|
|||||||
// @ts-expect-error
|
// @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 nonFixedElements = largeElements.filter((el) => {
|
const nonFixedElements = largeElements.filter((el) => {
|
||||||
|
if (fixedAllowed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const style = window.getComputedStyle(el);
|
const style = window.getComputedStyle(el);
|
||||||
return style.position !== "fixed" && style.position !== "sticky";
|
return style.position !== "fixed" && style.position !== "sticky";
|
||||||
});
|
});
|
||||||
|
|||||||
BIN
dist/extension.zip
vendored
BIN
dist/extension.zip
vendored
Binary file not shown.
204
dist/extension/birb.js
vendored
204
dist/extension/birb.js
vendored
@@ -70,8 +70,9 @@
|
|||||||
* @param {HTMLElement|null} element The element to detect drag events on
|
* @param {HTMLElement|null} element The element to detect drag events on
|
||||||
* @param {boolean} [parent] Whether to move the parent element when the child is dragged
|
* @param {boolean} [parent] Whether to move the parent element when the child is dragged
|
||||||
* @param {(top: number, left: number) => void} [callback] Callback for when element is moved
|
* @param {(top: number, left: number) => void} [callback] Callback for when element is moved
|
||||||
|
* @param {HTMLElement} [pageElement] The page element to constrain movement within
|
||||||
*/
|
*/
|
||||||
function makeDraggable(element, parent = true, callback = () => { }) {
|
function makeDraggable(element, parent = true, callback = () => { }, pageElement) {
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -98,6 +99,7 @@
|
|||||||
offsetX = touch.clientX - elementToMove.offsetLeft;
|
offsetX = touch.clientX - elementToMove.offsetLeft;
|
||||||
offsetY = touch.clientY - elementToMove.offsetTop;
|
offsetY = touch.clientY - elementToMove.offsetTop;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("mouseup", (e) => {
|
document.addEventListener("mouseup", (e) => {
|
||||||
@@ -117,9 +119,12 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("mousemove", (e) => {
|
document.addEventListener("mousemove", (e) => {
|
||||||
|
const page = pageElement || document.documentElement;
|
||||||
|
const maxX = page.scrollWidth - elementToMove.clientWidth;
|
||||||
|
const maxY = page.scrollHeight - elementToMove.clientHeight;
|
||||||
if (isMouseDown) {
|
if (isMouseDown) {
|
||||||
elementToMove.style.left = `${Math.max(0, e.clientX - offsetX)}px`;
|
elementToMove.style.left = `${Math.max(0, Math.min(maxX, e.clientX - offsetX))}px`;
|
||||||
elementToMove.style.top = `${Math.max(0, e.clientY - offsetY)}px`;
|
elementToMove.style.top = `${Math.max(0, Math.min(maxY, e.clientY - offsetY))}px`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -135,8 +140,9 @@
|
|||||||
/**
|
/**
|
||||||
* @param {() => void} func
|
* @param {() => void} func
|
||||||
* @param {Element} [closeButton]
|
* @param {Element} [closeButton]
|
||||||
|
* @param {boolean} [allowEscape] Whether to allow closing with the Escape key
|
||||||
*/
|
*/
|
||||||
function makeClosable(func, closeButton) {
|
function makeClosable(func, closeButton, allowEscape = true) {
|
||||||
if (closeButton) {
|
if (closeButton) {
|
||||||
onClick(closeButton, func);
|
onClick(closeButton, func);
|
||||||
}
|
}
|
||||||
@@ -144,7 +150,7 @@
|
|||||||
if (closeButton && !document.body.contains(closeButton)) {
|
if (closeButton && !document.body.contains(closeButton)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (e.key === "Escape") {
|
if (allowEscape && e.key === "Escape") {
|
||||||
func();
|
func();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -840,6 +846,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SAVE_KEY = "birbSaveData";
|
const SAVE_KEY = "birbSaveData";
|
||||||
|
const ROOT_PATH = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('./application.js').BirbSaveData} BirbSaveData
|
* @typedef {import('./application.js').BirbSaveData} BirbSaveData
|
||||||
@@ -900,6 +907,14 @@
|
|||||||
return window.location.href;
|
return window.location.href;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {HTMLElement} The current active page element where sticky notes can be applied
|
||||||
|
*/
|
||||||
|
getActivePage() {
|
||||||
|
// Default to root element
|
||||||
|
return document.documentElement;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a path is applicable given the context
|
* Checks if a path is applicable given the context
|
||||||
* @param {string} path Can be a site URL or another context-specific path
|
* @param {string} path Can be a site URL or another context-specific path
|
||||||
@@ -1062,7 +1077,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ObsidianContext extends Context {
|
class ObsidianContext extends Context {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
@@ -1077,8 +1091,12 @@
|
|||||||
* @returns {Promise<BirbSaveData|{}>}
|
* @returns {Promise<BirbSaveData|{}>}
|
||||||
*/
|
*/
|
||||||
async getSaveData() {
|
async getSaveData() {
|
||||||
// @ts-expect-error
|
return new Promise((resolve) => {
|
||||||
return await OBSIDIAN_PLUGIN.loadData() ?? {};
|
// @ts-expect-error
|
||||||
|
OBSIDIAN_PLUGIN.loadData().then((data) => {
|
||||||
|
resolve(data ?? {});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1087,7 +1105,7 @@
|
|||||||
*/
|
*/
|
||||||
async putSaveData(saveData) {
|
async putSaveData(saveData) {
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
return await OBSIDIAN_PLUGIN.saveData(saveData);
|
await OBSIDIAN_PLUGIN.saveData(saveData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
@@ -1095,24 +1113,54 @@
|
|||||||
this.putSaveData({});
|
this.putSaveData({});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
|
||||||
getFocusElementTopMargin() {
|
|
||||||
return 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
getFocusableElements() {
|
getFocusableElements() {
|
||||||
const elements = [
|
const elements = [
|
||||||
".workspace-leaf",
|
".workspace-leaf",
|
||||||
".cm-callout",
|
".cm-callout",
|
||||||
".HyperMD-codeblock-begin"
|
".HyperMD-codeblock-begin",
|
||||||
|
".status-bar",
|
||||||
|
".mobile-navbar"
|
||||||
];
|
];
|
||||||
return super.getFocusableElements().concat(elements);
|
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 */
|
/** @override */
|
||||||
areStickyNotesEnabled() {
|
areStickyNotesEnabled() {
|
||||||
return false;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1176,13 +1224,17 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {StickyNote} stickyNote
|
* @param {StickyNote} stickyNote
|
||||||
|
* @param {HTMLElement} page
|
||||||
* @param {() => void} onSave
|
* @param {() => void} onSave
|
||||||
* @param {() => void} onDelete
|
* @param {() => void} onDelete
|
||||||
* @returns {HTMLElement}
|
* @returns {HTMLElement}
|
||||||
*/
|
*/
|
||||||
function renderStickyNote(stickyNote, onSave, onDelete) {
|
function renderStickyNote(stickyNote, page, onSave, onDelete) {
|
||||||
const noteElement = makeElement("birb-window");
|
const noteElement = makeElement("birb-window");
|
||||||
noteElement.classList.add("birb-sticky-note");
|
noteElement.classList.add("birb-sticky-note");
|
||||||
|
const color = getColor(stickyNote.id);
|
||||||
|
noteElement.style.setProperty("--birb-highlight", color);
|
||||||
|
noteElement.style.setProperty("--birb-border-color", color);
|
||||||
|
|
||||||
// Create header
|
// Create header
|
||||||
const header = makeElement("birb-window-header");
|
const header = makeElement("birb-window-header");
|
||||||
@@ -1205,13 +1257,13 @@
|
|||||||
|
|
||||||
noteElement.style.top = `${stickyNote.top}px`;
|
noteElement.style.top = `${stickyNote.top}px`;
|
||||||
noteElement.style.left = `${stickyNote.left}px`;
|
noteElement.style.left = `${stickyNote.left}px`;
|
||||||
document.body.appendChild(noteElement);
|
page.appendChild(noteElement);
|
||||||
|
|
||||||
makeDraggable(header, true, (top, left) => {
|
makeDraggable(header, true, (top, left) => {
|
||||||
stickyNote.top = top;
|
stickyNote.top = top;
|
||||||
stickyNote.left = left;
|
stickyNote.left = left;
|
||||||
onSave();
|
onSave();
|
||||||
});
|
}, page);
|
||||||
|
|
||||||
if (closeButton) {
|
if (closeButton) {
|
||||||
makeClosable(() => {
|
makeClosable(() => {
|
||||||
@@ -1219,7 +1271,7 @@
|
|||||||
onDelete();
|
onDelete();
|
||||||
noteElement.remove();
|
noteElement.remove();
|
||||||
}
|
}
|
||||||
}, closeButton);
|
}, closeButton, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (textarea && textarea instanceof HTMLTextAreaElement) {
|
if (textarea && textarea instanceof HTMLTextAreaElement) {
|
||||||
@@ -1256,10 +1308,11 @@
|
|||||||
const existingNotes = document.querySelectorAll(".birb-sticky-note");
|
const existingNotes = document.querySelectorAll(".birb-sticky-note");
|
||||||
existingNotes.forEach(note => note.remove());
|
existingNotes.forEach(note => note.remove());
|
||||||
// Render all sticky notes
|
// Render all sticky notes
|
||||||
|
const pageElement = getContext().getActivePage();
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
for (let stickyNote of stickyNotes) {
|
for (let stickyNote of stickyNotes) {
|
||||||
if (context.isPathApplicable(stickyNote.site)) {
|
if (context.isPathApplicable(stickyNote.site)) {
|
||||||
renderStickyNote(stickyNote, onSave, () => onDelete(stickyNote));
|
renderStickyNote(stickyNote, pageElement, onSave, () => onDelete(stickyNote));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1270,18 +1323,33 @@
|
|||||||
* @param {(note: StickyNote) => void} onDelete
|
* @param {(note: StickyNote) => void} onDelete
|
||||||
*/
|
*/
|
||||||
function createNewStickyNote(stickyNotes, onSave, onDelete) {
|
function createNewStickyNote(stickyNotes, onSave, onDelete) {
|
||||||
|
if (getContext().areStickyNotesEnabled() === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const id = Date.now().toString();
|
const id = Date.now().toString();
|
||||||
const site = getContext().getPath();
|
const site = getContext().getPath();
|
||||||
const stickyNote = new StickyNote(id, site, "");
|
const stickyNote = new StickyNote(id, site, "");
|
||||||
const element = renderStickyNote(stickyNote, onSave, () => onDelete(stickyNote));
|
const page = getContext().getActivePage();
|
||||||
element.style.left = `${window.innerWidth / 2 - element.offsetWidth / 2}px`;
|
const element = renderStickyNote(stickyNote, page, onSave, () => onDelete(stickyNote));
|
||||||
element.style.top = `${window.scrollY + window.innerHeight / 2 - element.offsetHeight / 2}px`;
|
element.style.left = `${page.clientWidth / 2 - element.offsetWidth / 2}px`;
|
||||||
|
element.style.top = `${page.scrollTop + page.clientHeight / 2 - element.offsetHeight / 2}px`;
|
||||||
stickyNote.top = parseInt(element.style.top, 10);
|
stickyNote.top = parseInt(element.style.top, 10);
|
||||||
stickyNote.left = parseInt(element.style.left, 10);
|
stickyNote.left = parseInt(element.style.left, 10);
|
||||||
stickyNotes.push(stickyNote);
|
stickyNotes.push(stickyNote);
|
||||||
onSave();
|
onSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a color based on the mod of the sticky note ID
|
||||||
|
* @param {string} id
|
||||||
|
* @returns {string} A color hex code
|
||||||
|
*/
|
||||||
|
function getColor(id) {
|
||||||
|
const colors = ["#ff8baa", "#79bcff", "#d18bff", "#6de192", "#ffd17c", "#ffb37c", "#ff7c7c"];
|
||||||
|
const index = parseInt(id, 10) % colors.length;
|
||||||
|
return colors[index];
|
||||||
|
}
|
||||||
|
|
||||||
const MENU_ID = "birb-menu";
|
const MENU_ID = "birb-menu";
|
||||||
const MENU_EXIT_ID = "birb-menu-exit";
|
const MENU_EXIT_ID = "birb-menu-exit";
|
||||||
|
|
||||||
@@ -1290,23 +1358,34 @@
|
|||||||
* @param {string} text
|
* @param {string} text
|
||||||
* @param {() => void} action
|
* @param {() => void} action
|
||||||
* @param {boolean} [removeMenu]
|
* @param {boolean} [removeMenu]
|
||||||
* @param {boolean} [isDebug]
|
|
||||||
*/
|
*/
|
||||||
constructor(text, action, removeMenu = true, isDebug = false) {
|
constructor(text, action, removeMenu = true) {
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.removeMenu = removeMenu;
|
this.removeMenu = removeMenu;
|
||||||
this.isDebug = isDebug;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DebugMenuItem extends MenuItem {
|
class ConditionalMenuItem extends MenuItem {
|
||||||
|
/**
|
||||||
|
* @param {string} text
|
||||||
|
* @param {() => void} action
|
||||||
|
* @param {() => boolean} condition
|
||||||
|
* @param {boolean} [removeMenu]
|
||||||
|
*/
|
||||||
|
constructor(text, action, condition, removeMenu = true) {
|
||||||
|
super(text, action, removeMenu);
|
||||||
|
this.condition = condition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DebugMenuItem extends ConditionalMenuItem {
|
||||||
/**
|
/**
|
||||||
* @param {string} text
|
* @param {string} text
|
||||||
* @param {() => void} action
|
* @param {() => void} action
|
||||||
*/
|
*/
|
||||||
constructor(text, action, removeMenu = true) {
|
constructor(text, action, removeMenu = true) {
|
||||||
super(text, action, removeMenu, true);
|
super(text, action, () => isDebug(), removeMenu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1352,7 +1431,7 @@
|
|||||||
let content = makeElement("birb-window-content");
|
let content = makeElement("birb-window-content");
|
||||||
const removeCallback = () => removeMenu();
|
const removeCallback = () => removeMenu();
|
||||||
for (const item of menuItems) {
|
for (const item of menuItems) {
|
||||||
if (!item.isDebug || isDebug()) {
|
if (!(item instanceof ConditionalMenuItem) || item.condition()) {
|
||||||
content.appendChild(makeMenuItem(item, removeCallback));
|
content.appendChild(makeMenuItem(item, removeCallback));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1409,7 +1488,7 @@
|
|||||||
}
|
}
|
||||||
const removeCallback = () => removeMenu();
|
const removeCallback = () => removeMenu();
|
||||||
for (const item of menuItems) {
|
for (const item of menuItems) {
|
||||||
if (!item.isDebug || isDebug()) {
|
if (!(item instanceof ConditionalMenuItem) || item.condition()) {
|
||||||
content.appendChild(makeMenuItem(item, removeCallback));
|
content.appendChild(makeMenuItem(item, removeCallback));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1445,7 +1524,14 @@
|
|||||||
const WINDOW_PIXEL_SIZE = CANVAS_PIXEL_SIZE * BIRB_CSS_SCALE;
|
const WINDOW_PIXEL_SIZE = CANVAS_PIXEL_SIZE * BIRB_CSS_SCALE;
|
||||||
|
|
||||||
// Build-time assets
|
// Build-time assets
|
||||||
const STYLESHEET = `:root {
|
const STYLESHEET = `@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url("https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf") format('opentype');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
--birb-border-size: 2px;
|
--birb-border-size: 2px;
|
||||||
--birb-neg-border-size: calc(var(--birb-border-size) * -1);
|
--birb-neg-border-size: calc(var(--birb-border-size) * -1);
|
||||||
--birb-double-border-size: calc(var(--birb-border-size) * 2);
|
--birb-double-border-size: calc(var(--birb-border-size) * 2);
|
||||||
@@ -1566,6 +1652,7 @@
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
color: var(--birb-background-color);
|
color: var(--birb-background-color);
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.birb-window-close {
|
.birb-window-close {
|
||||||
@@ -1764,6 +1851,17 @@
|
|||||||
.birb-sticky-note {
|
.birb-sticky-note {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
animation: fade-in 0.15s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.birb-sticky-note > .birb-window-content {
|
.birb-sticky-note > .birb-window-content {
|
||||||
@@ -1786,7 +1884,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.birb-sticky-note-input:focus {
|
.birb-sticky-note-input:focus {
|
||||||
outline: none;
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
}`;
|
}`;
|
||||||
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABD5JREFUeJztnTFrFEEYht9JLAJidwju2YpdBAvzAyIWaXJXpRS0MBCwEBTJDwghhaAgGLTSyupMY2UqG9PYWQRb7yJyYJEIacxnkZ11bm5n9+7Y3Zm9ex8Imezd7Te7O9+zM7N7G4AQQgghhBBCCJkJlO8KkPAREXG9ppRiGyK1hY23BvgUkI7dbjYBAJ1ud6BcRR0IITOKxLSiSFpRNFTOkmNR8VtRJF8WF0U2NobKZccnpEzmfFeA5NNuNvG00UCn3R4qV8nB58942mgkZULqDgVYI3wJqNPtYrvfH1i23e8nQ2BCCCkFcwj8ZXEx+alqCJxWhypjE0ICQFKoOrZPAZl1oPwImTFE5Hzy3/hddXzfAvIhf0LK5ILvCtSNgxs3vMRVSikREZ+3nvB2F0JmFN3z0b0/9oKqx9cUBJleeEYfAzPp2BuqFr3v9W4XkcqPgS1dtoEZIe0CAM/AxAOy220JAG/zn3HsoNs/83R0cu8DNM+85g9yvqJVJBQwAYDdbksXvcx/KqWSOoTW+7Pzwkee1pHMiyDmzjQaH/QyETHfU0qDsIc+xnKIiITWEEl5PGh+8HqsfQp4FMxUWNvpJcvoPzdOAZriOVy7DzwCdm6/SV7f7bYH5mPKkFEIAiZE41vAGYhSKpHetHNlXsnRXynkWDhXIiIydzEaWHbveQ8f1+ew8uoMAHDy+wgA8P5JNHCWKUJGQwLGoIBvrbTxoPlBv7ewuITUDHGJ7/uPY3x9cd3LBaOyuDKvZOXVGT6uz6EICWYKELGA7r9O70JrASKWIAwZpQYb4yD4FjAJm7Wdnrx/Es36cc6VX6jD9VBwDoH1jbeu1035wZpzSGOSYfLZn96QgLX87Nj2cNy1TaPGJuFwurcsC6v7SpcBYGHVr/x8C3htp+d1Ys8VP+4I1SbPMisaCwune8vY+PUJAPDy8m0AwN3DdyMF+P7jGAAm6orr+Gk9UFvAGt0TTVkXQAnWlv/i26/8+KULuPp6mLgEZOZbySJy9j7rJMGRBWizsLqPmw8Pce3qpdTPWgdiIgH5FjAhmlDEpzndWxYzB+x8q0BA4sr/mRAgDAmmYYsPE/S+fAuYkJDpby3JxoUOMDjyqap9OwWIGkkwV4CI5/VsCZ18OwEANDYPXJ/9H2RC6fgWMCGh099aShr4nZ9vgfO2712C5oXJkPMut2JpEtLyS6OxeVDYhvsWMCEkF9GdEFuEWoIh599Ij8OKNwL9raXM9xUpP2RciTYFbNep6DoQQjJRX19cP084hwhDJleAWkJ5EixTPDo2UoRXVR0IIU4UzofeAyKcKsynYXSePU6eiqHLZT6gwPqid2r8sutACMnHfmJO6Pk41n+FU0qh8+xx8rdZRom9Lr3erPjs+RESBvGXEYAa5ONYj8Q3h6J2uQry4oe+swmZduqWg2Pfl+dcUQUb7js+IWS6+Ac8zd6eLzTjoQAAAABJRU5ErkJggg==";
|
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABD5JREFUeJztnTFrFEEYht9JLAJidwju2YpdBAvzAyIWaXJXpRS0MBCwEBTJDwghhaAgGLTSyupMY2UqG9PYWQRb7yJyYJEIacxnkZ11bm5n9+7Y3Zm9ex8Imezd7Te7O9+zM7N7G4AQQgghhBBCCJkJlO8KkPAREXG9ppRiGyK1hY23BvgUkI7dbjYBAJ1ud6BcRR0IITOKxLSiSFpRNFTOkmNR8VtRJF8WF0U2NobKZccnpEzmfFeA5NNuNvG00UCn3R4qV8nB58942mgkZULqDgVYI3wJqNPtYrvfH1i23e8nQ2BCCCkFcwj8ZXEx+alqCJxWhypjE0ICQFKoOrZPAZl1oPwImTFE5Hzy3/hddXzfAvIhf0LK5ILvCtSNgxs3vMRVSikREZ+3nvB2F0JmFN3z0b0/9oKqx9cUBJleeEYfAzPp2BuqFr3v9W4XkcqPgS1dtoEZIe0CAM/AxAOy220JAG/zn3HsoNs/83R0cu8DNM+85g9yvqJVJBQwAYDdbksXvcx/KqWSOoTW+7Pzwkee1pHMiyDmzjQaH/QyETHfU0qDsIc+xnKIiITWEEl5PGh+8HqsfQp4FMxUWNvpJcvoPzdOAZriOVy7DzwCdm6/SV7f7bYH5mPKkFEIAiZE41vAGYhSKpHetHNlXsnRXynkWDhXIiIydzEaWHbveQ8f1+ew8uoMAHDy+wgA8P5JNHCWKUJGQwLGoIBvrbTxoPlBv7ewuITUDHGJ7/uPY3x9cd3LBaOyuDKvZOXVGT6uz6EICWYKELGA7r9O70JrASKWIAwZpQYb4yD4FjAJm7Wdnrx/Es36cc6VX6jD9VBwDoH1jbeu1035wZpzSGOSYfLZn96QgLX87Nj2cNy1TaPGJuFwurcsC6v7SpcBYGHVr/x8C3htp+d1Ys8VP+4I1SbPMisaCwune8vY+PUJAPDy8m0AwN3DdyMF+P7jGAAm6orr+Gk9UFvAGt0TTVkXQAnWlv/i26/8+KULuPp6mLgEZOZbySJy9j7rJMGRBWizsLqPmw8Pce3qpdTPWgdiIgH5FjAhmlDEpzndWxYzB+x8q0BA4sr/mRAgDAmmYYsPE/S+fAuYkJDpby3JxoUOMDjyqap9OwWIGkkwV4CI5/VsCZ18OwEANDYPXJ/9H2RC6fgWMCGh099aShr4nZ9vgfO2712C5oXJkPMut2JpEtLyS6OxeVDYhvsWMCEkF9GdEFuEWoIh599Ij8OKNwL9raXM9xUpP2RciTYFbNep6DoQQjJRX19cP084hwhDJleAWkJ5EixTPDo2UoRXVR0IIU4UzofeAyKcKsynYXSePU6eiqHLZT6gwPqid2r8sutACMnHfmJO6Pk41n+FU0qh8+xx8rdZRom9Lr3erPjs+RESBvGXEYAa5ONYj8Q3h6J2uQry4oe+swmZduqWg2Pfl+dcUQUb7js+IWS6+Ac8zd6eLzTjoQAAAABJRU5ErkJggg==";
|
||||||
const FEATHER_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAARhJREFUWIXtlbENwjAQRf8hSiZIRQ+9WQNRUFIAKzACBSsAA1Ag1mAABqCCBomG3hQQ9OMEx4ZDNH5SikSJ3/fZ5wCJRCKRSPwZ0RzMWmtLAhGvQyUAi9mXP/aFaGjJRQQiguHihMvcFMJUVUYlAMuHixPGy4en1WmVQqgHYHkuZjiEj6a2/LjtYzTY0eiZbgC37Mxh1UN3sn/dr6cCz/LHB/DJj9s+2oMdbtdz6TtfFwQHcMvOInfmQNjsgchNWLXmdfK6gyioAu/6uKrsm1kWLAciKuCuey5nYuXAh234bdmZ6INIUw4E/Ix49xtjCmXfzLL8nY/ktdgnAKwxxgIoXIyqmAOwvIqfiN0ALNd21HYBO9XXGMAdnZTYyHWzWjQAAAAASUVORK5CYII=";
|
const FEATHER_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAARhJREFUWIXtlbENwjAQRf8hSiZIRQ+9WQNRUFIAKzACBSsAA1Ag1mAABqCCBomG3hQQ9OMEx4ZDNH5SikSJ3/fZ5wCJRCKRSPwZ0RzMWmtLAhGvQyUAi9mXP/aFaGjJRQQiguHihMvcFMJUVUYlAMuHixPGy4en1WmVQqgHYHkuZjiEj6a2/LjtYzTY0eiZbgC37Mxh1UN3sn/dr6cCz/LHB/DJj9s+2oMdbtdz6TtfFwQHcMvOInfmQNjsgchNWLXmdfK6gyioAu/6uKrsm1kWLAciKuCuey5nYuXAh234bdmZ6INIUw4E/Ix49xtjCmXfzLL8nY/ktdgnAKwxxgIoXIyqmAOwvIqfiN0ALNd21HYBO9XXGMAdnZTYyHWzWjQAAAAASUVORK5CYII=";
|
||||||
@@ -1807,7 +1906,7 @@
|
|||||||
const AFK_TIME = isDebug() ? 0 : 1000 * 5;
|
const AFK_TIME = isDebug() ? 0 : 1000 * 5;
|
||||||
const PET_BOOST_DURATION = 1000 * 60 * 5;
|
const PET_BOOST_DURATION = 1000 * 60 * 5;
|
||||||
const PET_MENU_COOLDOWN = 1000;
|
const PET_MENU_COOLDOWN = 1000;
|
||||||
const URL_CHECK_INTERVAL = 500;
|
const URL_CHECK_INTERVAL = 150;
|
||||||
const HOP_DELAY = 500;
|
const HOP_DELAY = 500;
|
||||||
|
|
||||||
// Random event chances per tick
|
// Random event chances per tick
|
||||||
@@ -1910,9 +2009,7 @@
|
|||||||
const menuItems = [
|
const menuItems = [
|
||||||
new MenuItem(`Pet ${birdBirb()}`, pet),
|
new MenuItem(`Pet ${birdBirb()}`, pet),
|
||||||
new MenuItem("Field Guide", insertFieldGuide),
|
new MenuItem("Field Guide", insertFieldGuide),
|
||||||
...(getContext().areStickyNotesEnabled() ? [
|
new ConditionalMenuItem("Sticky Note", () => createNewStickyNote(stickyNotes, save, deleteStickyNote), () => getContext().areStickyNotesEnabled()),
|
||||||
new MenuItem("Sticky Note", () => createNewStickyNote(stickyNotes, save, deleteStickyNote))
|
|
||||||
] : []),
|
|
||||||
new MenuItem(`Hide ${birdBirb()}`, () => birb.setVisible(false)),
|
new MenuItem(`Hide ${birdBirb()}`, () => birb.setVisible(false)),
|
||||||
new DebugMenuItem("Freeze/Unfreeze", () => {
|
new DebugMenuItem("Freeze/Unfreeze", () => {
|
||||||
frozen = !frozen;
|
frozen = !frozen;
|
||||||
@@ -1949,7 +2046,7 @@
|
|||||||
insertModal(`${birdBirb()} Mode`, message);
|
insertModal(`${birdBirb()} Mode`, message);
|
||||||
}),
|
}),
|
||||||
new Separator(),
|
new Separator(),
|
||||||
new MenuItem("2025.11.13.27", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.11.13.27"); }, false),
|
new MenuItem("2025.11.14.205", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.11.14.205"); }, false),
|
||||||
];
|
];
|
||||||
|
|
||||||
const styleElement = document.createElement("style");
|
const styleElement = document.createElement("style");
|
||||||
@@ -2066,31 +2163,6 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preload font
|
|
||||||
const MONOCRAFT_SRC = "https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf";
|
|
||||||
const fontLink = document.createElement("link");
|
|
||||||
fontLink.rel = "stylesheet";
|
|
||||||
fontLink.href = `url(${MONOCRAFT_SRC}) format('opentype')`;
|
|
||||||
document.head.appendChild(fontLink);
|
|
||||||
|
|
||||||
// Add stylesheet font-face
|
|
||||||
const fontFace = `
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Monocraft';
|
|
||||||
src: url(${MONOCRAFT_SRC}) format('opentype');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const fontStyle = document.createElement("style");
|
|
||||||
fontStyle.textContent = fontFace;
|
|
||||||
document.head.appendChild(fontStyle);
|
|
||||||
} catch (e) {
|
|
||||||
error("Failed to load font: " + e);
|
|
||||||
}
|
|
||||||
|
|
||||||
load().then(onLoad);
|
load().then(onLoad);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2148,7 +2220,7 @@
|
|||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
const currentPath = getContext().getPath().split("?")[0];
|
const currentPath = getContext().getPath().split("?")[0];
|
||||||
if (currentPath !== lastPath) {
|
if (currentPath !== lastPath) {
|
||||||
log("Path changed, updating sticky notes");
|
log("Path changed, updating sticky notes: " + currentPath);
|
||||||
lastPath = currentPath;
|
lastPath = currentPath;
|
||||||
drawStickyNotes(stickyNotes, save, deleteStickyNote);
|
drawStickyNotes(stickyNotes, save, deleteStickyNote);
|
||||||
}
|
}
|
||||||
@@ -2593,7 +2665,11 @@
|
|||||||
// @ts-expect-error
|
// @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 nonFixedElements = largeElements.filter((el) => {
|
const nonFixedElements = largeElements.filter((el) => {
|
||||||
|
if (fixedAllowed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const style = window.getComputedStyle(el);
|
const style = window.getComputedStyle(el);
|
||||||
return style.position !== "fixed" && style.position !== "sticky";
|
return style.position !== "fixed" && style.position !== "sticky";
|
||||||
});
|
});
|
||||||
|
|||||||
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.13.27",
|
"version": "2025.11.14.205",
|
||||||
"homepage_url": "https://idreesinc.com",
|
"homepage_url": "https://idreesinc.com",
|
||||||
"icons": {
|
"icons": {
|
||||||
"48": "images/icons/transparent/48x48x1.png",
|
"48": "images/icons/transparent/48x48x1.png",
|
||||||
|
|||||||
207
dist/obsidian/main.js
vendored
207
dist/obsidian/main.js
vendored
File diff suppressed because one or more lines are too long
2
dist/obsidian/manifest.json
vendored
2
dist/obsidian/manifest.json
vendored
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "pocket-bird",
|
"id": "pocket-bird",
|
||||||
"name": "Pocket Bird",
|
"name": "Pocket Bird",
|
||||||
"version": "2025.11.13.27",
|
"version": "2025.11.14.205",
|
||||||
"minAppVersion": "0.15.0",
|
"minAppVersion": "0.15.0",
|
||||||
"description": "It's a pet bird in your Obsidian, what more could you want?",
|
"description": "It's a pet bird in your Obsidian, what more could you want?",
|
||||||
"author": "Idrees Hassan",
|
"author": "Idrees Hassan",
|
||||||
|
|||||||
208
dist/userscript/birb.user.js
vendored
208
dist/userscript/birb.user.js
vendored
@@ -1,8 +1,8 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name Pocket Bird
|
// @name Pocket Bird
|
||||||
// @namespace https://idreesinc.com
|
// @namespace https://idreesinc.com
|
||||||
// @version 2025.11.13.27
|
// @version 2025.11.14.205
|
||||||
// @description It's a bird that hops around your web browser, the future is here
|
// @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
|
||||||
// @updateURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/userscript/birb.user.js
|
// @updateURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/userscript/birb.user.js
|
||||||
@@ -84,8 +84,9 @@
|
|||||||
* @param {HTMLElement|null} element The element to detect drag events on
|
* @param {HTMLElement|null} element The element to detect drag events on
|
||||||
* @param {boolean} [parent] Whether to move the parent element when the child is dragged
|
* @param {boolean} [parent] Whether to move the parent element when the child is dragged
|
||||||
* @param {(top: number, left: number) => void} [callback] Callback for when element is moved
|
* @param {(top: number, left: number) => void} [callback] Callback for when element is moved
|
||||||
|
* @param {HTMLElement} [pageElement] The page element to constrain movement within
|
||||||
*/
|
*/
|
||||||
function makeDraggable(element, parent = true, callback = () => { }) {
|
function makeDraggable(element, parent = true, callback = () => { }, pageElement) {
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -112,6 +113,7 @@
|
|||||||
offsetX = touch.clientX - elementToMove.offsetLeft;
|
offsetX = touch.clientX - elementToMove.offsetLeft;
|
||||||
offsetY = touch.clientY - elementToMove.offsetTop;
|
offsetY = touch.clientY - elementToMove.offsetTop;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("mouseup", (e) => {
|
document.addEventListener("mouseup", (e) => {
|
||||||
@@ -131,9 +133,12 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("mousemove", (e) => {
|
document.addEventListener("mousemove", (e) => {
|
||||||
|
const page = pageElement || document.documentElement;
|
||||||
|
const maxX = page.scrollWidth - elementToMove.clientWidth;
|
||||||
|
const maxY = page.scrollHeight - elementToMove.clientHeight;
|
||||||
if (isMouseDown) {
|
if (isMouseDown) {
|
||||||
elementToMove.style.left = `${Math.max(0, e.clientX - offsetX)}px`;
|
elementToMove.style.left = `${Math.max(0, Math.min(maxX, e.clientX - offsetX))}px`;
|
||||||
elementToMove.style.top = `${Math.max(0, e.clientY - offsetY)}px`;
|
elementToMove.style.top = `${Math.max(0, Math.min(maxY, e.clientY - offsetY))}px`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -149,8 +154,9 @@
|
|||||||
/**
|
/**
|
||||||
* @param {() => void} func
|
* @param {() => void} func
|
||||||
* @param {Element} [closeButton]
|
* @param {Element} [closeButton]
|
||||||
|
* @param {boolean} [allowEscape] Whether to allow closing with the Escape key
|
||||||
*/
|
*/
|
||||||
function makeClosable(func, closeButton) {
|
function makeClosable(func, closeButton, allowEscape = true) {
|
||||||
if (closeButton) {
|
if (closeButton) {
|
||||||
onClick(closeButton, func);
|
onClick(closeButton, func);
|
||||||
}
|
}
|
||||||
@@ -158,7 +164,7 @@
|
|||||||
if (closeButton && !document.body.contains(closeButton)) {
|
if (closeButton && !document.body.contains(closeButton)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (e.key === "Escape") {
|
if (allowEscape && e.key === "Escape") {
|
||||||
func();
|
func();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -854,6 +860,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SAVE_KEY = "birbSaveData";
|
const SAVE_KEY = "birbSaveData";
|
||||||
|
const ROOT_PATH = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('./application.js').BirbSaveData} BirbSaveData
|
* @typedef {import('./application.js').BirbSaveData} BirbSaveData
|
||||||
@@ -914,6 +921,14 @@
|
|||||||
return window.location.href;
|
return window.location.href;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {HTMLElement} The current active page element where sticky notes can be applied
|
||||||
|
*/
|
||||||
|
getActivePage() {
|
||||||
|
// Default to root element
|
||||||
|
return document.documentElement;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a path is applicable given the context
|
* Checks if a path is applicable given the context
|
||||||
* @param {string} path Can be a site URL or another context-specific path
|
* @param {string} path Can be a site URL or another context-specific path
|
||||||
@@ -1076,7 +1091,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ObsidianContext extends Context {
|
class ObsidianContext extends Context {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
@@ -1091,8 +1105,12 @@
|
|||||||
* @returns {Promise<BirbSaveData|{}>}
|
* @returns {Promise<BirbSaveData|{}>}
|
||||||
*/
|
*/
|
||||||
async getSaveData() {
|
async getSaveData() {
|
||||||
// @ts-expect-error
|
return new Promise((resolve) => {
|
||||||
return await OBSIDIAN_PLUGIN.loadData() ?? {};
|
// @ts-expect-error
|
||||||
|
OBSIDIAN_PLUGIN.loadData().then((data) => {
|
||||||
|
resolve(data ?? {});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1101,7 +1119,7 @@
|
|||||||
*/
|
*/
|
||||||
async putSaveData(saveData) {
|
async putSaveData(saveData) {
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
return await OBSIDIAN_PLUGIN.saveData(saveData);
|
await OBSIDIAN_PLUGIN.saveData(saveData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
@@ -1109,24 +1127,54 @@
|
|||||||
this.putSaveData({});
|
this.putSaveData({});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
|
||||||
getFocusElementTopMargin() {
|
|
||||||
return 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
getFocusableElements() {
|
getFocusableElements() {
|
||||||
const elements = [
|
const elements = [
|
||||||
".workspace-leaf",
|
".workspace-leaf",
|
||||||
".cm-callout",
|
".cm-callout",
|
||||||
".HyperMD-codeblock-begin"
|
".HyperMD-codeblock-begin",
|
||||||
|
".status-bar",
|
||||||
|
".mobile-navbar"
|
||||||
];
|
];
|
||||||
return super.getFocusableElements().concat(elements);
|
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 */
|
/** @override */
|
||||||
areStickyNotesEnabled() {
|
areStickyNotesEnabled() {
|
||||||
return false;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1190,13 +1238,17 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {StickyNote} stickyNote
|
* @param {StickyNote} stickyNote
|
||||||
|
* @param {HTMLElement} page
|
||||||
* @param {() => void} onSave
|
* @param {() => void} onSave
|
||||||
* @param {() => void} onDelete
|
* @param {() => void} onDelete
|
||||||
* @returns {HTMLElement}
|
* @returns {HTMLElement}
|
||||||
*/
|
*/
|
||||||
function renderStickyNote(stickyNote, onSave, onDelete) {
|
function renderStickyNote(stickyNote, page, onSave, onDelete) {
|
||||||
const noteElement = makeElement("birb-window");
|
const noteElement = makeElement("birb-window");
|
||||||
noteElement.classList.add("birb-sticky-note");
|
noteElement.classList.add("birb-sticky-note");
|
||||||
|
const color = getColor(stickyNote.id);
|
||||||
|
noteElement.style.setProperty("--birb-highlight", color);
|
||||||
|
noteElement.style.setProperty("--birb-border-color", color);
|
||||||
|
|
||||||
// Create header
|
// Create header
|
||||||
const header = makeElement("birb-window-header");
|
const header = makeElement("birb-window-header");
|
||||||
@@ -1219,13 +1271,13 @@
|
|||||||
|
|
||||||
noteElement.style.top = `${stickyNote.top}px`;
|
noteElement.style.top = `${stickyNote.top}px`;
|
||||||
noteElement.style.left = `${stickyNote.left}px`;
|
noteElement.style.left = `${stickyNote.left}px`;
|
||||||
document.body.appendChild(noteElement);
|
page.appendChild(noteElement);
|
||||||
|
|
||||||
makeDraggable(header, true, (top, left) => {
|
makeDraggable(header, true, (top, left) => {
|
||||||
stickyNote.top = top;
|
stickyNote.top = top;
|
||||||
stickyNote.left = left;
|
stickyNote.left = left;
|
||||||
onSave();
|
onSave();
|
||||||
});
|
}, page);
|
||||||
|
|
||||||
if (closeButton) {
|
if (closeButton) {
|
||||||
makeClosable(() => {
|
makeClosable(() => {
|
||||||
@@ -1233,7 +1285,7 @@
|
|||||||
onDelete();
|
onDelete();
|
||||||
noteElement.remove();
|
noteElement.remove();
|
||||||
}
|
}
|
||||||
}, closeButton);
|
}, closeButton, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (textarea && textarea instanceof HTMLTextAreaElement) {
|
if (textarea && textarea instanceof HTMLTextAreaElement) {
|
||||||
@@ -1270,10 +1322,11 @@
|
|||||||
const existingNotes = document.querySelectorAll(".birb-sticky-note");
|
const existingNotes = document.querySelectorAll(".birb-sticky-note");
|
||||||
existingNotes.forEach(note => note.remove());
|
existingNotes.forEach(note => note.remove());
|
||||||
// Render all sticky notes
|
// Render all sticky notes
|
||||||
|
const pageElement = getContext().getActivePage();
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
for (let stickyNote of stickyNotes) {
|
for (let stickyNote of stickyNotes) {
|
||||||
if (context.isPathApplicable(stickyNote.site)) {
|
if (context.isPathApplicable(stickyNote.site)) {
|
||||||
renderStickyNote(stickyNote, onSave, () => onDelete(stickyNote));
|
renderStickyNote(stickyNote, pageElement, onSave, () => onDelete(stickyNote));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1284,18 +1337,33 @@
|
|||||||
* @param {(note: StickyNote) => void} onDelete
|
* @param {(note: StickyNote) => void} onDelete
|
||||||
*/
|
*/
|
||||||
function createNewStickyNote(stickyNotes, onSave, onDelete) {
|
function createNewStickyNote(stickyNotes, onSave, onDelete) {
|
||||||
|
if (getContext().areStickyNotesEnabled() === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const id = Date.now().toString();
|
const id = Date.now().toString();
|
||||||
const site = getContext().getPath();
|
const site = getContext().getPath();
|
||||||
const stickyNote = new StickyNote(id, site, "");
|
const stickyNote = new StickyNote(id, site, "");
|
||||||
const element = renderStickyNote(stickyNote, onSave, () => onDelete(stickyNote));
|
const page = getContext().getActivePage();
|
||||||
element.style.left = `${window.innerWidth / 2 - element.offsetWidth / 2}px`;
|
const element = renderStickyNote(stickyNote, page, onSave, () => onDelete(stickyNote));
|
||||||
element.style.top = `${window.scrollY + window.innerHeight / 2 - element.offsetHeight / 2}px`;
|
element.style.left = `${page.clientWidth / 2 - element.offsetWidth / 2}px`;
|
||||||
|
element.style.top = `${page.scrollTop + page.clientHeight / 2 - element.offsetHeight / 2}px`;
|
||||||
stickyNote.top = parseInt(element.style.top, 10);
|
stickyNote.top = parseInt(element.style.top, 10);
|
||||||
stickyNote.left = parseInt(element.style.left, 10);
|
stickyNote.left = parseInt(element.style.left, 10);
|
||||||
stickyNotes.push(stickyNote);
|
stickyNotes.push(stickyNote);
|
||||||
onSave();
|
onSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a color based on the mod of the sticky note ID
|
||||||
|
* @param {string} id
|
||||||
|
* @returns {string} A color hex code
|
||||||
|
*/
|
||||||
|
function getColor(id) {
|
||||||
|
const colors = ["#ff8baa", "#79bcff", "#d18bff", "#6de192", "#ffd17c", "#ffb37c", "#ff7c7c"];
|
||||||
|
const index = parseInt(id, 10) % colors.length;
|
||||||
|
return colors[index];
|
||||||
|
}
|
||||||
|
|
||||||
const MENU_ID = "birb-menu";
|
const MENU_ID = "birb-menu";
|
||||||
const MENU_EXIT_ID = "birb-menu-exit";
|
const MENU_EXIT_ID = "birb-menu-exit";
|
||||||
|
|
||||||
@@ -1304,23 +1372,34 @@
|
|||||||
* @param {string} text
|
* @param {string} text
|
||||||
* @param {() => void} action
|
* @param {() => void} action
|
||||||
* @param {boolean} [removeMenu]
|
* @param {boolean} [removeMenu]
|
||||||
* @param {boolean} [isDebug]
|
|
||||||
*/
|
*/
|
||||||
constructor(text, action, removeMenu = true, isDebug = false) {
|
constructor(text, action, removeMenu = true) {
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.removeMenu = removeMenu;
|
this.removeMenu = removeMenu;
|
||||||
this.isDebug = isDebug;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DebugMenuItem extends MenuItem {
|
class ConditionalMenuItem extends MenuItem {
|
||||||
|
/**
|
||||||
|
* @param {string} text
|
||||||
|
* @param {() => void} action
|
||||||
|
* @param {() => boolean} condition
|
||||||
|
* @param {boolean} [removeMenu]
|
||||||
|
*/
|
||||||
|
constructor(text, action, condition, removeMenu = true) {
|
||||||
|
super(text, action, removeMenu);
|
||||||
|
this.condition = condition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DebugMenuItem extends ConditionalMenuItem {
|
||||||
/**
|
/**
|
||||||
* @param {string} text
|
* @param {string} text
|
||||||
* @param {() => void} action
|
* @param {() => void} action
|
||||||
*/
|
*/
|
||||||
constructor(text, action, removeMenu = true) {
|
constructor(text, action, removeMenu = true) {
|
||||||
super(text, action, removeMenu, true);
|
super(text, action, () => isDebug(), removeMenu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1366,7 +1445,7 @@
|
|||||||
let content = makeElement("birb-window-content");
|
let content = makeElement("birb-window-content");
|
||||||
const removeCallback = () => removeMenu();
|
const removeCallback = () => removeMenu();
|
||||||
for (const item of menuItems) {
|
for (const item of menuItems) {
|
||||||
if (!item.isDebug || isDebug()) {
|
if (!(item instanceof ConditionalMenuItem) || item.condition()) {
|
||||||
content.appendChild(makeMenuItem(item, removeCallback));
|
content.appendChild(makeMenuItem(item, removeCallback));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1423,7 +1502,7 @@
|
|||||||
}
|
}
|
||||||
const removeCallback = () => removeMenu();
|
const removeCallback = () => removeMenu();
|
||||||
for (const item of menuItems) {
|
for (const item of menuItems) {
|
||||||
if (!item.isDebug || isDebug()) {
|
if (!(item instanceof ConditionalMenuItem) || item.condition()) {
|
||||||
content.appendChild(makeMenuItem(item, removeCallback));
|
content.appendChild(makeMenuItem(item, removeCallback));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1459,7 +1538,14 @@
|
|||||||
const WINDOW_PIXEL_SIZE = CANVAS_PIXEL_SIZE * BIRB_CSS_SCALE;
|
const WINDOW_PIXEL_SIZE = CANVAS_PIXEL_SIZE * BIRB_CSS_SCALE;
|
||||||
|
|
||||||
// Build-time assets
|
// Build-time assets
|
||||||
const STYLESHEET = `:root {
|
const STYLESHEET = `@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url("https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf") format('opentype');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
--birb-border-size: 2px;
|
--birb-border-size: 2px;
|
||||||
--birb-neg-border-size: calc(var(--birb-border-size) * -1);
|
--birb-neg-border-size: calc(var(--birb-border-size) * -1);
|
||||||
--birb-double-border-size: calc(var(--birb-border-size) * 2);
|
--birb-double-border-size: calc(var(--birb-border-size) * 2);
|
||||||
@@ -1580,6 +1666,7 @@
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
color: var(--birb-background-color);
|
color: var(--birb-background-color);
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.birb-window-close {
|
.birb-window-close {
|
||||||
@@ -1778,6 +1865,17 @@
|
|||||||
.birb-sticky-note {
|
.birb-sticky-note {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
animation: fade-in 0.15s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.birb-sticky-note > .birb-window-content {
|
.birb-sticky-note > .birb-window-content {
|
||||||
@@ -1800,7 +1898,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.birb-sticky-note-input:focus {
|
.birb-sticky-note-input:focus {
|
||||||
outline: none;
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
}`;
|
}`;
|
||||||
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABD5JREFUeJztnTFrFEEYht9JLAJidwju2YpdBAvzAyIWaXJXpRS0MBCwEBTJDwghhaAgGLTSyupMY2UqG9PYWQRb7yJyYJEIacxnkZ11bm5n9+7Y3Zm9ex8Imezd7Te7O9+zM7N7G4AQQgghhBBCCJkJlO8KkPAREXG9ppRiGyK1hY23BvgUkI7dbjYBAJ1ud6BcRR0IITOKxLSiSFpRNFTOkmNR8VtRJF8WF0U2NobKZccnpEzmfFeA5NNuNvG00UCn3R4qV8nB58942mgkZULqDgVYI3wJqNPtYrvfH1i23e8nQ2BCCCkFcwj8ZXEx+alqCJxWhypjE0ICQFKoOrZPAZl1oPwImTFE5Hzy3/hddXzfAvIhf0LK5ILvCtSNgxs3vMRVSikREZ+3nvB2F0JmFN3z0b0/9oKqx9cUBJleeEYfAzPp2BuqFr3v9W4XkcqPgS1dtoEZIe0CAM/AxAOy220JAG/zn3HsoNs/83R0cu8DNM+85g9yvqJVJBQwAYDdbksXvcx/KqWSOoTW+7Pzwkee1pHMiyDmzjQaH/QyETHfU0qDsIc+xnKIiITWEEl5PGh+8HqsfQp4FMxUWNvpJcvoPzdOAZriOVy7DzwCdm6/SV7f7bYH5mPKkFEIAiZE41vAGYhSKpHetHNlXsnRXynkWDhXIiIydzEaWHbveQ8f1+ew8uoMAHDy+wgA8P5JNHCWKUJGQwLGoIBvrbTxoPlBv7ewuITUDHGJ7/uPY3x9cd3LBaOyuDKvZOXVGT6uz6EICWYKELGA7r9O70JrASKWIAwZpQYb4yD4FjAJm7Wdnrx/Es36cc6VX6jD9VBwDoH1jbeu1035wZpzSGOSYfLZn96QgLX87Nj2cNy1TaPGJuFwurcsC6v7SpcBYGHVr/x8C3htp+d1Ys8VP+4I1SbPMisaCwune8vY+PUJAPDy8m0AwN3DdyMF+P7jGAAm6orr+Gk9UFvAGt0TTVkXQAnWlv/i26/8+KULuPp6mLgEZOZbySJy9j7rJMGRBWizsLqPmw8Pce3qpdTPWgdiIgH5FjAhmlDEpzndWxYzB+x8q0BA4sr/mRAgDAmmYYsPE/S+fAuYkJDpby3JxoUOMDjyqap9OwWIGkkwV4CI5/VsCZ18OwEANDYPXJ/9H2RC6fgWMCGh099aShr4nZ9vgfO2712C5oXJkPMut2JpEtLyS6OxeVDYhvsWMCEkF9GdEFuEWoIh599Ij8OKNwL9raXM9xUpP2RciTYFbNep6DoQQjJRX19cP084hwhDJleAWkJ5EixTPDo2UoRXVR0IIU4UzofeAyKcKsynYXSePU6eiqHLZT6gwPqid2r8sutACMnHfmJO6Pk41n+FU0qh8+xx8rdZRom9Lr3erPjs+RESBvGXEYAa5ONYj8Q3h6J2uQry4oe+swmZduqWg2Pfl+dcUQUb7js+IWS6+Ac8zd6eLzTjoQAAAABJRU5ErkJggg==";
|
const SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUAAAAAgCAYAAABjE6FEAAAAAXNSR0IArs4c6QAABD5JREFUeJztnTFrFEEYht9JLAJidwju2YpdBAvzAyIWaXJXpRS0MBCwEBTJDwghhaAgGLTSyupMY2UqG9PYWQRb7yJyYJEIacxnkZ11bm5n9+7Y3Zm9ex8Imezd7Te7O9+zM7N7G4AQQgghhBBCCJkJlO8KkPAREXG9ppRiGyK1hY23BvgUkI7dbjYBAJ1ud6BcRR0IITOKxLSiSFpRNFTOkmNR8VtRJF8WF0U2NobKZccnpEzmfFeA5NNuNvG00UCn3R4qV8nB58942mgkZULqDgVYI3wJqNPtYrvfH1i23e8nQ2BCCCkFcwj8ZXEx+alqCJxWhypjE0ICQFKoOrZPAZl1oPwImTFE5Hzy3/hddXzfAvIhf0LK5ILvCtSNgxs3vMRVSikREZ+3nvB2F0JmFN3z0b0/9oKqx9cUBJleeEYfAzPp2BuqFr3v9W4XkcqPgS1dtoEZIe0CAM/AxAOy220JAG/zn3HsoNs/83R0cu8DNM+85g9yvqJVJBQwAYDdbksXvcx/KqWSOoTW+7Pzwkee1pHMiyDmzjQaH/QyETHfU0qDsIc+xnKIiITWEEl5PGh+8HqsfQp4FMxUWNvpJcvoPzdOAZriOVy7DzwCdm6/SV7f7bYH5mPKkFEIAiZE41vAGYhSKpHetHNlXsnRXynkWDhXIiIydzEaWHbveQ8f1+ew8uoMAHDy+wgA8P5JNHCWKUJGQwLGoIBvrbTxoPlBv7ewuITUDHGJ7/uPY3x9cd3LBaOyuDKvZOXVGT6uz6EICWYKELGA7r9O70JrASKWIAwZpQYb4yD4FjAJm7Wdnrx/Es36cc6VX6jD9VBwDoH1jbeu1035wZpzSGOSYfLZn96QgLX87Nj2cNy1TaPGJuFwurcsC6v7SpcBYGHVr/x8C3htp+d1Ys8VP+4I1SbPMisaCwune8vY+PUJAPDy8m0AwN3DdyMF+P7jGAAm6orr+Gk9UFvAGt0TTVkXQAnWlv/i26/8+KULuPp6mLgEZOZbySJy9j7rJMGRBWizsLqPmw8Pce3qpdTPWgdiIgH5FjAhmlDEpzndWxYzB+x8q0BA4sr/mRAgDAmmYYsPE/S+fAuYkJDpby3JxoUOMDjyqap9OwWIGkkwV4CI5/VsCZ18OwEANDYPXJ/9H2RC6fgWMCGh099aShr4nZ9vgfO2712C5oXJkPMut2JpEtLyS6OxeVDYhvsWMCEkF9GdEFuEWoIh599Ij8OKNwL9raXM9xUpP2RciTYFbNep6DoQQjJRX19cP084hwhDJleAWkJ5EixTPDo2UoRXVR0IIU4UzofeAyKcKsynYXSePU6eiqHLZT6gwPqid2r8sutACMnHfmJO6Pk41n+FU0qh8+xx8rdZRom9Lr3erPjs+RESBvGXEYAa5ONYj8Q3h6J2uQry4oe+swmZduqWg2Pfl+dcUQUb7js+IWS6+Ac8zd6eLzTjoQAAAABJRU5ErkJggg==";
|
||||||
const FEATHER_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAARhJREFUWIXtlbENwjAQRf8hSiZIRQ+9WQNRUFIAKzACBSsAA1Ag1mAABqCCBomG3hQQ9OMEx4ZDNH5SikSJ3/fZ5wCJRCKRSPwZ0RzMWmtLAhGvQyUAi9mXP/aFaGjJRQQiguHihMvcFMJUVUYlAMuHixPGy4en1WmVQqgHYHkuZjiEj6a2/LjtYzTY0eiZbgC37Mxh1UN3sn/dr6cCz/LHB/DJj9s+2oMdbtdz6TtfFwQHcMvOInfmQNjsgchNWLXmdfK6gyioAu/6uKrsm1kWLAciKuCuey5nYuXAh234bdmZ6INIUw4E/Ix49xtjCmXfzLL8nY/ktdgnAKwxxgIoXIyqmAOwvIqfiN0ALNd21HYBO9XXGMAdnZTYyHWzWjQAAAAASUVORK5CYII=";
|
const FEATHER_SPRITE_SHEET = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAARhJREFUWIXtlbENwjAQRf8hSiZIRQ+9WQNRUFIAKzACBSsAA1Ag1mAABqCCBomG3hQQ9OMEx4ZDNH5SikSJ3/fZ5wCJRCKRSPwZ0RzMWmtLAhGvQyUAi9mXP/aFaGjJRQQiguHihMvcFMJUVUYlAMuHixPGy4en1WmVQqgHYHkuZjiEj6a2/LjtYzTY0eiZbgC37Mxh1UN3sn/dr6cCz/LHB/DJj9s+2oMdbtdz6TtfFwQHcMvOInfmQNjsgchNWLXmdfK6gyioAu/6uKrsm1kWLAciKuCuey5nYuXAh234bdmZ6INIUw4E/Ix49xtjCmXfzLL8nY/ktdgnAKwxxgIoXIyqmAOwvIqfiN0ALNd21HYBO9XXGMAdnZTYyHWzWjQAAAAASUVORK5CYII=";
|
||||||
@@ -1821,7 +1920,7 @@
|
|||||||
const AFK_TIME = isDebug() ? 0 : 1000 * 5;
|
const AFK_TIME = isDebug() ? 0 : 1000 * 5;
|
||||||
const PET_BOOST_DURATION = 1000 * 60 * 5;
|
const PET_BOOST_DURATION = 1000 * 60 * 5;
|
||||||
const PET_MENU_COOLDOWN = 1000;
|
const PET_MENU_COOLDOWN = 1000;
|
||||||
const URL_CHECK_INTERVAL = 500;
|
const URL_CHECK_INTERVAL = 150;
|
||||||
const HOP_DELAY = 500;
|
const HOP_DELAY = 500;
|
||||||
|
|
||||||
// Random event chances per tick
|
// Random event chances per tick
|
||||||
@@ -1924,9 +2023,7 @@
|
|||||||
const menuItems = [
|
const menuItems = [
|
||||||
new MenuItem(`Pet ${birdBirb()}`, pet),
|
new MenuItem(`Pet ${birdBirb()}`, pet),
|
||||||
new MenuItem("Field Guide", insertFieldGuide),
|
new MenuItem("Field Guide", insertFieldGuide),
|
||||||
...(getContext().areStickyNotesEnabled() ? [
|
new ConditionalMenuItem("Sticky Note", () => createNewStickyNote(stickyNotes, save, deleteStickyNote), () => getContext().areStickyNotesEnabled()),
|
||||||
new MenuItem("Sticky Note", () => createNewStickyNote(stickyNotes, save, deleteStickyNote))
|
|
||||||
] : []),
|
|
||||||
new MenuItem(`Hide ${birdBirb()}`, () => birb.setVisible(false)),
|
new MenuItem(`Hide ${birdBirb()}`, () => birb.setVisible(false)),
|
||||||
new DebugMenuItem("Freeze/Unfreeze", () => {
|
new DebugMenuItem("Freeze/Unfreeze", () => {
|
||||||
frozen = !frozen;
|
frozen = !frozen;
|
||||||
@@ -1963,7 +2060,7 @@
|
|||||||
insertModal(`${birdBirb()} Mode`, message);
|
insertModal(`${birdBirb()} Mode`, message);
|
||||||
}),
|
}),
|
||||||
new Separator(),
|
new Separator(),
|
||||||
new MenuItem("2025.11.13.27", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.11.13.27"); }, false),
|
new MenuItem("2025.11.14.205", () => { alert("Thank you for using Pocket Bird! You are on version: 2025.11.14.205"); }, false),
|
||||||
];
|
];
|
||||||
|
|
||||||
const styleElement = document.createElement("style");
|
const styleElement = document.createElement("style");
|
||||||
@@ -2080,31 +2177,6 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preload font
|
|
||||||
const MONOCRAFT_SRC = "https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf";
|
|
||||||
const fontLink = document.createElement("link");
|
|
||||||
fontLink.rel = "stylesheet";
|
|
||||||
fontLink.href = `url(${MONOCRAFT_SRC}) format('opentype')`;
|
|
||||||
document.head.appendChild(fontLink);
|
|
||||||
|
|
||||||
// Add stylesheet font-face
|
|
||||||
const fontFace = `
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Monocraft';
|
|
||||||
src: url(${MONOCRAFT_SRC}) format('opentype');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const fontStyle = document.createElement("style");
|
|
||||||
fontStyle.textContent = fontFace;
|
|
||||||
document.head.appendChild(fontStyle);
|
|
||||||
} catch (e) {
|
|
||||||
error("Failed to load font: " + e);
|
|
||||||
}
|
|
||||||
|
|
||||||
load().then(onLoad);
|
load().then(onLoad);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2162,7 +2234,7 @@
|
|||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
const currentPath = getContext().getPath().split("?")[0];
|
const currentPath = getContext().getPath().split("?")[0];
|
||||||
if (currentPath !== lastPath) {
|
if (currentPath !== lastPath) {
|
||||||
log("Path changed, updating sticky notes");
|
log("Path changed, updating sticky notes: " + currentPath);
|
||||||
lastPath = currentPath;
|
lastPath = currentPath;
|
||||||
drawStickyNotes(stickyNotes, save, deleteStickyNote);
|
drawStickyNotes(stickyNotes, save, deleteStickyNote);
|
||||||
}
|
}
|
||||||
@@ -2607,7 +2679,11 @@
|
|||||||
// @ts-expect-error
|
// @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 nonFixedElements = largeElements.filter((el) => {
|
const nonFixedElements = largeElements.filter((el) => {
|
||||||
|
if (fixedAllowed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const style = window.getComputedStyle(el);
|
const style = window.getComputedStyle(el);
|
||||||
return style.position !== "fixed" && style.position !== "sticky";
|
return style.position !== "fixed" && style.position !== "sticky";
|
||||||
});
|
});
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 688 KiB After Width: | Height: | Size: 688 KiB |
15
platform-specific/obsidian/wrapper.js
Normal file
15
platform-specific/obsidian/wrapper.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
const { Plugin, Notice } = require('obsidian');
|
||||||
|
module.exports = class PocketBird extends Plugin {
|
||||||
|
onload() {
|
||||||
|
console.log("Loading Pocket Bird version __VERSION__...");
|
||||||
|
const OBSIDIAN_PLUGIN = this;
|
||||||
|
__CODE__
|
||||||
|
console.log("Pocket Bird loaded!");
|
||||||
|
}
|
||||||
|
|
||||||
|
onunload() {
|
||||||
|
// Remove the birb when the plugin is unloaded
|
||||||
|
document.getElementById('birb')?.remove();
|
||||||
|
console.log('Pocket Bird unloaded!');
|
||||||
|
}
|
||||||
|
};
|
||||||
13
platform-specific/userscript/header.txt
Normal file
13
platform-specific/userscript/header.txt
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// ==UserScript==
|
||||||
|
// @name Pocket Bird
|
||||||
|
// @namespace https://idreesinc.com
|
||||||
|
// @version __VERSION__
|
||||||
|
// @description It's a pet bird in your browser, what more could you want?
|
||||||
|
// @author Idrees
|
||||||
|
// @downloadURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/userscript/birb.user.js
|
||||||
|
// @updateURL https://github.com/IdreesInc/Pocket-Bird/raw/refs/heads/main/dist/userscript/birb.user.js
|
||||||
|
// @match *://*/*
|
||||||
|
// @grant GM_setValue
|
||||||
|
// @grant GM_getValue
|
||||||
|
// @grant GM_deleteValue
|
||||||
|
// ==/UserScript==
|
||||||
@@ -2,7 +2,7 @@ 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 } from './context.js';
|
import { getContext, ObsidianContext } from './context.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Directions,
|
Directions,
|
||||||
@@ -31,6 +31,7 @@ import {
|
|||||||
} from './stickyNotes.js';
|
} from './stickyNotes.js';
|
||||||
import {
|
import {
|
||||||
MenuItem,
|
MenuItem,
|
||||||
|
ConditionalMenuItem,
|
||||||
DebugMenuItem,
|
DebugMenuItem,
|
||||||
Separator,
|
Separator,
|
||||||
insertMenu,
|
insertMenu,
|
||||||
@@ -90,7 +91,7 @@ const UPDATE_INTERVAL = 1000 / 60; // 60 FPS
|
|||||||
const AFK_TIME = isDebug() ? 0 : 1000 * 5;
|
const AFK_TIME = isDebug() ? 0 : 1000 * 5;
|
||||||
const PET_BOOST_DURATION = 1000 * 60 * 5;
|
const PET_BOOST_DURATION = 1000 * 60 * 5;
|
||||||
const PET_MENU_COOLDOWN = 1000;
|
const PET_MENU_COOLDOWN = 1000;
|
||||||
const URL_CHECK_INTERVAL = 500;
|
const URL_CHECK_INTERVAL = 150;
|
||||||
const HOP_DELAY = 500;
|
const HOP_DELAY = 500;
|
||||||
|
|
||||||
// Random event chances per tick
|
// Random event chances per tick
|
||||||
@@ -193,9 +194,7 @@ Promise.all([
|
|||||||
const menuItems = [
|
const menuItems = [
|
||||||
new MenuItem(`Pet ${birdBirb()}`, pet),
|
new MenuItem(`Pet ${birdBirb()}`, pet),
|
||||||
new MenuItem("Field Guide", insertFieldGuide),
|
new MenuItem("Field Guide", insertFieldGuide),
|
||||||
...(getContext().areStickyNotesEnabled() ? [
|
new ConditionalMenuItem("Sticky Note", () => createNewStickyNote(stickyNotes, save, deleteStickyNote), () => getContext().areStickyNotesEnabled()),
|
||||||
new MenuItem("Sticky Note", () => createNewStickyNote(stickyNotes, save, deleteStickyNote))
|
|
||||||
] : []),
|
|
||||||
new MenuItem(`Hide ${birdBirb()}`, () => birb.setVisible(false)),
|
new MenuItem(`Hide ${birdBirb()}`, () => birb.setVisible(false)),
|
||||||
new DebugMenuItem("Freeze/Unfreeze", () => {
|
new DebugMenuItem("Freeze/Unfreeze", () => {
|
||||||
frozen = !frozen;
|
frozen = !frozen;
|
||||||
@@ -349,31 +348,6 @@ Promise.all([
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Preload font
|
|
||||||
const MONOCRAFT_SRC = "https://cdn.jsdelivr.net/gh/idreesinc/Monocraft@99b32ab40612ff2533a69d8f14bd8b3d9e604456/dist/Monocraft.otf";
|
|
||||||
const fontLink = document.createElement("link");
|
|
||||||
fontLink.rel = "stylesheet";
|
|
||||||
fontLink.href = `url(${MONOCRAFT_SRC}) format('opentype')`;
|
|
||||||
document.head.appendChild(fontLink);
|
|
||||||
|
|
||||||
// Add stylesheet font-face
|
|
||||||
const fontFace = `
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Monocraft';
|
|
||||||
src: url(${MONOCRAFT_SRC}) format('opentype');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const fontStyle = document.createElement("style");
|
|
||||||
fontStyle.textContent = fontFace;
|
|
||||||
document.head.appendChild(fontStyle);
|
|
||||||
} catch (e) {
|
|
||||||
error("Failed to load font: " + e);
|
|
||||||
}
|
|
||||||
|
|
||||||
load().then(onLoad);
|
load().then(onLoad);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,7 +405,7 @@ Promise.all([
|
|||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
const currentPath = getContext().getPath().split("?")[0];
|
const currentPath = getContext().getPath().split("?")[0];
|
||||||
if (currentPath !== lastPath) {
|
if (currentPath !== lastPath) {
|
||||||
log("Path changed, updating sticky notes");
|
log("Path changed, updating sticky notes: " + currentPath);
|
||||||
lastPath = currentPath;
|
lastPath = currentPath;
|
||||||
drawStickyNotes(stickyNotes, save, deleteStickyNote);
|
drawStickyNotes(stickyNotes, save, deleteStickyNote);
|
||||||
}
|
}
|
||||||
@@ -880,7 +854,11 @@ Promise.all([
|
|||||||
// @ts-expect-error
|
// @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 nonFixedElements = largeElements.filter((el) => {
|
const nonFixedElements = largeElements.filter((el) => {
|
||||||
|
if (fixedAllowed) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const style = window.getComputedStyle(el);
|
const style = window.getComputedStyle(el);
|
||||||
return style.position !== "fixed" && style.position !== "sticky";
|
return style.position !== "fixed" && style.position !== "sticky";
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { debug, log, error } from "./shared.js";
|
import { debug, log, error } from "./shared.js";
|
||||||
|
|
||||||
const SAVE_KEY = "birbSaveData";
|
const SAVE_KEY = "birbSaveData";
|
||||||
|
const ROOT_PATH = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('./application.js').BirbSaveData} BirbSaveData
|
* @typedef {import('./application.js').BirbSaveData} BirbSaveData
|
||||||
@@ -61,6 +62,14 @@ export class Context {
|
|||||||
return window.location.href;
|
return window.location.href;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {HTMLElement} The current active page element where sticky notes can be applied
|
||||||
|
*/
|
||||||
|
getActivePage() {
|
||||||
|
// Default to root element
|
||||||
|
return document.documentElement;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a path is applicable given the context
|
* Checks if a path is applicable given the context
|
||||||
* @param {string} path Can be a site URL or another context-specific path
|
* @param {string} path Can be a site URL or another context-specific path
|
||||||
@@ -222,8 +231,7 @@ class BrowserExtensionContext extends Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ObsidianContext extends Context {
|
export class ObsidianContext extends Context {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
@@ -238,8 +246,12 @@ class ObsidianContext extends Context {
|
|||||||
* @returns {Promise<BirbSaveData|{}>}
|
* @returns {Promise<BirbSaveData|{}>}
|
||||||
*/
|
*/
|
||||||
async getSaveData() {
|
async getSaveData() {
|
||||||
// @ts-expect-error
|
return new Promise((resolve) => {
|
||||||
return await OBSIDIAN_PLUGIN.loadData() ?? {};
|
// @ts-expect-error
|
||||||
|
OBSIDIAN_PLUGIN.loadData().then((data) => {
|
||||||
|
resolve(data ?? {});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -248,7 +260,7 @@ class ObsidianContext extends Context {
|
|||||||
*/
|
*/
|
||||||
async putSaveData(saveData) {
|
async putSaveData(saveData) {
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
return await OBSIDIAN_PLUGIN.saveData(saveData);
|
await OBSIDIAN_PLUGIN.saveData(saveData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
@@ -256,24 +268,54 @@ class ObsidianContext extends Context {
|
|||||||
this.putSaveData({});
|
this.putSaveData({});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @override */
|
|
||||||
getFocusElementTopMargin() {
|
|
||||||
return 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
getFocusableElements() {
|
getFocusableElements() {
|
||||||
const elements = [
|
const elements = [
|
||||||
".workspace-leaf",
|
".workspace-leaf",
|
||||||
".cm-callout",
|
".cm-callout",
|
||||||
".HyperMD-codeblock-begin"
|
".HyperMD-codeblock-begin",
|
||||||
|
".status-bar",
|
||||||
|
".mobile-navbar"
|
||||||
];
|
];
|
||||||
return super.getFocusableElements().concat(elements);
|
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 */
|
/** @override */
|
||||||
areStickyNotesEnabled() {
|
areStickyNotesEnabled() {
|
||||||
return false;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
25
src/menu.js
25
src/menu.js
@@ -15,23 +15,34 @@ export class MenuItem {
|
|||||||
* @param {string} text
|
* @param {string} text
|
||||||
* @param {() => void} action
|
* @param {() => void} action
|
||||||
* @param {boolean} [removeMenu]
|
* @param {boolean} [removeMenu]
|
||||||
* @param {boolean} [isDebug]
|
|
||||||
*/
|
*/
|
||||||
constructor(text, action, removeMenu = true, isDebug = false) {
|
constructor(text, action, removeMenu = true) {
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.removeMenu = removeMenu;
|
this.removeMenu = removeMenu;
|
||||||
this.isDebug = isDebug;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DebugMenuItem extends MenuItem {
|
export class ConditionalMenuItem extends MenuItem {
|
||||||
|
/**
|
||||||
|
* @param {string} text
|
||||||
|
* @param {() => void} action
|
||||||
|
* @param {() => boolean} condition
|
||||||
|
* @param {boolean} [removeMenu]
|
||||||
|
*/
|
||||||
|
constructor(text, action, condition, removeMenu = true) {
|
||||||
|
super(text, action, removeMenu);
|
||||||
|
this.condition = condition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DebugMenuItem extends ConditionalMenuItem {
|
||||||
/**
|
/**
|
||||||
* @param {string} text
|
* @param {string} text
|
||||||
* @param {() => void} action
|
* @param {() => void} action
|
||||||
*/
|
*/
|
||||||
constructor(text, action, removeMenu = true) {
|
constructor(text, action, removeMenu = true) {
|
||||||
super(text, action, removeMenu, true);
|
super(text, action, () => isDebug(), removeMenu);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +88,7 @@ export function insertMenu(menuItems, title, updateLocationCallback) {
|
|||||||
let content = makeElement("birb-window-content");
|
let content = makeElement("birb-window-content");
|
||||||
const removeCallback = () => removeMenu();
|
const removeCallback = () => removeMenu();
|
||||||
for (const item of menuItems) {
|
for (const item of menuItems) {
|
||||||
if (!item.isDebug || isDebug()) {
|
if (!(item instanceof ConditionalMenuItem) || item.condition()) {
|
||||||
content.appendChild(makeMenuItem(item, removeCallback));
|
content.appendChild(makeMenuItem(item, removeCallback));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,7 +145,7 @@ export function switchMenuItems(menuItems, updateLocationCallback) {
|
|||||||
}
|
}
|
||||||
const removeCallback = () => removeMenu();
|
const removeCallback = () => removeMenu();
|
||||||
for (const item of menuItems) {
|
for (const item of menuItems) {
|
||||||
if (!item.isDebug || isDebug()) {
|
if (!(item instanceof ConditionalMenuItem) || item.condition()) {
|
||||||
content.appendChild(makeMenuItem(item, removeCallback));
|
content.appendChild(makeMenuItem(item, removeCallback));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,8 +67,9 @@ export function onClick(element, action) {
|
|||||||
* @param {HTMLElement|null} element The element to detect drag events on
|
* @param {HTMLElement|null} element The element to detect drag events on
|
||||||
* @param {boolean} [parent] Whether to move the parent element when the child is dragged
|
* @param {boolean} [parent] Whether to move the parent element when the child is dragged
|
||||||
* @param {(top: number, left: number) => void} [callback] Callback for when element is moved
|
* @param {(top: number, left: number) => void} [callback] Callback for when element is moved
|
||||||
|
* @param {HTMLElement} [pageElement] The page element to constrain movement within
|
||||||
*/
|
*/
|
||||||
export function makeDraggable(element, parent = true, callback = () => { }) {
|
export function makeDraggable(element, parent = true, callback = () => { }, pageElement) {
|
||||||
if (!element) {
|
if (!element) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -95,6 +96,7 @@ export function makeDraggable(element, parent = true, callback = () => { }) {
|
|||||||
offsetX = touch.clientX - elementToMove.offsetLeft;
|
offsetX = touch.clientX - elementToMove.offsetLeft;
|
||||||
offsetY = touch.clientY - elementToMove.offsetTop;
|
offsetY = touch.clientY - elementToMove.offsetTop;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("mouseup", (e) => {
|
document.addEventListener("mouseup", (e) => {
|
||||||
@@ -114,9 +116,12 @@ export function makeDraggable(element, parent = true, callback = () => { }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("mousemove", (e) => {
|
document.addEventListener("mousemove", (e) => {
|
||||||
|
const page = pageElement || document.documentElement;
|
||||||
|
const maxX = page.scrollWidth - elementToMove.clientWidth;
|
||||||
|
const maxY = page.scrollHeight - elementToMove.clientHeight;
|
||||||
if (isMouseDown) {
|
if (isMouseDown) {
|
||||||
elementToMove.style.left = `${Math.max(0, e.clientX - offsetX)}px`;
|
elementToMove.style.left = `${Math.max(0, Math.min(maxX, e.clientX - offsetX))}px`;
|
||||||
elementToMove.style.top = `${Math.max(0, e.clientY - offsetY)}px`;
|
elementToMove.style.top = `${Math.max(0, Math.min(maxY, e.clientY - offsetY))}px`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -132,8 +137,9 @@ export function makeDraggable(element, parent = true, callback = () => { }) {
|
|||||||
/**
|
/**
|
||||||
* @param {() => void} func
|
* @param {() => void} func
|
||||||
* @param {Element} [closeButton]
|
* @param {Element} [closeButton]
|
||||||
|
* @param {boolean} [allowEscape] Whether to allow closing with the Escape key
|
||||||
*/
|
*/
|
||||||
export function makeClosable(func, closeButton) {
|
export function makeClosable(func, closeButton, allowEscape = true) {
|
||||||
if (closeButton) {
|
if (closeButton) {
|
||||||
onClick(closeButton, func);
|
onClick(closeButton, func);
|
||||||
}
|
}
|
||||||
@@ -141,7 +147,7 @@ export function makeClosable(func, closeButton) {
|
|||||||
if (closeButton && !document.body.contains(closeButton)) {
|
if (closeButton && !document.body.contains(closeButton)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (e.key === "Escape") {
|
if (allowEscape && e.key === "Escape") {
|
||||||
func();
|
func();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,13 +33,17 @@ export class StickyNote {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {StickyNote} stickyNote
|
* @param {StickyNote} stickyNote
|
||||||
|
* @param {HTMLElement} page
|
||||||
* @param {() => void} onSave
|
* @param {() => void} onSave
|
||||||
* @param {() => void} onDelete
|
* @param {() => void} onDelete
|
||||||
* @returns {HTMLElement}
|
* @returns {HTMLElement}
|
||||||
*/
|
*/
|
||||||
export function renderStickyNote(stickyNote, onSave, onDelete) {
|
export function renderStickyNote(stickyNote, page, onSave, onDelete) {
|
||||||
const noteElement = makeElement("birb-window");
|
const noteElement = makeElement("birb-window");
|
||||||
noteElement.classList.add("birb-sticky-note");
|
noteElement.classList.add("birb-sticky-note");
|
||||||
|
const color = getColor(stickyNote.id);
|
||||||
|
noteElement.style.setProperty("--birb-highlight", color);
|
||||||
|
noteElement.style.setProperty("--birb-border-color", color);
|
||||||
|
|
||||||
// Create header
|
// Create header
|
||||||
const header = makeElement("birb-window-header");
|
const header = makeElement("birb-window-header");
|
||||||
@@ -62,13 +66,13 @@ export function renderStickyNote(stickyNote, onSave, onDelete) {
|
|||||||
|
|
||||||
noteElement.style.top = `${stickyNote.top}px`;
|
noteElement.style.top = `${stickyNote.top}px`;
|
||||||
noteElement.style.left = `${stickyNote.left}px`;
|
noteElement.style.left = `${stickyNote.left}px`;
|
||||||
document.body.appendChild(noteElement);
|
page.appendChild(noteElement);
|
||||||
|
|
||||||
makeDraggable(header, true, (top, left) => {
|
makeDraggable(header, true, (top, left) => {
|
||||||
stickyNote.top = top;
|
stickyNote.top = top;
|
||||||
stickyNote.left = left;
|
stickyNote.left = left;
|
||||||
onSave();
|
onSave();
|
||||||
});
|
}, page);
|
||||||
|
|
||||||
if (closeButton) {
|
if (closeButton) {
|
||||||
makeClosable(() => {
|
makeClosable(() => {
|
||||||
@@ -76,7 +80,7 @@ export function renderStickyNote(stickyNote, onSave, onDelete) {
|
|||||||
onDelete();
|
onDelete();
|
||||||
noteElement.remove();
|
noteElement.remove();
|
||||||
}
|
}
|
||||||
}, closeButton);
|
}, closeButton, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (textarea && textarea instanceof HTMLTextAreaElement) {
|
if (textarea && textarea instanceof HTMLTextAreaElement) {
|
||||||
@@ -113,10 +117,11 @@ export function drawStickyNotes(stickyNotes, onSave, onDelete) {
|
|||||||
const existingNotes = document.querySelectorAll(".birb-sticky-note");
|
const existingNotes = document.querySelectorAll(".birb-sticky-note");
|
||||||
existingNotes.forEach(note => note.remove());
|
existingNotes.forEach(note => note.remove());
|
||||||
// Render all sticky notes
|
// Render all sticky notes
|
||||||
|
const pageElement = getContext().getActivePage();
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
for (let stickyNote of stickyNotes) {
|
for (let stickyNote of stickyNotes) {
|
||||||
if (context.isPathApplicable(stickyNote.site)) {
|
if (context.isPathApplicable(stickyNote.site)) {
|
||||||
renderStickyNote(stickyNote, onSave, () => onDelete(stickyNote));
|
renderStickyNote(stickyNote, pageElement, onSave, () => onDelete(stickyNote));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,14 +132,29 @@ export function drawStickyNotes(stickyNotes, onSave, onDelete) {
|
|||||||
* @param {(note: StickyNote) => void} onDelete
|
* @param {(note: StickyNote) => void} onDelete
|
||||||
*/
|
*/
|
||||||
export function createNewStickyNote(stickyNotes, onSave, onDelete) {
|
export function createNewStickyNote(stickyNotes, onSave, onDelete) {
|
||||||
|
if (getContext().areStickyNotesEnabled() === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const id = Date.now().toString();
|
const id = Date.now().toString();
|
||||||
const site = getContext().getPath();
|
const site = getContext().getPath();
|
||||||
const stickyNote = new StickyNote(id, site, "");
|
const stickyNote = new StickyNote(id, site, "");
|
||||||
const element = renderStickyNote(stickyNote, onSave, () => onDelete(stickyNote));
|
const page = getContext().getActivePage();
|
||||||
element.style.left = `${window.innerWidth / 2 - element.offsetWidth / 2}px`;
|
const element = renderStickyNote(stickyNote, page, onSave, () => onDelete(stickyNote));
|
||||||
element.style.top = `${window.scrollY + window.innerHeight / 2 - element.offsetHeight / 2}px`;
|
element.style.left = `${page.clientWidth / 2 - element.offsetWidth / 2}px`;
|
||||||
|
element.style.top = `${page.scrollTop + page.clientHeight / 2 - element.offsetHeight / 2}px`;
|
||||||
stickyNote.top = parseInt(element.style.top, 10);
|
stickyNote.top = parseInt(element.style.top, 10);
|
||||||
stickyNote.left = parseInt(element.style.left, 10);
|
stickyNote.left = parseInt(element.style.left, 10);
|
||||||
stickyNotes.push(stickyNote);
|
stickyNotes.push(stickyNote);
|
||||||
onSave();
|
onSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a color based on the mod of the sticky note ID
|
||||||
|
* @param {string} id
|
||||||
|
* @returns {string} A color hex code
|
||||||
|
*/
|
||||||
|
function getColor(id) {
|
||||||
|
const colors = ["#ff8baa", "#79bcff", "#d18bff", "#6de192", "#ffd17c", "#ffb37c", "#ff7c7c"];
|
||||||
|
const index = parseInt(id, 10) % colors.length;
|
||||||
|
return colors[index];
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: 'Monocraft';
|
||||||
|
src: url("__MONOCRAFT_SRC__") format('opentype');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--birb-border-size: 2px;
|
--birb-border-size: 2px;
|
||||||
--birb-neg-border-size: calc(var(--birb-border-size) * -1);
|
--birb-neg-border-size: calc(var(--birb-border-size) * -1);
|
||||||
@@ -119,6 +126,7 @@
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
color: var(--birb-background-color);
|
color: var(--birb-background-color);
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.birb-window-close {
|
.birb-window-close {
|
||||||
@@ -317,6 +325,17 @@
|
|||||||
.birb-sticky-note {
|
.birb-sticky-note {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
animation: fade-in 0.15s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.birb-sticky-note > .birb-window-content {
|
.birb-sticky-note > .birb-window-content {
|
||||||
@@ -339,5 +358,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.birb-sticky-note-input:focus {
|
.birb-sticky-note-input:focus {
|
||||||
outline: none;
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user