@type-editor/changeset 0.0.2
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/.editorconfig +8 -0
- package/.prettierignore +8 -0
- package/.prettierrc +9 -0
- package/LICENSE +48 -0
- package/README.md +153 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +107 -0
- package/dist/index.js +1 -0
- package/eslint.config.ts +69 -0
- package/package.json +54 -0
- package/src/Change.ts +456 -0
- package/src/ChangeSet.ts +578 -0
- package/src/Span.ts +170 -0
- package/src/compute-diff.ts +100 -0
- package/src/default-encoder.ts +41 -0
- package/src/index.ts +8 -0
- package/src/max-simplify-distance.ts +7 -0
- package/src/myers-diff/run-myers-diff.ts +261 -0
- package/src/simplify-changes/expand-to-word-boundaries.ts +43 -0
- package/src/simplify-changes/fill-change.ts +99 -0
- package/src/simplify-changes/get-text.ts +69 -0
- package/src/simplify-changes/has-word-boundary.ts +34 -0
- package/src/simplify-changes/is-letter.ts +62 -0
- package/src/simplify-changes/simplify-adjacent-changes.ts +111 -0
- package/src/simplify-changes.ts +42 -0
- package/src/tokenizer/tokenize-block-node.ts +47 -0
- package/src/tokenizer/tokenize-fragment.ts +46 -0
- package/src/tokenizer/tokenize-textNode.ts +31 -0
- package/src/types/ChangeJSON.ts +23 -0
- package/src/types/ChangeSetConfig.ts +19 -0
- package/src/types/TokenEncoder.ts +52 -0
- package/src/types/TrimmedRange.ts +10 -0
- package/tsconfig.json +27 -0
- package/typedoc.json +11 -0
- package/vite.config.ts +54 -0
package/.editorconfig
ADDED
package/.prettierignore
ADDED
package/.prettierrc
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Original ProseMirror License (https://github.com/ProseMirror)
|
|
2
|
+
|
|
3
|
+
Copyright (C) 2015-2017 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
// -------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
Type Editor License
|
|
27
|
+
|
|
28
|
+
MIT License
|
|
29
|
+
|
|
30
|
+
Copyright (c) 2026 - present (type-editor at mailbox.org)
|
|
31
|
+
|
|
32
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
33
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
34
|
+
in the Software without restriction, including without limitation the rights
|
|
35
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
36
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
37
|
+
furnished to do so, subject to the following conditions:
|
|
38
|
+
|
|
39
|
+
The above copyright notice and this permission notice shall be included in all
|
|
40
|
+
copies or substantial portions of the Software.
|
|
41
|
+
|
|
42
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
43
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
44
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
45
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
46
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
47
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
48
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# @type-editor/changeset
|
|
2
|
+
|
|
3
|
+
A refactored version of ProseMirror's [prosemirror-changeset](https://github.com/ProseMirror/prosemirror-changeset) module, providing tools for tracking and comparing document changes over time.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @type-editor/changeset
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
This module provides utilities for tracking changes to a document from a given point in the past. It condenses step maps down to a flat sequence of replacements and simplifies replacements that partially undo themselves by comparing their content.
|
|
14
|
+
|
|
15
|
+
The `ChangeSet` maintains two coordinate systems:
|
|
16
|
+
- **A coordinates**: Positions in the original (starting) document
|
|
17
|
+
- **B coordinates**: Positions in the current (modified) document
|
|
18
|
+
|
|
19
|
+
## Use Cases
|
|
20
|
+
|
|
21
|
+
- **Track Changes**: Visualize insertions and deletions in collaborative editing
|
|
22
|
+
- **Change History**: Build "diff view" features showing document modifications
|
|
23
|
+
- **Attribution**: Track which users made which changes when combined with metadata
|
|
24
|
+
|
|
25
|
+
## Core Classes
|
|
26
|
+
|
|
27
|
+
### ChangeSet
|
|
28
|
+
|
|
29
|
+
The main class for tracking document changes. A `ChangeSet` collects and simplifies a sequence of document modifications, making it easy to visualize what changed between two document states.
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { ChangeSet } from '@type-editor/changeset';
|
|
33
|
+
|
|
34
|
+
// Create a changeset from a starting document
|
|
35
|
+
const changeSet = ChangeSet.create(startDoc);
|
|
36
|
+
|
|
37
|
+
// Add steps as document changes occur
|
|
38
|
+
const updated = changeSet.addSteps(newDoc, stepMaps, metadata);
|
|
39
|
+
|
|
40
|
+
// Access the tracked changes
|
|
41
|
+
for (const change of updated.changes) {
|
|
42
|
+
console.log(`Replaced ${change.fromA}-${change.toA} with content at ${change.fromB}-${change.toB}`);
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
#### Methods
|
|
47
|
+
|
|
48
|
+
| Method | Description |
|
|
49
|
+
|----------------------------------------|---------------------------------------------------------------------|
|
|
50
|
+
| `create(doc, combine?, tokenEncoder?)` | Creates a new changeset tracking from the given document. |
|
|
51
|
+
| `addSteps(doc, stepMaps, data?)` | Computes a new changeset by adding step maps and optional metadata. |
|
|
52
|
+
| `changes` | The array of changes tracked from the starting document. |
|
|
53
|
+
| `startDoc` | The starting document that changes are tracked relative to. |
|
|
54
|
+
|
|
55
|
+
### Change
|
|
56
|
+
|
|
57
|
+
Represents a change between two document versions. A `Change` tracks a replaced range in the document, including both what was deleted from the old version and what was inserted in the new version.
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
interface Change<Data> {
|
|
61
|
+
fromA: number; // Start position in old document
|
|
62
|
+
toA: number; // End position in old document
|
|
63
|
+
fromB: number; // Start position in new document
|
|
64
|
+
toB: number; // End position in new document
|
|
65
|
+
deleted: ReadonlyArray<Span<Data>>; // Metadata spans for deleted content
|
|
66
|
+
inserted: ReadonlyArray<Span<Data>>; // Metadata spans for inserted content
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
#### Methods
|
|
71
|
+
|
|
72
|
+
| Method | Description |
|
|
73
|
+
|-------------------------|-----------------------------------------------------------------------------|
|
|
74
|
+
| `fromJSON(json)` | Static method that deserializes a Change from its JSON representation. |
|
|
75
|
+
| `toJSON()` | Serializes this Change to a JSON-compatible representation. |
|
|
76
|
+
| `slice(startA, endA, startB, endB)` | Creates a sub-change by slicing ranges from both coordinate systems. |
|
|
77
|
+
|
|
78
|
+
#### Serialization Example
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { Change } from '@type-editor/changeset';
|
|
82
|
+
|
|
83
|
+
// Serialize a change to JSON
|
|
84
|
+
const json = change.toJSON();
|
|
85
|
+
|
|
86
|
+
// Deserialize a change from JSON
|
|
87
|
+
const restored = Change.fromJSON(json);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Span
|
|
91
|
+
|
|
92
|
+
Stores metadata for a part of a change. A `Span` represents a contiguous range in a document with associated metadata.
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { Span } from '@type-editor/changeset';
|
|
96
|
+
|
|
97
|
+
const span = new Span(10, { author: 'user1' });
|
|
98
|
+
console.log(span.length); // 10
|
|
99
|
+
console.log(span.data); // { author: 'user1' }
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Utility Functions
|
|
103
|
+
|
|
104
|
+
### computeDiff
|
|
105
|
+
|
|
106
|
+
Computes the difference between two document fragments using Myers' diff algorithm.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { computeDiff } from '@type-editor/changeset';
|
|
110
|
+
|
|
111
|
+
const changes = computeDiff(fragmentA, fragmentB, range);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### simplifyChanges
|
|
115
|
+
|
|
116
|
+
Simplifies a set of changes for presentation by expanding insertions and deletions to word boundaries.
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { simplifyChanges } from '@type-editor/changeset';
|
|
120
|
+
|
|
121
|
+
const simplified = simplifyChanges(changes, document);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Token Encoder
|
|
125
|
+
|
|
126
|
+
A `TokenEncoder` can be provided when creating a `ChangeSet` to influence how the diffing algorithm compares document content. The default encoder compares nodes by name and text by character, ignoring marks and attributes.
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import type { TokenEncoder } from '@type-editor/changeset';
|
|
130
|
+
|
|
131
|
+
const customEncoder: TokenEncoder<string> = {
|
|
132
|
+
encodeCharacter: (char, marks) => String.fromCharCode(char),
|
|
133
|
+
encodeNodeStart: (node) => node.type.name,
|
|
134
|
+
encodeNodeEnd: (node) => `/${node.type.name}`,
|
|
135
|
+
compareTokens: (a, b) => a === b
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const changeSet = ChangeSet.create(doc, combine, customEncoder);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### TokenEncoder Interface
|
|
142
|
+
|
|
143
|
+
| Method | Description |
|
|
144
|
+
|--------------------------------|-----------------------------------------------------|
|
|
145
|
+
| `encodeCharacter(char, marks)` | Encode a character with its applied marks. |
|
|
146
|
+
| `encodeNodeStart(node)` | Encode the start of a node or the entire leaf node. |
|
|
147
|
+
| `encodeNodeEnd(node)` | Encode the end token for a node. |
|
|
148
|
+
| `compareTokens(a, b)` | Compare two encoded tokens for equality. |
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
MIT
|
|
153
|
+
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("@type-editor/commons");class e{static none=[];_length;_data;constructor(t,e){this._length=t,this._data=e}get length(){return this._length}get data(){return this._data}cut(t){return t===this.length?this:new e(t,this._data)}static slice(t,n,o){if(n===o)return e.none;if(0===n){if(o>=e.len(t))return t}const r=[];let s=0;for(let e=0;s<o&&e<t.length;e++){const i=t[e],l=s+i.length,h=Math.max(n,s),f=Math.min(o,l)-h;f>0&&r.push(i.cut(f)),s=l}return r}static join(n,o,r){if(0===n.length)return o;if(0===o.length)return n;const s=n[n.length-1],i=o[0],l=r(s.data,i.data);if(t.isUndefinedOrNull(l))return n.concat(o);const h=n.slice(0,n.length-1),f=new e(s.length+i.length,l);h.push(f);for(let t=1;t<o.length;t++)h.push(o[t]);return h}static len(t){let e=0;for(const n of t)e+=n.length;return e}}class n{_fromA;_toA;_fromB;_toB;_deleted;_inserted;constructor(t,e,n,o,r,s){this._fromA=t,this._toA=e,this._fromB=n,this._toB=o,this._deleted=r,this._inserted=s}get fromA(){return this._fromA}get toA(){return this._toA}get fromB(){return this._fromB}get toB(){return this._toB}get deleted(){return this._deleted}get inserted(){return this._inserted}get lenA(){return this._toA-this._fromA}get lenB(){return this._toB-this._fromB}static merge(t,e,n){if(0===t.length)return e;if(0===e.length)return t;const o=[];let r=t[0],s=e[0],i=0,l=0,h=0,f=0;for(;(r||s)&&(r||s);)if(r&&(!s||r.toB<s._fromA))o.push(this.adjustChangeForYOffset(r,f)),h+=r.lenB-r.lenA,r=++i<t.length?t[i]:null;else if(s&&(!r||s.toA<r.fromB))o.push(this.adjustChangeForXOffset(s,h)),f+=s.lenB-s.lenA,s=++l<e.length?e[l]:null;else{const a=this.mergeOverlappingChanges(t,e,r,s,i,l,h,f,n);a.change&&o.push(a.change);for(let e=i;e<a.indexX;e++)h+=t[e].lenB-t[e].lenA;for(let t=l;t<a.indexY;t++)f+=e[t].lenB-e[t].lenA;r=a.nextX,s=a.nextY,i=a.indexX,l=a.indexY}return o}static fromJSON(t){return new n(t.fromA,t.toA,t.fromB,t.toB,t.deleted.map(t=>new e(t.length,t.data)),t.inserted.map(t=>new e(t.length,t.data)))}static adjustChangeForYOffset(t,e){return 0===e?t:new n(t.fromA,t.toA,t.fromB+e,t.toB+e,t.deleted,t.inserted)}static adjustChangeForXOffset(t,e){return 0===e?t:new n(t.fromA-e,t.toA-e,t.fromB,t.toB,t.deleted,t.inserted)}static mergeOverlappingChanges(t,o,r,s,i,l,h,f,a){if(!r||!s)throw new Error("mergeOverlappingChanges called with null change");let c=Math.min(r.fromB,s.fromA);const u=Math.min(r.fromA,s.fromA-h);let m=u;const d=Math.min(s.fromB,r.fromB+f);let g=d,B=e.none,A=e.none,p=!1,_=!1,M=r,x=s;for(;;){const n=this.getNextBoundary(M,c,"fromB","toB"),r=this.getNextBoundary(x,c,"fromA","toA"),s=Math.min(n,r),h=M&&c>=M.fromB,f=x&&c>=x.fromA;if(!h&&!f)break;if(h&&c===M?.fromB&&!p&&(B=e.join(B,M.deleted,a),m+=M.lenA,p=!0),h&&!f&&M){const t=e.slice(M.inserted,c-M.fromB,s-M.fromB);A=e.join(A,t,a),g+=s-c}if(f&&c===x?.fromA&&!_&&(A=e.join(A,x.inserted,a),g+=x.lenB,_=!0),f&&!h&&x){const t=e.slice(x.deleted,c-x.fromA,s-x.fromA);B=e.join(B,t,a),m+=s-c}h&&s===M?.toB&&(M=++i<t.length?t[i]:null,p=!1),f&&s===x?.toA&&(x=++l<o.length?o[l]:null,_=!1),c=s}return{change:u<m||d<g?new n(u,m,d,g,B,A):null,nextX:M,nextY:x,indexX:i,indexY:l}}static getNextBoundary(t,e,n,o){return t?e>=t[n]?t[o]:t[n]:Number.MAX_SAFE_INTEGER}slice(t,o,r,s){const i=0===t&&o===this.lenA,l=0===r&&s===this.lenB;return i&&l?this:new n(this._fromA+t,this._fromA+o,this._fromB+r,this._fromB+s,e.slice(this._deleted,t,o),e.slice(this._inserted,r,s))}toJSON(){return this}}const o={encodeCharacter:t=>t,encodeNodeStart:t=>t.type.name,encodeNodeEnd:t=>-function(t){let e=t.schema.cached.changeSetIDs;if(!e){e={};const n=Object.keys(t.schema.nodes);for(let t=0;t<n.length;t++)e[n[t]]=t+1;t.schema.cached.changeSetIDs=e}return e[t.name]}(t.type),compareTokens:(t,e)=>t===e};function r(t,e,n,o,r){const l=n.endA-n.start,h=n.endB-n.start,f=Math.min(5e3,l+h),a=f+1,c=[],u=new Array(2*a).fill(-1);for(let m=0;m<=f;m++){for(let f=-m;f<=m;f+=2){if(s(t,e,u,f,a,n,l,h,o))return i(c,u,f,a,m,n,r)}m%2==0&&c.push(u.slice())}return null}function s(t,e,n,o,r,s,i,l,h){const f=n[o+1+r],a=n[o-1+r];let c=f<a?a:f+1,u=c+o;const m=s.start;for(;c<i&&u<l&&h(t[m+c],e[m+u]);)c++,u++;return n[o+r]=c,c>=i&&u>=l}function i(t,e,n,o,r,s,i){const h=[],f=function(t,e){const n=Math.max(t,e),o=Math.floor(n/10);return Math.min(15,Math.max(2,o))}(s.endA-s.start,s.endB-s.start),a={fromA:-1,toA:-1,fromB:-1,toB:-1};let c=n,u=e;for(let m=r-1;m>=0;m--){const e=u[c+1+o],n=u[c-1+o];let r,d;e<n?(c--,r=n+s.start,d=r+c,l(a,h,i,f,r,r,d,d+1)):(c++,r=e+s.start,d=r+c,l(a,h,i,f,r,r+1,d,d)),u=t[m>>1]}return a.fromA>-1&&h.push(i.slice(a.fromA,a.toA,a.fromB,a.toB)),h.reverse()}function l(t,e,n,o,r,s,i,l){t.fromA>-1&&t.fromA<s+o?(t.fromA=r,t.fromB=i):(t.fromA>-1&&e.push(n.slice(t.fromA,t.toA,t.fromB,t.toB)),t.fromA=r,t.toA=s,t.fromB=i,t.toB=l)}function h(t,e,n,o,r,s,i){n===r&&i.push(e.encodeNodeStart(t));const l=Math.max(r+1,n)-r-1,h=Math.min(s-1,o)-r-1;a(t.content,e,l,h,i),o===s&&i.push(e.encodeNodeEnd(t))}function f(t,e,n,o,r,s){const i=t.text;if(i)for(let l=n;l<o;l++){const n=i.charCodeAt(l-r);s.push(e.encodeCharacter(n,t.marks))}}function a(t,e,n,o,r){let s=0;for(let i=0;i<t.childCount;i++){const l=t.child(i),a=s+l.nodeSize,c=Math.max(s,n),u=Math.min(a,o);c<u&&(l.isText?f(l,e,c,u,s,r):l.isLeaf?r.push(e.encodeNodeStart(l)):h(l,e,c,u,s,a,r)),s=a}return r}function c(t,e,n,s=o){const i=a(t,s,n.fromA,n.toA,[]),l=a(e,s,n.fromB,n.toB,[]),h=(t,e)=>s.compareTokens(t,e),f=function(t,e,n){let o=0,r=t.length,s=e.length;for(;o<t.length&&o<e.length&&n(t[o],e[o]);)o++;for(;r>o&&s>o&&n(t[r-1],e[s-1]);)r--,s--;return{start:o,endA:r,endB:s}}(i,l,h);if(f.start===i.length&&f.start===l.length)return[];if(function(t){return t.endA===t.start||t.endB===t.start||t.endA===t.endB&&t.endA===t.start+1}(f))return[n.slice(f.start,f.endA,f.start,f.endB)];return r(i,l,f,h,n)??[n.slice(f.start,f.endA,f.start,f.endB)]}class u{static computeDiff=c;static MAX_POSITION=2e8;static MIN_POSITION=-2e8;_changes;config;constructor(t,e){this.config=t,this._changes=e}get changes(){return this._changes}get startDoc(){return this.config.doc}static create(t,e=(t,e)=>t===e?t:null,n=o,r=[]){return new u({combine:e,doc:t,encoder:n},r)}addSteps(t,e,o){const r=this.buildChangesFromStepMaps(e,o);if(0===r.length)return this;const s=this.mergeAll(r,this.config.combine),i=n.merge(this._changes,s,this.config.combine),l=this.minimizeChanges(i,s,t);return new u(this.config,l)}map(t){const o=n=>{const o=t(n);return o===n.data?n:new e(n.length,o)},r={doc:this.config.doc,combine:this.config.combine,encoder:this.config.encoder};return new u(r,this._changes.map(t=>new n(t.fromA,t.toA,t.fromB,t.toB,t.deleted.map(o),t.inserted.map(o))))}changedRange(t,e){if(t===this)return null;const n=e?this.computeTouchedRange(e):null,o=n?n.toB-n.fromB-(n.toA-n.fromA):0,r=t=>!n||t<=n.fromA?t:t+o;let s=n?n.fromB:u.MAX_POSITION,i=n?n.toB:u.MIN_POSITION;const l=(t,e=t)=>{s=Math.min(t,s),i=Math.max(e,i)},h=this._changes,f=t.changes;for(let a=0,c=0;a<h.length||c<f.length;){const t=h[a],e=f[c];t&&e&&this.sameRanges(t,e,r)?(a++,c++):e&&(!t||r(t.fromB)>=e.fromB)?(l(e.fromB,e.toB),c++):t&&(l(r(t.fromB),r(t.toB)),a++)}return s<=i?{from:s,to:i}:null}buildChangesFromStepMaps(t,o){const r=[],s=Array.isArray(o);for(let i=0;i<t.length;i++){const l=s?o[i]:o;let h=0;t[i].forEach((t,o,s,i)=>{const f=t===o?e.none:[new e(o-t,l)],a=s===i?e.none:[new e(i-s,l)];r.push(new n(t+h,o+h,s,i,f,a)),h+=i-s-(o-t)})}return r}minimizeChanges(t,e,n){let o=t;for(let r=0;r<o.length;r++){const s=o[r];if(!this.shouldMinimizeChange(s,e))continue;const i=c(this.config.doc.content,n.content,s,this.config.encoder);this.isCompletelyDifferent(i,s)||(o===t&&(o=t.slice()),this.applyDiffToChanges(o,r,i),r+=i.length-1)}return o}shouldMinimizeChange(t,e){return t.fromA!==t.toA&&t.fromB!==t.toB&&e.some(e=>e.toB>t.fromB&&e.fromB<t.toB)}isCompletelyDifferent(t,e){return 1===t.length&&0===t[0].fromB&&t[0].toB===e.toB-e.fromB}applyDiffToChanges(t,e,n){1===n.length?t[e]=n[0]:t.splice(e,1,...n)}mergeAll(t,e,o=0,r=t.length){if(r===o+1)return[t[o]];const s=o+r>>1;return n.merge(this.mergeAll(t,e,o,s),this.mergeAll(t,e,s,r),e)}computeTouchedRange(t){const e=this.computeEndRange(t);if(!e)return null;const n=t.map(t=>t.invert()).reverse(),o=this.computeEndRange(n);return o?{fromA:o.from,toA:o.to,fromB:e.from,toB:e.to}:null}computeEndRange(t){let e=u.MAX_POSITION,n=u.MIN_POSITION;for(const o of t)e!==u.MAX_POSITION&&(e=o.map(e,-1),n=o.map(n,1)),o.forEach((t,o,r,s)=>{e=Math.min(e,r),n=Math.max(n,s)});return e===u.MAX_POSITION?null:{from:e,to:n}}sameRanges(t,e,n){return n(t.fromB)===e.fromB&&n(t.toB)===e.toB&&this.sameSpans(t.deleted,e.deleted)&&this.sameSpans(t.inserted,e.inserted)}sameSpans(t,e){if(t.length!==e.length)return!1;for(let n=0;n<t.length;n++)if(t[n].length!==e[n].length||t[n].data!==e[n].data)return!1;return!0}}let m;try{m=new RegExp("[\\p{Alphabetic}_]","u")}catch(x){}const d=/[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;function g(t){if(t<128)return t>=48&&t<=57||t>=65&&t<=90||t>=97&&t<=122;const e=String.fromCharCode(t);return m?m.test(e):e.toUpperCase()!==e.toLowerCase()||d.test(e)}function B(t,e,n,o,r){let s=o,i=r;if(s<n&&g(t.charCodeAt(s-e)))for(;s>e&&g(t.charCodeAt(s-1-e));)s--;if(i>e&&i<=n&&g(t.charCodeAt(i-1-e)))for(;i<n&&g(t.charCodeAt(i-e));)i++;return[s,i]}function A(t,o,r){const s=t[0].fromA-(t[0].fromB-o),i=t[t.length-1],l=i.toA+(r-i.toB);let h=e.none,f=e.none;const a=t[0].deleted.length?t[0].deleted:t[0].inserted.length?t[0].inserted:null;let c=a?.[0]?.data,u=(t[0].inserted.length?t[0].inserted:t[0].deleted.length?t[0].deleted:null)?.[0]?.data,m=s,d=o;for(let n=0;n<=t.length;n++){const o=n===t.length?null:t[n],s=o?o.fromA:l,i=o?o.fromB:r;if(s>m&&(h=e.join(h,[new e(s-m,c)],p)),i>d&&(f=e.join(f,[new e(i-d,u)],p)),!o)break;h=e.join(h,o.deleted,p),f=e.join(f,o.inserted,p),h.length>0&&(c=h[h.length-1].data),f.length>0&&(u=f[f.length-1].data),m=o.toA,d=o.toB}return new n(s,l,o,r,h,f)}function p(t,e){return t===e?t:null}function _(t,e,n,o,r){for(let s=n;s<o;s++){if(!(!(s>=r)&&g(t.charCodeAt(s-e))))return!0}return!1}function M(t,e,o,r,s){const i=Math.max(0,t[e].fromB-30),l=Math.min(r.content.size,t[o-1].toB+30),h=function(t,e,n){const o=[];return function t(e,n,r){let s=0;for(let i=0;i<e.childCount;i++){const l=e.child(i),h=s+l.nodeSize,f=Math.max(s,n),a=Math.min(h,r);if(f<a)if(l.isText){const t=Math.max(0,n-s),e=Math.min(l.text.length,r-s);o.push(l.text.slice(t,e))}else if(l.isLeaf)o.push(" ");else{f===s&&o.push(" ");const e=Math.max(0,f-s-1),n=Math.min(l.content.size,a-s);t(l.content,e,n),a===h&&o.push(" ")}s=h}}(t,e,n),o.join("")}(r.content,i,l);for(let f=e;f<o;f++){const e=f;let r=t[f],a=r.lenA,c=r.lenB;for(;f<o-1;){const e=t[f+1];if(_(h,i,r.toB,e.fromB,l))break;a+=e.lenA,c+=e.lenB,r=e,f++}if(c>0&&a>0&&!(1===c&&1===a)){const[o,r]=B(h,i,l,t[e].fromB,t[f].toB),a=A(t.slice(e,f+1),o,r),c=s.length>0?s[s.length-1]:null;c?.toA===a.fromA?s[s.length-1]=new n(c.fromA,a.toA,c.fromB,a.toB,c.deleted.concat(a.deleted),c.inserted.concat(a.inserted)):s.push(a)}else for(let n=e;n<=f;n++)s.push(t[n])}}exports.Change=n,exports.ChangeSet=u,exports.DefaultEncoder=o,exports.Span=e,exports.computeDiff=c,exports.simplifyChanges=function(t,e){const n=[];for(let o=0;o<t.length;o++){const r=o;let s=t[o].toB;for(;o<t.length-1&&t[o+1].fromB<=s+30;)s=t[++o].toB;M(t,r,o+1,e,n)}return n};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Fragment } from '@type-editor/model';
|
|
2
|
+
import { Mark } from '@type-editor/model';
|
|
3
|
+
import { Node as Node_2 } from '@type-editor/model';
|
|
4
|
+
import { StepMap } from '@type-editor/transform';
|
|
5
|
+
|
|
6
|
+
export declare class Change<Data = any> {
|
|
7
|
+
private readonly _fromA;
|
|
8
|
+
private readonly _toA;
|
|
9
|
+
private readonly _fromB;
|
|
10
|
+
private readonly _toB;
|
|
11
|
+
private readonly _deleted;
|
|
12
|
+
private readonly _inserted;
|
|
13
|
+
constructor(fromA: number, toA: number, fromB: number, toB: number, deleted: ReadonlyArray<Span<Data>>, inserted: ReadonlyArray<Span<Data>>);
|
|
14
|
+
get fromA(): number;
|
|
15
|
+
get toA(): number;
|
|
16
|
+
get fromB(): number;
|
|
17
|
+
get toB(): number;
|
|
18
|
+
get deleted(): ReadonlyArray<Span<Data>>;
|
|
19
|
+
get inserted(): ReadonlyArray<Span<Data>>;
|
|
20
|
+
get lenA(): number;
|
|
21
|
+
get lenB(): number;
|
|
22
|
+
static merge<Data>(x: ReadonlyArray<Change<Data>>, y: ReadonlyArray<Change<Data>>, combine: (dataA: Data, dataB: Data) => Data): ReadonlyArray<Change<Data>>;
|
|
23
|
+
static fromJSON<Data>(json: ChangeJSON<Data>): Change<Data>;
|
|
24
|
+
private static adjustChangeForYOffset;
|
|
25
|
+
private static adjustChangeForXOffset;
|
|
26
|
+
private static mergeOverlappingChanges;
|
|
27
|
+
private static getNextBoundary;
|
|
28
|
+
slice(startA: number, endA: number, startB: number, endB: number): Change<Data>;
|
|
29
|
+
toJSON(): ChangeJSON<Data>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
declare interface ChangeJSON<Data> {
|
|
33
|
+
fromA: number;
|
|
34
|
+
toA: number;
|
|
35
|
+
fromB: number;
|
|
36
|
+
toB: number;
|
|
37
|
+
deleted: ReadonlyArray<{
|
|
38
|
+
length: number;
|
|
39
|
+
data: Data;
|
|
40
|
+
}>;
|
|
41
|
+
inserted: ReadonlyArray<{
|
|
42
|
+
length: number;
|
|
43
|
+
data: Data;
|
|
44
|
+
}>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export declare class ChangeSet<Data = any> {
|
|
48
|
+
static computeDiff: typeof computeDiff;
|
|
49
|
+
private static readonly MAX_POSITION;
|
|
50
|
+
private static readonly MIN_POSITION;
|
|
51
|
+
private readonly _changes;
|
|
52
|
+
private readonly config;
|
|
53
|
+
constructor(config: ChangeSetConfig<Data>, changes: ReadonlyArray<Change<Data>>);
|
|
54
|
+
get changes(): ReadonlyArray<Change<Data>>;
|
|
55
|
+
get startDoc(): Node_2;
|
|
56
|
+
static create<Data = any>(doc: Node_2, combine?: (dataA: Data, dataB: Data) => Data, tokenEncoder?: TokenEncoder<any>, changes?: ReadonlyArray<Change<Data>>): ChangeSet<Data>;
|
|
57
|
+
addSteps(newDoc: Node_2, maps: ReadonlyArray<StepMap>, data: Data | ReadonlyArray<Data>): ChangeSet<Data>;
|
|
58
|
+
map<NewData = Data>(callbackFunc: (range: Span<Data>) => NewData): ChangeSet<NewData>;
|
|
59
|
+
changedRange(changeSet: ChangeSet, maps?: ReadonlyArray<StepMap>): {
|
|
60
|
+
from: number;
|
|
61
|
+
to: number;
|
|
62
|
+
} | null;
|
|
63
|
+
private buildChangesFromStepMaps;
|
|
64
|
+
private minimizeChanges;
|
|
65
|
+
private shouldMinimizeChange;
|
|
66
|
+
private isCompletelyDifferent;
|
|
67
|
+
private applyDiffToChanges;
|
|
68
|
+
private mergeAll;
|
|
69
|
+
private computeTouchedRange;
|
|
70
|
+
private computeEndRange;
|
|
71
|
+
private sameRanges;
|
|
72
|
+
private sameSpans;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export declare interface ChangeSetConfig<Data> {
|
|
76
|
+
doc: Node_2;
|
|
77
|
+
combine: (dataA: Data, dataB: Data) => Data;
|
|
78
|
+
encoder: TokenEncoder<any>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export declare function computeDiff<T>(fragA: Fragment, fragB: Fragment, range: Change, encoder?: TokenEncoder<T>): Array<Change>;
|
|
82
|
+
|
|
83
|
+
export declare const DefaultEncoder: TokenEncoder<number | string>;
|
|
84
|
+
|
|
85
|
+
export declare function simplifyChanges(changes: ReadonlyArray<Change>, doc: Node_2): Array<Change>;
|
|
86
|
+
|
|
87
|
+
export declare class Span<Data = any> {
|
|
88
|
+
static readonly none: ReadonlyArray<Span>;
|
|
89
|
+
private readonly _length;
|
|
90
|
+
private readonly _data;
|
|
91
|
+
constructor(length: number, data: Data);
|
|
92
|
+
get length(): number;
|
|
93
|
+
get data(): Data;
|
|
94
|
+
private cut;
|
|
95
|
+
static slice<Data>(spans: ReadonlyArray<Span<Data>>, from: number, to: number): ReadonlyArray<Span<Data>>;
|
|
96
|
+
static join<Data>(spanListA: ReadonlyArray<Span<Data>>, spanListB: ReadonlyArray<Span<Data>>, combine: (dataA: Data, dataB: Data) => Data): ReadonlyArray<Span<Data>>;
|
|
97
|
+
private static len;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export declare interface TokenEncoder<T> {
|
|
101
|
+
encodeCharacter(char: number, marks: ReadonlyArray<Mark>): T;
|
|
102
|
+
encodeNodeStart(node: Node_2): T;
|
|
103
|
+
encodeNodeEnd(node: Node_2): T;
|
|
104
|
+
compareTokens(a: T, b: T): boolean;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export { }
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isUndefinedOrNull as t}from"@type-editor/commons";class e{static none=[];_length;_data;constructor(t,e){this._length=t,this._data=e}get length(){return this._length}get data(){return this._data}cut(t){return t===this.length?this:new e(t,this._data)}static slice(t,n,o){if(n===o)return e.none;if(0===n){if(o>=e.len(t))return t}const r=[];let s=0;for(let e=0;s<o&&e<t.length;e++){const i=t[e],l=s+i.length,h=Math.max(n,s),f=Math.min(o,l)-h;f>0&&r.push(i.cut(f)),s=l}return r}static join(n,o,r){if(0===n.length)return o;if(0===o.length)return n;const s=n[n.length-1],i=o[0],l=r(s.data,i.data);if(t(l))return n.concat(o);const h=n.slice(0,n.length-1),f=new e(s.length+i.length,l);h.push(f);for(let t=1;t<o.length;t++)h.push(o[t]);return h}static len(t){let e=0;for(const n of t)e+=n.length;return e}}class n{_fromA;_toA;_fromB;_toB;_deleted;_inserted;constructor(t,e,n,o,r,s){this._fromA=t,this._toA=e,this._fromB=n,this._toB=o,this._deleted=r,this._inserted=s}get fromA(){return this._fromA}get toA(){return this._toA}get fromB(){return this._fromB}get toB(){return this._toB}get deleted(){return this._deleted}get inserted(){return this._inserted}get lenA(){return this._toA-this._fromA}get lenB(){return this._toB-this._fromB}static merge(t,e,n){if(0===t.length)return e;if(0===e.length)return t;const o=[];let r=t[0],s=e[0],i=0,l=0,h=0,f=0;for(;(r||s)&&(r||s);)if(r&&(!s||r.toB<s._fromA))o.push(this.adjustChangeForYOffset(r,f)),h+=r.lenB-r.lenA,r=++i<t.length?t[i]:null;else if(s&&(!r||s.toA<r.fromB))o.push(this.adjustChangeForXOffset(s,h)),f+=s.lenB-s.lenA,s=++l<e.length?e[l]:null;else{const c=this.mergeOverlappingChanges(t,e,r,s,i,l,h,f,n);c.change&&o.push(c.change);for(let e=i;e<c.indexX;e++)h+=t[e].lenB-t[e].lenA;for(let t=l;t<c.indexY;t++)f+=e[t].lenB-e[t].lenA;r=c.nextX,s=c.nextY,i=c.indexX,l=c.indexY}return o}static fromJSON(t){return new n(t.fromA,t.toA,t.fromB,t.toB,t.deleted.map(t=>new e(t.length,t.data)),t.inserted.map(t=>new e(t.length,t.data)))}static adjustChangeForYOffset(t,e){return 0===e?t:new n(t.fromA,t.toA,t.fromB+e,t.toB+e,t.deleted,t.inserted)}static adjustChangeForXOffset(t,e){return 0===e?t:new n(t.fromA-e,t.toA-e,t.fromB,t.toB,t.deleted,t.inserted)}static mergeOverlappingChanges(t,o,r,s,i,l,h,f,c){if(!r||!s)throw new Error("mergeOverlappingChanges called with null change");let a=Math.min(r.fromB,s.fromA);const u=Math.min(r.fromA,s.fromA-h);let m=u;const d=Math.min(s.fromB,r.fromB+f);let g=d,B=e.none,A=e.none,p=!1,_=!1,M=r,x=s;for(;;){const n=this.getNextBoundary(M,a,"fromB","toB"),r=this.getNextBoundary(x,a,"fromA","toA"),s=Math.min(n,r),h=M&&a>=M.fromB,f=x&&a>=x.fromA;if(!h&&!f)break;if(h&&a===M?.fromB&&!p&&(B=e.join(B,M.deleted,c),m+=M.lenA,p=!0),h&&!f&&M){const t=e.slice(M.inserted,a-M.fromB,s-M.fromB);A=e.join(A,t,c),g+=s-a}if(f&&a===x?.fromA&&!_&&(A=e.join(A,x.inserted,c),g+=x.lenB,_=!0),f&&!h&&x){const t=e.slice(x.deleted,a-x.fromA,s-x.fromA);B=e.join(B,t,c),m+=s-a}h&&s===M?.toB&&(M=++i<t.length?t[i]:null,p=!1),f&&s===x?.toA&&(x=++l<o.length?o[l]:null,_=!1),a=s}return{change:u<m||d<g?new n(u,m,d,g,B,A):null,nextX:M,nextY:x,indexX:i,indexY:l}}static getNextBoundary(t,e,n,o){return t?e>=t[n]?t[o]:t[n]:Number.MAX_SAFE_INTEGER}slice(t,o,r,s){const i=0===t&&o===this.lenA,l=0===r&&s===this.lenB;return i&&l?this:new n(this._fromA+t,this._fromA+o,this._fromB+r,this._fromB+s,e.slice(this._deleted,t,o),e.slice(this._inserted,r,s))}toJSON(){return this}}const o={encodeCharacter:t=>t,encodeNodeStart:t=>t.type.name,encodeNodeEnd:t=>-function(t){let e=t.schema.cached.changeSetIDs;if(!e){e={};const n=Object.keys(t.schema.nodes);for(let t=0;t<n.length;t++)e[n[t]]=t+1;t.schema.cached.changeSetIDs=e}return e[t.name]}(t.type),compareTokens:(t,e)=>t===e};function r(t,e,n,o,r){const l=n.endA-n.start,h=n.endB-n.start,f=Math.min(5e3,l+h),c=f+1,a=[],u=new Array(2*c).fill(-1);for(let m=0;m<=f;m++){for(let f=-m;f<=m;f+=2){if(s(t,e,u,f,c,n,l,h,o))return i(a,u,f,c,m,n,r)}m%2==0&&a.push(u.slice())}return null}function s(t,e,n,o,r,s,i,l,h){const f=n[o+1+r],c=n[o-1+r];let a=f<c?c:f+1,u=a+o;const m=s.start;for(;a<i&&u<l&&h(t[m+a],e[m+u]);)a++,u++;return n[o+r]=a,a>=i&&u>=l}function i(t,e,n,o,r,s,i){const h=[],f=function(t,e){const n=Math.max(t,e),o=Math.floor(n/10);return Math.min(15,Math.max(2,o))}(s.endA-s.start,s.endB-s.start),c={fromA:-1,toA:-1,fromB:-1,toB:-1};let a=n,u=e;for(let m=r-1;m>=0;m--){const e=u[a+1+o],n=u[a-1+o];let r,d;e<n?(a--,r=n+s.start,d=r+a,l(c,h,i,f,r,r,d,d+1)):(a++,r=e+s.start,d=r+a,l(c,h,i,f,r,r+1,d,d)),u=t[m>>1]}return c.fromA>-1&&h.push(i.slice(c.fromA,c.toA,c.fromB,c.toB)),h.reverse()}function l(t,e,n,o,r,s,i,l){t.fromA>-1&&t.fromA<s+o?(t.fromA=r,t.fromB=i):(t.fromA>-1&&e.push(n.slice(t.fromA,t.toA,t.fromB,t.toB)),t.fromA=r,t.toA=s,t.fromB=i,t.toB=l)}function h(t,e,n,o,r,s,i){n===r&&i.push(e.encodeNodeStart(t));const l=Math.max(r+1,n)-r-1,h=Math.min(s-1,o)-r-1;c(t.content,e,l,h,i),o===s&&i.push(e.encodeNodeEnd(t))}function f(t,e,n,o,r,s){const i=t.text;if(i)for(let l=n;l<o;l++){const n=i.charCodeAt(l-r);s.push(e.encodeCharacter(n,t.marks))}}function c(t,e,n,o,r){let s=0;for(let i=0;i<t.childCount;i++){const l=t.child(i),c=s+l.nodeSize,a=Math.max(s,n),u=Math.min(c,o);a<u&&(l.isText?f(l,e,a,u,s,r):l.isLeaf?r.push(e.encodeNodeStart(l)):h(l,e,a,u,s,c,r)),s=c}return r}function a(t,e,n,s=o){const i=c(t,s,n.fromA,n.toA,[]),l=c(e,s,n.fromB,n.toB,[]),h=(t,e)=>s.compareTokens(t,e),f=function(t,e,n){let o=0,r=t.length,s=e.length;for(;o<t.length&&o<e.length&&n(t[o],e[o]);)o++;for(;r>o&&s>o&&n(t[r-1],e[s-1]);)r--,s--;return{start:o,endA:r,endB:s}}(i,l,h);if(f.start===i.length&&f.start===l.length)return[];if(function(t){return t.endA===t.start||t.endB===t.start||t.endA===t.endB&&t.endA===t.start+1}(f))return[n.slice(f.start,f.endA,f.start,f.endB)];return r(i,l,f,h,n)??[n.slice(f.start,f.endA,f.start,f.endB)]}class u{static computeDiff=a;static MAX_POSITION=2e8;static MIN_POSITION=-2e8;_changes;config;constructor(t,e){this.config=t,this._changes=e}get changes(){return this._changes}get startDoc(){return this.config.doc}static create(t,e=(t,e)=>t===e?t:null,n=o,r=[]){return new u({combine:e,doc:t,encoder:n},r)}addSteps(t,e,o){const r=this.buildChangesFromStepMaps(e,o);if(0===r.length)return this;const s=this.mergeAll(r,this.config.combine),i=n.merge(this._changes,s,this.config.combine),l=this.minimizeChanges(i,s,t);return new u(this.config,l)}map(t){const o=n=>{const o=t(n);return o===n.data?n:new e(n.length,o)},r={doc:this.config.doc,combine:this.config.combine,encoder:this.config.encoder};return new u(r,this._changes.map(t=>new n(t.fromA,t.toA,t.fromB,t.toB,t.deleted.map(o),t.inserted.map(o))))}changedRange(t,e){if(t===this)return null;const n=e?this.computeTouchedRange(e):null,o=n?n.toB-n.fromB-(n.toA-n.fromA):0,r=t=>!n||t<=n.fromA?t:t+o;let s=n?n.fromB:u.MAX_POSITION,i=n?n.toB:u.MIN_POSITION;const l=(t,e=t)=>{s=Math.min(t,s),i=Math.max(e,i)},h=this._changes,f=t.changes;for(let c=0,a=0;c<h.length||a<f.length;){const t=h[c],e=f[a];t&&e&&this.sameRanges(t,e,r)?(c++,a++):e&&(!t||r(t.fromB)>=e.fromB)?(l(e.fromB,e.toB),a++):t&&(l(r(t.fromB),r(t.toB)),c++)}return s<=i?{from:s,to:i}:null}buildChangesFromStepMaps(t,o){const r=[],s=Array.isArray(o);for(let i=0;i<t.length;i++){const l=s?o[i]:o;let h=0;t[i].forEach((t,o,s,i)=>{const f=t===o?e.none:[new e(o-t,l)],c=s===i?e.none:[new e(i-s,l)];r.push(new n(t+h,o+h,s,i,f,c)),h+=i-s-(o-t)})}return r}minimizeChanges(t,e,n){let o=t;for(let r=0;r<o.length;r++){const s=o[r];if(!this.shouldMinimizeChange(s,e))continue;const i=a(this.config.doc.content,n.content,s,this.config.encoder);this.isCompletelyDifferent(i,s)||(o===t&&(o=t.slice()),this.applyDiffToChanges(o,r,i),r+=i.length-1)}return o}shouldMinimizeChange(t,e){return t.fromA!==t.toA&&t.fromB!==t.toB&&e.some(e=>e.toB>t.fromB&&e.fromB<t.toB)}isCompletelyDifferent(t,e){return 1===t.length&&0===t[0].fromB&&t[0].toB===e.toB-e.fromB}applyDiffToChanges(t,e,n){1===n.length?t[e]=n[0]:t.splice(e,1,...n)}mergeAll(t,e,o=0,r=t.length){if(r===o+1)return[t[o]];const s=o+r>>1;return n.merge(this.mergeAll(t,e,o,s),this.mergeAll(t,e,s,r),e)}computeTouchedRange(t){const e=this.computeEndRange(t);if(!e)return null;const n=t.map(t=>t.invert()).reverse(),o=this.computeEndRange(n);return o?{fromA:o.from,toA:o.to,fromB:e.from,toB:e.to}:null}computeEndRange(t){let e=u.MAX_POSITION,n=u.MIN_POSITION;for(const o of t)e!==u.MAX_POSITION&&(e=o.map(e,-1),n=o.map(n,1)),o.forEach((t,o,r,s)=>{e=Math.min(e,r),n=Math.max(n,s)});return e===u.MAX_POSITION?null:{from:e,to:n}}sameRanges(t,e,n){return n(t.fromB)===e.fromB&&n(t.toB)===e.toB&&this.sameSpans(t.deleted,e.deleted)&&this.sameSpans(t.inserted,e.inserted)}sameSpans(t,e){if(t.length!==e.length)return!1;for(let n=0;n<t.length;n++)if(t[n].length!==e[n].length||t[n].data!==e[n].data)return!1;return!0}}let m;try{m=new RegExp("[\\p{Alphabetic}_]","u")}catch(C){}const d=/[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;function g(t){if(t<128)return t>=48&&t<=57||t>=65&&t<=90||t>=97&&t<=122;const e=String.fromCharCode(t);return m?m.test(e):e.toUpperCase()!==e.toLowerCase()||d.test(e)}function B(t,e,n,o,r){let s=o,i=r;if(s<n&&g(t.charCodeAt(s-e)))for(;s>e&&g(t.charCodeAt(s-1-e));)s--;if(i>e&&i<=n&&g(t.charCodeAt(i-1-e)))for(;i<n&&g(t.charCodeAt(i-e));)i++;return[s,i]}function A(t,o,r){const s=t[0].fromA-(t[0].fromB-o),i=t[t.length-1],l=i.toA+(r-i.toB);let h=e.none,f=e.none;const c=t[0].deleted.length?t[0].deleted:t[0].inserted.length?t[0].inserted:null;let a=c?.[0]?.data,u=(t[0].inserted.length?t[0].inserted:t[0].deleted.length?t[0].deleted:null)?.[0]?.data,m=s,d=o;for(let n=0;n<=t.length;n++){const o=n===t.length?null:t[n],s=o?o.fromA:l,i=o?o.fromB:r;if(s>m&&(h=e.join(h,[new e(s-m,a)],p)),i>d&&(f=e.join(f,[new e(i-d,u)],p)),!o)break;h=e.join(h,o.deleted,p),f=e.join(f,o.inserted,p),h.length>0&&(a=h[h.length-1].data),f.length>0&&(u=f[f.length-1].data),m=o.toA,d=o.toB}return new n(s,l,o,r,h,f)}function p(t,e){return t===e?t:null}function _(t,e,n,o,r){for(let s=n;s<o;s++){if(!(!(s>=r)&&g(t.charCodeAt(s-e))))return!0}return!1}function M(t,e,o,r,s){const i=Math.max(0,t[e].fromB-30),l=Math.min(r.content.size,t[o-1].toB+30),h=function(t,e,n){const o=[];return function t(e,n,r){let s=0;for(let i=0;i<e.childCount;i++){const l=e.child(i),h=s+l.nodeSize,f=Math.max(s,n),c=Math.min(h,r);if(f<c)if(l.isText){const t=Math.max(0,n-s),e=Math.min(l.text.length,r-s);o.push(l.text.slice(t,e))}else if(l.isLeaf)o.push(" ");else{f===s&&o.push(" ");const e=Math.max(0,f-s-1),n=Math.min(l.content.size,c-s);t(l.content,e,n),c===h&&o.push(" ")}s=h}}(t,e,n),o.join("")}(r.content,i,l);for(let f=e;f<o;f++){const e=f;let r=t[f],c=r.lenA,a=r.lenB;for(;f<o-1;){const e=t[f+1];if(_(h,i,r.toB,e.fromB,l))break;c+=e.lenA,a+=e.lenB,r=e,f++}if(a>0&&c>0&&!(1===a&&1===c)){const[o,r]=B(h,i,l,t[e].fromB,t[f].toB),c=A(t.slice(e,f+1),o,r),a=s.length>0?s[s.length-1]:null;a?.toA===c.fromA?s[s.length-1]=new n(a.fromA,c.toA,a.fromB,c.toB,a.deleted.concat(c.deleted),a.inserted.concat(c.inserted)):s.push(c)}else for(let n=e;n<=f;n++)s.push(t[n])}}function x(t,e){const n=[];for(let o=0;o<t.length;o++){const r=o;let s=t[o].toB;for(;o<t.length-1&&t[o+1].fromB<=s+30;)s=t[++o].toB;M(t,r,o+1,e,n)}return n}export{n as Change,u as ChangeSet,o as DefaultEncoder,e as Span,a as computeDiff,x as simplifyChanges};
|
package/eslint.config.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import eslint from '@eslint/js';
|
|
2
|
+
import {defineConfig, globalIgnores} from 'eslint/config';
|
|
3
|
+
import tseslint from 'typescript-eslint';
|
|
4
|
+
import sortImports from 'eslint-plugin-simple-import-sort';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export default defineConfig(
|
|
8
|
+
eslint.configs.recommended,
|
|
9
|
+
...tseslint.configs.strictTypeChecked,
|
|
10
|
+
...tseslint.configs.stylisticTypeChecked,
|
|
11
|
+
{
|
|
12
|
+
languageOptions: {
|
|
13
|
+
parserOptions: {
|
|
14
|
+
projectService: true,
|
|
15
|
+
tsconfigRootDir: __dirname,
|
|
16
|
+
sourceType: 'module',
|
|
17
|
+
ecmaVersion: 'latest',
|
|
18
|
+
parser: '@typescript-eslint/parser',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
plugins: {
|
|
22
|
+
'simple-import-sort': sortImports,
|
|
23
|
+
},
|
|
24
|
+
rules: {
|
|
25
|
+
'simple-import-sort/imports': 'error',
|
|
26
|
+
'simple-import-sort/exports': 'error',
|
|
27
|
+
// Enforce the use of single quotes for strings
|
|
28
|
+
quotes: ['error', 'single'],
|
|
29
|
+
// Enforce semicolons at the end of statements
|
|
30
|
+
semi: ['error', 'always'],
|
|
31
|
+
// Require the use of === and !== (no implicit type conversions)
|
|
32
|
+
eqeqeq: ['error', 'always'],
|
|
33
|
+
curly: [2],
|
|
34
|
+
'@typescript-eslint/no-unnecessary-condition': 'off',
|
|
35
|
+
'@typescript-eslint/restrict-template-expressions': ['error', {
|
|
36
|
+
allowNumber: true,
|
|
37
|
+
}],
|
|
38
|
+
'@typescript-eslint/array-type': ['warn', {
|
|
39
|
+
default: 'generic'
|
|
40
|
+
}],
|
|
41
|
+
'@typescript-eslint/no-extraneous-class': 'off',
|
|
42
|
+
'@typescript-eslint/prefer-return-this-type': 'off',
|
|
43
|
+
'@typescript-eslint/class-literal-property-style': 'off',
|
|
44
|
+
|
|
45
|
+
'@typescript-eslint/no-unnecessary-boolean-literal-compare': 'warn',
|
|
46
|
+
|
|
47
|
+
'@typescript-eslint/no-unsafe-member-access': "error",
|
|
48
|
+
'@typescript-eslint/no-unsafe-return': "error",
|
|
49
|
+
'@typescript-eslint/no-unsafe-assignment': "error",
|
|
50
|
+
'@typescript-eslint/no-unsafe-call': "error",
|
|
51
|
+
'@typescript-eslint/no-unsafe-argument': "error",
|
|
52
|
+
'@typescript-eslint/no-explicit-any': "error",
|
|
53
|
+
'no-unused-vars': "off",
|
|
54
|
+
'@typescript-eslint/no-unused-vars': ['error', {
|
|
55
|
+
argsIgnorePattern: '^_',
|
|
56
|
+
varsIgnorePattern: '^_',
|
|
57
|
+
caughtErrorsIgnorePattern: '^_'
|
|
58
|
+
}]
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
globalIgnores([
|
|
62
|
+
".git/**/*",
|
|
63
|
+
"node_modules/**/*",
|
|
64
|
+
"build/**/*",
|
|
65
|
+
"dist/**/*",
|
|
66
|
+
"test/**/*",
|
|
67
|
+
"test-browser/**/*",
|
|
68
|
+
]),
|
|
69
|
+
);
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@type-editor/changeset",
|
|
3
|
+
"description": "This is a refactored version of the ProseMirror's 'changeset' module. Original: https://github.com/ProseMirror/prosemirror-changeset",
|
|
4
|
+
"version": "0.0.2",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://type-editor.io",
|
|
7
|
+
"maintainers": [
|
|
8
|
+
{
|
|
9
|
+
"name": "Type Editor",
|
|
10
|
+
"email": "type-editor@mailbox.org",
|
|
11
|
+
"web": "https://type-editor.io"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git://github.com/type-editor/type.git"
|
|
17
|
+
},
|
|
18
|
+
"type": "module",
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"module": "dist/index.js",
|
|
21
|
+
"types": "dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"import": "./dist/index.js",
|
|
26
|
+
"require": "./dist/index.cjs"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"type",
|
|
31
|
+
"type-editor",
|
|
32
|
+
"richtext-editor",
|
|
33
|
+
"prosemirror"
|
|
34
|
+
],
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@type-editor/commons": "^0.0.2",
|
|
37
|
+
"@type-editor/model": "^0.0.2",
|
|
38
|
+
"@type-editor/transform": "^0.0.2"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@type-editor/test-builder": "^0.0.2",
|
|
42
|
+
"@type-editor/commons": "^0.0.2",
|
|
43
|
+
"@type-editor/transform": "^0.0.2",
|
|
44
|
+
"@type-editor/model": "^0.0.2"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "vite build",
|
|
48
|
+
"build:prod": "NODE_ENV=production vite build --mode production",
|
|
49
|
+
"build:dev": "NODE_ENV=development tsc && vite build --mode development",
|
|
50
|
+
"build:ts": "tsc --noEmit",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"test:watch": "vitest"
|
|
53
|
+
}
|
|
54
|
+
}
|