diff --git a/packages/frontend/src/account.ts b/packages/frontend/src/account.ts index 36186ecac1..722f296732 100644 --- a/packages/frontend/src/account.ts +++ b/packages/frontend/src/account.ts @@ -22,23 +22,66 @@ type Account = Misskey.entities.MeDetailed & { token: string }; const accountData = miLocalStorage.getItem('account'); // TODO: 外部からはreadonlyに +/** + * Reactive state for the current account. "I" as in "I am logged in". + * Initialized from local storage if available, otherwise null. + * + * @type {Account | null} + */ export const $i = accountData ? reactive(JSON.parse(accountData) as Account) : null; +/** + * Whether the current account is a moderator. + * + * @type {boolean} + */ export const iAmModerator = $i != null && ($i.isAdmin === true || $i.isModerator === true); + +/** + * Whether the current account is an administrator. + * + * @type {boolean} + */ export const iAmAdmin = $i != null && $i.isAdmin; +/** + * Whether it is necessary to sign in; checks if the current + * account is null and throws an error if so. + * + * @throws {Error} If the current account is null + * @returns {Account} The current account + */ export function signinRequired() { if ($i == null) throw new Error('signin required'); return $i; } +/** + * Extracts the current number of notes from the current account. + * + * Note: This appears to only be used for the "notes1" achievement. + * + * Also, separating it like this might cause counts to get out-of-sync. + */ export let notesCount = $i == null ? 0 : $i.notesCount; + +/** + * Increments the number of notes by one. + * + * Documentation TODO: What about $i.notesCount? Why not increment that? + */ export function incNotesCount() { notesCount++; } export async function signout() { - if (!$i) return; + + // If we're not signed in, there's nothing to do. + if (!$i) { + // Error log: + console.error('signout() called when not signed in'); + return; + } waiting(); miLocalStorage.removeItem('account'); diff --git a/packages/frontend/src/local-storage.ts b/packages/frontend/src/local-storage.ts index 5b8ba77e01..828b01848f 100644 --- a/packages/frontend/src/local-storage.ts +++ b/packages/frontend/src/local-storage.ts @@ -3,6 +3,9 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +/** + * A typesafe enum of keys for localStorage. + */ export type Keys = 'v' | 'lastVersion' | @@ -44,16 +47,45 @@ export type Keys = // セッション毎に廃棄されるLocalStorage代替(セーフモードなどで使用できそう) //const safeSessionStorage = new Map(); + +/** + * A utility object for interacting with the browser's localStorage. + * + * It's mostly a small wrapper around window.localStorage, but it validates + * keys with a typesafe enum, and provides a few convenience methods for JSON. + */ export const miLocalStorage = { + /** + * Retrieves an item from localStorage. + * @param {Keys} key - The key of the item to retrieve. + * @returns {string | null} The value of the item, or null if the item does not exist. + */ getItem: (key: Keys): string | null => { return window.localStorage.getItem(key); }, + + /** + * Stores an item in localStorage. + * @param {Keys} key - The key of the item to store. + * @param {string} value - The value of the item to store. + */ setItem: (key: Keys, value: string): void => { window.localStorage.setItem(key, value); }, + + /** + * Removes an item from localStorage. + * @param {Keys} key - The key of the item to remove. + */ removeItem: (key: Keys): void => { window.localStorage.removeItem(key); }, + + /** + * Retrieves an item from localStorage and parses it as JSON. + * @param {Keys} key - The key of the item to retrieve. + * @returns {any | undefined} The parsed value of the item, or undefined if the item does not exist. + */ getItemAsJson: (key: Keys): any | undefined => { const item = miLocalStorage.getItem(key); if (item === null) { @@ -61,6 +93,12 @@ export const miLocalStorage = { } return JSON.parse(item); }, + + /** + * Stores an item in localStorage as a JSON string. + * @param {Keys} key - The key of the item to store. + * @param {any} value - The value of the item to store. + */ setItemAsJson: (key: Keys, value: any): void => { miLocalStorage.setItem(key, JSON.stringify(value)); }, diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts index ea1b673de9..0eb4e2ac53 100644 --- a/packages/frontend/src/os.ts +++ b/packages/frontend/src/os.ts @@ -136,7 +136,16 @@ export function promiseDialog>( return promise; } +/** + * Counter for generating unique popup IDs. + * @type {number} + */ let popupIdCount = 0; + +/** + * A reactive list of the currently opened popups. This is used in a Vue component + * in a v-for loop to render the popups. + */ export const popups = ref<{ id: number; component: Component; @@ -144,12 +153,23 @@ export const popups = ref<{ events: Record; }[]>([]); +/** + * An object containing z-index values for different priority levels. + */ const zIndexes = { veryLow: 500000, low: 1000000, middle: 2000000, high: 3000000, }; + +/** + * Claims a z-index value for a given priority level. + * Increments the z-index value for the specified priority by 100 and returns the new value. + * + * @param {keyof typeof zIndexes} [priority='low'] - The priority level for which to claim a z-index. + * @returns {number} The new z-index value for the specified priority. + */ export function claimZIndex(priority: keyof typeof zIndexes = 'low'): number { zIndexes[priority] += 100; return zIndexes[priority]; @@ -177,6 +197,15 @@ type EmitsExtractor = { [K in keyof T as K extends `onVnode${string}` ? never : K extends `on${infer E}` ? Uncapitalize : K extends string ? never : K]: T[K]; }; +/** + * Opens a popup with the specified component, props, and events. + * + * @template T - The type of the component. + * @param {T} component - The Vue component to display in the popup. + * @param {ComponentProps} props - The props to pass to the component. + * @param {ComponentEmit} [events={}] - The events to bind to the component. + * @returns {{ dispose: () => void }} An object containing a dispose function to close the popup. + */ export function popup( component: T, props: ComponentProps, @@ -184,13 +213,18 @@ export function popup( ): { dispose: () => void } { markRaw(component); + // Generate a unique ID for this popup. const id = ++popupIdCount; + + // On disposal, remove this popup from the list of open popups. const dispose = () => { // このsetTimeoutが無いと挙動がおかしくなる(autocompleteが閉じなくなる)。Vueのバグ? window.setTimeout(() => { popups.value = popups.value.filter(p => p.id !== id); }, 0); }; + + // Bundle the component, props, and events into a state object. const state = { component, props, @@ -198,13 +232,19 @@ export function popup( id, }; + // Add the popup to the list of open popups. popups.value.push(state); + // Return a function that can be called to close the popup. return { dispose, }; } +/** + * Open the page with the given path in a pop-up window. + * @param path The path of the page to open. + */ export function pageWindow(path: string) { const { dispose } = popup(MkPageWindow, { initialPath: path, @@ -213,6 +253,11 @@ export function pageWindow(path: string) { }); } +/** + * Displays a toast message to the user. + * + * @param {string} message - The message to display in the toast. + */ export function toast(message: string) { const { dispose } = popup(MkToast, { message, @@ -221,6 +266,15 @@ export function toast(message: string) { }); } +/** + * Displays an alert dialog to the user. + * + * @param {Object} props - The properties for the alert dialog. + * @param {'error' | 'info' | 'success' | 'warning' | 'waiting' | 'question'} [props.type] - The type of the alert. + * @param {string} [props.title] - The title of the alert dialog. + * @param {string} [props.text] - The text content of the alert dialog. + * @returns {Promise} A promise that resolves when the alert dialog is closed. + */ export function alert(props: { type?: 'error' | 'info' | 'success' | 'warning' | 'waiting' | 'question'; title?: string; diff --git a/packages/frontend/src/scripts/idb-proxy.ts b/packages/frontend/src/scripts/idb-proxy.ts index 20f51660c7..425225fc42 100644 --- a/packages/frontend/src/scripts/idb-proxy.ts +++ b/packages/frontend/src/scripts/idb-proxy.ts @@ -26,6 +26,7 @@ if (window.Cypress) { console.log('Cypress detected. It will use localStorage.'); } +// Check for the availability of indexedDB. if (idbAvailable) { await iset('idb-test', 'test') .catch(err => { @@ -37,16 +38,36 @@ if (idbAvailable) { console.error('indexedDB is unavailable. It will use localStorage.'); } +/** + * Get a value from indexedDB (or localStorage as a fallback). + * + * @param key The key of the item to retrieve. + * + * @returns The value of the item. + */ export async function get(key: string) { if (idbAvailable) return iget(key); return miLocalStorage.getItemAsJson(`${PREFIX}${key}`); } +/** + * Set a value in indexedDB (or localStorage as a fallback). + * + * @param {string} key - The key of the item to set. + * @param {any} val - The value of the item to set. + * @returns {Promise} - A promise that resolves when the value has been set.` + */ export async function set(key: string, val: any) { if (idbAvailable) return iset(key, val); return miLocalStorage.setItemAsJson(`${PREFIX}${key}`, val); } +/** + * Delete a value from indexedDB (or localStorage as a fallback). + * + * @param {string} key - The key of the item to delete. + * @returns {Promise} - A promise that resolves when the value has been deleted. + */ export async function del(key: string) { if (idbAvailable) return idel(key); return miLocalStorage.removeItem(`${PREFIX}${key}`);