@ncukondo/reference-manager 0.1.0 → 0.3.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 +40 -0
- package/dist/chunks/detector-DHztTaFY.js +619 -0
- package/dist/chunks/detector-DHztTaFY.js.map +1 -0
- package/dist/chunks/{detector-BF8Mcc72.js → loader-mQ25o6cV.js} +303 -664
- package/dist/chunks/loader-mQ25o6cV.js.map +1 -0
- package/dist/chunks/search-Be9vzUIH.js +29541 -0
- package/dist/chunks/search-Be9vzUIH.js.map +1 -0
- package/dist/cli/commands/add.d.ts +44 -16
- package/dist/cli/commands/add.d.ts.map +1 -1
- package/dist/cli/commands/cite.d.ts +49 -0
- package/dist/cli/commands/cite.d.ts.map +1 -0
- package/dist/cli/commands/fulltext.d.ts +101 -0
- package/dist/cli/commands/fulltext.d.ts.map +1 -0
- package/dist/cli/commands/index.d.ts +14 -10
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/list.d.ts +23 -6
- package/dist/cli/commands/list.d.ts.map +1 -1
- package/dist/cli/commands/remove.d.ts +47 -12
- package/dist/cli/commands/remove.d.ts.map +1 -1
- package/dist/cli/commands/search.d.ts +24 -7
- package/dist/cli/commands/search.d.ts.map +1 -1
- package/dist/cli/commands/update.d.ts +26 -13
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/execution-context.d.ts +60 -0
- package/dist/cli/execution-context.d.ts.map +1 -0
- package/dist/cli/helpers.d.ts +18 -0
- package/dist/cli/helpers.d.ts.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/server-client.d.ts +73 -10
- package/dist/cli/server-client.d.ts.map +1 -1
- package/dist/cli.js +1200 -528
- package/dist/cli.js.map +1 -1
- package/dist/config/csl-styles.d.ts +83 -0
- package/dist/config/csl-styles.d.ts.map +1 -0
- package/dist/config/defaults.d.ts +10 -0
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/schema.d.ts +84 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/core/csl-json/types.d.ts +15 -3
- package/dist/core/csl-json/types.d.ts.map +1 -1
- package/dist/core/library.d.ts +60 -0
- package/dist/core/library.d.ts.map +1 -1
- package/dist/features/format/bibtex.d.ts +6 -0
- package/dist/features/format/bibtex.d.ts.map +1 -0
- package/dist/features/format/citation-csl.d.ts +41 -0
- package/dist/features/format/citation-csl.d.ts.map +1 -0
- package/dist/features/format/citation-fallback.d.ts +24 -0
- package/dist/features/format/citation-fallback.d.ts.map +1 -0
- package/dist/features/format/index.d.ts +10 -0
- package/dist/features/format/index.d.ts.map +1 -0
- package/dist/features/format/json.d.ts +6 -0
- package/dist/features/format/json.d.ts.map +1 -0
- package/dist/features/format/pretty.d.ts +6 -0
- package/dist/features/format/pretty.d.ts.map +1 -0
- package/dist/features/fulltext/filename.d.ts +17 -0
- package/dist/features/fulltext/filename.d.ts.map +1 -0
- package/dist/features/fulltext/index.d.ts +7 -0
- package/dist/features/fulltext/index.d.ts.map +1 -0
- package/dist/features/fulltext/manager.d.ts +109 -0
- package/dist/features/fulltext/manager.d.ts.map +1 -0
- package/dist/features/fulltext/types.d.ts +12 -0
- package/dist/features/fulltext/types.d.ts.map +1 -0
- package/dist/features/import/cache.d.ts +37 -0
- package/dist/features/import/cache.d.ts.map +1 -0
- package/dist/features/import/detector.d.ts +42 -0
- package/dist/features/import/detector.d.ts.map +1 -0
- package/dist/features/import/fetcher.d.ts +49 -0
- package/dist/features/import/fetcher.d.ts.map +1 -0
- package/dist/features/import/importer.d.ts +61 -0
- package/dist/features/import/importer.d.ts.map +1 -0
- package/dist/features/import/index.d.ts +8 -0
- package/dist/features/import/index.d.ts.map +1 -0
- package/dist/features/import/normalizer.d.ts +15 -0
- package/dist/features/import/normalizer.d.ts.map +1 -0
- package/dist/features/import/parser.d.ts +33 -0
- package/dist/features/import/parser.d.ts.map +1 -0
- package/dist/features/import/rate-limiter.d.ts +45 -0
- package/dist/features/import/rate-limiter.d.ts.map +1 -0
- package/dist/features/operations/add.d.ts +65 -0
- package/dist/features/operations/add.d.ts.map +1 -0
- package/dist/features/operations/cite.d.ts +48 -0
- package/dist/features/operations/cite.d.ts.map +1 -0
- package/dist/features/operations/list.d.ts +28 -0
- package/dist/features/operations/list.d.ts.map +1 -0
- package/dist/features/operations/remove.d.ts +29 -0
- package/dist/features/operations/remove.d.ts.map +1 -0
- package/dist/features/operations/search.d.ts +30 -0
- package/dist/features/operations/search.d.ts.map +1 -0
- package/dist/features/operations/update.d.ts +39 -0
- package/dist/features/operations/update.d.ts.map +1 -0
- package/dist/index.js +18 -16
- package/dist/index.js.map +1 -1
- package/dist/server/index.d.ts +3 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/routes/add.d.ts +11 -0
- package/dist/server/routes/add.d.ts.map +1 -0
- package/dist/server/routes/cite.d.ts +9 -0
- package/dist/server/routes/cite.d.ts.map +1 -0
- package/dist/server/routes/list.d.ts +25 -0
- package/dist/server/routes/list.d.ts.map +1 -0
- package/dist/server/routes/references.d.ts.map +1 -1
- package/dist/server/routes/search.d.ts +26 -0
- package/dist/server/routes/search.d.ts.map +1 -0
- package/dist/server.js +215 -32
- package/dist/server.js.map +1 -1
- package/package.json +15 -4
- package/dist/chunks/detector-BF8Mcc72.js.map +0 -1
- package/dist/cli/output/bibtex.d.ts +0 -6
- package/dist/cli/output/bibtex.d.ts.map +0 -1
- package/dist/cli/output/index.d.ts +0 -7
- package/dist/cli/output/index.d.ts.map +0 -1
- package/dist/cli/output/json.d.ts +0 -6
- package/dist/cli/output/json.d.ts.map +0 -1
- package/dist/cli/output/pretty.d.ts +0 -6
- package/dist/cli/output/pretty.d.ts.map +0 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { randomUUID, createHash } from "node:crypto";
|
|
2
2
|
import { createReadStream, existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { readFile, mkdir, writeFile } from "node:fs/promises";
|
|
4
|
-
import {
|
|
4
|
+
import { C as CslLibrarySchema } from "./detector-DHztTaFY.js";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
|
+
import { homedir, tmpdir } from "node:os";
|
|
6
7
|
import { parse } from "@iarna/toml";
|
|
7
|
-
import {
|
|
8
|
+
import { z } from "zod";
|
|
8
9
|
function normalizeText(text) {
|
|
9
10
|
return text.toLowerCase().replace(/\s+/g, "_").replace(/[^a-z0-9_]/g, "").replace(/_+/g, "_").replace(/^_|_$/g, "");
|
|
10
11
|
}
|
|
@@ -32,7 +33,7 @@ function extractAuthorName(item) {
|
|
|
32
33
|
}
|
|
33
34
|
return "";
|
|
34
35
|
}
|
|
35
|
-
function extractYear
|
|
36
|
+
function extractYear(item) {
|
|
36
37
|
if (!item.issued || !item.issued["date-parts"] || item.issued["date-parts"].length === 0) {
|
|
37
38
|
return "";
|
|
38
39
|
}
|
|
@@ -57,7 +58,7 @@ function determineTitlePart(hasAuthor, hasYear, title) {
|
|
|
57
58
|
}
|
|
58
59
|
function generateId(item) {
|
|
59
60
|
const author = extractAuthorName(item);
|
|
60
|
-
const year = extractYear
|
|
61
|
+
const year = extractYear(item);
|
|
61
62
|
const title = item.title ? normalizeTitleSlug(item.title) : "";
|
|
62
63
|
const authorPart = author || "anon";
|
|
63
64
|
const yearPart = year || "nd";
|
|
@@ -295,54 +296,6 @@ async function computeFileHash(filePath) {
|
|
|
295
296
|
stream.on("error", (error) => reject(error));
|
|
296
297
|
});
|
|
297
298
|
}
|
|
298
|
-
const CslNameSchema = z.object({
|
|
299
|
-
family: z.string().optional(),
|
|
300
|
-
given: z.string().optional(),
|
|
301
|
-
literal: z.string().optional(),
|
|
302
|
-
"dropping-particle": z.string().optional(),
|
|
303
|
-
"non-dropping-particle": z.string().optional(),
|
|
304
|
-
suffix: z.string().optional()
|
|
305
|
-
});
|
|
306
|
-
const CslDateSchema = z.object({
|
|
307
|
-
"date-parts": z.array(z.array(z.number())).optional(),
|
|
308
|
-
raw: z.string().optional(),
|
|
309
|
-
season: z.string().optional(),
|
|
310
|
-
circa: z.boolean().optional(),
|
|
311
|
-
literal: z.string().optional()
|
|
312
|
-
});
|
|
313
|
-
const CslCustomSchema = z.object({
|
|
314
|
-
uuid: z.string(),
|
|
315
|
-
created_at: z.string(),
|
|
316
|
-
timestamp: z.string(),
|
|
317
|
-
additional_urls: z.array(z.string()).optional()
|
|
318
|
-
});
|
|
319
|
-
const CslItemSchema = z.object({
|
|
320
|
-
id: z.string(),
|
|
321
|
-
type: z.string(),
|
|
322
|
-
title: z.string().optional(),
|
|
323
|
-
author: z.array(CslNameSchema).optional(),
|
|
324
|
-
editor: z.array(CslNameSchema).optional(),
|
|
325
|
-
issued: CslDateSchema.optional(),
|
|
326
|
-
accessed: CslDateSchema.optional(),
|
|
327
|
-
"container-title": z.string().optional(),
|
|
328
|
-
volume: z.string().optional(),
|
|
329
|
-
issue: z.string().optional(),
|
|
330
|
-
page: z.string().optional(),
|
|
331
|
-
DOI: z.string().optional(),
|
|
332
|
-
PMID: z.string().optional(),
|
|
333
|
-
PMCID: z.string().optional(),
|
|
334
|
-
ISBN: z.string().optional(),
|
|
335
|
-
ISSN: z.string().optional(),
|
|
336
|
-
URL: z.string().optional(),
|
|
337
|
-
abstract: z.string().optional(),
|
|
338
|
-
publisher: z.string().optional(),
|
|
339
|
-
"publisher-place": z.string().optional(),
|
|
340
|
-
note: z.string().optional(),
|
|
341
|
-
keyword: z.array(z.string()).optional(),
|
|
342
|
-
custom: CslCustomSchema.optional()
|
|
343
|
-
// Allow additional fields
|
|
344
|
-
}).passthrough();
|
|
345
|
-
const CslLibrarySchema = z.array(CslItemSchema);
|
|
346
299
|
function parseKeyword(keyword) {
|
|
347
300
|
if (typeof keyword !== "string") {
|
|
348
301
|
return void 0;
|
|
@@ -478,6 +431,32 @@ class Library {
|
|
|
478
431
|
}
|
|
479
432
|
return this.removeReference(ref);
|
|
480
433
|
}
|
|
434
|
+
/**
|
|
435
|
+
* Update a reference by UUID.
|
|
436
|
+
* @param uuid - The UUID of the reference to update
|
|
437
|
+
* @param updates - Partial updates to apply to the reference
|
|
438
|
+
* @returns true if the reference was found and updated, false otherwise
|
|
439
|
+
*/
|
|
440
|
+
updateByUuid(uuid, updates, options = {}) {
|
|
441
|
+
const ref = this.uuidIndex.get(uuid);
|
|
442
|
+
if (!ref) {
|
|
443
|
+
return { updated: false };
|
|
444
|
+
}
|
|
445
|
+
return this.updateReference(ref, updates, options);
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Update a reference by ID.
|
|
449
|
+
* @param id - The ID of the reference to update
|
|
450
|
+
* @param updates - Partial updates to apply to the reference
|
|
451
|
+
* @returns true if the reference was found and updated, false otherwise
|
|
452
|
+
*/
|
|
453
|
+
updateById(id, updates, options = {}) {
|
|
454
|
+
const ref = this.idIndex.get(id);
|
|
455
|
+
if (!ref) {
|
|
456
|
+
return { updated: false };
|
|
457
|
+
}
|
|
458
|
+
return this.updateReference(ref, updates, options);
|
|
459
|
+
}
|
|
481
460
|
/**
|
|
482
461
|
* Find a reference by UUID
|
|
483
462
|
*/
|
|
@@ -545,6 +524,82 @@ class Library {
|
|
|
545
524
|
return false;
|
|
546
525
|
}
|
|
547
526
|
this.references.splice(index, 1);
|
|
527
|
+
this.removeFromIndices(ref);
|
|
528
|
+
return true;
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Update a reference with partial updates.
|
|
532
|
+
* Preserves uuid and created_at, updates timestamp.
|
|
533
|
+
*/
|
|
534
|
+
updateReference(ref, updates, options = {}) {
|
|
535
|
+
const index = this.references.indexOf(ref);
|
|
536
|
+
if (index === -1) {
|
|
537
|
+
return { updated: false };
|
|
538
|
+
}
|
|
539
|
+
const existingItem = ref.getItem();
|
|
540
|
+
const currentId = ref.getId();
|
|
541
|
+
const { newId, idChanged, collision } = this.resolveNewId(
|
|
542
|
+
updates.id ?? existingItem.id,
|
|
543
|
+
currentId,
|
|
544
|
+
options
|
|
545
|
+
);
|
|
546
|
+
if (collision) {
|
|
547
|
+
return { updated: false, idCollision: true };
|
|
548
|
+
}
|
|
549
|
+
const updatedItem = this.buildUpdatedItem(existingItem, updates, newId);
|
|
550
|
+
this.removeFromIndices(ref);
|
|
551
|
+
const newRef = new Reference(updatedItem);
|
|
552
|
+
this.references[index] = newRef;
|
|
553
|
+
this.addToIndices(newRef);
|
|
554
|
+
const result = { updated: true };
|
|
555
|
+
if (idChanged) {
|
|
556
|
+
result.idChanged = true;
|
|
557
|
+
result.newId = newId;
|
|
558
|
+
}
|
|
559
|
+
return result;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Resolve the new ID, handling collisions based on options.
|
|
563
|
+
*/
|
|
564
|
+
resolveNewId(requestedId, currentId, options) {
|
|
565
|
+
if (requestedId === currentId) {
|
|
566
|
+
return { newId: requestedId, idChanged: false, collision: false };
|
|
567
|
+
}
|
|
568
|
+
const conflictingRef = this.idIndex.get(requestedId);
|
|
569
|
+
if (!conflictingRef) {
|
|
570
|
+
return { newId: requestedId, idChanged: false, collision: false };
|
|
571
|
+
}
|
|
572
|
+
const onIdCollision = options.onIdCollision ?? "fail";
|
|
573
|
+
if (onIdCollision === "fail") {
|
|
574
|
+
return { newId: requestedId, idChanged: false, collision: true };
|
|
575
|
+
}
|
|
576
|
+
const existingIds = new Set(this.references.map((r) => r.getId()));
|
|
577
|
+
existingIds.delete(currentId);
|
|
578
|
+
const resolvedId = this.resolveIdCollision(requestedId, existingIds);
|
|
579
|
+
return { newId: resolvedId, idChanged: true, collision: false };
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Build the updated CslItem, preserving uuid and created_at.
|
|
583
|
+
*/
|
|
584
|
+
buildUpdatedItem(existingItem, updates, newId) {
|
|
585
|
+
return {
|
|
586
|
+
...existingItem,
|
|
587
|
+
...updates,
|
|
588
|
+
id: newId,
|
|
589
|
+
type: updates.type ?? existingItem.type,
|
|
590
|
+
custom: {
|
|
591
|
+
...existingItem.custom || {},
|
|
592
|
+
...updates.custom || {},
|
|
593
|
+
uuid: existingItem.custom?.uuid || "",
|
|
594
|
+
created_at: existingItem.custom?.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
595
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Remove a reference from all indices.
|
|
601
|
+
*/
|
|
602
|
+
removeFromIndices(ref) {
|
|
548
603
|
this.uuidIndex.delete(ref.getUuid());
|
|
549
604
|
this.idIndex.delete(ref.getId());
|
|
550
605
|
const doi = ref.getDoi();
|
|
@@ -555,7 +610,36 @@ class Library {
|
|
|
555
610
|
if (pmid) {
|
|
556
611
|
this.pmidIndex.delete(pmid);
|
|
557
612
|
}
|
|
558
|
-
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Generate an alphabetic suffix for ID collision resolution.
|
|
616
|
+
* 0 -> 'a', 1 -> 'b', ..., 25 -> 'z', 26 -> 'aa', etc.
|
|
617
|
+
*/
|
|
618
|
+
generateSuffix(index) {
|
|
619
|
+
const alphabet = "abcdefghijklmnopqrstuvwxyz";
|
|
620
|
+
let suffix = "";
|
|
621
|
+
let n = index;
|
|
622
|
+
do {
|
|
623
|
+
suffix = alphabet[n % 26] + suffix;
|
|
624
|
+
n = Math.floor(n / 26) - 1;
|
|
625
|
+
} while (n >= 0);
|
|
626
|
+
return suffix;
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Resolve ID collision by appending alphabetic suffix.
|
|
630
|
+
*/
|
|
631
|
+
resolveIdCollision(baseId, existingIds) {
|
|
632
|
+
if (!existingIds.has(baseId)) {
|
|
633
|
+
return baseId;
|
|
634
|
+
}
|
|
635
|
+
let index = 0;
|
|
636
|
+
let newId;
|
|
637
|
+
do {
|
|
638
|
+
const suffix = this.generateSuffix(index);
|
|
639
|
+
newId = `${baseId}${suffix}`;
|
|
640
|
+
index++;
|
|
641
|
+
} while (existingIds.has(newId));
|
|
642
|
+
return newId;
|
|
559
643
|
}
|
|
560
644
|
}
|
|
561
645
|
const logLevelSchema = z.enum(["silent", "info", "debug"]);
|
|
@@ -575,12 +659,29 @@ const serverConfigSchema = z.object({
|
|
|
575
659
|
autoStart: z.boolean(),
|
|
576
660
|
autoStopMinutes: z.number().int().nonnegative()
|
|
577
661
|
});
|
|
662
|
+
const citationFormatSchema = z.enum(["text", "html", "rtf"]);
|
|
663
|
+
const citationConfigSchema = z.object({
|
|
664
|
+
defaultStyle: z.string(),
|
|
665
|
+
cslDirectory: z.array(z.string()),
|
|
666
|
+
defaultLocale: z.string(),
|
|
667
|
+
defaultFormat: citationFormatSchema
|
|
668
|
+
});
|
|
669
|
+
const pubmedConfigSchema = z.object({
|
|
670
|
+
email: z.string().optional(),
|
|
671
|
+
apiKey: z.string().optional()
|
|
672
|
+
});
|
|
673
|
+
const fulltextConfigSchema = z.object({
|
|
674
|
+
directory: z.string().min(1)
|
|
675
|
+
});
|
|
578
676
|
const configSchema = z.object({
|
|
579
677
|
library: z.string().min(1),
|
|
580
678
|
logLevel: logLevelSchema,
|
|
581
679
|
backup: backupConfigSchema,
|
|
582
680
|
watch: watchConfigSchema,
|
|
583
|
-
server: serverConfigSchema
|
|
681
|
+
server: serverConfigSchema,
|
|
682
|
+
citation: citationConfigSchema,
|
|
683
|
+
pubmed: pubmedConfigSchema,
|
|
684
|
+
fulltext: fulltextConfigSchema
|
|
584
685
|
});
|
|
585
686
|
const partialConfigSchema = z.object({
|
|
586
687
|
library: z.string().min(1).optional(),
|
|
@@ -610,6 +711,24 @@ const partialConfigSchema = z.object({
|
|
|
610
711
|
auto_start: z.boolean().optional(),
|
|
611
712
|
autoStopMinutes: z.number().int().nonnegative().optional(),
|
|
612
713
|
auto_stop_minutes: z.number().int().nonnegative().optional()
|
|
714
|
+
}).optional(),
|
|
715
|
+
citation: z.object({
|
|
716
|
+
defaultStyle: z.string().optional(),
|
|
717
|
+
default_style: z.string().optional(),
|
|
718
|
+
cslDirectory: z.union([z.string(), z.array(z.string())]).optional(),
|
|
719
|
+
csl_directory: z.union([z.string(), z.array(z.string())]).optional(),
|
|
720
|
+
defaultLocale: z.string().optional(),
|
|
721
|
+
default_locale: z.string().optional(),
|
|
722
|
+
defaultFormat: citationFormatSchema.optional(),
|
|
723
|
+
default_format: citationFormatSchema.optional()
|
|
724
|
+
}).optional(),
|
|
725
|
+
pubmed: z.object({
|
|
726
|
+
email: z.string().optional(),
|
|
727
|
+
apiKey: z.string().optional(),
|
|
728
|
+
api_key: z.string().optional()
|
|
729
|
+
}).optional(),
|
|
730
|
+
fulltext: z.object({
|
|
731
|
+
directory: z.string().min(1).optional()
|
|
613
732
|
}).optional()
|
|
614
733
|
}).passthrough();
|
|
615
734
|
function normalizeBackupConfig(backup) {
|
|
@@ -662,6 +781,37 @@ function normalizeServerConfig(server) {
|
|
|
662
781
|
}
|
|
663
782
|
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
664
783
|
}
|
|
784
|
+
function normalizeCitationConfig(citation) {
|
|
785
|
+
const normalized = {};
|
|
786
|
+
const defaultStyle = citation.defaultStyle ?? citation.default_style;
|
|
787
|
+
if (defaultStyle !== void 0) {
|
|
788
|
+
normalized.defaultStyle = defaultStyle;
|
|
789
|
+
}
|
|
790
|
+
const cslDirectory = citation.cslDirectory ?? citation.csl_directory;
|
|
791
|
+
if (cslDirectory !== void 0) {
|
|
792
|
+
normalized.cslDirectory = Array.isArray(cslDirectory) ? cslDirectory : [cslDirectory];
|
|
793
|
+
}
|
|
794
|
+
const defaultLocale = citation.defaultLocale ?? citation.default_locale;
|
|
795
|
+
if (defaultLocale !== void 0) {
|
|
796
|
+
normalized.defaultLocale = defaultLocale;
|
|
797
|
+
}
|
|
798
|
+
const defaultFormat = citation.defaultFormat ?? citation.default_format;
|
|
799
|
+
if (defaultFormat !== void 0) {
|
|
800
|
+
normalized.defaultFormat = defaultFormat;
|
|
801
|
+
}
|
|
802
|
+
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
803
|
+
}
|
|
804
|
+
function normalizePubmedConfig(pubmed) {
|
|
805
|
+
const normalized = {};
|
|
806
|
+
if (pubmed.email !== void 0) {
|
|
807
|
+
normalized.email = pubmed.email;
|
|
808
|
+
}
|
|
809
|
+
const apiKey = pubmed.apiKey ?? pubmed.api_key;
|
|
810
|
+
if (apiKey !== void 0) {
|
|
811
|
+
normalized.apiKey = apiKey;
|
|
812
|
+
}
|
|
813
|
+
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
814
|
+
}
|
|
665
815
|
function normalizePartialConfig(partial) {
|
|
666
816
|
const normalized = {};
|
|
667
817
|
if (partial.library !== void 0) {
|
|
@@ -671,30 +821,39 @@ function normalizePartialConfig(partial) {
|
|
|
671
821
|
if (logLevel !== void 0) {
|
|
672
822
|
normalized.logLevel = logLevel;
|
|
673
823
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
);
|
|
678
|
-
if (backup) {
|
|
679
|
-
normalized.backup = backup;
|
|
680
|
-
}
|
|
824
|
+
const backup = partial.backup !== void 0 ? normalizeBackupConfig(partial.backup) : void 0;
|
|
825
|
+
if (backup) {
|
|
826
|
+
normalized.backup = backup;
|
|
681
827
|
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
normalized.watch = watch;
|
|
686
|
-
}
|
|
828
|
+
const watch = partial.watch !== void 0 ? normalizeWatchConfig(partial.watch) : void 0;
|
|
829
|
+
if (watch) {
|
|
830
|
+
normalized.watch = watch;
|
|
687
831
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
832
|
+
const server = partial.server !== void 0 ? normalizeServerConfig(partial.server) : void 0;
|
|
833
|
+
if (server) {
|
|
834
|
+
normalized.server = server;
|
|
835
|
+
}
|
|
836
|
+
const citation = partial.citation !== void 0 ? normalizeCitationConfig(partial.citation) : void 0;
|
|
837
|
+
if (citation) {
|
|
838
|
+
normalized.citation = citation;
|
|
839
|
+
}
|
|
840
|
+
const pubmed = partial.pubmed !== void 0 ? normalizePubmedConfig(partial.pubmed) : void 0;
|
|
841
|
+
if (pubmed) {
|
|
842
|
+
normalized.pubmed = pubmed;
|
|
843
|
+
}
|
|
844
|
+
const fulltext = partial.fulltext !== void 0 ? normalizeFulltextConfig(partial.fulltext) : void 0;
|
|
845
|
+
if (fulltext) {
|
|
846
|
+
normalized.fulltext = fulltext;
|
|
695
847
|
}
|
|
696
848
|
return normalized;
|
|
697
849
|
}
|
|
850
|
+
function normalizeFulltextConfig(fulltext) {
|
|
851
|
+
const normalized = {};
|
|
852
|
+
if (fulltext.directory !== void 0) {
|
|
853
|
+
normalized.directory = fulltext.directory;
|
|
854
|
+
}
|
|
855
|
+
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
856
|
+
}
|
|
698
857
|
function getDefaultBackupDirectory() {
|
|
699
858
|
return join(tmpdir(), "reference-manager", "backups");
|
|
700
859
|
}
|
|
@@ -707,6 +866,12 @@ function getDefaultUserConfigPath() {
|
|
|
707
866
|
function getDefaultCurrentDirConfigFilename() {
|
|
708
867
|
return ".reference-manager.config.toml";
|
|
709
868
|
}
|
|
869
|
+
function getDefaultCslDirectory() {
|
|
870
|
+
return join(homedir(), ".reference-manager", "csl");
|
|
871
|
+
}
|
|
872
|
+
function getDefaultFulltextDirectory() {
|
|
873
|
+
return join(homedir(), ".reference-manager", "fulltext");
|
|
874
|
+
}
|
|
710
875
|
const defaultConfig = {
|
|
711
876
|
library: getDefaultLibraryPath(),
|
|
712
877
|
logLevel: "info",
|
|
@@ -725,6 +890,19 @@ const defaultConfig = {
|
|
|
725
890
|
server: {
|
|
726
891
|
autoStart: false,
|
|
727
892
|
autoStopMinutes: 0
|
|
893
|
+
},
|
|
894
|
+
citation: {
|
|
895
|
+
defaultStyle: "apa",
|
|
896
|
+
cslDirectory: [getDefaultCslDirectory()],
|
|
897
|
+
defaultLocale: "en-US",
|
|
898
|
+
defaultFormat: "text"
|
|
899
|
+
},
|
|
900
|
+
pubmed: {
|
|
901
|
+
email: void 0,
|
|
902
|
+
apiKey: void 0
|
|
903
|
+
},
|
|
904
|
+
fulltext: {
|
|
905
|
+
directory: getDefaultFulltextDirectory()
|
|
728
906
|
}
|
|
729
907
|
};
|
|
730
908
|
function loadTOMLFile(path) {
|
|
@@ -744,6 +922,7 @@ function loadTOMLFile(path) {
|
|
|
744
922
|
}
|
|
745
923
|
function mergeConfigs(base, ...overrides) {
|
|
746
924
|
const result = { ...base };
|
|
925
|
+
const sectionKeys = ["backup", "watch", "server", "citation", "pubmed", "fulltext"];
|
|
747
926
|
for (const override of overrides) {
|
|
748
927
|
if (!override) continue;
|
|
749
928
|
if (override.library !== void 0) {
|
|
@@ -752,23 +931,13 @@ function mergeConfigs(base, ...overrides) {
|
|
|
752
931
|
if (override.logLevel !== void 0) {
|
|
753
932
|
result.logLevel = override.logLevel;
|
|
754
933
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
result.watch = {
|
|
763
|
-
...result.watch,
|
|
764
|
-
...override.watch
|
|
765
|
-
};
|
|
766
|
-
}
|
|
767
|
-
if (override.server !== void 0) {
|
|
768
|
-
result.server = {
|
|
769
|
-
...result.server,
|
|
770
|
-
...override.server
|
|
771
|
-
};
|
|
934
|
+
for (const key of sectionKeys) {
|
|
935
|
+
if (override[key] !== void 0) {
|
|
936
|
+
result[key] = {
|
|
937
|
+
...result[key],
|
|
938
|
+
...override[key]
|
|
939
|
+
};
|
|
940
|
+
}
|
|
772
941
|
}
|
|
773
942
|
}
|
|
774
943
|
return result;
|
|
@@ -792,7 +961,39 @@ function fillDefaults(partial) {
|
|
|
792
961
|
server: {
|
|
793
962
|
autoStart: partial.server?.autoStart ?? defaultConfig.server.autoStart,
|
|
794
963
|
autoStopMinutes: partial.server?.autoStopMinutes ?? defaultConfig.server.autoStopMinutes
|
|
795
|
-
}
|
|
964
|
+
},
|
|
965
|
+
citation: fillCitationDefaults(partial.citation),
|
|
966
|
+
pubmed: fillPubmedDefaults(partial.pubmed),
|
|
967
|
+
fulltext: fillFulltextDefaults(partial.fulltext)
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
function fillCitationDefaults(partial) {
|
|
971
|
+
return {
|
|
972
|
+
defaultStyle: partial?.defaultStyle ?? defaultConfig.citation.defaultStyle,
|
|
973
|
+
cslDirectory: partial?.cslDirectory ?? defaultConfig.citation.cslDirectory,
|
|
974
|
+
defaultLocale: partial?.defaultLocale ?? defaultConfig.citation.defaultLocale,
|
|
975
|
+
defaultFormat: partial?.defaultFormat ?? defaultConfig.citation.defaultFormat
|
|
976
|
+
};
|
|
977
|
+
}
|
|
978
|
+
function fillPubmedDefaults(partial) {
|
|
979
|
+
const email = process.env.PUBMED_EMAIL ?? partial?.email ?? defaultConfig.pubmed.email;
|
|
980
|
+
const apiKey = process.env.PUBMED_API_KEY ?? partial?.apiKey ?? defaultConfig.pubmed.apiKey;
|
|
981
|
+
return {
|
|
982
|
+
email,
|
|
983
|
+
apiKey
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
function expandTilde(path) {
|
|
987
|
+
if (path.startsWith("~/")) {
|
|
988
|
+
return join(homedir(), path.slice(2));
|
|
989
|
+
}
|
|
990
|
+
return path;
|
|
991
|
+
}
|
|
992
|
+
function fillFulltextDefaults(partial) {
|
|
993
|
+
const envDir = process.env.REFERENCE_MANAGER_FULLTEXT_DIR;
|
|
994
|
+
const directory = envDir ?? partial?.directory ?? defaultConfig.fulltext.directory;
|
|
995
|
+
return {
|
|
996
|
+
directory: expandTilde(directory)
|
|
796
997
|
};
|
|
797
998
|
}
|
|
798
999
|
function loadConfig(options = {}) {
|
|
@@ -822,567 +1023,7 @@ function loadConfig(options = {}) {
|
|
|
822
1023
|
);
|
|
823
1024
|
}
|
|
824
1025
|
}
|
|
825
|
-
const VALID_FIELDS = /* @__PURE__ */ new Set([
|
|
826
|
-
"author",
|
|
827
|
-
"title",
|
|
828
|
-
"year",
|
|
829
|
-
"doi",
|
|
830
|
-
"pmid",
|
|
831
|
-
"pmcid",
|
|
832
|
-
"url",
|
|
833
|
-
"keyword"
|
|
834
|
-
]);
|
|
835
|
-
function isWhitespace(query, index) {
|
|
836
|
-
return /\s/.test(query.charAt(index));
|
|
837
|
-
}
|
|
838
|
-
function isQuote(query, index) {
|
|
839
|
-
return query.charAt(index) === '"';
|
|
840
|
-
}
|
|
841
|
-
function tokenize(query) {
|
|
842
|
-
const tokens = [];
|
|
843
|
-
let i = 0;
|
|
844
|
-
while (i < query.length) {
|
|
845
|
-
if (isWhitespace(query, i)) {
|
|
846
|
-
i++;
|
|
847
|
-
continue;
|
|
848
|
-
}
|
|
849
|
-
const result = parseNextToken(query, i);
|
|
850
|
-
if (result.token) {
|
|
851
|
-
tokens.push(result.token);
|
|
852
|
-
}
|
|
853
|
-
i = result.nextIndex;
|
|
854
|
-
}
|
|
855
|
-
return {
|
|
856
|
-
original: query,
|
|
857
|
-
tokens
|
|
858
|
-
};
|
|
859
|
-
}
|
|
860
|
-
function hasWhitespaceBetween(query, start, end) {
|
|
861
|
-
for (let j = start; j < end; j++) {
|
|
862
|
-
if (isWhitespace(query, j)) {
|
|
863
|
-
return true;
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
return false;
|
|
867
|
-
}
|
|
868
|
-
function tryParseFieldValue(query, startIndex) {
|
|
869
|
-
const colonIndex = query.indexOf(":", startIndex);
|
|
870
|
-
if (colonIndex === -1) {
|
|
871
|
-
return null;
|
|
872
|
-
}
|
|
873
|
-
if (hasWhitespaceBetween(query, startIndex, colonIndex)) {
|
|
874
|
-
return null;
|
|
875
|
-
}
|
|
876
|
-
const fieldName = query.substring(startIndex, colonIndex);
|
|
877
|
-
if (!VALID_FIELDS.has(fieldName)) {
|
|
878
|
-
return null;
|
|
879
|
-
}
|
|
880
|
-
const afterColon = colonIndex + 1;
|
|
881
|
-
if (afterColon >= query.length || isWhitespace(query, afterColon)) {
|
|
882
|
-
return { token: null, nextIndex: afterColon };
|
|
883
|
-
}
|
|
884
|
-
if (isQuote(query, afterColon)) {
|
|
885
|
-
const quoteResult = parseQuotedValue(query, afterColon);
|
|
886
|
-
if (quoteResult.value !== null) {
|
|
887
|
-
return {
|
|
888
|
-
token: {
|
|
889
|
-
raw: query.substring(startIndex, quoteResult.nextIndex),
|
|
890
|
-
value: quoteResult.value,
|
|
891
|
-
field: fieldName,
|
|
892
|
-
isPhrase: true
|
|
893
|
-
},
|
|
894
|
-
nextIndex: quoteResult.nextIndex
|
|
895
|
-
};
|
|
896
|
-
}
|
|
897
|
-
return null;
|
|
898
|
-
}
|
|
899
|
-
const valueResult = parseUnquotedValue(query, afterColon);
|
|
900
|
-
return {
|
|
901
|
-
token: {
|
|
902
|
-
raw: query.substring(startIndex, valueResult.nextIndex),
|
|
903
|
-
value: valueResult.value,
|
|
904
|
-
field: fieldName,
|
|
905
|
-
isPhrase: false
|
|
906
|
-
},
|
|
907
|
-
nextIndex: valueResult.nextIndex
|
|
908
|
-
};
|
|
909
|
-
}
|
|
910
|
-
function parseQuotedToken(query, startIndex) {
|
|
911
|
-
const quoteResult = parseQuotedValue(query, startIndex);
|
|
912
|
-
if (quoteResult.value !== null) {
|
|
913
|
-
return {
|
|
914
|
-
token: {
|
|
915
|
-
raw: query.substring(startIndex, quoteResult.nextIndex),
|
|
916
|
-
value: quoteResult.value,
|
|
917
|
-
isPhrase: true
|
|
918
|
-
},
|
|
919
|
-
nextIndex: quoteResult.nextIndex
|
|
920
|
-
};
|
|
921
|
-
}
|
|
922
|
-
if (quoteResult.nextIndex > startIndex) {
|
|
923
|
-
return { token: null, nextIndex: quoteResult.nextIndex };
|
|
924
|
-
}
|
|
925
|
-
const valueResult = parseUnquotedValue(query, startIndex, true);
|
|
926
|
-
return {
|
|
927
|
-
token: {
|
|
928
|
-
raw: valueResult.value,
|
|
929
|
-
value: valueResult.value,
|
|
930
|
-
isPhrase: false
|
|
931
|
-
},
|
|
932
|
-
nextIndex: valueResult.nextIndex
|
|
933
|
-
};
|
|
934
|
-
}
|
|
935
|
-
function parseRegularToken(query, startIndex) {
|
|
936
|
-
const valueResult = parseUnquotedValue(query, startIndex);
|
|
937
|
-
return {
|
|
938
|
-
token: {
|
|
939
|
-
raw: valueResult.value,
|
|
940
|
-
value: valueResult.value,
|
|
941
|
-
isPhrase: false
|
|
942
|
-
},
|
|
943
|
-
nextIndex: valueResult.nextIndex
|
|
944
|
-
};
|
|
945
|
-
}
|
|
946
|
-
function parseNextToken(query, startIndex) {
|
|
947
|
-
const fieldResult = tryParseFieldValue(query, startIndex);
|
|
948
|
-
if (fieldResult !== null) {
|
|
949
|
-
return fieldResult;
|
|
950
|
-
}
|
|
951
|
-
if (isQuote(query, startIndex)) {
|
|
952
|
-
return parseQuotedToken(query, startIndex);
|
|
953
|
-
}
|
|
954
|
-
return parseRegularToken(query, startIndex);
|
|
955
|
-
}
|
|
956
|
-
function parseQuotedValue(query, startIndex) {
|
|
957
|
-
if (!isQuote(query, startIndex)) {
|
|
958
|
-
return { value: null, nextIndex: startIndex };
|
|
959
|
-
}
|
|
960
|
-
let i = startIndex + 1;
|
|
961
|
-
const valueStart = i;
|
|
962
|
-
while (i < query.length && !isQuote(query, i)) {
|
|
963
|
-
i++;
|
|
964
|
-
}
|
|
965
|
-
if (i >= query.length) {
|
|
966
|
-
return { value: null, nextIndex: startIndex };
|
|
967
|
-
}
|
|
968
|
-
const value = query.substring(valueStart, i);
|
|
969
|
-
i++;
|
|
970
|
-
if (value.trim() === "") {
|
|
971
|
-
return { value: null, nextIndex: i };
|
|
972
|
-
}
|
|
973
|
-
return { value, nextIndex: i };
|
|
974
|
-
}
|
|
975
|
-
function parseUnquotedValue(query, startIndex, includeQuotes = false) {
|
|
976
|
-
let i = startIndex;
|
|
977
|
-
while (i < query.length && !isWhitespace(query, i)) {
|
|
978
|
-
if (!includeQuotes && isQuote(query, i)) {
|
|
979
|
-
break;
|
|
980
|
-
}
|
|
981
|
-
i++;
|
|
982
|
-
}
|
|
983
|
-
return {
|
|
984
|
-
value: query.substring(startIndex, i),
|
|
985
|
-
nextIndex: i
|
|
986
|
-
};
|
|
987
|
-
}
|
|
988
|
-
function normalize(text) {
|
|
989
|
-
let normalized = text.normalize("NFKC");
|
|
990
|
-
normalized = normalized.toLowerCase();
|
|
991
|
-
normalized = normalized.normalize("NFD").replace(new RegExp("\\p{M}", "gu"), "");
|
|
992
|
-
normalized = normalized.replace(/[^\p{L}\p{N}/\s]/gu, " ");
|
|
993
|
-
normalized = normalized.replace(/\s+/g, " ").trim();
|
|
994
|
-
return normalized;
|
|
995
|
-
}
|
|
996
|
-
const ID_FIELDS = /* @__PURE__ */ new Set(["DOI", "PMID", "PMCID", "URL"]);
|
|
997
|
-
function extractYear$2(reference) {
|
|
998
|
-
if (reference.issued?.["date-parts"]?.[0]?.[0]) {
|
|
999
|
-
return String(reference.issued["date-parts"][0][0]);
|
|
1000
|
-
}
|
|
1001
|
-
return "0000";
|
|
1002
|
-
}
|
|
1003
|
-
function extractAuthors(reference) {
|
|
1004
|
-
if (!reference.author || reference.author.length === 0) {
|
|
1005
|
-
return "";
|
|
1006
|
-
}
|
|
1007
|
-
return reference.author.map((author) => {
|
|
1008
|
-
const family = author.family || "";
|
|
1009
|
-
const givenInitial = author.given ? author.given[0] : "";
|
|
1010
|
-
return givenInitial ? `${family} ${givenInitial}` : family;
|
|
1011
|
-
}).join(" ");
|
|
1012
|
-
}
|
|
1013
|
-
function getFieldValue(reference, field) {
|
|
1014
|
-
if (field === "year") {
|
|
1015
|
-
return extractYear$2(reference);
|
|
1016
|
-
}
|
|
1017
|
-
if (field === "author") {
|
|
1018
|
-
return extractAuthors(reference);
|
|
1019
|
-
}
|
|
1020
|
-
const value = reference[field];
|
|
1021
|
-
if (typeof value === "string") {
|
|
1022
|
-
return value;
|
|
1023
|
-
}
|
|
1024
|
-
if (field.startsWith("custom.")) {
|
|
1025
|
-
const customField = field.substring(7);
|
|
1026
|
-
const customValue = reference.custom?.[customField];
|
|
1027
|
-
if (typeof customValue === "string") {
|
|
1028
|
-
return customValue;
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
return null;
|
|
1032
|
-
}
|
|
1033
|
-
function matchUrl(queryValue, reference) {
|
|
1034
|
-
if (reference.URL === queryValue) {
|
|
1035
|
-
return {
|
|
1036
|
-
field: "URL",
|
|
1037
|
-
strength: "exact",
|
|
1038
|
-
value: reference.URL
|
|
1039
|
-
};
|
|
1040
|
-
}
|
|
1041
|
-
const additionalUrls = reference.custom?.additional_urls;
|
|
1042
|
-
if (Array.isArray(additionalUrls)) {
|
|
1043
|
-
for (const url of additionalUrls) {
|
|
1044
|
-
if (typeof url === "string" && url === queryValue) {
|
|
1045
|
-
return {
|
|
1046
|
-
field: "custom.additional_urls",
|
|
1047
|
-
strength: "exact",
|
|
1048
|
-
value: url
|
|
1049
|
-
};
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
return null;
|
|
1054
|
-
}
|
|
1055
|
-
function matchKeyword(queryValue, reference) {
|
|
1056
|
-
if (!reference.keyword || !Array.isArray(reference.keyword)) {
|
|
1057
|
-
return null;
|
|
1058
|
-
}
|
|
1059
|
-
const normalizedQuery = normalize(queryValue);
|
|
1060
|
-
for (const keyword of reference.keyword) {
|
|
1061
|
-
if (typeof keyword === "string") {
|
|
1062
|
-
const normalizedKeyword = normalize(keyword);
|
|
1063
|
-
if (normalizedKeyword.includes(normalizedQuery)) {
|
|
1064
|
-
return {
|
|
1065
|
-
field: "keyword",
|
|
1066
|
-
strength: "partial",
|
|
1067
|
-
value: keyword
|
|
1068
|
-
};
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
return null;
|
|
1073
|
-
}
|
|
1074
|
-
const FIELD_MAP = {
|
|
1075
|
-
author: "author",
|
|
1076
|
-
title: "title",
|
|
1077
|
-
doi: "DOI",
|
|
1078
|
-
pmid: "PMID",
|
|
1079
|
-
pmcid: "PMCID"
|
|
1080
|
-
};
|
|
1081
|
-
function matchYearField(tokenValue, reference) {
|
|
1082
|
-
const year = extractYear$2(reference);
|
|
1083
|
-
if (year === tokenValue) {
|
|
1084
|
-
return {
|
|
1085
|
-
field: "year",
|
|
1086
|
-
strength: "exact",
|
|
1087
|
-
value: year
|
|
1088
|
-
};
|
|
1089
|
-
}
|
|
1090
|
-
return null;
|
|
1091
|
-
}
|
|
1092
|
-
function matchFieldValue(field, tokenValue, reference) {
|
|
1093
|
-
const fieldValue = getFieldValue(reference, field);
|
|
1094
|
-
if (fieldValue === null) {
|
|
1095
|
-
return null;
|
|
1096
|
-
}
|
|
1097
|
-
if (ID_FIELDS.has(field)) {
|
|
1098
|
-
if (fieldValue === tokenValue) {
|
|
1099
|
-
return {
|
|
1100
|
-
field,
|
|
1101
|
-
strength: "exact",
|
|
1102
|
-
value: fieldValue
|
|
1103
|
-
};
|
|
1104
|
-
}
|
|
1105
|
-
return null;
|
|
1106
|
-
}
|
|
1107
|
-
const normalizedFieldValue = normalize(fieldValue);
|
|
1108
|
-
const normalizedQuery = normalize(tokenValue);
|
|
1109
|
-
if (normalizedFieldValue.includes(normalizedQuery)) {
|
|
1110
|
-
return {
|
|
1111
|
-
field,
|
|
1112
|
-
strength: "partial",
|
|
1113
|
-
value: fieldValue
|
|
1114
|
-
};
|
|
1115
|
-
}
|
|
1116
|
-
return null;
|
|
1117
|
-
}
|
|
1118
|
-
function matchSpecificField(token, reference) {
|
|
1119
|
-
const matches = [];
|
|
1120
|
-
const fieldToSearch = token.field;
|
|
1121
|
-
if (fieldToSearch === "url") {
|
|
1122
|
-
const urlMatch = matchUrl(token.value, reference);
|
|
1123
|
-
if (urlMatch) matches.push(urlMatch);
|
|
1124
|
-
return matches;
|
|
1125
|
-
}
|
|
1126
|
-
if (fieldToSearch === "year") {
|
|
1127
|
-
const yearMatch = matchYearField(token.value, reference);
|
|
1128
|
-
if (yearMatch) matches.push(yearMatch);
|
|
1129
|
-
return matches;
|
|
1130
|
-
}
|
|
1131
|
-
if (fieldToSearch === "keyword") {
|
|
1132
|
-
const keywordMatch = matchKeyword(token.value, reference);
|
|
1133
|
-
if (keywordMatch) matches.push(keywordMatch);
|
|
1134
|
-
return matches;
|
|
1135
|
-
}
|
|
1136
|
-
const actualField = FIELD_MAP[fieldToSearch] || fieldToSearch;
|
|
1137
|
-
const match = matchFieldValue(actualField, token.value, reference);
|
|
1138
|
-
if (match) matches.push(match);
|
|
1139
|
-
return matches;
|
|
1140
|
-
}
|
|
1141
|
-
const STANDARD_SEARCH_FIELDS = [
|
|
1142
|
-
"title",
|
|
1143
|
-
"author",
|
|
1144
|
-
"container-title",
|
|
1145
|
-
"publisher",
|
|
1146
|
-
"DOI",
|
|
1147
|
-
"PMID",
|
|
1148
|
-
"PMCID",
|
|
1149
|
-
"abstract"
|
|
1150
|
-
];
|
|
1151
|
-
function matchSingleField(field, tokenValue, reference) {
|
|
1152
|
-
if (field === "year") {
|
|
1153
|
-
return matchYearField(tokenValue, reference);
|
|
1154
|
-
}
|
|
1155
|
-
if (field === "URL") {
|
|
1156
|
-
return matchUrl(tokenValue, reference);
|
|
1157
|
-
}
|
|
1158
|
-
if (field === "keyword") {
|
|
1159
|
-
return matchKeyword(tokenValue, reference);
|
|
1160
|
-
}
|
|
1161
|
-
return matchFieldValue(field, tokenValue, reference);
|
|
1162
|
-
}
|
|
1163
|
-
function matchAllFields(token, reference) {
|
|
1164
|
-
const matches = [];
|
|
1165
|
-
const specialFields = ["year", "URL", "keyword"];
|
|
1166
|
-
for (const field of specialFields) {
|
|
1167
|
-
const match = matchSingleField(field, token.value, reference);
|
|
1168
|
-
if (match) matches.push(match);
|
|
1169
|
-
}
|
|
1170
|
-
for (const field of STANDARD_SEARCH_FIELDS) {
|
|
1171
|
-
const match = matchFieldValue(field, token.value, reference);
|
|
1172
|
-
if (match) matches.push(match);
|
|
1173
|
-
}
|
|
1174
|
-
return matches;
|
|
1175
|
-
}
|
|
1176
|
-
function matchToken(token, reference) {
|
|
1177
|
-
if (token.field) {
|
|
1178
|
-
return matchSpecificField(token, reference);
|
|
1179
|
-
}
|
|
1180
|
-
return matchAllFields(token, reference);
|
|
1181
|
-
}
|
|
1182
|
-
function matchReference(reference, tokens) {
|
|
1183
|
-
if (tokens.length === 0) {
|
|
1184
|
-
return null;
|
|
1185
|
-
}
|
|
1186
|
-
const tokenMatches = [];
|
|
1187
|
-
let overallStrength = "none";
|
|
1188
|
-
for (const token of tokens) {
|
|
1189
|
-
const matches = matchToken(token, reference);
|
|
1190
|
-
if (matches.length === 0) {
|
|
1191
|
-
return null;
|
|
1192
|
-
}
|
|
1193
|
-
const tokenStrength = matches.some((m) => m.strength === "exact") ? "exact" : "partial";
|
|
1194
|
-
if (tokenStrength === "exact") {
|
|
1195
|
-
overallStrength = "exact";
|
|
1196
|
-
} else if (tokenStrength === "partial" && overallStrength === "none") {
|
|
1197
|
-
overallStrength = "partial";
|
|
1198
|
-
}
|
|
1199
|
-
tokenMatches.push({
|
|
1200
|
-
token,
|
|
1201
|
-
matches
|
|
1202
|
-
});
|
|
1203
|
-
}
|
|
1204
|
-
const score = overallStrength === "exact" ? 100 + tokenMatches.length : 50 + tokenMatches.length;
|
|
1205
|
-
return {
|
|
1206
|
-
reference,
|
|
1207
|
-
tokenMatches,
|
|
1208
|
-
overallStrength,
|
|
1209
|
-
score
|
|
1210
|
-
};
|
|
1211
|
-
}
|
|
1212
|
-
function search(references, tokens) {
|
|
1213
|
-
const results = [];
|
|
1214
|
-
for (const reference of references) {
|
|
1215
|
-
const match = matchReference(reference, tokens);
|
|
1216
|
-
if (match) {
|
|
1217
|
-
results.push(match);
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
return results;
|
|
1221
|
-
}
|
|
1222
|
-
function extractYear$1(reference) {
|
|
1223
|
-
if (reference.issued?.["date-parts"]?.[0]?.[0]) {
|
|
1224
|
-
return String(reference.issued["date-parts"][0][0]);
|
|
1225
|
-
}
|
|
1226
|
-
return "0000";
|
|
1227
|
-
}
|
|
1228
|
-
function extractFirstAuthorFamily(reference) {
|
|
1229
|
-
if (!reference.author || reference.author.length === 0) {
|
|
1230
|
-
return "";
|
|
1231
|
-
}
|
|
1232
|
-
return reference.author[0]?.family || "";
|
|
1233
|
-
}
|
|
1234
|
-
function extractTitle(reference) {
|
|
1235
|
-
return reference.title || "";
|
|
1236
|
-
}
|
|
1237
|
-
function compareStrength(a, b) {
|
|
1238
|
-
const strengthOrder = { exact: 2, partial: 1, none: 0 };
|
|
1239
|
-
return strengthOrder[b] - strengthOrder[a];
|
|
1240
|
-
}
|
|
1241
|
-
function compareYear(a, b) {
|
|
1242
|
-
const yearA = extractYear$1(a);
|
|
1243
|
-
const yearB = extractYear$1(b);
|
|
1244
|
-
return Number(yearB) - Number(yearA);
|
|
1245
|
-
}
|
|
1246
|
-
function compareAuthor(a, b) {
|
|
1247
|
-
const authorA = extractFirstAuthorFamily(a).toLowerCase();
|
|
1248
|
-
const authorB = extractFirstAuthorFamily(b).toLowerCase();
|
|
1249
|
-
if (authorA === "" && authorB !== "") return 1;
|
|
1250
|
-
if (authorA !== "" && authorB === "") return -1;
|
|
1251
|
-
return authorA.localeCompare(authorB);
|
|
1252
|
-
}
|
|
1253
|
-
function compareTitle(a, b) {
|
|
1254
|
-
const titleA = extractTitle(a).toLowerCase();
|
|
1255
|
-
const titleB = extractTitle(b).toLowerCase();
|
|
1256
|
-
if (titleA === "" && titleB !== "") return 1;
|
|
1257
|
-
if (titleA !== "" && titleB === "") return -1;
|
|
1258
|
-
return titleA.localeCompare(titleB);
|
|
1259
|
-
}
|
|
1260
|
-
function sortResults(results) {
|
|
1261
|
-
const indexed = results.map((result, index) => ({ result, index }));
|
|
1262
|
-
const sorted = indexed.sort((a, b) => {
|
|
1263
|
-
const strengthDiff = compareStrength(a.result.overallStrength, b.result.overallStrength);
|
|
1264
|
-
if (strengthDiff !== 0) return strengthDiff;
|
|
1265
|
-
const yearDiff = compareYear(a.result.reference, b.result.reference);
|
|
1266
|
-
if (yearDiff !== 0) return yearDiff;
|
|
1267
|
-
const authorDiff = compareAuthor(a.result.reference, b.result.reference);
|
|
1268
|
-
if (authorDiff !== 0) return authorDiff;
|
|
1269
|
-
const titleDiff = compareTitle(a.result.reference, b.result.reference);
|
|
1270
|
-
if (titleDiff !== 0) return titleDiff;
|
|
1271
|
-
return a.index - b.index;
|
|
1272
|
-
});
|
|
1273
|
-
return sorted.map((item) => item.result);
|
|
1274
|
-
}
|
|
1275
|
-
function normalizeDoi(doi) {
|
|
1276
|
-
const normalized = doi.replace(/^https?:\/\/doi\.org\//i, "").replace(/^https?:\/\/dx\.doi\.org\//i, "").replace(/^doi:/i, "");
|
|
1277
|
-
return normalized;
|
|
1278
|
-
}
|
|
1279
|
-
function extractYear(item) {
|
|
1280
|
-
const dateParts = item.issued?.["date-parts"]?.[0];
|
|
1281
|
-
if (!dateParts || dateParts.length === 0) {
|
|
1282
|
-
return null;
|
|
1283
|
-
}
|
|
1284
|
-
return String(dateParts[0]);
|
|
1285
|
-
}
|
|
1286
|
-
function normalizeAuthors(item) {
|
|
1287
|
-
if (!item.author || item.author.length === 0) {
|
|
1288
|
-
return null;
|
|
1289
|
-
}
|
|
1290
|
-
const authorStrings = item.author.map((author) => {
|
|
1291
|
-
const family = author.family || "";
|
|
1292
|
-
const givenInitial = author.given ? author.given.charAt(0) : "";
|
|
1293
|
-
return `${family} ${givenInitial}`.trim();
|
|
1294
|
-
});
|
|
1295
|
-
return normalize(authorStrings.join(" "));
|
|
1296
|
-
}
|
|
1297
|
-
function checkDoiMatch(item, existing) {
|
|
1298
|
-
if (!item.DOI || !existing.DOI) {
|
|
1299
|
-
return null;
|
|
1300
|
-
}
|
|
1301
|
-
const normalizedItemDoi = normalizeDoi(item.DOI);
|
|
1302
|
-
const normalizedExistingDoi = normalizeDoi(existing.DOI);
|
|
1303
|
-
if (normalizedItemDoi === normalizedExistingDoi) {
|
|
1304
|
-
return {
|
|
1305
|
-
type: "doi",
|
|
1306
|
-
existing,
|
|
1307
|
-
details: {
|
|
1308
|
-
doi: normalizedExistingDoi
|
|
1309
|
-
}
|
|
1310
|
-
};
|
|
1311
|
-
}
|
|
1312
|
-
return null;
|
|
1313
|
-
}
|
|
1314
|
-
function checkPmidMatch(item, existing) {
|
|
1315
|
-
if (!item.PMID || !existing.PMID) {
|
|
1316
|
-
return null;
|
|
1317
|
-
}
|
|
1318
|
-
if (item.PMID === existing.PMID) {
|
|
1319
|
-
return {
|
|
1320
|
-
type: "pmid",
|
|
1321
|
-
existing,
|
|
1322
|
-
details: {
|
|
1323
|
-
pmid: existing.PMID
|
|
1324
|
-
}
|
|
1325
|
-
};
|
|
1326
|
-
}
|
|
1327
|
-
return null;
|
|
1328
|
-
}
|
|
1329
|
-
function checkTitleAuthorYearMatch(item, existing) {
|
|
1330
|
-
const itemTitle = item.title ? normalize(item.title) : null;
|
|
1331
|
-
const existingTitle = existing.title ? normalize(existing.title) : null;
|
|
1332
|
-
const itemAuthors = normalizeAuthors(item);
|
|
1333
|
-
const existingAuthors = normalizeAuthors(existing);
|
|
1334
|
-
const itemYear = extractYear(item);
|
|
1335
|
-
const existingYear = extractYear(existing);
|
|
1336
|
-
if (!itemTitle || !existingTitle || !itemAuthors || !existingAuthors || !itemYear || !existingYear) {
|
|
1337
|
-
return null;
|
|
1338
|
-
}
|
|
1339
|
-
if (itemTitle === existingTitle && itemAuthors === existingAuthors && itemYear === existingYear) {
|
|
1340
|
-
return {
|
|
1341
|
-
type: "title-author-year",
|
|
1342
|
-
existing,
|
|
1343
|
-
details: {
|
|
1344
|
-
normalizedTitle: existingTitle,
|
|
1345
|
-
normalizedAuthors: existingAuthors,
|
|
1346
|
-
year: existingYear
|
|
1347
|
-
}
|
|
1348
|
-
};
|
|
1349
|
-
}
|
|
1350
|
-
return null;
|
|
1351
|
-
}
|
|
1352
|
-
function checkSingleDuplicate(item, existing) {
|
|
1353
|
-
const doiMatch = checkDoiMatch(item, existing);
|
|
1354
|
-
if (doiMatch) {
|
|
1355
|
-
return doiMatch;
|
|
1356
|
-
}
|
|
1357
|
-
const pmidMatch = checkPmidMatch(item, existing);
|
|
1358
|
-
if (pmidMatch) {
|
|
1359
|
-
return pmidMatch;
|
|
1360
|
-
}
|
|
1361
|
-
return checkTitleAuthorYearMatch(item, existing);
|
|
1362
|
-
}
|
|
1363
|
-
function detectDuplicate(item, existingReferences) {
|
|
1364
|
-
const matches = [];
|
|
1365
|
-
const itemUuid = item.custom?.uuid;
|
|
1366
|
-
for (const existing of existingReferences) {
|
|
1367
|
-
if (itemUuid && existing.custom?.uuid === itemUuid) {
|
|
1368
|
-
continue;
|
|
1369
|
-
}
|
|
1370
|
-
const match = checkSingleDuplicate(item, existing);
|
|
1371
|
-
if (match) {
|
|
1372
|
-
matches.push(match);
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
return {
|
|
1376
|
-
isDuplicate: matches.length > 0,
|
|
1377
|
-
matches
|
|
1378
|
-
};
|
|
1379
|
-
}
|
|
1380
1026
|
export {
|
|
1381
|
-
generateUuid as A,
|
|
1382
|
-
isValidUuid as B,
|
|
1383
|
-
CslLibrarySchema as C,
|
|
1384
|
-
ensureCustomMetadata as D,
|
|
1385
|
-
extractUuidFromCustom as E,
|
|
1386
1027
|
Library as L,
|
|
1387
1028
|
Reference as R,
|
|
1388
1029
|
computeHash as a,
|
|
@@ -1395,21 +1036,19 @@ export {
|
|
|
1395
1036
|
getDefaultLibraryPath as h,
|
|
1396
1037
|
getDefaultUserConfigPath as i,
|
|
1397
1038
|
logLevelSchema as j,
|
|
1398
|
-
|
|
1039
|
+
parseCslJson as k,
|
|
1399
1040
|
loadConfig as l,
|
|
1400
|
-
|
|
1041
|
+
writeCslJson as m,
|
|
1401
1042
|
normalizePartialConfig as n,
|
|
1402
|
-
|
|
1043
|
+
generateId as o,
|
|
1403
1044
|
partialConfigSchema as p,
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1045
|
+
generateIdWithCollisionCheck as q,
|
|
1046
|
+
normalizeText as r,
|
|
1047
|
+
serializeCslJson as s,
|
|
1048
|
+
generateUuid as t,
|
|
1049
|
+
isValidUuid as u,
|
|
1050
|
+
ensureCustomMetadata as v,
|
|
1410
1051
|
watchConfigSchema as w,
|
|
1411
|
-
|
|
1412
|
-
generateIdWithCollisionCheck as y,
|
|
1413
|
-
normalizeText as z
|
|
1052
|
+
extractUuidFromCustom as x
|
|
1414
1053
|
};
|
|
1415
|
-
//# sourceMappingURL=
|
|
1054
|
+
//# sourceMappingURL=loader-mQ25o6cV.js.map
|