import type { z, ZodTypeDef } from 'zod'; import type { AttributeRenderer } from '../types.js'; import type { VRange } from '../types.js'; import type { BaseTextAttributes } from '../utils/index.js'; import { baseTextAttributes, getDefaultAttributeRenderer, } from '../utils/index.js'; import type { VEditor } from '../virgo.js'; export class VirgoAttributeService { private _marks: TextAttributes | null = null; private _attributeRenderer: AttributeRenderer = getDefaultAttributeRenderer(); private _attributeSchema: z.ZodSchema = baseTextAttributes as z.ZodSchema; constructor(public readonly editor: VEditor) {} get marks() { return this._marks; } get attributeRenderer() { return this._attributeRenderer; } setMarks = (marks: TextAttributes): void => { this._marks = marks; }; resetMarks = (): void => { this._marks = null; }; setAttributeSchema = ( schema: z.ZodSchema ) => { this._attributeSchema = schema; }; setAttributeRenderer = (renderer: AttributeRenderer) => { this._attributeRenderer = renderer; }; getFormat = (vRange: VRange, loose = false): TextAttributes => { const deltas = this.editor.deltaService .getDeltasByVRange(vRange) .filter( ([_, position]) => position.index + position.length > vRange.index && position.index <= vRange.index + vRange.length ); const maybeAttributesList = deltas.map(([delta]) => delta.attributes); if (loose) { return maybeAttributesList.reduce( (acc, cur) => ({ ...acc, ...cur }), {} ) as TextAttributes; } if ( !maybeAttributesList.length || // some text does not have any attribute maybeAttributesList.some(attributes => !attributes) ) { return {} as TextAttributes; } const attributesList = maybeAttributesList as TextAttributes[]; return attributesList.reduce((acc, cur) => { const newFormat = {} as TextAttributes; for (const key in acc) { const typedKey = key as keyof TextAttributes; // If the given range contains multiple different formats // such as links with different values, // we will treat it as having no format if (acc[typedKey] === cur[typedKey]) { // This cast is secure because we have checked that the value of the key is the same. // eslint-disable-next-line @typescript-eslint/no-explicit-any newFormat[typedKey] = acc[typedKey] as any; } } return newFormat; }); }; normalizeAttributes = (textAttributes?: TextAttributes) => { if (!textAttributes) { return undefined; } const attributeResult = this._attributeSchema.safeParse(textAttributes); if (!attributeResult.success) { console.error(attributeResult.error); return undefined; } return Object.fromEntries( // filter out undefined values Object.entries(attributeResult.data).filter(([_, v]) => v || v === null) ) as TextAttributes; }; }