@b9g/revise 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2021 Brian Kim
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,70 +1,159 @@
1
1
  # Revise.js
2
2
 
3
- Revise is a JavaScript library for creating
4
- [`contenteditable`-based](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable)
5
- rich-text editors in the browser. It provides a low-level, web-component based
6
- API for translating the DOM into string documents. The library also ships with
7
- a compact data structure for representing edits to strings. Revise is intended
8
- to be framework-agnostic, though currently it is only being tested with
9
- [Crank](crank.js.org).
3
+ Revise is a JavaScript library for building rich-text editors on top of
4
+ [`contenteditable`](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable).
5
+ It provides low-level primitives a custom element, an edit data structure,
6
+ a keyed reconciler, and a state coordinator so that any framework can build
7
+ editing experiences without fighting the DOM.
10
8
 
11
- ## Status
12
-
13
- At present, this library is in an early beta. It is recommended for developers
14
- who aren’t afraid of stepping through DOM code in a debugger.
15
-
16
- ## Usage
17
-
18
- TKTKTKT
19
-
20
- ## API
21
-
22
- ### `ContentAreaElement`
23
-
24
- A custom element class with an API similar to that of the JavaScript API for
25
- the [`<textarea>`
26
- element](https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement).
27
- This class provides special properties and methods for reading the current
28
- value of the element’s contents and manipulating its selection.
29
-
30
- - `value`
31
-
32
- - `selectionStart`
33
-
34
- - `selectionEnd`
9
+ ```
10
+ npm install @b9g/revise
11
+ ```
35
12
 
36
- - `selectionDirection`
37
-
38
- - `setSelectionRange(selectionStart: number, selectionEnd: number, selectionDirection?: SelectionDirection): void;`
39
-
40
- - `indexAt(node: Node | null, offset: number): number`
41
-
42
- - `nodeOffsetAt(index: number): [Node | null, number]
43
-
44
- - `source(name: string): boolean`
45
-
46
- - `ContentEvent`
47
-
48
- ### `Edit`
49
-
50
- - `operations(): Array<Operation>`
51
-
52
- - `apply(text: string): string`
53
-
54
- - `compose(that: Edit): Edit`
13
+ ## Status
55
14
 
56
- - `invert(): Edit`
15
+ Early beta. The API is stabilizing but may still change. Recommended for
16
+ developers who aren’t afraid of stepping through DOM code in a debugger.
17
+
18
+ ## Quick Start
19
+
20
+ Register the custom element, create an `EditableState`, and render keyed lines
21
+ inside a `<content-area>`:
57
22
 
58
- - `normalize(): Edit`
23
+ ```js
24
+ import {ContentAreaElement} from "@b9g/revise/contentarea.js";
25
+ import {EditableState} from "@b9g/revise/state.js";
59
26
 
60
- - `hasChangesBetween(start: number, end: number): boolean`
27
+ customElements.define("content-area", ContentAreaElement);
61
28
 
