import { Notice } from 'obsidian'; import PromiseWorker from 'promise-worker'; import { NoticeExt } from './obsidian-extensions'; export const DISALLOWED_FILENAME_CHARACTERS_RE = /[*"\\/<>:|?]/g; /** * Manages a category of notices to be displayed in the UI. Prevents multiple * notices being shown at the same time. */ export class Notifier { static DISAPPEARING_CLASS = 'mod-disappearing'; currentNotice?: NoticeExt; mutationObserver?: MutationObserver; constructor(public defaultMessage: string) {} unload(): void { this.hide(); } /** * @returns true if the notice was shown, and false otherwise */ show(message?: string): boolean { message = message || this.defaultMessage; if (this.currentNotice) return false; this.currentNotice = new Notice(message) as NoticeExt; // Set up mutation observer to watch for when the notice disappears. this.mutationObserver?.disconnect(); this.mutationObserver = new MutationObserver((changes, observer) => { const isDisappearing = changes.some((change) => { const el = change.target as HTMLElement; return ( change.type == 'attributes' && el.hasClass(NoticeExt.DISAPPEARING_CLASS) ); }); if (isDisappearing) { this.currentNotice = null; observer.disconnect(); this.mutationObserver = null; } }); this.mutationObserver.observe(this.currentNotice.noticeEl, { attributeFilter: ['class'], }); } hide(): void { this.currentNotice?.hide(); this.mutationObserver?.disconnect(); this.currentNotice = null; this.mutationObserver = null; } } /** * Manages a Worker, recording its state and optionally preventing * message postings before responses to prior messages have been received. */ export class WorkerManager { private worker = new PromiseWorker(this._worker); options: WorkerManagerOptions; /** * Only relevant when `blockingChannel` option is true. * Then this property is true iff the worker is currently processing a * received message, and has not yet posted a response. */ blocked = false; constructor(private _worker: Worker, options: WorkerManagerOptions) { this.options = { ...workerManagerDefaultOptions, ...options }; } /** * Attempt to post a message to the worker and return a promise response. * * If `blockingChannel` option is true and the channel is currently blocked, * the message will be discarded and an error will be thrown. */ async post<TResult = any, TInput = any>(msg: TInput): Promise<TResult> { if (this.options.blockingChannel && this.blocked) { throw new WorkerManagerBlocked(); } this.blocked = true; return this.worker.postMessage(msg).then( (result) => { this.blocked = false; return result; }, (error) => { this.blocked = false; throw error; }, ); } } export class WorkerManagerBlocked extends Error { constructor() { super('WorkerManager: discarded message because channel is blocked'); Object.setPrototypeOf(this, WorkerManagerBlocked.prototype); } } export interface WorkerManagerOptions { /** * If true, treat the worker channel as blocking -- when the worker receives * a message, no other messages can be sent until the worker sends a message. * Messages which are sent during the blocking period will be discarded. */ blockingChannel: boolean; } const workerManagerDefaultOptions: WorkerManagerOptions = { blockingChannel: false, };