Skip to content

Commit 64bf181

Browse files
authored
Remove legacy schema option and support type argument (#208)
1 parent 3e8770a commit 64bf181

14 files changed

Lines changed: 135 additions & 292 deletions

File tree

src/doc/edit.ts

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { is, isString, keys } from "../utils.js";
22
import { compareLine, comparePosition } from "./position.js";
33
import type {
4-
Doc,
4+
DocBase,
55
Fragment,
66
DocNode,
77
Position,
88
SelectionSnapshot,
9+
TextNode,
910
} from "./types.js";
1011
import { docToString, stringToFragment } from "./utils.js";
1112

@@ -99,14 +100,14 @@ export class Transaction {
99100
/**
100101
* @internal
101102
*/
102-
export const isDocEqual = (docA: Doc, docB: Doc): boolean =>
103+
export const isDocEqual = (docA: DocBase, docB: DocBase): boolean =>
103104
// TODO improve
104105
docA.length === docB.length && docA.every((l, i) => l === docB[i]);
105106

106107
/**
107108
* @internal
108109
*/
109-
export const isTextNode = (node: DocNode) => "text" in node;
110+
export const isTextNode = (node: DocNode): node is TextNode => "text" in node;
110111

111112
const isSameNode = (a: DocNode, b: DocNode): boolean => {
112113
const aKeys = keys(a);
@@ -128,7 +129,7 @@ const getNodeSize = (node: DocNode): number =>
128129
* @internal
129130
*/
130131
export const getLineSize = (line: readonly DocNode[]): number =>
131-
line.reduce((acc, n) => acc + getNodeSize(n), 0);
132+
line.reduce((acc: number, n) => acc + getNodeSize(n), 0);
132133

133134
const normalize = <T extends DocNode>(
134135
array: T[],
@@ -202,12 +203,12 @@ const split = <T extends DocNode>(
202203
return [nodes, []];
203204
};
204205

205-
const replaceRange = (
206-
doc: Doc,
206+
const replaceRange = <T extends DocBase>(
207+
doc: T,
207208
inserted: Fragment | string,
208209
start: Position,
209210
end?: Position,
210-
): Doc => {
211+
): T => {
211212
const [startLine] = start;
212213
const [endLine] = end || start;
213214

@@ -217,10 +218,14 @@ const replaceRange = (
217218
if (isString(inserted)) {
218219
// inherit style from previous text node
219220
const beforeLength = before.length;
220-
inserted = stringToFragment(
221-
inserted,
222-
beforeLength ? before[beforeLength - 1]! : undefined,
223-
);
221+
let anchorNode: TextNode | undefined;
222+
if (beforeLength) {
223+
const maybeAnchor = before[beforeLength - 1]!;
224+
if (isTextNode(maybeAnchor)) {
225+
anchorNode = maybeAnchor;
226+
}
227+
}
228+
inserted = stringToFragment(inserted, anchorNode);
224229
}
225230

226231
let lines: (readonly DocNode[])[];
@@ -234,14 +239,14 @@ const replaceRange = (
234239

235240
const newDoc = doc.slice();
236241
newDoc.splice(startLine, endLine - startLine + 1, ...lines);
237-
return newDoc;
242+
return newDoc as DocBase as T; // TODO improve
238243
};
239244

240245
/**
241246
* @internal
242247
*/
243248
export const sliceDoc = (
244-
doc: Doc,
249+
doc: DocBase,
245250
start: Position,
246251
end: Position,
247252
): Fragment => {
@@ -255,7 +260,7 @@ export const sliceDoc = (
255260
];
256261
};
257262

258-
const isValidPosition = (doc: Doc, [line, offset]: Position): boolean => {
263+
const isValidPosition = (doc: DocBase, [line, offset]: Position): boolean => {
259264
if (line >= 0 && line < doc.length) {
260265
if (offset >= 0 && offset <= getLineSize(doc[line]!)) {
261266
return true;
@@ -264,7 +269,7 @@ const isValidPosition = (doc: Doc, [line, offset]: Position): boolean => {
264269
return false;
265270
};
266271

267-
const isValidOperation = (doc: Doc, op: Operation): boolean => {
272+
const isValidOperation = (doc: DocBase, op: Operation): boolean => {
268273
switch (op._type) {
269274
case TYPE_DELETE: {
270275
const { _start: start, _end: end } = op;
@@ -350,12 +355,12 @@ const rebasePosition = (position: Position, op: EditOperation): Position => {
350355
/**
351356
* @internal
352357
*/
353-
export const applyTransaction = (
354-
doc: Doc,
358+
export const applyTransaction = <T extends DocBase>(
359+
doc: T,
355360
selection: SelectionSnapshot,
356361
tr: Transaction,
357362
onError?: (message: string) => void,
358-
): [Doc, SelectionSnapshot] | undefined => {
363+
): [T, SelectionSnapshot] | undefined => {
359364
try {
360365
for (const op of tr.ops) {
361366
if (isValidOperation(doc, op)) {

src/doc/types.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
export type TextNode = Readonly<{
2-
text: string;
3-
}>;
4-
export type VoidNode = Readonly<{
5-
data: Record<string, unknown>;
6-
}>;
1+
export interface TextNode {
2+
readonly text: string;
3+
}
4+
5+
export interface VoidNode {}
6+
77
export type DocNode = TextNode | VoidNode;
8-
export type Doc = readonly (readonly DocNode[])[];
8+
9+
export type DocBase = readonly (readonly DocNode[])[];
910
export type Fragment = readonly (readonly DocNode[])[];
1011

12+
export type InferNode<T extends DocBase> = T[number][number];
13+
1114
export type Position = readonly [line: number, offset: number];
1215
export type PositionRange = readonly [start: Position, end: Position];
1316

src/doc/utils.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import { isTextNode } from "./edit.js";
2-
import { type Doc, type DocNode, type Fragment } from "./types.js";
2+
import {
3+
type DocBase,
4+
type DocNode,
5+
type Fragment,
6+
type TextNode,
7+
} from "./types.js";
38

49
/**
510
* @internal
611
*/
712
export const docToString = (
8-
doc: Doc,
13+
doc: DocBase,
914
serializer: (node: DocNode) => string = (n) => (isTextNode(n) ? n.text : ""),
1015
): string => {
1116
return doc.reduce((acc, r, i) => {
@@ -19,9 +24,6 @@ export const docToString = (
1924
/**
2025
* @internal
2126
*/
22-
export const stringToFragment = (
23-
text: string,
24-
attrs?: Record<string, any>,
25-
): Fragment => {
26-
return text.split("\n").map((l) => (l ? [{ ...attrs, text: l }] : []));
27+
export const stringToFragment = (text: string, node?: TextNode): Fragment => {
28+
return text.split("\n").map((l) => (l ? [{ ...node, text: l }] : []));
2729
};

src/editor.ts

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
serializeRange,
1010
} from "./dom/index.js";
1111
import { createMutationObserver } from "./mutation.js";
12-
import type { Doc, Fragment, SelectionSnapshot } from "./doc/types.js";
12+
import type { DocBase, Fragment, SelectionSnapshot } from "./doc/types.js";
1313
import { isString, microtask } from "./utils.js";
1414
import type { EditorCommand } from "./commands.js";
1515
import {
@@ -19,7 +19,6 @@ import {
1919
isDocEqual,
2020
} from "./doc/edit.js";
2121
import { singlelinePlugin } from "./plugins/singleline.js";
22-
import type { DocSchema } from "./schema/index.js";
2322
import type { ParserConfig } from "./dom/parser.js";
2423
import { comparePosition, toRange } from "./doc/position.js";
2524
import type { PluginObject } from "./plugins/types.js";
@@ -95,15 +94,15 @@ type KeydownCallback = (keyboard: KeyboardPayload) => boolean | void;
9594
/**
9695
* Options of {@link createEditor}.
9796
*/
98-
export interface EditorOptions<T> {
99-
/**
100-
* TODO
101-
*/
102-
schema: DocSchema<T>;
97+
export interface EditorOptions<T extends DocBase> {
10398
/**
10499
* Initial document state.
105100
*/
106101
doc: T;
102+
/**
103+
* TODO
104+
*/
105+
singleline?: boolean;
107106
/**
108107
* Functions to handle copy events
109108
* @default [plainCopy()]
@@ -140,7 +139,7 @@ export interface EditorOptions<T> {
140139
* Methods of editor instance.
141140
*/
142141
export interface Editor {
143-
readonly doc: Doc;
142+
readonly doc: DocBase;
144143
readonly selection: SelectionSnapshot;
145144
/**
146145
* The getter/setter for the editor's read-only state.
@@ -164,9 +163,9 @@ export interface Editor {
164163
/**
165164
* A function to initialize editor.
166165
*/
167-
export const createEditor = <T>({
168-
schema: { single: isSingleline, js: docToJS, doc: jsToDoc },
166+
export const createEditor = <T extends DocBase>({
169167
doc: initialDoc,
168+
singleline: isSingleline,
170169
copy: copyExtensions = [plainCopy()],
171170
paste: pasteExtensions = [plainPaste()],
172171
isBlock = defaultIsBlockNode,
@@ -178,11 +177,11 @@ export const createEditor = <T>({
178177
let readonly = false;
179178
let setContentEditable: () => void = noop;
180179

181-
const doc = (): Doc => history.get()[0];
180+
const doc = (): T => history.get()[0];
182181

183182
const history = createHistory<
184-
readonly [doc: Doc, selection: SelectionSnapshot]
185-
>([jsToDoc(initialDoc), selection]);
183+
readonly [doc: T, selection: SelectionSnapshot]
184+
>([initialDoc, selection]);
186185

187186
const plugins: PluginObject[] = [];
188187
if (isSingleline) {
@@ -196,7 +195,7 @@ export const createEditor = <T>({
196195
const nextHistory = e.shiftKey ? history.redo() : history.undo();
197196

198197
if (nextHistory) {
199-
onChange(docToJS(nextHistory[0]));
198+
onChange(nextHistory[0]);
200199
selection = nextHistory[1];
201200
}
202201
return true;
@@ -209,13 +208,17 @@ export const createEditor = <T>({
209208
keydownHandlers.push(onKeyDownHandler);
210209
}
211210

212-
const applyTransaction = plugins.reduceRight(
211+
const applyTransaction = plugins.reduceRight<
212+
(
213+
doc: T,
214+
sel: SelectionSnapshot,
215+
tr: Transaction,
216+
) => ReturnType<typeof _applyTransaction>
217+
>(
213218
(acc, { apply: fn }) => {
214219
return fn ? (doc, sel, tr) => fn((tr) => acc(doc, sel, tr), tr) : acc;
215220
},
216-
(doc: Doc, sel: SelectionSnapshot, tr: Transaction) => {
217-
return _applyTransaction(doc, sel, tr, onError);
218-
},
221+
(doc, sel, tr) => _applyTransaction(doc, sel, tr, onError),
219222
);
220223

221224
const transactions: Transaction[] = [];
@@ -241,14 +244,14 @@ export const createEditor = <T>({
241244

242245
const commit = () => {
243246
if (transactions.length) {
244-
let nextDoc: Doc = doc();
247+
let nextDoc: T = doc();
245248
let nextSelection: SelectionSnapshot = selection;
246249

247250
let tr: Transaction | undefined;
248251
while ((tr = transactions.pop())) {
249252
const res = applyTransaction(nextDoc, nextSelection, tr);
250253
if (res) {
251-
nextDoc = res[0];
254+
nextDoc = res[0] as T; // TODO improve
252255
nextSelection = res[1];
253256
}
254257
}
@@ -258,7 +261,7 @@ export const createEditor = <T>({
258261
if (!isDocEqual(nextDoc, currentDoc)) {
259262
history.set([currentDoc, selection]);
260263
history.push([nextDoc, nextSelection]);
261-
onChange(docToJS(nextDoc));
264+
onChange(nextDoc);
262265
}
263266

264267
selection = nextSelection;

src/extensions/copy/plain.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import type { DocNode } from "../../doc/types.js";
1+
import type { DocBase, DocNode, InferNode } from "../../doc/types.js";
22
import { docToString } from "../../doc/utils.js";
33
import type { CopyExtension } from "./types.js";
44

5-
export const plainCopy = <T extends DocNode>(
6-
serializer?: (
7-
node: Extract<T, { data: any }> | { text: string } /* TODO */,
8-
) => string,
5+
export const plainCopy = <T extends DocBase>(
6+
serializer?: (node: InferNode<T>) => string,
97
): CopyExtension => {
108
return (dataTransfer, data) => {
119
dataTransfer.setData(

src/extensions/paste/html.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import type { DocNode, TextNode } from "../../doc/types.js";
1+
import type { DocBase, InferNode, TextNode } from "../../doc/types.js";
22
import { readDom } from "../../dom/index.js";
33
import { isCommentNode } from "../../dom/parser.js";
44
import type { PasteExtension } from "./types.js";
55

6-
export const htmlPaste = <T extends DocNode>(
7-
serializeText: (t: string) => Extract<T, TextNode>,
8-
serializers: ((node: HTMLElement) => Exclude<T, TextNode> | void)[] = [],
6+
export const htmlPaste = <T extends DocBase>(
7+
serializeText: (t: string) => Extract<InferNode<T>, TextNode>,
8+
serializers: ((
9+
node: HTMLElement,
10+
) => Exclude<InferNode<T>, TextNode> | void)[] = [],
911
): PasteExtension => {
1012
return (dataTransfer, config) => {
1113
const html = dataTransfer.getData("text/html");

src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
export type { EditorCommand } from "./commands.js";
22
export { Delete, InsertText, ReplaceAll } from "./commands.js";
33
export * from "./editor.js";
4-
export * from "./schema/index.js";
54
export * from "./extensions/index.js";
65
export * from "./presets/index.js";

src/plugins/types.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { Transaction } from "../doc/edit.js";
2-
import type { Doc, SelectionSnapshot } from "../doc/types.js";
2+
import type { DocBase, SelectionSnapshot } from "../doc/types.js";
33

44
/**
55
* @internal
66
*/
77
export interface PluginObject {
88
apply?: (
9-
next: (tr: Transaction) => [Doc, SelectionSnapshot] | undefined,
9+
next: (tr: Transaction) => [DocBase, SelectionSnapshot] | undefined,
1010
tr: Transaction,
11-
) => [Doc, SelectionSnapshot] | undefined;
11+
) => [DocBase, SelectionSnapshot] | undefined;
1212
mount?: (element: HTMLElement) => void;
1313
}
1414

0 commit comments

Comments
 (0)