@arbidocs/coedit 0.4.119 → 0.4.121

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/dist/commands.cjs CHANGED
@@ -10,6 +10,14 @@ var fflate = require('fflate');
10
10
  var DEFINED_TERM_STYLE = "DefinedTerm";
11
11
  var CROSS_REFERENCE_STYLE = "CrossReference";
12
12
  var BROKEN_CROSS_REFERENCE_STYLE = "CrossReferenceBroken";
13
+ function engineOffset(paragraph, offset, pastDeletions = false) {
14
+ let out = offset;
15
+ for (const r of paragraph.revisions ?? [])
16
+ if (r.kind === "delete" && (r.offset < offset || pastDeletions && r.offset === offset))
17
+ out += r.length;
18
+ return out;
19
+ }
20
+ var engineLength = (paragraph) => engineOffset(paragraph, paragraph.text.length, true);
13
21
  var WATCHED_STYLES = /* @__PURE__ */ new Set([BROKEN_CROSS_REFERENCE_STYLE]);
14
22
  var attr = (node, name) => node.attributes?.find((a) => a.localName === name)?.value;
15
23
  var FIELD_ATOM = "\uFFFC";
@@ -44,6 +52,7 @@ function readParagraph(node, topLevel, bookmarks) {
44
52
  let runStyle;
45
53
  const fields = [];
46
54
  const notes = [];
55
+ const revisions = [];
47
56
  let current = null;
48
57
  const field = (c) => {
49
58
  const type = attr(c, "fldCharType");
@@ -85,7 +94,31 @@ function readParagraph(node, topLevel, bookmarks) {
85
94
  }
86
95
  continue;
87
96
  }
88
- if (c.localName === "delText" || c.localName === "del" || c.localName === "rPr") continue;
97
+ if (c.localName === "del") {
98
+ const deleted = deletedText(c);
99
+ if (deleted && fieldDepth === 0)
100
+ revisions.push({
101
+ kind: "delete",
102
+ offset: text.length,
103
+ length: deleted.length,
104
+ author: attr(c, "author"),
105
+ text: deleted
106
+ });
107
+ continue;
108
+ }
109
+ if (c.localName === "ins") {
110
+ const start = text.length;
111
+ walk(c, link);
112
+ if (text.length > start && fieldDepth === 0)
113
+ revisions.push({
114
+ kind: "insert",
115
+ offset: start,
116
+ length: text.length - start,
117
+ author: attr(c, "author")
118
+ });
119
+ continue;
120
+ }
121
+ if (c.localName === "delText" || c.localName === "rPr") continue;
89
122
  if (c.localName === "r") {
90
123
  const rStyle = c.children?.find((x) => x.localName === "rPr")?.children?.find((x) => x.localName === "rStyle");
91
124
  const watched = rStyle && attr(rStyle, "val");
@@ -148,9 +181,20 @@ function readParagraph(node, topLevel, bookmarks) {
148
181
  links,
149
182
  ...fields.length ? { fields } : {},
150
183
  ...marks.length ? { marks } : {},
151
- ...notes.length ? { notes } : {}
184
+ ...notes.length ? { notes } : {},
185
+ ...revisions.length ? { revisions } : {}
152
186
  };
153
187
  }
188
+ function deletedText(node) {
189
+ let out = "";
190
+ for (const c of node.children ?? []) {
191
+ if (c.localName === "instrText") continue;
192
+ if (c.localName === "delText") out += (c.children ?? []).map((t) => t.value ?? "").join("");
193
+ else if (c.localName === "tab") out += " ";
194
+ else if (c.localName !== "rPr") out += deletedText(c);
195
+ }
196
+ return out;
197
+ }
154
198
  function styleNames(root) {
155
199
  const names = /* @__PURE__ */ new Map();
156
200
  for (const style of root?.children ?? []) {
@@ -1041,7 +1085,7 @@ function insertParagraphs(port, index, body, count, keep) {
1041
1085
  port.setCaret({ paragraphId: before[index].id, offset: 0 });
1042
1086
  } else {
1043
1087
  const last = before[before.length - 1];
1044
- port.setCaret({ paragraphId: last.id, offset: last.text.length });
1088
+ port.setCaret({ paragraphId: last.id, offset: engineLength(last) });
1045
1089
  port.splitParagraph();
1046
1090
  const opened = port.paragraphs();
1047
1091
  port.setCaret({ paragraphId: opened[opened.length - 1].id, offset: 0 });
@@ -1073,25 +1117,47 @@ function addDefinition(port, definition, keep) {
1073
1117
  );
1074
1118
  }
1075
1119
  var hasBookmark = (paragraphs, name) => paragraphs.some((p) => p.bookmarks.includes(name));
1120
+ var paragraphOf = (port, id) => port.paragraphs().find((p) => p.id === id);
1121
+ function replaceSpan(port, span, insert, length) {
1122
+ const { paragraphId, from, to } = span;
1123
+ if (from > to) throw new Error("A replacement runs forwards.");
1124
+ const before = paragraphOf(port, paragraphId);
1125
+ if (!before) throw new Error("That paragraph is no longer in the document.");
1126
+ const length0 = engineLength(before);
1127
+ port.setCaret({ paragraphId, offset: to });
1128
+ if (!insert()) return false;
1129
+ if (from < to) {
1130
+ try {
1131
+ port.deleteRange({ paragraphId, offset: from }, { paragraphId, offset: to });
1132
+ } catch (error) {
1133
+ port.deleteRange({ paragraphId, offset: to }, { paragraphId, offset: to + length });
1134
+ throw error;
1135
+ }
1136
+ }
1137
+ const after = paragraphOf(port, paragraphId);
1138
+ const kept = after && engineLength(after) - length0 === length;
1139
+ port.setCaret({ paragraphId, offset: (kept ? to : from) + length });
1140
+ return true;
1141
+ }
1076
1142
  function insertDefinedTerm(port, definition, options = {}) {
1077
1143
  const start = port.caret();
1078
1144
  if (!start) throw new Error("Place the cursor where the term should go.");
1079
- let caret = start;
1080
- if (options.replace) {
1081
- caret = { paragraphId: start.paragraphId, offset: Math.max(0, start.offset - options.replace) };
1082
- port.deleteRange(caret, start);
1083
- }
1084
1145
  const bookmark = definitionBookmark(definition.id);
1085
1146
  const addedDefinition = !hasBookmark(port.paragraphs(), bookmark);
1086
- if (addedDefinition) caret = addDefinition(port, definition, caret) ?? caret;
1087
- port.setCaret(caret);
1088
- if (!port.paste(
1147
+ const caret = addedDefinition && addDefinition(port, definition, start) || start;
1148
+ const insert = () => port.paste(
1089
1149
  termLinkXml(definition.term, bookmark, definitionTooltip(definition)),
1090
1150
  definition.term,
1091
1151
  false
1152
+ );
1153
+ const from = Math.max(0, caret.offset - (options.replace ?? 0));
1154
+ if (!replaceSpan(
1155
+ port,
1156
+ { paragraphId: caret.paragraphId, from, to: caret.offset },
1157
+ insert,
1158
+ definition.term.length
1092
1159
  ))
1093
1160
  throw new Error("The term could not be inserted.");
1094
- port.setCaret({ paragraphId: caret.paragraphId, offset: caret.offset + definition.term.length });
1095
1161
  return { addedDefinition };
1096
1162
  }
1097
1163
  function definitionsById(definitions) {
@@ -1102,6 +1168,144 @@ function definitionOfAnchor(anchor, definitions) {
1102
1168
  return id ? definitions.get(id) : void 0;
1103
1169
  }
1104
1170
 
1171
+ // src/commands/everywhere.ts
1172
+ var WORD = "[\\p{L}\\p{N}_\\-\u2010\u2011]";
1173
+ var escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1174
+ function needlesOf(spec) {
1175
+ const term = spec.term.replace(/\s+/g, " ").trim();
1176
+ const variants = [
1177
+ ...new Set((spec.variants ?? []).map((v) => v.replace(/\s+/g, " ").trim()))
1178
+ ].filter((v) => v && v !== term);
1179
+ const needles = [
1180
+ ...variants.map((text) => ({ text, kind: "variant" })),
1181
+ ...spec.link ? [{ text: term, kind: "term" }] : []
1182
+ ];
1183
+ return needles.map((n) => ({
1184
+ ...n,
1185
+ pattern: new RegExp(
1186
+ `(?<!${WORD})${n.text.split(" ").map(escape).join("\\s+")}(?!${WORD})`,
1187
+ "gu"
1188
+ )
1189
+ }));
1190
+ }
1191
+ function matchesIn(text, needles) {
1192
+ const found = [];
1193
+ for (const needle of needles)
1194
+ for (const m of text.matchAll(needle.pattern))
1195
+ found.push({ offset: m.index ?? 0, text: m[0], kind: needle.kind });
1196
+ found.sort((a, b) => a.offset - b.offset || b.text.length - a.text.length);
1197
+ const out = [];
1198
+ for (const m of found)
1199
+ if (!out.length || m.offset >= out[out.length - 1].offset + out[out.length - 1].text.length)
1200
+ out.push(m);
1201
+ return out;
1202
+ }
1203
+ var overlaps = (start, end, from, to) => start < to && from < end;
1204
+ function findTermUses(paragraphs, spec, bookmark) {
1205
+ const needles = needlesOf(spec);
1206
+ const defining = new Set([spec.term, ...spec.variants ?? []].map(termKey));
1207
+ const uses = [];
1208
+ const skipped = [];
1209
+ for (const p of paragraphs) {
1210
+ const at = { paragraphId: p.id, ...p.paraId ? { paraId: p.paraId } : {} };
1211
+ const defined = parseDefinition(p.text.split(FIELD_ATOM).join(""));
1212
+ const isDefinition = defined !== null && defining.has(termKey(defined.term));
1213
+ for (const m of matchesIn(p.text, needles)) {
1214
+ const use = { ...at, ...m };
1215
+ const end = m.offset + m.text.length;
1216
+ const link = p.links.find(
1217
+ (l) => l.offset !== void 0 && overlaps(m.offset, end, l.offset, l.offset + l.text.length)
1218
+ );
1219
+ const pending = (p.revisions ?? []).some(
1220
+ (r) => r.kind === "insert" ? overlaps(m.offset, end, r.offset, r.offset + r.length) : r.offset > m.offset && r.offset < end
1221
+ );
1222
+ const reason = isDefinition ? "definition" : link ? link.anchor === bookmark ? "linked" : "link" : pending ? "pending-change" : null;
1223
+ if (reason) skipped.push({ ...use, reason });
1224
+ else uses.push(use);
1225
+ }
1226
+ for (const field of p.fields ?? [])
1227
+ for (const m of matchesIn(field.result, needles))
1228
+ skipped.push({ ...at, offset: field.offset, text: m.text, kind: m.kind, reason: "field" });
1229
+ }
1230
+ return { uses, skipped };
1231
+ }
1232
+ function replacedText(text, uses, term) {
1233
+ let out = text;
1234
+ for (const use of [...uses].sort((a, b) => b.offset - a.offset))
1235
+ out = out.slice(0, use.offset) + term + out.slice(use.offset + use.text.length);
1236
+ return out;
1237
+ }
1238
+ var TermEverywhereError = class extends Error {
1239
+ constructor(message) {
1240
+ super(message);
1241
+ this.name = "TermEverywhereError";
1242
+ }
1243
+ };
1244
+ function applyTermEverywhere(port, spec, { accept } = {}) {
1245
+ const term = spec.term.replace(/\s+/g, " ").trim();
1246
+ if (!term) throw new Error("Name the defined term.");
1247
+ let addedDefinition = false;
1248
+ let link;
1249
+ if (spec.link) {
1250
+ if (!spec.definition) throw new Error(`Linking "${term}" needs its definition.`);
1251
+ link = {
1252
+ bookmark: definitionBookmark(spec.definition.id),
1253
+ tooltip: definitionTooltip(spec.definition)
1254
+ };
1255
+ if (!port.paragraphs().some((p) => p.bookmarks.includes(link.bookmark))) {
1256
+ addDefinition(port, spec.definition);
1257
+ addedDefinition = true;
1258
+ }
1259
+ }
1260
+ const paragraphs = port.paragraphs();
1261
+ const { uses, skipped } = findTermUses(paragraphs, { ...spec, term }, link?.bookmark);
1262
+ const changed = [];
1263
+ const insert = link ? () => port.paste(termLinkXml(term, link.bookmark, link.tooltip), term, false) : () => {
1264
+ port.typeText(term);
1265
+ return true;
1266
+ };
1267
+ for (const p of paragraphs) {
1268
+ const mine = uses.filter((u) => u.paragraphId === p.id);
1269
+ if (!mine.length) continue;
1270
+ if (accept && !accept(p)) {
1271
+ skipped.push(...mine.map((u) => ({ ...u, reason: "unsupported-content" })));
1272
+ continue;
1273
+ }
1274
+ const current = () => port.paragraphs().find((x) => p.paraId ? x.paraId === p.paraId : x.id === p.id);
1275
+ const done = [];
1276
+ for (const use of [...mine].sort((a, b) => b.offset - a.offset)) {
1277
+ const now = current();
1278
+ let ok = false;
1279
+ if (now) {
1280
+ const span = {
1281
+ paragraphId: now.id,
1282
+ from: engineOffset(p, use.offset, true),
1283
+ to: engineOffset(p, use.offset + use.text.length)
1284
+ };
1285
+ try {
1286
+ ok = replaceSpan(port, span, insert, term.length);
1287
+ } catch {
1288
+ ok = false;
1289
+ }
1290
+ }
1291
+ if (ok) {
1292
+ done.push(use);
1293
+ changed.push({ ...use, to: term, linked: Boolean(link) });
1294
+ } else skipped.push({ ...use, reason: "refused" });
1295
+ }
1296
+ const want = replacedText(p.text, done, term);
1297
+ const got = current()?.text;
1298
+ if (got !== want)
1299
+ throw new TermEverywhereError(
1300
+ `A paragraph did not read as intended after the changes: expected "${want}", got "${got ?? "(missing)"}".`
1301
+ );
1302
+ }
1303
+ changed.sort(
1304
+ (a, b) => paragraphs.findIndex((p) => p.id === a.paragraphId) - paragraphs.findIndex((p) => p.id === b.paragraphId) || a.offset - b.offset
1305
+ );
1306
+ return { changed, skipped, addedDefinition };
1307
+ }
1308
+
1105
1309
  // src/commands/index.ts
1106
1310
  function documentReader(view) {
1107
1311
  const root = () => view.part().root;
@@ -1150,9 +1354,11 @@ exports.DEFINITIONS_HEADING = DEFINITIONS_HEADING;
1150
1354
  exports.DEFINITION_BOOKMARK_PREFIX = DEFINITION_BOOKMARK_PREFIX;
1151
1355
  exports.FIELD_ATOM = FIELD_ATOM;
1152
1356
  exports.REF_BOOKMARK_PREFIX = REF_BOOKMARK_PREFIX;
1357
+ exports.TermEverywhereError = TermEverywhereError;
1153
1358
  exports.WATCHED_STYLES = WATCHED_STYLES;
1154
1359
  exports.W_NAMESPACES = W_NAMESPACES;
1155
1360
  exports.addDefinition = addDefinition;
1361
+ exports.applyTermEverywhere = applyTermEverywhere;
1156
1362
  exports.brokenCrossReferences = brokenCrossReferences;
1157
1363
  exports.compareTerms = compareTerms;
1158
1364
  exports.crossReferenceXml = crossReferenceXml;
@@ -1170,11 +1376,14 @@ exports.documentReferenceTooltip = documentReferenceTooltip;
1170
1376
  exports.documentStyleIds = documentStyleIds;
1171
1377
  exports.documentXml = documentXml;
1172
1378
  exports.docxPackage = docxPackage;
1379
+ exports.engineLength = engineLength;
1380
+ exports.engineOffset = engineOffset;
1173
1381
  exports.ensureTargetBookmark = ensureTargetBookmark;
1174
1382
  exports.esc = esc;
1175
1383
  exports.fieldNodeIds = fieldNodeIds;
1176
1384
  exports.filterTargets = filterTargets;
1177
1385
  exports.findDefinitionsSection = findDefinitionsSection;
1386
+ exports.findTermUses = findTermUses;
1178
1387
  exports.fragmentPackage = fragmentPackage;
1179
1388
  exports.headingParagraphXml = headingParagraphXml;
1180
1389
  exports.hyperlinkRelationship = hyperlinkRelationship;
@@ -1199,6 +1408,7 @@ exports.refInstruction = refInstruction;
1199
1408
  exports.refResult = refResult;
1200
1409
  exports.refTooltip = refTooltip;
1201
1410
  exports.refreshCrossReferences = refreshCrossReferences;
1411
+ exports.replaceSpan = replaceSpan;
1202
1412
  exports.sectionStates = sectionStates;
1203
1413
  exports.staleCrossReferences = staleCrossReferences;
1204
1414
  exports.styleNames = styleNames;