feat(editor): eslint/tsconfig migration + type fixes

@robonen/editor: migrate to eslint flat config + composite tsconfig; fix
convergence test type annotations.
This commit is contained in:
2026-06-07 16:30:05 +07:00
parent 626fbc70d8
commit 09272dffeb
136 changed files with 7248 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
import type { Attrs } from './attrs';
import { attrsEq } from './attrs';
/**
* An inline formatting mark applied to a run of text (bold, italic, link, ...).
* `type` is the registry key; `attrs` holds mark-specific data (e.g. link href).
*/
export interface Mark {
readonly type: string;
readonly attrs?: Attrs;
}
/** A normalized set of marks: at most one per type, sorted by `type`. */
export type Marks = readonly Mark[];
/** Structural equality for two marks (type + attrs). */
export function markEq(a: Mark, b: Mark): boolean {
return a.type === b.type && attrsEq(a.attrs, b.attrs);
}
/** Ordered structural equality for two normalized mark sets. */
export function marksEq(a: Marks, b: Marks): boolean {
if (a === b)
return true;
if (a.length !== b.length)
return false;
return a.every((mark, i) => markEq(mark, b[i]!));
}
/**
* Canonicalize a mark set: keep the last occurrence per `type` (so a re-applied
* mark with new attrs wins) and sort by `type`. The deterministic order is what
* makes {@link marksEq} an O(n) comparison and keeps the model diff-stable.
*/
export function normalizeMarks(marks: Marks): Marks {
if (marks.length <= 1)
return marks;
const byType = new Map<string, Mark>();
for (const mark of marks)
byType.set(mark.type, mark);
return [...byType.values()].sort((a, b) => (a.type < b.type ? -1 : a.type > b.type ? 1 : 0));
}
/** Whether `marks` contains a mark structurally equal to `mark`. */
export function hasMark(marks: Marks, mark: Mark): boolean {
return marks.some(m => markEq(m, mark));
}
/** Whether `marks` contains any mark of the given `type`. */
export function hasMarkType(marks: Marks, type: string): boolean {
return marks.some(m => m.type === type);
}