dsh-plugin-term-dictionary 1.0.0 → 1.1.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/CHANGELOG.md +84 -23
- package/README.md +8 -4
- package/lib/client.js +827 -59
- package/lib/core/copy.js +62 -0
- package/lib/core/dictionary.js +114 -10
- package/lib/core/entries.js +35 -0
- package/lib/core/pack.js +42 -15
- package/lib/core/settings.js +56 -2
- package/lib/core/store.js +31 -4
- package/lib/core/styles.js +17 -0
- package/lib/core/transfer.js +72 -2
- package/lib/core/views.js +398 -26
- package/package.json +1 -1
package/lib/core/transfer.js
CHANGED
|
@@ -70,6 +70,9 @@ function toRecord(entry) {
|
|
|
70
70
|
}
|
|
71
71
|
};
|
|
72
72
|
if (typeof entry?.domain === "string" && entry.domain !== "") record.domain = entry.domain;
|
|
73
|
+
// A group travels through an export, so a backup restores the structure it was filed under; it does NOT
|
|
74
|
+
// travel in a pack, where the reader organizes entries their own way (see `pack.js`'s allowlist).
|
|
75
|
+
if (typeof entry?.group === "string" && entry.group !== "") record.group = entry.group;
|
|
73
76
|
if (Array.isArray(entry?.aliases) && entry.aliases.length > 0) record.aliases = [...entry.aliases];
|
|
74
77
|
if (entry?.pinned === true) record.pinned = true;
|
|
75
78
|
if (typeof entry?.source === "string" && entry.source !== "") record.source = entry.source;
|
|
@@ -151,6 +154,9 @@ function toEntry(raw) {
|
|
|
151
154
|
usage: typeof definition.usage === "string" ? definition.usage : ""
|
|
152
155
|
},
|
|
153
156
|
domain: typeof raw.domain === "string" ? raw.domain.trim() : "",
|
|
157
|
+
// A group is read back from a file like any other label the user assigned — that is what makes an
|
|
158
|
+
// export a backup of the STRUCTURE and not only of the words.
|
|
159
|
+
group: typeof raw.group === "string" ? raw.group.trim() : "",
|
|
154
160
|
aliases: Array.isArray(raw.aliases) ? raw.aliases.filter((alias) => typeof alias === "string" && alias.trim() !== "") : [],
|
|
155
161
|
pinned: raw.pinned === true
|
|
156
162
|
};
|
|
@@ -191,6 +197,64 @@ function parseImport(text) {
|
|
|
191
197
|
return { ok: true, entries, skipped };
|
|
192
198
|
}
|
|
193
199
|
|
|
200
|
+
/**
|
|
201
|
+
* The folder path a file sits in, as a group path.
|
|
202
|
+
*
|
|
203
|
+
* The picked folder's own name IS the first group: choosing a folder called `packs` is how you say "file
|
|
204
|
+
* these under `packs`", and dropping the name would put everything at the top level — which is precisely
|
|
205
|
+
* the structure the user just picked a folder to express.
|
|
206
|
+
*
|
|
207
|
+
* @param path - a `relative/path/file.json`, as `webkitRelativePath` reports it.
|
|
208
|
+
* @returns the group path.
|
|
209
|
+
*/
|
|
210
|
+
function folderPathOf(path) {
|
|
211
|
+
const parts = String(path)
|
|
212
|
+
.split("/")
|
|
213
|
+
.filter((part) => part !== "");
|
|
214
|
+
// Everything but the file name.
|
|
215
|
+
return parts.slice(0, -1).join("/");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Read a whole directory into one import.
|
|
220
|
+
*
|
|
221
|
+
* A folder is a mixed bag: several exported dictionaries, somebody's downloaded pack files, an
|
|
222
|
+
* `index.json` that lists packs rather than holding entries, and often a README or a screenshot. So this
|
|
223
|
+
* reports what it could NOT use instead of refusing the folder — the alternative is a user with twenty
|
|
224
|
+
* files and one bad one, told "no" with no idea which file it was. Files that are not JSON are not
|
|
225
|
+
* reported at all: they are not mistakes.
|
|
226
|
+
*
|
|
227
|
+
* @param files - `[{ path, text }]`, the directory's files as the browser hands them over.
|
|
228
|
+
* @param options - `useFolderAsGroup` files each entry under the folder it came from, which is the shape
|
|
229
|
+
* people actually keep: one folder per topic, and the reason the option exists at all.
|
|
230
|
+
* @returns `{ entries, files, used, skipped }`, where `skipped` is `[{ name, error }]`.
|
|
231
|
+
*/
|
|
232
|
+
function collectFolderImport(files, options) {
|
|
233
|
+
const list = Array.isArray(files) ? files : [];
|
|
234
|
+
const entries = [];
|
|
235
|
+
const skipped = [];
|
|
236
|
+
const useFolder = options?.useFolderAsGroup === true;
|
|
237
|
+
let used = 0;
|
|
238
|
+
let jsonFiles = 0;
|
|
239
|
+
for (const file of list) {
|
|
240
|
+
const path = typeof file?.path === "string" ? file.path : "";
|
|
241
|
+
if (!/\.json$/i.test(path)) continue;
|
|
242
|
+
jsonFiles++;
|
|
243
|
+
const parsed = parseImport(typeof file?.text === "string" ? file.text : "");
|
|
244
|
+
if (parsed.ok !== true) {
|
|
245
|
+
skipped.push({ name: path, error: parsed.error });
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
used++;
|
|
249
|
+
const folder = useFolder ? folderPathOf(path) : "";
|
|
250
|
+
for (const entry of parsed.entries) {
|
|
251
|
+
const hasGroup = typeof entry.group === "string" && entry.group !== "";
|
|
252
|
+
entries.push(folder !== "" && !hasGroup ? { ...entry, group: folder } : entry);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return { entries, files: jsonFiles, used, skipped };
|
|
256
|
+
}
|
|
257
|
+
|
|
194
258
|
/**
|
|
195
259
|
* Decide what an import would do, without doing any of it.
|
|
196
260
|
*
|
|
@@ -236,10 +300,15 @@ function planImport(existing, incoming, deletedKeys, options) {
|
|
|
236
300
|
// Opting in does not empty the `deleted` bucket by accident: it moves those entries into
|
|
237
301
|
// `accepted`, and the bucket is then genuinely empty, so the report says "0 you deleted" and
|
|
238
302
|
// means it.
|
|
303
|
+
//
|
|
304
|
+
// `revived` names the accepted entries that were previously DELETED, separately from the ones that are
|
|
305
|
+
// simply new. The caller must not treat the two the same: a new term is a write, while a deleted term is
|
|
306
|
+
// a RESTORATION — a decision that has to beat the tombstone, not an edit that hopes to. Running both
|
|
307
|
+
// through the same write is how "a term that was collected and then deleted cannot be restored" happened.
|
|
239
308
|
const retract = options?.includeDeleted === true;
|
|
240
309
|
return retract
|
|
241
|
-
? { accepted: [...accepted, ...wasDeleted], existing: alreadyHave, deleted: [], duplicates }
|
|
242
|
-
: { accepted, existing: alreadyHave, deleted: wasDeleted, duplicates };
|
|
310
|
+
? { accepted: [...accepted, ...wasDeleted], revived: wasDeleted, existing: alreadyHave, deleted: [], duplicates }
|
|
311
|
+
: { accepted, revived: [], existing: alreadyHave, deleted: wasDeleted, duplicates };
|
|
243
312
|
}
|
|
244
313
|
|
|
245
314
|
/**
|
|
@@ -301,6 +370,7 @@ module.exports = {
|
|
|
301
370
|
buildExport,
|
|
302
371
|
buildFeedbackReport,
|
|
303
372
|
parseImport,
|
|
373
|
+
collectFolderImport,
|
|
304
374
|
planImport,
|
|
305
375
|
domainsIn,
|
|
306
376
|
toRecord,
|