62
- - `Edit.createBuilder(value: string): EditBuilder`
29
+ const state = new EditableState({value: "Hello World!\n"});
30
+ ```
31
+
32
+ When `<content-area>` detects a user edit, it fires a `contentchange` event
33
+ with an `Edit` object describing the change. Prevent the default DOM mutation,
34
+ apply the edit to state, then re-render:
63
35
 
64
- - `Edit.diff(text1: string, text2: string, hint?: number): Edit`
36
+ ```js
37
+ area.addEventListener("contentchange", (ev) => {
38
+ const {edit, source} = ev.detail;
39
+ if (source === "render") return; // ignore our own re-renders
65
40
 
66
- ### `EditBuilder`
41
+ const selection = area.getSelectionRange();
42
+ ev.preventDefault();
43
+ state.applyEdit(edit);
44
+ // Re-render your UI from state.value, then restore selection
45
+ });
46
+ ```
67
47
 
68
- ### `EditHistory`
48
+ After re-rendering, mark the DOM update so the next `contentchange` is tagged
49
+ as a render (not a user edit), and restore the cursor:
69
50
 
70
- ### `Keyer`
51
+ ```js
52
+ area.source("render");
53
+ area.setSelectionRange(sel.start, sel.end, sel.direction);
54
+ ```
55
+
56
+ Use `state.keyer.keyAt(index)` to get stable keys for each line so your
57
+ framework can reconcile DOM nodes efficiently:
58
+
59
+ ```js
60
+ const lines = state.value.split("\n");
61
+ let cursor = 0;
62
+ for (const line of lines) {
63
+ const key = state.keyer.keyAt(cursor);
64
+ cursor += line.length + 1;
65
+ // render <div key={key}>{line || <br/>}</div>
66
+ }
67
+ ```
68
+
69
+ Undo/redo is built in:
70
+
71
+ ```js
72
+ state.undo(); // returns true if there was something to undo
73
+ state.redo();
74
+ state.checkpoint(); // break the current edit group (e.g. on cursor move)
75
+ ```
76
+
77
+ See the [live demos](https://bikeshaving.github.io/revise/) for complete
78
+ examples with syntax highlighting, rainbow text, and social highlighting.
79
+
80
+ ## Modules
81
+
82
+ ### `@b9g/revise/contentarea.js`
83
+
84
+ **`ContentAreaElement`** — A custom element (`<content-area>`) with an API
85
+ modeled after
86
+ [`HTMLTextAreaElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement).
87
+ Wraps a `contenteditable` element and translates DOM mutations into `Edit`
88
+ objects.
89
+
90
+ - `value: string` — The text content of the element.
91
+ - `selectionStart: number`
92
+ - `selectionEnd: number`
93
+ - `selectionDirection: SelectionDirection`
94
+ - `getSelectionRange(): SelectionRange`
95
+ - `setSelectionRange(start, end, direction?): void`
96
+ - `indexAt(node, offset): number` — Convert a DOM position to a text index.
97
+ - `nodeOffsetAt(index): [Node | null, number]` — Convert a text index to a DOM position.
98
+ - `source(name): boolean` — Tag the next DOM mutation cycle so `contentchange` events include a `source` property. Returns `true` if content changed.
99
+
100
+ **`ContentEvent`** — The event dispatched on `contentchange`. `event.detail`
101
+ contains `{edit: Edit, source: string | null}`. Call `event.preventDefault()`
102
+ to revert the DOM mutation and apply the edit to your own state instead.
103
+
104
+ ### `@b9g/revise/edit.js`
105
+
106
+ **`Edit`** — A compact, composable data structure for representing changes to
107
+ strings.
108
+
109
+ - `apply(text): string` — Apply the edit to a string.
110
+ - `invert(): Edit` — Return the inverse edit (for undo).
111
+ - `compose(that): Edit` — Compose two sequential edits into one.
112
+ - `normalize(): Edit` — Normalize the edit (remove no-ops).
113
+ - `operations(): Array<Operation>` — Get the list of retain/insert/delete operations.
114
+ - `hasChangesBetween(start, end): boolean`
115
+ - `Edit.diff(text1, text2, hint?): Edit` — Compute an edit from two strings.
116
+ - `Edit.createBuilder(value): EditBuilder` — Create a builder for constructing edits operation-by-operation.
117
+
118
+ **`EditBuilder`** — Fluent builder for constructing edits:
119
+ `insert(value)`, `retain(length)`, `delete(length)`, `concat(edit)`, `build()`.
120
+
121
+ ### `@b9g/revise/history.js`
122
+
123
+ **`EditHistory`** — Undo/redo stack that automatically composes consecutive
124
+ simple edits into groups.
125
+
126
+ - `append(edit): void` — Record an edit. Consecutive simple edits are composed; complex edits (multiple operations) trigger a checkpoint.
127
+ - `checkpoint(): void` — Break the current edit group.
128
+ - `undo(): Edit | undefined` — Pop the undo stack and return the inverted edit.
129
+ - `redo(): Edit | undefined` — Pop the redo stack and return the edit.
130
+ - `canUndo(): boolean`
131
+ - `canRedo(): boolean`
132
+
133
+ ### `@b9g/revise/keyer.js`
134
+
135
+ **`Keyer`** — Assigns stable integer keys to text positions and keeps them in
136
+ sync as edits are applied. Use `keyAt(index)` to get a stable key for the
137
+ character at a given index, then call `transform(edit)` after each edit to
138
+ update all key positions.
139
+
140
+ - `keyAt(index): number` — Get or create a stable key for a text position.
141
+ - `transform(edit): void` — Update all key positions after an edit.
142
+
143
+ ### `@b9g/revise/state.js`
144
+
145
+ **`EditableState`** — A framework-agnostic state coordinator that ties
146
+ together value, keyer, history, and selection.
147
+
148
+ - `value: string` — The current text.
149
+ - `keyer: Keyer` — The keyer for stable line keys.
150
+ - `history: EditHistory` — The undo/redo history.
151
+ - `selection: SelectionRange | undefined` — Selection computed from the last edit.
152
+ - `applyEdit(edit, options?): void` — Apply an edit, updating value/keyer/history/selection.
153
+ - `setValue(newValue, options?): void` — Set a new value by diffing against the current value.
154
+ - `undo(): boolean` — Undo the last edit group.
155
+ - `redo(): boolean` — Redo the last undone edit group.
156
+ - `canUndo(): boolean`
157
+ - `canRedo(): boolean`
158
+ - `checkpoint(): void` — Break the current edit group.
159
+ - `reset(value?): void` — Reset all state.
package/package.json CHANGED
@@ -1,44 +1,72 @@
1
1
  {
2
2
  "name": "@b9g/revise",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "license": "MIT",
5
+ "devDependencies": {
6
+ "@b9g/libuild": "file:../libuild",
7
+ "playwright-test": "^8.1.1",
8
+ "typescript": "^5.0.0"
9
+ },
5
10
  "type": "module",
11
+ "types": "src/contentarea.d.ts",
12
+ "main": "src/contentarea.cjs",
13
+ "module": "src/contentarea.js",
6
14
  "exports": {
7
15
  "./edit.js": {
8
- "import": "./edit.js",
9
- "require": "./edit.cjs"
16
+ "types": "./src/edit.d.ts",
17
+ "import": "./src/edit.js",
18
+ "require": "./src/edit.cjs"
10
19
  },
11
20
  "./contentarea.js": {
12
- "import": "./contentarea.js",
13
- "require": "./contentarea.cjs"
21
+ "types": "./src/contentarea.d.ts",
22
+ "import": "./src/contentarea.js",
23
+ "require": "./src/contentarea.cjs"
14
24
  },
15
25
  "./keyer.js": {
16
- "import": "./keyer.js",
17
- "require": "./keyer.cjs"
26
+ "types": "./src/keyer.d.ts",
27
+ "import": "./src/keyer.js",
28
+ "require": "./src/keyer.cjs"
18
29
  },
19
30
  "./history.js": {
20
- "import": "./history.js",
21
- "require": "./history.cjs"
22
- }
23
- },
24
- "devDependencies": {
25
- "@typescript-eslint/eslint-plugin": "^5.33.0",
26
- "@typescript-eslint/parser": "^5.33.0",
27
- "eslint": "^8.22.0",
28
- "eslint-config-prettier": "^8.5.0",
29
- "eslint-plugin-prettier": "^4.2.1",
30
- "eslint-plugin-react": "^7.30.1",
31
- "magic-string": "^0.26.2",
32
- "playwright-test": "^8.1.1",
33
- "prettier": "^2.7.1",
34
- "rollup": "^2.78.0",
35
- "rollup-plugin-typescript2": "^0.32.1",
36
- "shx": "^0.3.4",
37
- "ts-node": "^10.9.1",
38
- "typescript": "^4.7.4",
39
- "uvu": "^0.5.6"
40
- },
41
- "publishConfig": {
42
- "access": "public"
31
+ "types": "./src/history.d.ts",
32
+ "import": "./src/history.js",
33
+ "require": "./src/history.cjs"
34
+ },
35
+ "./state.js": {
36
+ "types": "./src/state.d.ts",
37
+ "import": "./src/state.js",
38
+ "require": "./src/state.cjs"
39
+ },
40
+ ".": {
41
+ "types": "./src/contentarea.d.ts",
42
+ "import": "./src/contentarea.js",
43
+ "require": "./src/contentarea.cjs"
44
+ },
45
+ "./contentarea": {
46
+ "types": "./src/contentarea.d.ts",
47
+ "import": "./src/contentarea.js",
48
+ "require": "./src/contentarea.cjs"
49
+ },
50
+ "./edit": {
51
+ "types": "./src/edit.d.ts",
52
+ "import": "./src/edit.js",
53
+ "require": "./src/edit.cjs"
54
+ },
55
+ "./history": {
56
+ "types": "./src/history.d.ts",
57
+ "import": "./src/history.js",
58
+ "require": "./src/history.cjs"
59
+ },
60
+ "./keyer": {
61
+ "types": "./src/keyer.d.ts",
62
+ "import": "./src/keyer.js",
63
+ "require": "./src/keyer.cjs"
64
+ },
65
+ "./state": {
66
+ "types": "./src/state.d.ts",
67
+ "import": "./src/state.js",
68
+ "require": "./src/state.cjs"
69
+ },
70
+ "./package.json": "./package.json"
43
71
  }
