@ncukondo/reference-manager 0.7.1 → 0.9.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 +71 -4
- package/dist/chunks/{action-menu-CTtINmWd.js → action-menu-BN2HHFPR.js} +2 -2
- package/dist/chunks/{action-menu-CTtINmWd.js.map → action-menu-BN2HHFPR.js.map} +1 -1
- package/dist/chunks/{file-watcher-D7oyc-9z.js → file-watcher-DdhXSm1l.js} +3 -3
- package/dist/chunks/file-watcher-DdhXSm1l.js.map +1 -0
- package/dist/chunks/{index-_7NEUoS7.js → index-DmMZCOno.js} +316 -41
- package/dist/chunks/index-DmMZCOno.js.map +1 -0
- package/dist/chunks/{loader-BItrdVWG.js → loader-C-bdImuO.js} +44 -7
- package/dist/chunks/loader-C-bdImuO.js.map +1 -0
- package/dist/cli/commands/add.d.ts +4 -0
- package/dist/cli/commands/add.d.ts.map +1 -1
- package/dist/cli/commands/fulltext.d.ts +20 -3
- package/dist/cli/commands/fulltext.d.ts.map +1 -1
- package/dist/cli/commands/remove.d.ts +7 -16
- package/dist/cli/commands/remove.d.ts.map +1 -1
- package/dist/cli/commands/update.d.ts +40 -0
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli.js +582 -152
- package/dist/cli.js.map +1 -1
- package/dist/core/library.d.ts.map +1 -1
- package/dist/features/duplicate/types.d.ts +1 -1
- package/dist/features/duplicate/types.d.ts.map +1 -1
- package/dist/features/import/detector.d.ts +1 -1
- package/dist/features/import/detector.d.ts.map +1 -1
- package/dist/features/import/fetcher.d.ts +6 -0
- package/dist/features/import/fetcher.d.ts.map +1 -1
- package/dist/features/import/importer.d.ts +3 -1
- package/dist/features/import/importer.d.ts.map +1 -1
- package/dist/features/import/parser.d.ts +16 -0
- package/dist/features/import/parser.d.ts.map +1 -1
- package/dist/features/operations/add.d.ts +4 -0
- package/dist/features/operations/add.d.ts.map +1 -1
- package/dist/features/operations/fulltext/index.d.ts +1 -0
- package/dist/features/operations/fulltext/index.d.ts.map +1 -1
- package/dist/features/operations/fulltext/open.d.ts +36 -0
- package/dist/features/operations/fulltext/open.d.ts.map +1 -0
- package/dist/features/operations/index.d.ts +2 -0
- package/dist/features/operations/index.d.ts.map +1 -1
- package/dist/features/operations/json-output.d.ts +93 -0
- package/dist/features/operations/json-output.d.ts.map +1 -0
- package/dist/features/operations/remove.d.ts +11 -0
- package/dist/features/operations/remove.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/server.js +2 -2
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/opener.d.ts +13 -0
- package/dist/utils/opener.d.ts.map +1 -0
- package/package.json +1 -1
- package/dist/chunks/file-watcher-D7oyc-9z.js.map +0 -1
- package/dist/chunks/index-_7NEUoS7.js.map +0 -1
- package/dist/chunks/loader-BItrdVWG.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
|
-
import { e as CslItemSchema, d as detectDuplicate, h as generateId, q as sortOrderSchema, u as sortFieldSchema, p as pickDefined, t as tokenize, s as search$1, b as sortResults, v as searchSortFieldSchema, L as Library, F as FileWatcher } from "./file-watcher-
|
|
2
|
+
import { e as CslItemSchema, d as detectDuplicate, h as generateId, q as sortOrderSchema, u as sortFieldSchema, p as pickDefined, t as tokenize, s as search$1, b as sortResults, v as searchSortFieldSchema, L as Library, F as FileWatcher } from "./file-watcher-DdhXSm1l.js";
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
4
|
import { Cite } from "@citation-js/core";
|
|
5
5
|
import "@citation-js/plugin-doi";
|
|
@@ -7,8 +7,9 @@ import "@citation-js/plugin-isbn";
|
|
|
7
7
|
import "@citation-js/plugin-bibtex";
|
|
8
8
|
import "@citation-js/plugin-ris";
|
|
9
9
|
import { z } from "zod";
|
|
10
|
-
import "node:path";
|
|
10
|
+
import { join } from "node:path";
|
|
11
11
|
import "@citation-js/plugin-csl";
|
|
12
|
+
import { unlink } from "node:fs/promises";
|
|
12
13
|
async function updateReference(library, options) {
|
|
13
14
|
const { identifier, idType = "id", updates, onIdCollision = "fail" } = options;
|
|
14
15
|
const updateResult = await library.update(identifier, updates, { idType, onIdCollision });
|
|
@@ -120,6 +121,61 @@ const BUILTIN_STYLES = ["apa", "vancouver", "harvard"];
|
|
|
120
121
|
function isBuiltinStyle(styleName) {
|
|
121
122
|
return BUILTIN_STYLES.includes(styleName);
|
|
122
123
|
}
|
|
124
|
+
function getFulltextAttachmentTypes(item) {
|
|
125
|
+
const types = [];
|
|
126
|
+
const fulltext = item.custom?.fulltext;
|
|
127
|
+
if (fulltext?.pdf) {
|
|
128
|
+
types.push("pdf");
|
|
129
|
+
}
|
|
130
|
+
if (fulltext?.markdown) {
|
|
131
|
+
types.push("markdown");
|
|
132
|
+
}
|
|
133
|
+
return types;
|
|
134
|
+
}
|
|
135
|
+
async function deleteFulltextFiles(item, fulltextDirectory) {
|
|
136
|
+
const fulltext = item.custom?.fulltext;
|
|
137
|
+
if (!fulltext) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const filesToDelete = [];
|
|
141
|
+
if (fulltext.pdf) {
|
|
142
|
+
filesToDelete.push(join(fulltextDirectory, fulltext.pdf));
|
|
143
|
+
}
|
|
144
|
+
if (fulltext.markdown) {
|
|
145
|
+
filesToDelete.push(join(fulltextDirectory, fulltext.markdown));
|
|
146
|
+
}
|
|
147
|
+
for (const filePath of filesToDelete) {
|
|
148
|
+
try {
|
|
149
|
+
await unlink(filePath);
|
|
150
|
+
} catch {
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async function removeReference(library, options) {
|
|
155
|
+
const { identifier, idType = "id", fulltextDirectory, deleteFulltext = false } = options;
|
|
156
|
+
const result = await library.remove(identifier, { idType });
|
|
157
|
+
if (!result.removed || !result.removedItem) {
|
|
158
|
+
return { removed: false };
|
|
159
|
+
}
|
|
160
|
+
let deletedFulltextTypes;
|
|
161
|
+
if (deleteFulltext && fulltextDirectory) {
|
|
162
|
+
deletedFulltextTypes = getFulltextAttachmentTypes(result.removedItem);
|
|
163
|
+
if (deletedFulltextTypes.length > 0) {
|
|
164
|
+
await deleteFulltextFiles(result.removedItem, fulltextDirectory);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
await library.save();
|
|
168
|
+
return {
|
|
169
|
+
removed: true,
|
|
170
|
+
removedItem: result.removedItem,
|
|
171
|
+
...deletedFulltextTypes && deletedFulltextTypes.length > 0 && { deletedFulltextTypes }
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const remove = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
175
|
+
__proto__: null,
|
|
176
|
+
getFulltextAttachmentTypes,
|
|
177
|
+
removeReference
|
|
178
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
123
179
|
const DEFAULT_TTL_MS = 60 * 60 * 1e3;
|
|
124
180
|
const pmidCache = /* @__PURE__ */ new Map();
|
|
125
181
|
const doiCache = /* @__PURE__ */ new Map();
|
|
@@ -208,7 +264,8 @@ function normalizeIsbn(isbn) {
|
|
|
208
264
|
const EXTENSION_MAP = {
|
|
209
265
|
".json": "json",
|
|
210
266
|
".bib": "bibtex",
|
|
211
|
-
".ris": "ris"
|
|
267
|
+
".ris": "ris",
|
|
268
|
+
".nbib": "nbib"
|
|
212
269
|
};
|
|
213
270
|
const DOI_URL_PREFIXES = [
|
|
214
271
|
"https://doi.org/",
|
|
@@ -237,6 +294,9 @@ function detectByContent(content) {
|
|
|
237
294
|
if (trimmed.startsWith("TY -")) {
|
|
238
295
|
return "ris";
|
|
239
296
|
}
|
|
297
|
+
if (trimmed.startsWith("PMID-")) {
|
|
298
|
+
return "nbib";
|
|
299
|
+
}
|
|
240
300
|
return detectIdentifier(trimmed);
|
|
241
301
|
}
|
|
242
302
|
function detectIdentifier(input) {
|
|
@@ -440,13 +500,15 @@ function buildPmidResult(pmid, foundItems, validationErrors) {
|
|
|
440
500
|
return {
|
|
441
501
|
pmid,
|
|
442
502
|
success: false,
|
|
443
|
-
error: `Invalid CSL-JSON data: ${validationError}
|
|
503
|
+
error: `Invalid CSL-JSON data: ${validationError}`,
|
|
504
|
+
reason: "validation_error"
|
|
444
505
|
};
|
|
445
506
|
}
|
|
446
507
|
return {
|
|
447
508
|
pmid,
|
|
448
509
|
success: false,
|
|
449
|
-
error: `PMID ${pmid} not found
|
|
510
|
+
error: `PMID ${pmid} not found`,
|
|
511
|
+
reason: "not_found"
|
|
450
512
|
};
|
|
451
513
|
}
|
|
452
514
|
async function fetchPmids(pmids, config) {
|
|
@@ -467,7 +529,8 @@ async function fetchPmids(pmids, config) {
|
|
|
467
529
|
return pmids.map((pmid) => ({
|
|
468
530
|
pmid,
|
|
469
531
|
success: false,
|
|
470
|
-
error: errorMsg
|
|
532
|
+
error: errorMsg,
|
|
533
|
+
reason: "fetch_error"
|
|
471
534
|
}));
|
|
472
535
|
}
|
|
473
536
|
const data = await response.json();
|
|
@@ -479,7 +542,8 @@ async function fetchPmids(pmids, config) {
|
|
|
479
542
|
return pmids.map((pmid) => ({
|
|
480
543
|
pmid,
|
|
481
544
|
success: false,
|
|
482
|
-
error: errorMsg
|
|
545
|
+
error: errorMsg,
|
|
546
|
+
reason: "fetch_error"
|
|
483
547
|
}));
|
|
484
548
|
}
|
|
485
549
|
}
|
|
@@ -487,7 +551,8 @@ async function fetchDoi(doi) {
|
|
|
487
551
|
if (!DOI_PATTERN.test(doi)) {
|
|
488
552
|
return {
|
|
489
553
|
success: false,
|
|
490
|
-
error: `Invalid DOI format: ${doi}
|
|
554
|
+
error: `Invalid DOI format: ${doi}`,
|
|
555
|
+
reason: "validation_error"
|
|
491
556
|
};
|
|
492
557
|
}
|
|
493
558
|
const rateLimiter = getRateLimiter("crossref", {});
|
|
@@ -498,14 +563,16 @@ async function fetchDoi(doi) {
|
|
|
498
563
|
if (!rawItems || !Array.isArray(rawItems) || rawItems.length === 0) {
|
|
499
564
|
return {
|
|
500
565
|
success: false,
|
|
501
|
-
error: `No data returned for DOI ${doi}
|
|
566
|
+
error: `No data returned for DOI ${doi}`,
|
|
567
|
+
reason: "not_found"
|
|
502
568
|
};
|
|
503
569
|
}
|
|
504
570
|
const parseResult = CslItemSchema.safeParse(rawItems[0]);
|
|
505
571
|
if (!parseResult.success) {
|
|
506
572
|
return {
|
|
507
573
|
success: false,
|
|
508
|
-
error: `Invalid CSL-JSON data for DOI ${doi}: ${parseResult.error.message}
|
|
574
|
+
error: `Invalid CSL-JSON data for DOI ${doi}: ${parseResult.error.message}`,
|
|
575
|
+
reason: "validation_error"
|
|
509
576
|
};
|
|
510
577
|
}
|
|
511
578
|
return { success: true, item: parseResult.data };
|
|
@@ -513,7 +580,8 @@ async function fetchDoi(doi) {
|
|
|
513
580
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
514
581
|
return {
|
|
515
582
|
success: false,
|
|
516
|
-
error: errorMsg
|
|
583
|
+
error: errorMsg,
|
|
584
|
+
reason: "fetch_error"
|
|
517
585
|
};
|
|
518
586
|
}
|
|
519
587
|
}
|
|
@@ -523,7 +591,8 @@ async function fetchIsbn(isbn) {
|
|
|
523
591
|
if (!ISBN10_PATTERN.test(isbn) && !ISBN13_PATTERN.test(isbn)) {
|
|
524
592
|
return {
|
|
525
593
|
success: false,
|
|
526
|
-
error: `Invalid ISBN format: ${isbn}
|
|
594
|
+
error: `Invalid ISBN format: ${isbn}`,
|
|
595
|
+
reason: "validation_error"
|
|
527
596
|
};
|
|
528
597
|
}
|
|
529
598
|
const rateLimiter = getRateLimiter("isbn", {});
|
|
@@ -534,14 +603,16 @@ async function fetchIsbn(isbn) {
|
|
|
534
603
|
if (!rawItems || !Array.isArray(rawItems) || rawItems.length === 0) {
|
|
535
604
|
return {
|
|
536
605
|
success: false,
|
|
537
|
-
error: `No data returned for ISBN ${isbn}
|
|
606
|
+
error: `No data returned for ISBN ${isbn}`,
|
|
607
|
+
reason: "not_found"
|
|
538
608
|
};
|
|
539
609
|
}
|
|
540
610
|
const parseResult = CslItemSchema.safeParse(rawItems[0]);
|
|
541
611
|
if (!parseResult.success) {
|
|
542
612
|
return {
|
|
543
613
|
success: false,
|
|
544
|
-
error: `Invalid CSL-JSON data for ISBN ${isbn}: ${parseResult.error.message}
|
|
614
|
+
error: `Invalid CSL-JSON data for ISBN ${isbn}: ${parseResult.error.message}`,
|
|
615
|
+
reason: "validation_error"
|
|
545
616
|
};
|
|
546
617
|
}
|
|
547
618
|
return { success: true, item: parseResult.data };
|
|
@@ -549,7 +620,8 @@ async function fetchIsbn(isbn) {
|
|
|
549
620
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
550
621
|
return {
|
|
551
622
|
success: false,
|
|
552
|
-
error: errorMsg
|
|
623
|
+
error: errorMsg,
|
|
624
|
+
reason: "fetch_error"
|
|
553
625
|
};
|
|
554
626
|
}
|
|
555
627
|
}
|
|
@@ -559,6 +631,145 @@ function parseBibtex(content) {
|
|
|
559
631
|
function parseRis(content) {
|
|
560
632
|
return parseWithCitationJs(content, "ris");
|
|
561
633
|
}
|
|
634
|
+
const NBIB_TO_RIS_TAG_MAP = {
|
|
635
|
+
PMID: "AN",
|
|
636
|
+
// PubMed ID -> Accession Number
|
|
637
|
+
TI: "TI",
|
|
638
|
+
// Title -> Title
|
|
639
|
+
FAU: "AU",
|
|
640
|
+
// Full Author -> Author
|
|
641
|
+
AU: "AU",
|
|
642
|
+
// Author -> Author (short form, use FAU when available)
|
|
643
|
+
JT: "JO",
|
|
644
|
+
// Journal Title -> Journal Name
|
|
645
|
+
TA: "JA",
|
|
646
|
+
// Title Abbreviation -> Journal Abbreviation
|
|
647
|
+
AB: "AB",
|
|
648
|
+
// Abstract -> Abstract
|
|
649
|
+
VI: "VL",
|
|
650
|
+
// Volume -> Volume
|
|
651
|
+
IP: "IS",
|
|
652
|
+
// Issue/Part -> Issue Number
|
|
653
|
+
PG: "SP",
|
|
654
|
+
// Pagination -> Start Page (includes range)
|
|
655
|
+
DP: "PY",
|
|
656
|
+
// Date of Publication -> Publication Year
|
|
657
|
+
LA: "LA",
|
|
658
|
+
// Language -> Language
|
|
659
|
+
MH: "KW",
|
|
660
|
+
// MeSH Headings -> Keywords
|
|
661
|
+
OT: "KW",
|
|
662
|
+
// Other Terms -> Keywords
|
|
663
|
+
AD: "AD",
|
|
664
|
+
// Affiliation/Address -> Author Address
|
|
665
|
+
IS: "SN",
|
|
666
|
+
// ISSN -> Serial Number
|
|
667
|
+
PT: "TY"
|
|
668
|
+
// Publication Type -> Type of Reference
|
|
669
|
+
};
|
|
670
|
+
const NBIB_PUBLICATION_TYPE_MAP = {
|
|
671
|
+
"Journal Article": "JOUR",
|
|
672
|
+
Review: "JOUR",
|
|
673
|
+
Book: "BOOK",
|
|
674
|
+
"Book Chapter": "CHAP",
|
|
675
|
+
"Conference Paper": "CPAPER",
|
|
676
|
+
Thesis: "THES",
|
|
677
|
+
Report: "RPRT"
|
|
678
|
+
};
|
|
679
|
+
function parseNbibEntry(entry) {
|
|
680
|
+
const result = [];
|
|
681
|
+
const lines = entry.split("\n");
|
|
682
|
+
let currentTag = "";
|
|
683
|
+
let currentValue = "";
|
|
684
|
+
for (const line of lines) {
|
|
685
|
+
const tagMatch = line.match(/^([A-Z]{2,4})\s*-\s*(.*)$/);
|
|
686
|
+
if (tagMatch) {
|
|
687
|
+
if (currentTag) {
|
|
688
|
+
result.push({ tag: currentTag, value: currentValue.trim() });
|
|
689
|
+
}
|
|
690
|
+
currentTag = tagMatch[1] ?? "";
|
|
691
|
+
currentValue = tagMatch[2] ?? "";
|
|
692
|
+
} else if (currentTag && line.match(/^\s+/)) {
|
|
693
|
+
currentValue += ` ${line.trim()}`;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
if (currentTag) {
|
|
697
|
+
result.push({ tag: currentTag, value: currentValue.trim() });
|
|
698
|
+
}
|
|
699
|
+
return result;
|
|
700
|
+
}
|
|
701
|
+
function convertNbibTagToRisLine(tag, value) {
|
|
702
|
+
if (tag === "AID" && value.includes("[doi]")) {
|
|
703
|
+
const doi = value.replace(/\s*\[doi\].*$/, "").trim();
|
|
704
|
+
return { line: `DO - ${doi}` };
|
|
705
|
+
}
|
|
706
|
+
if (tag === "AID" && value.includes("[pii]")) {
|
|
707
|
+
const pii = value.replace(/\s*\[pii\].*$/, "").trim();
|
|
708
|
+
return { line: `C1 - ${pii}` };
|
|
709
|
+
}
|
|
710
|
+
const risTag = NBIB_TO_RIS_TAG_MAP[tag];
|
|
711
|
+
if (!risTag) {
|
|
712
|
+
return null;
|
|
713
|
+
}
|
|
714
|
+
if (risTag === "PY") {
|
|
715
|
+
const yearMatch = value.match(/^(\d{4})/);
|
|
716
|
+
return yearMatch ? { line: `PY - ${yearMatch[1]}` } : null;
|
|
717
|
+
}
|
|
718
|
+
if (risTag === "TY") {
|
|
719
|
+
const risType = NBIB_PUBLICATION_TYPE_MAP[value] || "JOUR";
|
|
720
|
+
return { line: `TY - ${risType}`, isType: true };
|
|
721
|
+
}
|
|
722
|
+
return { line: `${risTag} - ${value}` };
|
|
723
|
+
}
|
|
724
|
+
function convertSingleNbibEntryToRis(entry) {
|
|
725
|
+
const parsed = parseNbibEntry(entry);
|
|
726
|
+
if (parsed.length === 0) {
|
|
727
|
+
return "";
|
|
728
|
+
}
|
|
729
|
+
const risLines = [];
|
|
730
|
+
let hasType = false;
|
|
731
|
+
for (const { tag, value } of parsed) {
|
|
732
|
+
const converted = convertNbibTagToRisLine(tag, value);
|
|
733
|
+
if (!converted) {
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
if (converted.isType) {
|
|
737
|
+
risLines.unshift(converted.line);
|
|
738
|
+
hasType = true;
|
|
739
|
+
} else {
|
|
740
|
+
risLines.push(converted.line);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
if (!hasType) {
|
|
744
|
+
risLines.unshift("TY - JOUR");
|
|
745
|
+
}
|
|
746
|
+
risLines.push("ER -");
|
|
747
|
+
return risLines.join("\n");
|
|
748
|
+
}
|
|
749
|
+
function convertNbibToRis(content) {
|
|
750
|
+
const trimmed = content.trim();
|
|
751
|
+
if (!trimmed) {
|
|
752
|
+
return "";
|
|
753
|
+
}
|
|
754
|
+
const entries = trimmed.split(/\n\s*\n/).filter((e) => e.trim());
|
|
755
|
+
const risEntries = entries.map((entry) => convertSingleNbibEntryToRis(entry)).filter(Boolean);
|
|
756
|
+
return risEntries.join("\n\n");
|
|
757
|
+
}
|
|
758
|
+
function parseNbib(content) {
|
|
759
|
+
const trimmed = content.trim();
|
|
760
|
+
if (!trimmed) {
|
|
761
|
+
return { success: true, items: [] };
|
|
762
|
+
}
|
|
763
|
+
const risContent = convertNbibToRis(trimmed);
|
|
764
|
+
if (!risContent) {
|
|
765
|
+
return {
|
|
766
|
+
success: false,
|
|
767
|
+
items: [],
|
|
768
|
+
error: "Failed to convert NBIB to RIS: No valid entries found"
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
return parseRis(risContent);
|
|
772
|
+
}
|
|
562
773
|
function parseWithCitationJs(content, format) {
|
|
563
774
|
const trimmed = content.trim();
|
|
564
775
|
if (!trimmed) {
|
|
@@ -622,7 +833,8 @@ function buildUnknownResults(unknowns) {
|
|
|
622
833
|
return unknowns.map((unknown) => ({
|
|
623
834
|
success: false,
|
|
624
835
|
error: `Cannot interpret '${unknown}' as identifier (not a valid PMID or DOI)`,
|
|
625
|
-
source: unknown
|
|
836
|
+
source: unknown,
|
|
837
|
+
reason: "validation_error"
|
|
626
838
|
}));
|
|
627
839
|
}
|
|
628
840
|
async function fetchPmidsWithCache(pmids, pubmedConfig) {
|
|
@@ -650,7 +862,8 @@ async function fetchPmidsWithCache(pmids, pubmedConfig) {
|
|
|
650
862
|
results.push({
|
|
651
863
|
success: false,
|
|
652
864
|
error: fetchResult.error,
|
|
653
|
-
source: fetchResult.pmid
|
|
865
|
+
source: fetchResult.pmid,
|
|
866
|
+
reason: fetchResult.reason
|
|
654
867
|
});
|
|
655
868
|
}
|
|
656
869
|
}
|
|
@@ -670,7 +883,12 @@ async function fetchDoisWithCache(dois) {
|
|
|
670
883
|
cacheDoiResult(doi, fetchResult.item);
|
|
671
884
|
results.push({ success: true, item: fetchResult.item, source: doi });
|
|
672
885
|
} else {
|
|
673
|
-
results.push({
|
|
886
|
+
results.push({
|
|
887
|
+
success: false,
|
|
888
|
+
error: fetchResult.error,
|
|
889
|
+
source: doi,
|
|
890
|
+
reason: fetchResult.reason
|
|
891
|
+
});
|
|
674
892
|
}
|
|
675
893
|
}
|
|
676
894
|
return results;
|
|
@@ -688,7 +906,12 @@ async function fetchIsbnsWithCache(isbns) {
|
|
|
688
906
|
cacheIsbnResult(isbn, fetchResult.item);
|
|
689
907
|
results.push({ success: true, item: fetchResult.item, source: isbn });
|
|
690
908
|
} else {
|
|
691
|
-
results.push({
|
|
909
|
+
results.push({
|
|
910
|
+
success: false,
|
|
911
|
+
error: fetchResult.error,
|
|
912
|
+
source: isbn,
|
|
913
|
+
reason: fetchResult.reason
|
|
914
|
+
});
|
|
692
915
|
}
|
|
693
916
|
}
|
|
694
917
|
return results;
|
|
@@ -709,7 +932,8 @@ function parseJsonContent(content) {
|
|
|
709
932
|
results.push({
|
|
710
933
|
success: false,
|
|
711
934
|
error: `Invalid CSL-JSON: ${parseResult.error.message}`,
|
|
712
|
-
source: "json"
|
|
935
|
+
source: "json",
|
|
936
|
+
reason: "validation_error"
|
|
713
937
|
});
|
|
714
938
|
}
|
|
715
939
|
}
|
|
@@ -717,7 +941,14 @@ function parseJsonContent(content) {
|
|
|
717
941
|
} catch (error) {
|
|
718
942
|
const message = error instanceof Error ? error.message : String(error);
|
|
719
943
|
return {
|
|
720
|
-
results: [
|
|
944
|
+
results: [
|
|
945
|
+
{
|
|
946
|
+
success: false,
|
|
947
|
+
error: `Failed to parse JSON: ${message}`,
|
|
948
|
+
source: "json",
|
|
949
|
+
reason: "parse_error"
|
|
950
|
+
}
|
|
951
|
+
]
|
|
721
952
|
};
|
|
722
953
|
}
|
|
723
954
|
}
|
|
@@ -726,7 +957,12 @@ function parseBibtexContent(content) {
|
|
|
726
957
|
if (!parseResult.success) {
|
|
727
958
|
return {
|
|
728
959
|
results: [
|
|
729
|
-
{
|
|
960
|
+
{
|
|
961
|
+
success: false,
|
|
962
|
+
error: parseResult.error ?? "Failed to parse BibTeX",
|
|
963
|
+
source: "bibtex",
|
|
964
|
+
reason: "parse_error"
|
|
965
|
+
}
|
|
730
966
|
]
|
|
731
967
|
};
|
|
732
968
|
}
|
|
@@ -746,7 +982,12 @@ function parseRisContent(content) {
|
|
|
746
982
|
if (!parseResult.success) {
|
|
747
983
|
return {
|
|
748
984
|
results: [
|
|
749
|
-
{
|
|
985
|
+
{
|
|
986
|
+
success: false,
|
|
987
|
+
error: parseResult.error ?? "Failed to parse RIS",
|
|
988
|
+
source: "ris",
|
|
989
|
+
reason: "parse_error"
|
|
990
|
+
}
|
|
750
991
|
]
|
|
751
992
|
};
|
|
752
993
|
}
|
|
@@ -761,6 +1002,31 @@ function parseRisContent(content) {
|
|
|
761
1002
|
}))
|
|
762
1003
|
};
|
|
763
1004
|
}
|
|
1005
|
+
function parseNbibContent(content) {
|
|
1006
|
+
const parseResult = parseNbib(content);
|
|
1007
|
+
if (!parseResult.success) {
|
|
1008
|
+
return {
|
|
1009
|
+
results: [
|
|
1010
|
+
{
|
|
1011
|
+
success: false,
|
|
1012
|
+
error: parseResult.error ?? "Failed to parse NBIB",
|
|
1013
|
+
source: "nbib",
|
|
1014
|
+
reason: "parse_error"
|
|
1015
|
+
}
|
|
1016
|
+
]
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
if (parseResult.items.length === 0) {
|
|
1020
|
+
return { results: [] };
|
|
1021
|
+
}
|
|
1022
|
+
return {
|
|
1023
|
+
results: parseResult.items.map((item) => ({
|
|
1024
|
+
success: true,
|
|
1025
|
+
item,
|
|
1026
|
+
source: "nbib"
|
|
1027
|
+
}))
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
764
1030
|
async function importFromContent(content, format, _options) {
|
|
765
1031
|
let actualFormat;
|
|
766
1032
|
if (format === "auto") {
|
|
@@ -771,7 +1037,8 @@ async function importFromContent(content, format, _options) {
|
|
|
771
1037
|
{
|
|
772
1038
|
success: false,
|
|
773
1039
|
error: "Cannot detect input format. Use --format to specify explicitly.",
|
|
774
|
-
source: "content"
|
|
1040
|
+
source: "content",
|
|
1041
|
+
reason: "validation_error"
|
|
775
1042
|
}
|
|
776
1043
|
]
|
|
777
1044
|
};
|
|
@@ -786,13 +1053,16 @@ async function importFromContent(content, format, _options) {
|
|
|
786
1053
|
return parseBibtexContent(content);
|
|
787
1054
|
case "ris":
|
|
788
1055
|
return parseRisContent(content);
|
|
1056
|
+
case "nbib":
|
|
1057
|
+
return parseNbibContent(content);
|
|
789
1058
|
default:
|
|
790
1059
|
return {
|
|
791
1060
|
results: [
|
|
792
1061
|
{
|
|
793
1062
|
success: false,
|
|
794
1063
|
error: `Unsupported format for content parsing: ${actualFormat}`,
|
|
795
|
-
source: "content"
|
|
1064
|
+
source: "content",
|
|
1065
|
+
reason: "validation_error"
|
|
796
1066
|
}
|
|
797
1067
|
]
|
|
798
1068
|
};
|
|
@@ -839,7 +1109,8 @@ async function processFile(filePath, options) {
|
|
|
839
1109
|
{
|
|
840
1110
|
success: false,
|
|
841
1111
|
error: `Failed to read file: ${message}`,
|
|
842
|
-
source: filePath
|
|
1112
|
+
source: filePath,
|
|
1113
|
+
reason: "fetch_error"
|
|
843
1114
|
}
|
|
844
1115
|
];
|
|
845
1116
|
}
|
|
@@ -858,7 +1129,8 @@ async function processIdentifiers(inputs, options) {
|
|
|
858
1129
|
results.push({
|
|
859
1130
|
success: false,
|
|
860
1131
|
error: `Cannot interpret '${input}' as identifier (not a valid PMID, DOI, or ISBN).${hint}`,
|
|
861
|
-
source: input
|
|
1132
|
+
source: input,
|
|
1133
|
+
reason: "validation_error"
|
|
862
1134
|
});
|
|
863
1135
|
}
|
|
864
1136
|
}
|
|
@@ -893,7 +1165,7 @@ async function importFromInputs(inputs, options) {
|
|
|
893
1165
|
}
|
|
894
1166
|
async function processStdinContent(content, options) {
|
|
895
1167
|
const format = options.format || "auto";
|
|
896
|
-
if (format === "json" || format === "bibtex" || format === "ris") {
|
|
1168
|
+
if (format === "json" || format === "bibtex" || format === "ris" || format === "nbib") {
|
|
897
1169
|
const result = await importFromContent(content, format);
|
|
898
1170
|
return result.results.map((r) => ({
|
|
899
1171
|
...r,
|
|
@@ -905,7 +1177,7 @@ async function processStdinContent(content, options) {
|
|
|
905
1177
|
return processIdentifiers(identifiers2, options);
|
|
906
1178
|
}
|
|
907
1179
|
const detectedFormat = detectByContent(content);
|
|
908
|
-
if (detectedFormat === "json" || detectedFormat === "bibtex" || detectedFormat === "ris") {
|
|
1180
|
+
if (detectedFormat === "json" || detectedFormat === "bibtex" || detectedFormat === "ris" || detectedFormat === "nbib") {
|
|
909
1181
|
const result = await importFromContent(content, detectedFormat);
|
|
910
1182
|
return result.results.map((r) => ({
|
|
911
1183
|
...r,
|
|
@@ -962,7 +1234,10 @@ function buildImportOptions(options) {
|
|
|
962
1234
|
}
|
|
963
1235
|
async function processImportResult(result, existingItems, addedIds, force, library) {
|
|
964
1236
|
if (!result.success) {
|
|
965
|
-
return {
|
|
1237
|
+
return {
|
|
1238
|
+
type: "failed",
|
|
1239
|
+
item: { source: result.source, error: result.error, reason: result.reason }
|
|
1240
|
+
};
|
|
966
1241
|
}
|
|
967
1242
|
const item = result.item;
|
|
968
1243
|
if (!force) {
|
|
@@ -971,7 +1246,11 @@ async function processImportResult(result, existingItems, addedIds, force, libra
|
|
|
971
1246
|
if (existingMatch) {
|
|
972
1247
|
return {
|
|
973
1248
|
type: "skipped",
|
|
974
|
-
item: {
|
|
1249
|
+
item: {
|
|
1250
|
+
source: result.source,
|
|
1251
|
+
existingId: existingMatch.existing.id ?? "",
|
|
1252
|
+
duplicateType: existingMatch.type
|
|
1253
|
+
}
|
|
975
1254
|
};
|
|
976
1255
|
}
|
|
977
1256
|
}
|
|
@@ -979,10 +1258,12 @@ async function processImportResult(result, existingItems, addedIds, force, libra
|
|
|
979
1258
|
const generatedId = generateId(item);
|
|
980
1259
|
const { id, changed } = resolveIdCollision(generatedId, allExistingIds);
|
|
981
1260
|
const finalItem = { ...item, id };
|
|
982
|
-
await library.add(finalItem);
|
|
1261
|
+
const addedToLibrary = await library.add(finalItem);
|
|
983
1262
|
addedIds.add(id);
|
|
1263
|
+
const uuid = addedToLibrary.custom?.uuid ?? "";
|
|
984
1264
|
const addedItem = {
|
|
985
1265
|
id,
|
|
1266
|
+
uuid,
|
|
986
1267
|
title: typeof finalItem.title === "string" ? finalItem.title : ""
|
|
987
1268
|
};
|
|
988
1269
|
if (changed) {
|
|
@@ -1526,14 +1807,6 @@ function createListRoute(library) {
|
|
|
1526
1807
|
});
|
|
1527
1808
|
return route;
|
|
1528
1809
|
}
|
|
1529
|
-
async function removeReference(library, options) {
|
|
1530
|
-
const { identifier, idType = "id" } = options;
|
|
1531
|
-
const result = await library.remove(identifier, { idType });
|
|
1532
|
-
if (result.removed) {
|
|
1533
|
-
await library.save();
|
|
1534
|
-
}
|
|
1535
|
-
return result;
|
|
1536
|
-
}
|
|
1537
1810
|
function createReferencesRoute(library) {
|
|
1538
1811
|
const route = new Hono();
|
|
1539
1812
|
route.get("/", async (c) => {
|
|
@@ -1785,8 +2058,10 @@ export {
|
|
|
1785
2058
|
cite as d,
|
|
1786
2059
|
search as e,
|
|
1787
2060
|
formatBibliographyCSL as f,
|
|
2061
|
+
getFulltextAttachmentTypes as g,
|
|
1788
2062
|
list as l,
|
|
2063
|
+
remove as r,
|
|
1789
2064
|
startServerWithFileWatcher as s,
|
|
1790
2065
|
updateReference as u
|
|
1791
2066
|
};
|
|
1792
|
-
//# sourceMappingURL=index-
|
|
2067
|
+
//# sourceMappingURL=index-DmMZCOno.js.map
|