import { makeElement, makeDraggable, makeClosable } from './shared.js'; /** * @typedef {Object} SavedStickyNote * @property {string} id * @property {string} site * @property {string} content * @property {number} top * @property {number} left */ export class StickyNote { /** * @param {string} id * @param {string} [site] * @param {string} [content] * @param {number} [top] * @param {number} [left] */ constructor(id, site = "", content = "", top = 0, left = 0) { this.id = id; this.site = site; this.content = content; this.top = top; this.left = left; } } /** * Parse URL parameters into a key-value map * @param {string} url * @returns {Record} */ export function parseUrlParams(url) { const queryString = url.split("?")[1]; if (!queryString) return {}; return queryString.split("&").reduce((params, param) => { const [key, value] = param.split("="); return { ...params, [key]: value }; }, {}); } /** * @param {StickyNote} stickyNote * @returns {boolean} Whether the given sticky note is applicable to the current site/page */ export function isStickyNoteApplicable(stickyNote) { const stickyNoteUrl = stickyNote.site; const currentUrl = window.location.href; const stickyNoteWebsite = stickyNoteUrl.split("?")[0]; const currentWebsite = currentUrl.split("?")[0]; if (stickyNoteWebsite !== currentWebsite) { return false; } const stickyNoteParams = parseUrlParams(stickyNoteUrl); const currentParams = parseUrlParams(currentUrl); if (window.location.hostname === "www.youtube.com") { if (currentParams.v !== undefined && currentParams.v !== stickyNoteParams.v) { return false; } } return true; } /** * @param {StickyNote} stickyNote * @param {() => void} onSave * @param {() => void} onDelete * @returns {HTMLElement} */ export function renderStickyNote(stickyNote, onSave, onDelete) { const noteElement = makeElement("birb-window"); noteElement.classList.add("birb-sticky-note"); // Create header const header = makeElement("birb-window-header"); const titleDiv = makeElement("birb-window-title", "Sticky Note"); const closeButton = makeElement("birb-window-close", "x"); header.appendChild(titleDiv); header.appendChild(closeButton); // Create content const content = makeElement("birb-window-content"); const textarea = document.createElement("textarea"); textarea.className = "birb-sticky-note-input"; textarea.style.width = "150px"; textarea.placeholder = "Write your notes here and they'll stick to the page!"; textarea.value = stickyNote.content; content.appendChild(textarea); noteElement.appendChild(header); noteElement.appendChild(content); noteElement.style.top = `${stickyNote.top}px`; noteElement.style.left = `${stickyNote.left}px`; document.body.appendChild(noteElement); makeDraggable(header, true, (top, left) => { stickyNote.top = top; stickyNote.left = left; onSave(); }); if (closeButton) { makeClosable(() => { if (stickyNote.content.trim() === "" || confirm("Are you sure you want to delete this sticky note?")) { onDelete(); noteElement.remove(); } }, closeButton); } if (textarea && textarea instanceof HTMLTextAreaElement) { let saveTimeout; // Save after debounce textarea.addEventListener("input", () => { stickyNote.content = textarea.value; if (saveTimeout) { clearTimeout(saveTimeout); } saveTimeout = setTimeout(() => { onSave(); }, 250); }); } // On window resize window.addEventListener("resize", () => { const modTop = `${stickyNote.top - Math.min(window.innerHeight - noteElement.offsetHeight, stickyNote.top)}px`; const modLeft = `${stickyNote.left - Math.min(window.innerWidth - noteElement.offsetWidth, stickyNote.left)}px`; noteElement.style.transform = `scale(var(--birb-ui-scale)) translate(-${modLeft}, -${modTop})`; }); return noteElement; } /** * @param {StickyNote[]} stickyNotes * @param {() => void} onSave * @param {(note: StickyNote) => void} onDelete */ export function drawStickyNotes(stickyNotes, onSave, onDelete) { // Remove all existing sticky notes const existingNotes = document.querySelectorAll(".birb-sticky-note"); existingNotes.forEach(note => note.remove()); // Render all sticky notes for (let stickyNote of stickyNotes) { if (isStickyNoteApplicable(stickyNote)) { renderStickyNote(stickyNote, onSave, () => onDelete(stickyNote)); } } } /** * @param {StickyNote[]} stickyNotes * @param {() => void} onSave * @param {(note: StickyNote) => void} onDelete */ export function createNewStickyNote(stickyNotes, onSave, onDelete) { const id = Date.now().toString(); const site = window.location.href; const stickyNote = new StickyNote(id, site, ""); const element = renderStickyNote(stickyNote, onSave, () => onDelete(stickyNote)); element.style.left = `${window.innerWidth / 2 - element.offsetWidth / 2}px`; element.style.top = `${window.scrollY + window.innerHeight / 2 - element.offsetHeight / 2}px`; stickyNote.top = parseInt(element.style.top, 10); stickyNote.left = parseInt(element.style.left, 10); stickyNotes.push(stickyNote); onSave(); }