44
- }
72
+ }
@@ -1,18 +1,18 @@
1
- export type Subseq = Array<number>;
2
- export declare function measure(subseq: Subseq): {
3
- length: number;
4
- includedLength: number;
5
- excludedLength: number;
6
- };
7
- export declare function pushSegment(subseq: Subseq, length: number, included: boolean): void;
8
- export declare function contains(subseq: Subseq, index: number): boolean;
9
- export declare function clear(subseq: Subseq): Subseq;
10
- export declare function fill(subseq: Subseq): Subseq;
11
- export declare function complement(subseq: Subseq): Subseq;
12
- export declare function align(subseq1: Subseq, subseq2: Subseq): Array<[number, boolean, boolean]>;
13
- export declare function union(subseq1: Subseq, subseq2: Subseq): Subseq;
14
- export declare function intersection(subseq1: Subseq, subseq2: Subseq): Subseq;
15
- export declare function difference(subseq1: Subseq, subseq2: Subseq): Subseq;
16
- export declare function shrink(subseq1: Subseq, subseq2: Subseq): Subseq;
17
- export declare function expand(subseq1: Subseq, subseq2: Subseq): Subseq;
18
- export declare function interleave(subseq1: Subseq, subseq2: Subseq): [Subseq, Subseq];
1
+ export type Subseq = Array<number>;
2
+ export declare function measure(subseq: Subseq): {
3
+ length: number;
4
+ includedLength: number;
5
+ excludedLength: number;
6
+ };
7
+ export declare function pushSegment(subseq: Subseq, length: number, included: boolean): void;
8
+ export declare function contains(subseq: Subseq, index: number): boolean;
9
+ export declare function clear(subseq: Subseq): Subseq;
10
+ export declare function fill(subseq: Subseq): Subseq;
11
+ export declare function complement(subseq: Subseq): Subseq;
12
+ export declare function align(subseq1: Subseq, subseq2: Subseq): Array<[number, boolean, boolean]>;
13
+ export declare function union(subseq1: Subseq, subseq2: Subseq): Subseq;
14
+ export declare function intersection(subseq1: Subseq, subseq2: Subseq): Subseq;
15
+ export declare function difference(subseq1: Subseq, subseq2: Subseq): Subseq;
16
+ export declare function shrink(subseq1: Subseq, subseq2: Subseq): Subseq;
17
+ export declare function expand(subseq1: Subseq, subseq2: Subseq): Subseq;
18
+ export declare function interleave(subseq1: Subseq, subseq2: Subseq): [Subseq, Subseq];