linked-rolls 0.57.0 → 0.59.0
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/README.md +54 -367
- package/lib/{constraints.d.ts → analysis/constraints.d.ts} +1 -1
- package/lib/{constraints.js → analysis/constraints.js} +8 -7
- package/lib/analysis/ownPaper.d.ts +26 -0
- package/lib/analysis/ownPaper.js +41 -0
- package/lib/{reservations.d.ts → analysis/reservations.d.ts} +3 -3
- package/lib/{reservations.js → analysis/reservations.js} +3 -3
- package/lib/{sigla.d.ts → analysis/sigla.d.ts} +2 -2
- package/lib/{sigla.js → analysis/sigla.js} +4 -4
- package/lib/{witnesses.d.ts → analysis/witnesses.d.ts} +2 -2
- package/lib/{witnesses.js → analysis/witnesses.js} +2 -2
- package/lib/{Collation.d.ts → collation/Collation.d.ts} +4 -4
- package/lib/{Collation.js → collation/Collation.js} +4 -4
- package/lib/{alignment.d.ts → collation/alignment.d.ts} +4 -4
- package/lib/{alignment.js → collation/alignment.js} +10 -16
- package/lib/{scatter.d.ts → collation/scatter.d.ts} +4 -4
- package/lib/{scatter.js → collation/scatter.js} +3 -3
- package/lib/{statistics.d.ts → collation/statistics.d.ts} +1 -1
- package/lib/{statistics.js → collation/statistics.js} +2 -2
- package/lib/{substitution.d.ts → collation/substitution.d.ts} +9 -24
- package/lib/{substitution.js → collation/substitution.js} +13 -37
- package/lib/emulation/Emulation.d.ts +64 -0
- package/lib/emulation/Emulation.js +282 -0
- package/lib/emulation/negotiation.d.ts +14 -0
- package/lib/emulation/negotiation.js +20 -0
- package/lib/index.d.ts +43 -35
- package/lib/index.js +36 -35
- package/lib/{asJsonLd.d.ts → io/asJsonLd.d.ts} +1 -1
- package/lib/{asJsonLd.js → io/asJsonLd.js} +3 -3
- package/lib/{context.d.ts → io/context.d.ts} +4 -4
- package/lib/{context.js → io/context.js} +4 -4
- package/lib/{importJsonLd.d.ts → io/importJsonLd.d.ts} +1 -1
- package/lib/io/importJsonLd.js +128 -0
- package/lib/{migrate.js → io/migrate.js} +59 -55
- package/lib/{Agent.d.ts → model/Agent.d.ts} +1 -1
- package/lib/{Assumption.d.ts → model/Assumption.d.ts} +1 -1
- package/lib/{Edit.d.ts → model/Edit.d.ts} +1 -1
- package/lib/{Edition.d.ts → model/Edition.d.ts} +2 -2
- package/lib/{Feature.d.ts → model/Feature.d.ts} +1 -1
- package/lib/{FeatureSource.d.ts → model/FeatureSource.d.ts} +1 -1
- package/lib/{Perforator.d.ts → model/Perforator.d.ts} +1 -1
- package/lib/{RollCopy.d.ts → model/RollCopy.d.ts} +3 -10
- package/lib/{RollCopy.js → model/RollCopy.js} +3 -5
- package/lib/{Symbol.d.ts → model/Symbol.d.ts} +1 -1
- package/lib/{Version.d.ts → model/Version.d.ts} +10 -2
- package/lib/{Version.js → model/Version.js} +24 -1
- package/lib/{vocabulary.js → model/vocabulary.js} +2 -2
- package/lib/ops/beliefs.d.ts +9 -0
- package/lib/ops/beliefs.js +31 -0
- package/lib/ops/collation.d.ts +62 -0
- package/lib/ops/collation.js +228 -0
- package/lib/ops/commands.d.ts +9 -0
- package/lib/ops/commands.js +29 -0
- package/lib/ops/copies.d.ts +74 -0
- package/lib/ops/copies.js +135 -0
- package/lib/ops/draft.d.ts +59 -0
- package/lib/ops/draft.js +81 -0
- package/lib/ops/edits.d.ts +10 -0
- package/lib/ops/edits.js +82 -0
- package/lib/ops/features.d.ts +84 -0
- package/lib/ops/features.js +296 -0
- package/lib/ops/forget.d.ts +31 -0
- package/lib/ops/forget.js +110 -0
- package/lib/ops/immutable.d.ts +16 -0
- package/lib/ops/immutable.js +46 -0
- package/lib/ops/index.d.ts +13 -0
- package/lib/ops/index.js +7 -0
- package/lib/ops/versions.d.ts +33 -0
- package/lib/ops/versions.js +95 -0
- package/lib/readers/AtonParser.js +5 -7
- package/lib/readers/phillipsEroll.d.ts +3 -3
- package/lib/readers/phillipsEroll.js +4 -5
- package/lib/readers/spencerBar.d.ts +2 -2
- package/lib/readers/spencerBar.js +2 -3
- package/lib/readers/stanfordAton.d.ts +3 -3
- package/lib/readers/stanfordAton.js +2 -3
- package/lib/schema.json +0 -13
- package/lib/{ReproducingSystem.d.ts → systems/ReproducingSystem.d.ts} +3 -3
- package/lib/{TrackerBar.d.ts → systems/TrackerBar.d.ts} +48 -8
- package/lib/{TrackerBar.js → systems/TrackerBar.js} +11 -2
- package/lib/systems/index.d.ts +8 -2
- package/lib/systems/index.js +7 -1
- package/lib/systems/welte.d.ts +100 -0
- package/lib/systems/welte.js +119 -0
- package/lib/systems/welteLicensee/bar.d.ts +1 -1
- package/lib/systems/welteLicensee/bar.js +5 -2
- package/lib/systems/welteLicensee/system.d.ts +1 -1
- package/lib/systems/welteLicensee/system.js +1 -1
- package/lib/systems/welteT100/bar.d.ts +27 -1
- package/lib/systems/welteT100/bar.js +44 -2
- package/lib/systems/welteT100/system.d.ts +6 -39
- package/lib/systems/welteT100/system.js +19 -128
- package/lib/systems/welteT98/bar.d.ts +1 -1
- package/lib/systems/welteT98/bar.js +18 -2
- package/lib/systems/welteT98/system.d.ts +20 -38
- package/lib/systems/welteT98/system.js +20 -121
- package/lib/validate.d.ts +1 -1
- package/lib/{EditionView.d.ts → view/EditionView.d.ts} +17 -41
- package/lib/{EditionView.js → view/EditionView.js} +26 -75
- package/package.json +7 -2
- package/lib/Emulation.d.ts +0 -36
- package/lib/Emulation.js +0 -250
- package/lib/editionOps.d.ts +0 -260
- package/lib/editionOps.js +0 -1079
- package/lib/importJsonLd.js +0 -57
- /package/lib/{migrate.d.ts → io/migrate.d.ts} +0 -0
- /package/lib/{Agent.js → model/Agent.js} +0 -0
- /package/lib/{Assumption.js → model/Assumption.js} +0 -0
- /package/lib/{ConditionState.d.ts → model/ConditionState.d.ts} +0 -0
- /package/lib/{ConditionState.js → model/ConditionState.js} +0 -0
- /package/lib/{Edit.js → model/Edit.js} +0 -0
- /package/lib/{Edition.js → model/Edition.js} +0 -0
- /package/lib/{Feature.js → model/Feature.js} +0 -0
- /package/lib/{FeatureSource.js → model/FeatureSource.js} +0 -0
- /package/lib/{Perforator.js → model/Perforator.js} +0 -0
- /package/lib/{Quantity.d.ts → model/Quantity.d.ts} +0 -0
- /package/lib/{Quantity.js → model/Quantity.js} +0 -0
- /package/lib/{Symbol.js → model/Symbol.js} +0 -0
- /package/lib/{TrackCalibration.d.ts → model/TrackCalibration.d.ts} +0 -0
- /package/lib/{TrackCalibration.js → model/TrackCalibration.js} +0 -0
- /package/lib/{notes.d.ts → model/notes.d.ts} +0 -0
- /package/lib/{notes.js → model/notes.js} +0 -0
- /package/lib/{procedures.d.ts → model/procedures.d.ts} +0 -0
- /package/lib/{procedures.js → model/procedures.js} +0 -0
- /package/lib/{vocabulary.d.ts → model/vocabulary.d.ts} +0 -0
- /package/lib/{sorted.d.ts → shared/sorted.d.ts} +0 -0
- /package/lib/{sorted.js → shared/sorted.js} +0 -0
- /package/lib/{utils.d.ts → shared/utils.d.ts} +0 -0
- /package/lib/{utils.js → shared/utils.js} +0 -0
- /package/lib/{ReproducingSystem.js → systems/ReproducingSystem.js} +0 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { v4 } from "uuid";
|
|
2
|
+
import { featuresByAct, featuresOf, statesNothing } from "../model/RollCopy.js";
|
|
3
|
+
import { idOf } from "../model/Assumption.js";
|
|
4
|
+
import { conditions as conditionsAllowed, featuresBorneBy, isPatch, withBorneFeatures } from "../model/Feature.js";
|
|
5
|
+
import { mm } from "../model/Quantity.js";
|
|
6
|
+
import { onCopy, onFeature, stateOf } from "./draft.js";
|
|
7
|
+
import { without, mapped, pruned, replacing, isRecord } from "./immutable.js";
|
|
8
|
+
import { renameReferences, forgetFeatures } from "./forget.js";
|
|
9
|
+
/**
|
|
10
|
+
* Whether the feature's own kind allows a condition of this kind. The
|
|
11
|
+
* type narrowed to is the weaker statement, holding of a condition any
|
|
12
|
+
* kind of feature may be in.
|
|
13
|
+
*/
|
|
14
|
+
const allows = (feature, condition) => {
|
|
15
|
+
const allowed = conditionsAllowed[feature.type];
|
|
16
|
+
return allowed.includes(condition.conditionType);
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* States the condition of the feature, in place of any earlier
|
|
20
|
+
* statement. Throws where the kind of feature is in no such condition;
|
|
21
|
+
* `conditions` says which conditions each kind of feature may be in.
|
|
22
|
+
*/
|
|
23
|
+
export const stateFeatureCondition = (copyId, featureId, condition) => onFeature(copyId, featureId, feature => {
|
|
24
|
+
if (!allows(feature, condition)) {
|
|
25
|
+
throw new Error(`A ${feature.type} is in no '${condition.conditionType}' condition`);
|
|
26
|
+
}
|
|
27
|
+
feature.condition = condition;
|
|
28
|
+
});
|
|
29
|
+
/**
|
|
30
|
+
* The ids of the features named and of everything they bear: a feature
|
|
31
|
+
* of a patch stands nowhere once the patch is gone.
|
|
32
|
+
*/
|
|
33
|
+
const goneWith = (features, named) => features.flatMap(feature => named.has(feature.id)
|
|
34
|
+
? withBorneFeatures(feature).map(borne => borne.id)
|
|
35
|
+
: goneWith(featuresBorneBy(feature), named));
|
|
36
|
+
/** The feature with the named ones gone from what it bears, or the very same one where it bears none of them. */
|
|
37
|
+
const withoutBorne = (feature, named) => {
|
|
38
|
+
if (!isPatch(feature) || !feature.features)
|
|
39
|
+
return feature;
|
|
40
|
+
const borne = withoutFeatures(feature.features, named);
|
|
41
|
+
return borne === feature.features ? feature : { ...feature, features: borne };
|
|
42
|
+
};
|
|
43
|
+
/** The features without those named, wherever they lie, and without whatever those bore. */
|
|
44
|
+
const withoutFeatures = (features, named) => mapped(without(features, feature => named.has(feature.id)), feature => withoutBorne(feature, named));
|
|
45
|
+
/** The act with its features rewritten, or the very same one where the rewriting left them. */
|
|
46
|
+
const rewritingAct = (rewrite) => (modification) => {
|
|
47
|
+
if (modification.type === 'Alteration')
|
|
48
|
+
return replacing(modification, 'produced', rewrite(modification.produced));
|
|
49
|
+
if (modification.type === 'Attachment')
|
|
50
|
+
return replacing(modification, 'added', rewrite(modification.added));
|
|
51
|
+
return modification;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Writes the rewriting onto the features of every act of the copy, and
|
|
55
|
+
* takes out an act it left with nothing to state.
|
|
56
|
+
*/
|
|
57
|
+
const rewriteFeatures = (copy, rewrite) => {
|
|
58
|
+
const state = stateOf(copy);
|
|
59
|
+
const produced = state.production?.produced;
|
|
60
|
+
if (produced && copy.production) {
|
|
61
|
+
const rewritten = rewrite(produced);
|
|
62
|
+
if (rewritten !== produced)
|
|
63
|
+
copy.production.produced = rewritten;
|
|
64
|
+
}
|
|
65
|
+
copy.modifications = pruned(state.modifications, rewritingAct(rewrite), statesNothing);
|
|
66
|
+
};
|
|
67
|
+
/** The features of the act that brought the named feature about, where that act produces features. */
|
|
68
|
+
const producedBeside = (copy, featureId) => {
|
|
69
|
+
const names = (features) => features.some(feature => feature.id === featureId);
|
|
70
|
+
const punched = copy.production?.produced;
|
|
71
|
+
if (punched && names(punched))
|
|
72
|
+
return punched;
|
|
73
|
+
const act = copy.modifications.find(act => act.type === 'Alteration' && names(act.produced));
|
|
74
|
+
return act?.type === 'Alteration' ? act.produced : undefined;
|
|
75
|
+
};
|
|
76
|
+
/** The patches of the attachment that glued the named one on. */
|
|
77
|
+
const gluedBeside = (copy, patchId) => {
|
|
78
|
+
const act = copy.modifications.find(act => act.type === 'Attachment' && act.added.some(patch => patch.id === patchId));
|
|
79
|
+
return act?.type === 'Attachment' ? act.added : undefined;
|
|
80
|
+
};
|
|
81
|
+
/** The list the copy states a new feature in, making the act it belongs to where there is none. */
|
|
82
|
+
const actFor = (copy, patch, act) => {
|
|
83
|
+
const beside = act.beside !== undefined
|
|
84
|
+
? (patch ? gluedBeside(copy, act.beside) : producedBeside(copy, act.beside))
|
|
85
|
+
: undefined;
|
|
86
|
+
if (beside)
|
|
87
|
+
return beside;
|
|
88
|
+
if (patch) {
|
|
89
|
+
const added = [];
|
|
90
|
+
copy.modifications.push({ type: 'Attachment', added, ...(act.purpose && { purpose: act.purpose }) });
|
|
91
|
+
return added;
|
|
92
|
+
}
|
|
93
|
+
if (act.punched) {
|
|
94
|
+
if (!copy.production)
|
|
95
|
+
copy.production = {};
|
|
96
|
+
if (!copy.production.produced)
|
|
97
|
+
copy.production.produced = [];
|
|
98
|
+
return copy.production.produced;
|
|
99
|
+
}
|
|
100
|
+
const produced = [];
|
|
101
|
+
copy.modifications.push({ type: 'Alteration', produced, ...(act.purpose && { purpose: act.purpose }) });
|
|
102
|
+
return produced;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* States that the copy bears the feature, in the act that brought it
|
|
106
|
+
* about: a patch was glued on by an attachment, anything else was
|
|
107
|
+
* produced, by the punching or by a later act. Which act, `FeatureAct`
|
|
108
|
+
* says; a `beside` that names nothing the copy bears is passed over and
|
|
109
|
+
* the feature goes into an act of its own.
|
|
110
|
+
*/
|
|
111
|
+
export const addFeature = (copyId, feature, act = {}) => onCopy(copyId, copy => {
|
|
112
|
+
actFor(copy, isPatch(feature), act).push(feature);
|
|
113
|
+
});
|
|
114
|
+
/**
|
|
115
|
+
* States that the patch bears the feature. What a patch bears came onto
|
|
116
|
+
* the copy with the patch, so it belongs to no act of its own and
|
|
117
|
+
* states no place: it stands where the patch stands.
|
|
118
|
+
*/
|
|
119
|
+
export const addBorneFeature = (copyId, patchId, feature) => onCopy(copyId, copy => {
|
|
120
|
+
const patch = featuresOf(copy).flatMap(withBorneFeatures).find(borne => borne.id === patchId);
|
|
121
|
+
if (!patch || !isPatch(patch))
|
|
122
|
+
return;
|
|
123
|
+
if (!patch.features)
|
|
124
|
+
patch.features = [];
|
|
125
|
+
patch.features.push(feature);
|
|
126
|
+
});
|
|
127
|
+
/**
|
|
128
|
+
* Takes the features off the copy, and out of the versions with what
|
|
129
|
+
* only they carried. A feature may be named wherever the copy bears it:
|
|
130
|
+
* a patch goes with everything glued onto it, and a feature of a patch
|
|
131
|
+
* may be taken back on its own, the patch staying where it is. An act
|
|
132
|
+
* left having produced or added nothing goes as well.
|
|
133
|
+
*/
|
|
134
|
+
export const removeFeatures = (copyId, featureIds) => onCopy(copyId, (copy, draft) => {
|
|
135
|
+
const named = new Set(featureIds);
|
|
136
|
+
const features = featuresOf(stateOf(copy));
|
|
137
|
+
rewriteFeatures(copy, (stated) => withoutFeatures(stated, named));
|
|
138
|
+
forgetFeatures(draft, new Set(goneWith(features, named)));
|
|
139
|
+
});
|
|
140
|
+
/**
|
|
141
|
+
* Whether two records of the edition state the same, the identity of a
|
|
142
|
+
* statement left out: two transcriptions reading the same word say the
|
|
143
|
+
* same, whichever ids they were given.
|
|
144
|
+
*/
|
|
145
|
+
const sayTheSame = (a, b) => {
|
|
146
|
+
if (a === b)
|
|
147
|
+
return true;
|
|
148
|
+
if (!isRecord(a) || !isRecord(b) || Array.isArray(a) !== Array.isArray(b))
|
|
149
|
+
return false;
|
|
150
|
+
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
151
|
+
keys.delete('id');
|
|
152
|
+
return [...keys].every(key => sayTheSame(a[key], b[key]));
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* What a feature states beyond its identity, its place along the roll,
|
|
156
|
+
* its depiction and its condition. What a patch bears counts by
|
|
157
|
+
* identity: two patches bear the same only where they bear the very
|
|
158
|
+
* same features, so that no merge takes a feature of a patch away.
|
|
159
|
+
*/
|
|
160
|
+
const nature = (feature) => {
|
|
161
|
+
const { id, horizontal, depiction, condition, ...rest } = feature;
|
|
162
|
+
return isPatch(feature) && feature.features
|
|
163
|
+
? { ...rest, features: feature.features.map(borne => borne.id) }
|
|
164
|
+
: rest;
|
|
165
|
+
};
|
|
166
|
+
const conditionsOf = (features) => features.flatMap(feature => feature.condition ? [feature.condition] : []);
|
|
167
|
+
/**
|
|
168
|
+
* What stands in the way of reading the features as one, or nothing
|
|
169
|
+
* where they may be merged. Features may be merged where they differ
|
|
170
|
+
* in nothing but their place along the roll, their depiction and the
|
|
171
|
+
* condition at most one of them states. How far apart they lie is not
|
|
172
|
+
* asked: whether a gap is a bridge of the perforator, a tear or two
|
|
173
|
+
* perforations of their own is the editor's reading.
|
|
174
|
+
*/
|
|
175
|
+
export const mergeObstacle = (features) => {
|
|
176
|
+
if (features.length < 2)
|
|
177
|
+
return 'fewer-than-two';
|
|
178
|
+
const [first, ...rest] = features;
|
|
179
|
+
if (rest.some(feature => feature.type !== first.type))
|
|
180
|
+
return 'different-types';
|
|
181
|
+
if (rest.some(feature => !sayTheSame(feature.vertical, first.vertical)))
|
|
182
|
+
return 'different-tracks';
|
|
183
|
+
if (rest.some(feature => !sayTheSame(nature(feature), nature(first))))
|
|
184
|
+
return 'unlike-features';
|
|
185
|
+
const conditions = conditionsOf(features);
|
|
186
|
+
if (conditions.some(condition => !sayTheSame(condition, conditions[0])))
|
|
187
|
+
return 'differing-conditions';
|
|
188
|
+
return undefined;
|
|
189
|
+
};
|
|
190
|
+
/** The acts of the copy that state any of the named features. */
|
|
191
|
+
const actsHolding = (copy, named) => featuresByAct(copy).filter(features => features.some(feature => named.has(feature.id)));
|
|
192
|
+
/**
|
|
193
|
+
* What stands in the way of reading the named features of the copy as
|
|
194
|
+
* one, which is the whole of what `mergeFeatures` asks: whether one act
|
|
195
|
+
* brought them all about, and what the features themselves say. An id
|
|
196
|
+
* the copy does not bear at a place of its own is passed over.
|
|
197
|
+
*/
|
|
198
|
+
const obstacleIn = (copy, featureIds) => {
|
|
199
|
+
const named = new Set(featureIds);
|
|
200
|
+
const acts = actsHolding(copy, named);
|
|
201
|
+
return acts.length > 1
|
|
202
|
+
? 'different-acts'
|
|
203
|
+
: mergeObstacle((acts[0] ?? []).filter(feature => named.has(feature.id)));
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* What stands in the way of reading the features the ids name as one,
|
|
207
|
+
* as `mergeFeatures` will find it. `mergeObstacle` asks only what the
|
|
208
|
+
* features themselves say, which cannot reach `different-acts`: one
|
|
209
|
+
* feature is the work of one act, and where the features stand is the
|
|
210
|
+
* edition's business rather than theirs.
|
|
211
|
+
*/
|
|
212
|
+
export const mergeObstacleIn = (view, featureIds) => {
|
|
213
|
+
const copy = featureIds.map(id => view.copyOf(id)).find(copy => copy !== undefined);
|
|
214
|
+
return copy ? obstacleIn(copy, featureIds) : 'fewer-than-two';
|
|
215
|
+
};
|
|
216
|
+
/** The items with the replacement in the place of the first it stands for, the others dropped. */
|
|
217
|
+
const standingFor = (items, stands, replacement) => {
|
|
218
|
+
const first = items.findIndex(stands);
|
|
219
|
+
if (first < 0)
|
|
220
|
+
return items;
|
|
221
|
+
return items.flatMap((item, i) => i === first ? [replacement] : stands(item) ? [] : [item]);
|
|
222
|
+
};
|
|
223
|
+
const spanning = (features) => ({
|
|
224
|
+
unit: 'mm',
|
|
225
|
+
from: mm(Math.min(...features.map(feature => feature.horizontal.from))),
|
|
226
|
+
to: mm(Math.max(...features.map(feature => feature.horizontal.to)))
|
|
227
|
+
});
|
|
228
|
+
/**
|
|
229
|
+
* The feature the merged ones give way to. It spans them all and is in
|
|
230
|
+
* every other respect the one of them that states a condition, the
|
|
231
|
+
* others being alike in all but their place. The depiction goes: a
|
|
232
|
+
* region of the scan showing one part depicts no more than that part.
|
|
233
|
+
*/
|
|
234
|
+
const mergedFrom = (features, id) => {
|
|
235
|
+
const stating = features.find(feature => feature.condition) ?? features[0];
|
|
236
|
+
const merged = { ...stating, id, horizontal: spanning(features) };
|
|
237
|
+
delete merged.depiction;
|
|
238
|
+
return merged;
|
|
239
|
+
};
|
|
240
|
+
/** The symbol carried by the merged feature where it named one of those it replaces, and naming it once. */
|
|
241
|
+
const carriedByMerged = (replaced, mergedId) => (symbol) => {
|
|
242
|
+
const names = (carrier) => replaced.has(idOf(carrier));
|
|
243
|
+
const first = symbol.carriers.find(names);
|
|
244
|
+
if (!first)
|
|
245
|
+
return symbol;
|
|
246
|
+
return { ...symbol, carriers: standingFor(symbol.carriers, names, { ...first, id: mergedId }) };
|
|
247
|
+
};
|
|
248
|
+
/** Points the versions at the merged feature wherever they name one it replaces. */
|
|
249
|
+
const carryOver = (draft, replaced, mergedId) => {
|
|
250
|
+
const recarry = carriedByMerged(replaced, mergedId);
|
|
251
|
+
const recarrying = (edit) => {
|
|
252
|
+
const insert = edit.insert && mapped(edit.insert, recarry);
|
|
253
|
+
return insert === edit.insert ? edit : { ...edit, insert };
|
|
254
|
+
};
|
|
255
|
+
const versions = stateOf(draft.versions);
|
|
256
|
+
draft.versions.forEach((version, i) => {
|
|
257
|
+
const edits = versions[i].edits;
|
|
258
|
+
if (edits)
|
|
259
|
+
version.edits = mapped(edits, recarrying);
|
|
260
|
+
});
|
|
261
|
+
};
|
|
262
|
+
/**
|
|
263
|
+
* Replaces the features of the copy with a single one covering them
|
|
264
|
+
* all, where a scan has split what the editor reads as one feature.
|
|
265
|
+
* The merged feature takes the place of the first it replaces and
|
|
266
|
+
* spans from the first to the last, any gap between them included.
|
|
267
|
+
* What the replaced features carried is carried by the merged one, and
|
|
268
|
+
* whatever else named them, a modification or a comprehension, names
|
|
269
|
+
* the merged one in their stead.
|
|
270
|
+
*
|
|
271
|
+
* Only the features the copy bears itself are merged. A feature of a
|
|
272
|
+
* patch states no place along the roll of its own, so there is nothing
|
|
273
|
+
* for a merge to span, and an id naming one is passed over as an id the
|
|
274
|
+
* copy does not bear is. Features of two different acts are not merged
|
|
275
|
+
* either: one feature is the work of one act.
|
|
276
|
+
*
|
|
277
|
+
* Throws where the features cannot stand for one; `mergeObstacle`
|
|
278
|
+
* says beforehand whether they can.
|
|
279
|
+
*/
|
|
280
|
+
export const mergeFeatures = (copyId, featureIds) => onCopy(copyId, (copy, draft) => {
|
|
281
|
+
const state = stateOf(copy);
|
|
282
|
+
const obstacle = obstacleIn(state, featureIds);
|
|
283
|
+
if (obstacle) {
|
|
284
|
+
throw new Error(`The features of copy ${copyId} cannot be merged: ${obstacle}`);
|
|
285
|
+
}
|
|
286
|
+
const named = new Set(featureIds);
|
|
287
|
+
const toMerge = (actsHolding(state, named)[0] ?? []).filter(feature => named.has(feature.id));
|
|
288
|
+
const replaced = new Set(toMerge.map(feature => feature.id));
|
|
289
|
+
const mergedId = v4();
|
|
290
|
+
rewriteFeatures(copy, (stated) => {
|
|
291
|
+
const stands = (feature) => replaced.has(feature.id);
|
|
292
|
+
return stated.some(stands) ? standingFor(stated, stands, mergedFrom(stated.filter(stands), mergedId)) : stated;
|
|
293
|
+
});
|
|
294
|
+
carryOver(draft, replaced, mergedId);
|
|
295
|
+
renameReferences(draft, ids => standingFor(ids, id => replaced.has(id), mergedId));
|
|
296
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Striking features from the edition, and rewriting the ids by which the edition names them outside the versions. */
|
|
2
|
+
import { Draft } from "immer";
|
|
3
|
+
import { Edition } from "../model/Edition.js";
|
|
4
|
+
import { AnySymbol } from "../model/Symbol.js";
|
|
5
|
+
import { RollCopy } from "../model/RollCopy.js";
|
|
6
|
+
import { Ids } from "./draft.js";
|
|
7
|
+
/** The ids of every feature the copy bears, those a patch bears among them. */
|
|
8
|
+
export declare const featureIdsOf: (copy: RollCopy) => Ids;
|
|
9
|
+
/**
|
|
10
|
+
* A symbol every carrier of which lies among the features loses its
|
|
11
|
+
* evidence with them. A symbol without carriers, such as a label,
|
|
12
|
+
* stands on its own.
|
|
13
|
+
*/
|
|
14
|
+
export declare const carriedOnlyOn: (features: Ids) => (symbol: AnySymbol) => boolean;
|
|
15
|
+
/** The ids a statement names, as the rewriting leaves them. */
|
|
16
|
+
type Rename = (ids: string[]) => string[];
|
|
17
|
+
/**
|
|
18
|
+
* Rewrites the ids by which the edition names features and symbols
|
|
19
|
+
* outside the versions. A belief is annotatable anywhere, so the whole
|
|
20
|
+
* edition is read; what the rewriting leaves alone is left the very
|
|
21
|
+
* object it was.
|
|
22
|
+
*/
|
|
23
|
+
export declare const renameReferences: (draft: Draft<Edition>, rename: Rename) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Strikes the features from the edition: their carriers go, a symbol
|
|
26
|
+
* that had no other carrier goes with them, an edit left exchanging
|
|
27
|
+
* nothing goes as well, and so does every reference the versions, the
|
|
28
|
+
* modifications of the copies and the comprehensions made to what went.
|
|
29
|
+
*/
|
|
30
|
+
export declare const forgetFeatures: (draft: Draft<Edition>, features: Ids) => void;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { isCommand, placementRelations } from "../model/Symbol.js";
|
|
2
|
+
import { editsOf } from "../model/Version.js";
|
|
3
|
+
import { featuresOf, statesNothing } from "../model/RollCopy.js";
|
|
4
|
+
import { idOf } from "../model/Assumption.js";
|
|
5
|
+
import { withBorneFeatures } from "../model/Feature.js";
|
|
6
|
+
import { stateOf, droppedIds, insertedIn, edited } from "./draft.js";
|
|
7
|
+
import { without, mapped, pruned, replacing, deeply, writeInto } from "./immutable.js";
|
|
8
|
+
/** The ids of every feature the copy bears, those a patch bears among them. */
|
|
9
|
+
export const featureIdsOf = (copy) => new Set(featuresOf(copy).flatMap(withBorneFeatures).map(feature => feature.id));
|
|
10
|
+
/**
|
|
11
|
+
* A symbol every carrier of which lies among the features loses its
|
|
12
|
+
* evidence with them. A symbol without carriers, such as a label,
|
|
13
|
+
* stands on its own.
|
|
14
|
+
*/
|
|
15
|
+
export const carriedOnlyOn = (features) => (symbol) => symbol.carriers.length > 0 && symbol.carriers.every(carrier => features.has(idOf(carrier)));
|
|
16
|
+
const references = [...placementRelations, 'pairedWith'];
|
|
17
|
+
/** The command without its references to the dropped symbols, or the very same one where it makes none. */
|
|
18
|
+
const forgettingReferences = (command, dropped) => {
|
|
19
|
+
const stale = references.filter(relation => {
|
|
20
|
+
const reference = command[relation];
|
|
21
|
+
return reference !== undefined && dropped.has(idOf(reference));
|
|
22
|
+
});
|
|
23
|
+
if (stale.length === 0)
|
|
24
|
+
return command;
|
|
25
|
+
const kept = { ...command };
|
|
26
|
+
stale.forEach(relation => { delete kept[relation]; });
|
|
27
|
+
return kept;
|
|
28
|
+
};
|
|
29
|
+
/** The symbol without the features among its carriers, and without references to what went with them. */
|
|
30
|
+
const forgettingCarriers = (features, dropped) => (symbol) => {
|
|
31
|
+
const carriers = without(symbol.carriers, carrier => features.has(idOf(carrier)));
|
|
32
|
+
const relieved = carriers === symbol.carriers ? symbol : { ...symbol, carriers };
|
|
33
|
+
return isCommand(relieved) ? forgettingReferences(relieved, dropped) : relieved;
|
|
34
|
+
};
|
|
35
|
+
/** The edit without the dropped symbols and the features, or the very same edit where it had none of them. */
|
|
36
|
+
const forgettingFeatures = (features, dropped) => {
|
|
37
|
+
const forgetCarriers = forgettingCarriers(features, dropped);
|
|
38
|
+
return (edit) => {
|
|
39
|
+
const insert = edit.insert && mapped(without(edit.insert, symbol => dropped.has(symbol.id)), forgetCarriers);
|
|
40
|
+
const deleted = edit.delete && without(edit.delete, id => dropped.has(id));
|
|
41
|
+
if (insert === edit.insert && deleted === edit.delete)
|
|
42
|
+
return edit;
|
|
43
|
+
return { ...edit, ...(insert && { insert }), ...(deleted && { delete: deleted }) };
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
const isCopy = (record) => 'type' in record && record.type === 'RollCopy';
|
|
47
|
+
const isBelief = (record) => 'type' in record && record.type === 'belief';
|
|
48
|
+
const isComprehension = (reason) => reason.type === 'meaningComprehension';
|
|
49
|
+
const comprehendsNothing = (reason) => isComprehension(reason) && reason.comprehends.length === 0;
|
|
50
|
+
/**
|
|
51
|
+
* The modification with what it names rewritten, or the very same one
|
|
52
|
+
* where that leaves it as it was. Only a removal names anything by id:
|
|
53
|
+
* what an act produced or glued on it states itself.
|
|
54
|
+
*/
|
|
55
|
+
const renamingMembers = (rename) => (modification) => modification.type === 'Removal'
|
|
56
|
+
? replacing(modification, 'removed', rename(modification.removed))
|
|
57
|
+
: modification;
|
|
58
|
+
/** The reason with what a comprehension comprehends rewritten; any other reason names nothing of the kind. */
|
|
59
|
+
const renamingComprehended = (rename) => (reason) => isComprehension(reason) ? replacing(reason, 'comprehends', rename(reason.comprehends)) : reason;
|
|
60
|
+
/**
|
|
61
|
+
* The record with the ids it names rewritten: what a copy's removals
|
|
62
|
+
* removed, and what the comprehensions among a belief's reasons
|
|
63
|
+
* comprehend. A removal or a comprehension the rewriting emptied goes
|
|
64
|
+
* with what it named; one that named nothing before stays, as an edit
|
|
65
|
+
* that was empty before it does.
|
|
66
|
+
*/
|
|
67
|
+
const renaming = (rename) => (record) => {
|
|
68
|
+
if (isCopy(record)) {
|
|
69
|
+
return replacing(record, 'modifications', pruned(record.modifications, renamingMembers(rename), statesNothing));
|
|
70
|
+
}
|
|
71
|
+
if (isBelief(record)) {
|
|
72
|
+
return replacing(record, 'reasons', pruned(record.reasons, renamingComprehended(rename), comprehendsNothing));
|
|
73
|
+
}
|
|
74
|
+
return record;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Rewrites the ids by which the edition names features and symbols
|
|
78
|
+
* outside the versions. A belief is annotatable anywhere, so the whole
|
|
79
|
+
* edition is read; what the rewriting leaves alone is left the very
|
|
80
|
+
* object it was.
|
|
81
|
+
*/
|
|
82
|
+
export const renameReferences = (draft, rename) => {
|
|
83
|
+
const before = stateOf(draft);
|
|
84
|
+
const after = deeply(renaming(rename))(before);
|
|
85
|
+
if (after !== before)
|
|
86
|
+
writeInto(draft, before, after);
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Strikes the features from the edition: their carriers go, a symbol
|
|
90
|
+
* that had no other carrier goes with them, an edit left exchanging
|
|
91
|
+
* nothing goes as well, and so does every reference the versions, the
|
|
92
|
+
* modifications of the copies and the comprehensions made to what went.
|
|
93
|
+
*/
|
|
94
|
+
export const forgetFeatures = (draft, features) => {
|
|
95
|
+
const versions = stateOf(draft.versions);
|
|
96
|
+
const dropped = new Set(insertedIn(versions).filter(carriedOnlyOn(features)).map(symbol => symbol.id));
|
|
97
|
+
const forget = forgettingFeatures(features, dropped);
|
|
98
|
+
const edits = versions.map(version => version.edits && edited(version.edits, forget));
|
|
99
|
+
const gone = new Set([
|
|
100
|
+
...features,
|
|
101
|
+
...dropped,
|
|
102
|
+
...versions.flatMap((version, i) => droppedIds(editsOf(version), edits[i] ?? []))
|
|
103
|
+
]);
|
|
104
|
+
renameReferences(draft, ids => without(ids, id => gone.has(id)));
|
|
105
|
+
draft.versions.forEach((version, i) => {
|
|
106
|
+
const stated = edits[i];
|
|
107
|
+
if (stated)
|
|
108
|
+
version.edits = stated;
|
|
109
|
+
});
|
|
110
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Rewritings that return the very object they were given wherever they change nothing, so that an immer draft is left untouched there. */
|
|
2
|
+
/** The items that do not match, or the very same array when none does, so that a draft stays untouched. */
|
|
3
|
+
export declare const without: <T>(items: T[], matches: (item: T) => boolean) => T[];
|
|
4
|
+
/** The items each changed, or the very same array when the change left every one as it was. */
|
|
5
|
+
export declare const mapped: <T>(items: T[], change: (item: T) => T) => T[];
|
|
6
|
+
/** The items each changed, less those the change emptied; the very same array where it changed none. */
|
|
7
|
+
export declare const pruned: <T>(items: T[], change: (item: T) => T, emptied: (item: T) => boolean) => T[];
|
|
8
|
+
/** The record with the field replaced, or the very same record where that is what stood there. */
|
|
9
|
+
export declare const replacing: <T extends object, K extends keyof T>(record: T, key: K, value: T[K]) => T;
|
|
10
|
+
/** A record of the edition, as the walk over it sees one. */
|
|
11
|
+
export type Node = Record<string, unknown>;
|
|
12
|
+
export declare const isRecord: (value: unknown) => value is Node;
|
|
13
|
+
/** The value with the change applied to every record within it and then to itself, innermost first. */
|
|
14
|
+
export declare const deeply: (change: (record: object) => object) => (value: unknown) => unknown;
|
|
15
|
+
/** Writes onto the draft what the rewriting changed, as deep as the change reaches. */
|
|
16
|
+
export declare const writeInto: (draft: Node, before: Node, after: Node) => void;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** Rewritings that return the very object they were given wherever they change nothing, so that an immer draft is left untouched there. */
|
|
2
|
+
/** The items that do not match, or the very same array when none does, so that a draft stays untouched. */
|
|
3
|
+
export const without = (items, matches) => items.some(matches) ? items.filter(item => !matches(item)) : items;
|
|
4
|
+
/** The items each changed, or the very same array when the change left every one as it was. */
|
|
5
|
+
export const mapped = (items, change) => {
|
|
6
|
+
const changed = items.map(change);
|
|
7
|
+
return changed.every((item, i) => item === items[i]) ? items : changed;
|
|
8
|
+
};
|
|
9
|
+
/** The items each changed, less those the change emptied; the very same array where it changed none. */
|
|
10
|
+
export const pruned = (items, change, emptied) => {
|
|
11
|
+
const changed = mapped(items, change);
|
|
12
|
+
return changed === items ? items : changed.filter((item, i) => item === items[i] || !emptied(item));
|
|
13
|
+
};
|
|
14
|
+
/** The record with the field replaced, or the very same record where that is what stood there. */
|
|
15
|
+
export const replacing = (record, key, value) => record[key] === value ? record : { ...record, [key]: value };
|
|
16
|
+
export const isRecord = (value) => typeof value === 'object' && value !== null;
|
|
17
|
+
/** The record's values each changed, or the very same record where the change left every one as it was. */
|
|
18
|
+
const withValues = (record, change) => {
|
|
19
|
+
const entries = Object.entries(record);
|
|
20
|
+
const changed = entries.map(([key, value]) => [key, change(value)]);
|
|
21
|
+
return changed.every(([, value], i) => value === entries[i][1]) ? record : Object.fromEntries(changed);
|
|
22
|
+
};
|
|
23
|
+
/** The value with the change applied to every record within it and then to itself, innermost first. */
|
|
24
|
+
export const deeply = (change) => {
|
|
25
|
+
const changed = (value) => Array.isArray(value) ? mapped(value, changed)
|
|
26
|
+
: isRecord(value) ? change(withValues(value, changed))
|
|
27
|
+
: value;
|
|
28
|
+
return changed;
|
|
29
|
+
};
|
|
30
|
+
/** Whether the two stand alike, so that what differs within them can be written where it lies. */
|
|
31
|
+
const alike = (before, after) => {
|
|
32
|
+
if (!isRecord(before) || !isRecord(after))
|
|
33
|
+
return false;
|
|
34
|
+
if (Array.isArray(before))
|
|
35
|
+
return Array.isArray(after) && before.length === after.length;
|
|
36
|
+
return !Array.isArray(after);
|
|
37
|
+
};
|
|
38
|
+
/** Writes onto the draft what the rewriting changed, as deep as the change reaches. */
|
|
39
|
+
export const writeInto = (draft, before, after) => Object.entries(after).forEach(([key, value]) => {
|
|
40
|
+
if (value === before[key])
|
|
41
|
+
return;
|
|
42
|
+
if (alike(before[key], value))
|
|
43
|
+
writeInto(draft[key], before[key], value);
|
|
44
|
+
else
|
|
45
|
+
draft[key] = value;
|
|
46
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The operations on an edition, each written onto an immer draft of it.
|
|
3
|
+
* They are grouped by what they change under `ops/`; this module names
|
|
4
|
+
* the ones the library offers.
|
|
5
|
+
*/
|
|
6
|
+
export type { EditionOp } from "./draft.js";
|
|
7
|
+
export { createVersion, addCopy, alignCopy, unalignCopy, shortenCopy, unshortenCopy, stateSource, clearSource, nameCopy, stateCarriage, clearCarriage, addGeneralCondition, symbolsCarriedOnlyBy, removeCopy } from "./copies.js";
|
|
8
|
+
export { stateFeatureCondition, addFeature, addBorneFeature, removeFeatures, mergeObstacle, mergeObstacleIn, mergeFeatures, type FeatureAct, type MergeObstacle } from "./features.js";
|
|
9
|
+
export { detachVersion, removeVersion, removeSymbols, deriveVersion, stateDerivation, clearDerivation } from "./versions.js";
|
|
10
|
+
export { connectVersions, collateSymbols, separateReadings } from "./collation.js";
|
|
11
|
+
export { mergeEdits, splitEdit } from "./edits.js";
|
|
12
|
+
export { placeCommand, unplaceCommand, pairCommands, unpairCommand } from "./commands.js";
|
|
13
|
+
export { createBelief, clearBelief, setCertainty, addReason, removeReason } from "./beliefs.js";
|
package/lib/ops/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { createVersion, addCopy, alignCopy, unalignCopy, shortenCopy, unshortenCopy, stateSource, clearSource, nameCopy, stateCarriage, clearCarriage, addGeneralCondition, symbolsCarriedOnlyBy, removeCopy } from "./copies.js";
|
|
2
|
+
export { stateFeatureCondition, addFeature, addBorneFeature, removeFeatures, mergeObstacle, mergeObstacleIn, mergeFeatures } from "./features.js";
|
|
3
|
+
export { detachVersion, removeVersion, removeSymbols, deriveVersion, stateDerivation, clearDerivation } from "./versions.js";
|
|
4
|
+
export { connectVersions, collateSymbols, separateReadings } from "./collation.js";
|
|
5
|
+
export { mergeEdits, splitEdit } from "./edits.js";
|
|
6
|
+
export { placeCommand, unplaceCommand, pairCommands, unpairCommand } from "./commands.js";
|
|
7
|
+
export { createBelief, clearBelief, setCertainty, addReason, removeReason } from "./beliefs.js";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { EditionView } from "../view/EditionView.js";
|
|
2
|
+
import { Derivation, Version } from "../model/Version.js";
|
|
3
|
+
import { Belief } from "../model/Assumption.js";
|
|
4
|
+
import { EditionOp } from "./draft.js";
|
|
5
|
+
/** The derivations the version states beside its principal one, less any naming the parent. */
|
|
6
|
+
export declare const hypothesesBeside: (version: Readonly<Version>, parentId: string) => Derivation[];
|
|
7
|
+
/**
|
|
8
|
+
* Makes the version stand on its own: what it inherited becomes its
|
|
9
|
+
* own insertions, and its derivations go, the hypotheses among them,
|
|
10
|
+
* with the motivations that belonged to them.
|
|
11
|
+
*/
|
|
12
|
+
export declare const detachVersion: (given: EditionView, versionId: string) => EditionOp;
|
|
13
|
+
/**
|
|
14
|
+
* Takes the version out. Whatever read its text against it comes to
|
|
15
|
+
* stand on its own, a hypothesis that something derives from it goes,
|
|
16
|
+
* and so does a copy's statement that it carries the version.
|
|
17
|
+
*/
|
|
18
|
+
export declare const removeVersion: (given: EditionView, versionId: string) => EditionOp;
|
|
19
|
+
/** Takes the symbols out of the version's own insertions, and the edits that had nothing else. */
|
|
20
|
+
export declare const removeSymbols: (versionId: string, symbolIds: readonly string[]) => EditionOp;
|
|
21
|
+
/** Moves the edits into a new version based on this one. */
|
|
22
|
+
export declare const deriveVersion: (versionId: string, editIds: readonly string[]) => EditionOp;
|
|
23
|
+
/**
|
|
24
|
+
* States that the version may also derive from the parent, beside what
|
|
25
|
+
* it derives from already: a hypothesis, such as a contamination, under
|
|
26
|
+
* the belief given. The text stays read against the principal derivation
|
|
27
|
+
* unless the belief holds this one more certain. A version derives from
|
|
28
|
+
* itself, twice from one parent, or from one of its own descendants in
|
|
29
|
+
* no statement.
|
|
30
|
+
*/
|
|
31
|
+
export declare const stateDerivation: (versionId: string, parentId: string, belief?: Belief) => EditionOp;
|
|
32
|
+
/** Takes back the hypothesis that the version derives from the parent; the principal derivation goes with `detachVersion`. */
|
|
33
|
+
export declare const clearDerivation: (versionId: string, parentId: string) => EditionOp;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { v4 } from "uuid";
|
|
2
|
+
import { derivesFrom, editsOf, principalDerivationOf } from "../model/Version.js";
|
|
3
|
+
import { assignReference, idOf } from "../model/Assumption.js";
|
|
4
|
+
import { onVersion, stateOf, insertion, dropInsertions, referenceHeld, withoutReferences, reading } from "./draft.js";
|
|
5
|
+
import { without } from "./immutable.js";
|
|
6
|
+
import { dropStatements } from "./copies.js";
|
|
7
|
+
/** Whether the version's text is read against the parent, its principal derivation naming it. */
|
|
8
|
+
const readsAgainst = (version, parentId) => {
|
|
9
|
+
const principal = principalDerivationOf(version);
|
|
10
|
+
return principal !== undefined && idOf(principal) === parentId;
|
|
11
|
+
};
|
|
12
|
+
/** The derivations the version states beside its principal one, less any naming the parent. */
|
|
13
|
+
export const hypothesesBeside = (version, parentId) => {
|
|
14
|
+
const principal = principalDerivationOf(version);
|
|
15
|
+
return (version.basedOn ?? []).filter(derivation => derivation !== principal && idOf(derivation) !== parentId);
|
|
16
|
+
};
|
|
17
|
+
/** Takes out the derivations that match, and the list itself where none is left. */
|
|
18
|
+
const dropDerivations = (version, matches) => {
|
|
19
|
+
const derivations = stateOf(version).basedOn;
|
|
20
|
+
if (!derivations?.some(matches))
|
|
21
|
+
return;
|
|
22
|
+
const kept = withoutReferences(derivations, matches);
|
|
23
|
+
if (kept)
|
|
24
|
+
version.basedOn = kept;
|
|
25
|
+
else
|
|
26
|
+
delete version.basedOn;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Makes the version stand on its own: what it inherited becomes its
|
|
30
|
+
* own insertions, and its derivations go, the hypotheses among them,
|
|
31
|
+
* with the motivations that belonged to them.
|
|
32
|
+
*/
|
|
33
|
+
export const detachVersion = (given, versionId) => reading(given, view => {
|
|
34
|
+
const edits = view.snapshot(versionId).map(insertion);
|
|
35
|
+
return onVersion(versionId, version => {
|
|
36
|
+
version.edits = edits;
|
|
37
|
+
delete version.basedOn;
|
|
38
|
+
version.motivations = [];
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
/**
|
|
42
|
+
* Takes the version out. Whatever read its text against it comes to
|
|
43
|
+
* stand on its own, a hypothesis that something derives from it goes,
|
|
44
|
+
* and so does a copy's statement that it carries the version.
|
|
45
|
+
*/
|
|
46
|
+
export const removeVersion = (given, versionId) => reading(given, view => {
|
|
47
|
+
const detachments = view.edition.versions
|
|
48
|
+
.filter(version => readsAgainst(version, versionId))
|
|
49
|
+
.map(version => detachVersion(view, version.id));
|
|
50
|
+
const namesIt = (reference) => idOf(reference) === versionId;
|
|
51
|
+
return draft => {
|
|
52
|
+
detachments.forEach(detach => detach(draft));
|
|
53
|
+
draft.versions.forEach(version => dropDerivations(version, namesIt));
|
|
54
|
+
draft.copies.forEach(copy => dropStatements(copy, namesIt));
|
|
55
|
+
draft.versions = without(draft.versions, version => version.id === versionId);
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
/** Takes the symbols out of the version's own insertions, and the edits that had nothing else. */
|
|
59
|
+
export const removeSymbols = (versionId, symbolIds) => onVersion(versionId, version => dropInsertions(version, new Set(symbolIds)));
|
|
60
|
+
/** Moves the edits into a new version based on this one. */
|
|
61
|
+
export const deriveVersion = (versionId, editIds) => onVersion(versionId, (version, draft) => {
|
|
62
|
+
const chosen = new Set(editIds);
|
|
63
|
+
const moved = editsOf(version).filter(edit => chosen.has(edit.id));
|
|
64
|
+
if (version.edits)
|
|
65
|
+
version.edits = without(version.edits, edit => chosen.has(edit.id));
|
|
66
|
+
draft.versions.push({
|
|
67
|
+
id: v4(),
|
|
68
|
+
system: stateOf(version).system,
|
|
69
|
+
basedOn: [assignReference(versionId)],
|
|
70
|
+
edits: moved,
|
|
71
|
+
motivations: []
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
/**
|
|
75
|
+
* States that the version may also derive from the parent, beside what
|
|
76
|
+
* it derives from already: a hypothesis, such as a contamination, under
|
|
77
|
+
* the belief given. The text stays read against the principal derivation
|
|
78
|
+
* unless the belief holds this one more certain. A version derives from
|
|
79
|
+
* itself, twice from one parent, or from one of its own descendants in
|
|
80
|
+
* no statement.
|
|
81
|
+
*/
|
|
82
|
+
export const stateDerivation = (versionId, parentId, belief) => onVersion(versionId, (version, draft) => {
|
|
83
|
+
const derivations = stateOf(version).basedOn ?? [];
|
|
84
|
+
if (parentId === versionId || derivations.some(derivation => idOf(derivation) === parentId))
|
|
85
|
+
return;
|
|
86
|
+
if (derivesFrom(stateOf(draft.versions), parentId, versionId))
|
|
87
|
+
return;
|
|
88
|
+
version.basedOn = [...derivations, referenceHeld(parentId, belief)];
|
|
89
|
+
});
|
|
90
|
+
/** Takes back the hypothesis that the version derives from the parent; the principal derivation goes with `detachVersion`. */
|
|
91
|
+
export const clearDerivation = (versionId, parentId) => onVersion(versionId, version => {
|
|
92
|
+
if (readsAgainst(stateOf(version), parentId))
|
|
93
|
+
return;
|
|
94
|
+
dropDerivations(version, derivation => idOf(derivation) === parentId);
|
|
95
|
+
});
|