@ncukondo/reference-manager 0.32.0 → 0.33.1

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.
Files changed (62) hide show
  1. package/dist/chunks/{SearchableMultiSelect-jAKQd_B0.js → SearchableMultiSelect-a5BIz7Gb.js} +2 -2
  2. package/dist/chunks/{SearchableMultiSelect-jAKQd_B0.js.map → SearchableMultiSelect-a5BIz7Gb.js.map} +1 -1
  3. package/dist/chunks/{action-menu-DLEwSKrj.js → action-menu-CjmHSfmi.js} +3 -3
  4. package/dist/chunks/{action-menu-DLEwSKrj.js.map → action-menu-CjmHSfmi.js.map} +1 -1
  5. package/dist/chunks/{checker-kVM4S67y.js → checker-D_kpqqRi.js} +4 -4
  6. package/dist/chunks/{checker-kVM4S67y.js.map → checker-D_kpqqRi.js.map} +1 -1
  7. package/dist/chunks/{crossref-client-DcJ42Qt6.js → crossref-client-CXp-1QlL.js} +2 -2
  8. package/dist/chunks/{crossref-client-DcJ42Qt6.js.map → crossref-client-CXp-1QlL.js.map} +1 -1
  9. package/dist/chunks/{fix-interaction-DWUzp9Ri.js → fix-interaction-Va_6E2Kd.js} +5 -5
  10. package/dist/chunks/{fix-interaction-DWUzp9Ri.js.map → fix-interaction-Va_6E2Kd.js.map} +1 -1
  11. package/dist/chunks/{index-B4-i4PrU.js → index-CgOvxl5f.js} +679 -16
  12. package/dist/chunks/index-CgOvxl5f.js.map +1 -0
  13. package/dist/chunks/{index-CmkN2-2A.js → index-Cv1Tph02.js} +75 -32
  14. package/dist/chunks/index-Cv1Tph02.js.map +1 -0
  15. package/dist/chunks/{index-B8iEozpf.js → index-DwvwJZOU.js} +3 -3
  16. package/dist/chunks/index-DwvwJZOU.js.map +1 -0
  17. package/dist/chunks/{index-BuQm8A5F.js → index-Kfj6MVHP.js} +4 -4
  18. package/dist/chunks/{index-BuQm8A5F.js.map → index-Kfj6MVHP.js.map} +1 -1
  19. package/dist/chunks/{loader-BG2eomDC.js → loader-CMu82BI5.js} +42 -1
  20. package/dist/chunks/loader-CMu82BI5.js.map +1 -0
  21. package/dist/chunks/{pubmed-client-Bhzz4Gg5.js → pubmed-client-Cmq5_Bun.js} +2 -2
  22. package/dist/chunks/{pubmed-client-Bhzz4Gg5.js.map → pubmed-client-Cmq5_Bun.js.map} +1 -1
  23. package/dist/chunks/{reference-select-B9b9Ez7Q.js → reference-select-Afwq1-X1.js} +3 -3
  24. package/dist/chunks/{reference-select-B9b9Ez7Q.js.map → reference-select-Afwq1-X1.js.map} +1 -1
  25. package/dist/chunks/{style-select-aXByJLOo.js → style-select-iM-Af4O2.js} +3 -3
  26. package/dist/chunks/{style-select-aXByJLOo.js.map → style-select-iM-Af4O2.js.map} +1 -1
  27. package/dist/cli/commands/add.d.ts +9 -1
  28. package/dist/cli/commands/add.d.ts.map +1 -1
  29. package/dist/cli/index.d.ts.map +1 -1
  30. package/dist/cli.js +2 -2
  31. package/dist/config/defaults.d.ts.map +1 -1
  32. package/dist/config/key-parser.d.ts.map +1 -1
  33. package/dist/config/loader.d.ts.map +1 -1
  34. package/dist/config/schema.d.ts +42 -0
  35. package/dist/config/schema.d.ts.map +1 -1
  36. package/dist/features/attachments/types.d.ts +5 -1
  37. package/dist/features/attachments/types.d.ts.map +1 -1
  38. package/dist/features/import/browser.d.ts +16 -0
  39. package/dist/features/import/browser.d.ts.map +1 -0
  40. package/dist/features/import/detector.d.ts +11 -2
  41. package/dist/features/import/detector.d.ts.map +1 -1
  42. package/dist/features/import/importer.d.ts +17 -0
  43. package/dist/features/import/importer.d.ts.map +1 -1
  44. package/dist/features/import/url-archive.d.ts +18 -0
  45. package/dist/features/import/url-archive.d.ts.map +1 -0
  46. package/dist/features/import/url-fetcher.d.ts +35 -0
  47. package/dist/features/import/url-fetcher.d.ts.map +1 -0
  48. package/dist/features/import/url-fulltext.d.ts +10 -0
  49. package/dist/features/import/url-fulltext.d.ts.map +1 -0
  50. package/dist/features/import/url-metadata.d.ts +34 -0
  51. package/dist/features/import/url-metadata.d.ts.map +1 -0
  52. package/dist/features/operations/add.d.ts +11 -0
  53. package/dist/features/operations/add.d.ts.map +1 -1
  54. package/dist/features/operations/json-output.d.ts +1 -0
  55. package/dist/features/operations/json-output.d.ts.map +1 -1
  56. package/dist/index.js +1 -1
  57. package/dist/server.js +1 -1
  58. package/package.json +8 -1
  59. package/dist/chunks/index-B4-i4PrU.js.map +0 -1
  60. package/dist/chunks/index-B8iEozpf.js.map +0 -1
  61. package/dist/chunks/index-CmkN2-2A.js.map +0 -1
  62. package/dist/chunks/loader-BG2eomDC.js.map +0 -1
@@ -1,22 +1,25 @@
1
1
  import { Hono } from "hono";
2
2
  import { h as CslItemSchema, g as detectDuplicate, m as generateId, a as sortOrderSchema, b as sortFieldSchema, p as pickDefined, t as tokenize, s as search$1, f as sortResults, y as searchSortFieldSchema, L as Library, F as FileWatcher } from "./file-watcher-CWHg1yol.js";
3
3
  import * as fs from "node:fs";
4
- import { mkdtempSync, writeFileSync, existsSync, readFileSync } from "node:fs";
4
+ import { mkdtempSync, writeFileSync, readFileSync, existsSync } from "node:fs";
5
+ import fs__default, { stat, rename, copyFile, unlink, rm, readFile, mkdir, writeFile, access, mkdtemp, rmdir } from "node:fs/promises";
6
+ import { tmpdir } from "node:os";
7
+ import * as path from "node:path";
8
+ import path__default, { extname, join, dirname, basename } from "node:path";
5
9
  import { Cite, plugins, util } from "@citation-js/core";
6
10
  import "@citation-js/plugin-doi";
7
11
  import "@citation-js/plugin-isbn";
8
12
  import "@citation-js/plugin-bibtex";
9
13
  import "@citation-js/plugin-ris";
14
+ import { createRequire } from "node:module";
15
+ import TurndownService from "turndown";
16
+ import { gfm } from "turndown-plugin-gfm";
10
17
  import { z } from "zod";
11
- import * as path from "node:path";
12
- import path__default, { extname, join, dirname, basename } from "node:path";
13
18
  import "@citation-js/plugin-csl";
14
- import fs__default, { stat, rename, copyFile, unlink, rm, readFile, mkdir, writeFile, access, mkdtemp, rmdir } from "node:fs/promises";
15
- import { tmpdir } from "node:os";
16
19
  import { exec, execFile } from "node:child_process";
17
20
  import "node:crypto";
18
21
  const name = "@ncukondo/reference-manager";
19
- const version = "0.32.0";
22
+ const version = "0.33.1";
20
23
  const description = "A local reference management tool using CSL-JSON as the single source of truth";
21
24
  const publishConfig = { "access": "public" };
22
25
  const type$1 = "module";
@@ -27,8 +30,8 @@ const types = "./dist/index.d.ts";
27
30
  const exports$1 = { ".": { "import": "./dist/index.js", "types": "./dist/index.d.ts" } };
28
31
  const files = ["dist", "bin"];
29
32
  const scripts = { "dev": "vite", "build": "vite build && tsc --emitDeclarationOnly", "preview": "vite preview", "test": "vitest --project unit", "test:unit": "vitest --project unit", "test:e2e": "vitest run --project e2e", "test:remote": "vitest --project remote", "test:all": "vitest --project unit && vitest --project e2e && vitest --project remote", "test:watch": "vitest --watch --project unit", "test:coverage": "vitest run --coverage --project unit", "lint": "biome check .", "lint:fix": "biome check --write .", "format": "biome format --write .", "typecheck": "tsc --noEmit", "check:paths": "! grep -rn --include='*.ts' --exclude='*.test.ts' --exclude='*.d.ts' --exclude='path.ts' '\\.replace(/\\\\\\\\' src/ 2>/dev/null", "check:no-process-exit": "! grep -rn --include='*.ts' --exclude='*.test.ts' 'process\\.exit(' src/cli/ 2>/dev/null | grep -v 'helpers.ts' | grep -v '// '", "prepublishOnly": "npm run build", "prepare": "husky || true" };
30
- const dependencies = { "@citation-js/core": "^0.7.16", "@citation-js/plugin-bibtex": "^0.7.21", "@citation-js/plugin-csl": "^0.7.16", "@citation-js/plugin-doi": "^0.7.21", "@citation-js/plugin-isbn": "^0.4.0", "@citation-js/plugin-ris": "^0.7.21", "@iarna/toml": "^2.2.5", "@modelcontextprotocol/sdk": "^1.25.1", "@ncukondo/academic-fulltext": "^0.2.6", "chokidar": "^5.0.0", "commander": "^12.1.0", "env-paths": "^3.0.0", "hono": "^4.11.1", "ink": "^6.6.0", "ink-ui": "^0.4.0", "js-yaml": "^4.1.1", "react": "^19.2.3", "tabtab": "^3.0.2", "write-file-atomic": "^7.0.0", "yaml": "^2.8.2", "zod": "^4.1.13" };
31
- const devDependencies = { "@biomejs/biome": "^1.9.0", "@hono/node-server": "^1.19.7", "@types/chokidar": "^1.7.5", "@types/js-yaml": "^4.0.9", "@types/node": "^22.19.2", "@types/react": "^19.2.9", "@types/tabtab": "^3.0.4", "@types/write-file-atomic": "^4.0.3", "@vitest/coverage-v8": "^2.1.9", "husky": "^9.1.7", "lint-staged": "^16.2.7", "react-devtools-core": "^6.1.5", "typescript": "^5.6.0", "vite": "^6.0.0", "vite-node": "^2.1.0", "vitest": "^2.1.0" };
33
+ const dependencies = { "@citation-js/core": "^0.7.16", "@citation-js/plugin-bibtex": "^0.7.21", "@citation-js/plugin-csl": "^0.7.16", "@citation-js/plugin-doi": "^0.7.21", "@citation-js/plugin-isbn": "^0.4.0", "@citation-js/plugin-ris": "^0.7.21", "@iarna/toml": "^2.2.5", "@modelcontextprotocol/sdk": "^1.25.1", "@mozilla/readability": "^0.6.0", "@ncukondo/academic-fulltext": "^0.2.6", "chokidar": "^5.0.0", "commander": "^12.1.0", "env-paths": "^3.0.0", "hono": "^4.11.1", "ink": "^6.6.0", "ink-ui": "^0.4.0", "js-yaml": "^4.1.1", "playwright-core": "^1.58.2", "react": "^19.2.3", "tabtab": "^3.0.2", "turndown": "^7.2.2", "turndown-plugin-gfm": "^1.0.2", "write-file-atomic": "^7.0.0", "yaml": "^2.8.2", "zod": "^4.1.13" };
34
+ const devDependencies = { "@biomejs/biome": "^1.9.0", "@hono/node-server": "^1.19.7", "@types/chokidar": "^1.7.5", "@types/js-yaml": "^4.0.9", "@types/jsdom": "^28.0.1", "@types/node": "^22.19.2", "@types/react": "^19.2.9", "@types/tabtab": "^3.0.4", "@types/turndown": "^5.0.6", "@types/write-file-atomic": "^4.0.3", "@vitest/coverage-v8": "^2.1.9", "husky": "^9.1.7", "jsdom": "^29.0.1", "lint-staged": "^16.2.7", "react-devtools-core": "^6.1.5", "typescript": "^5.6.0", "vite": "^6.0.0", "vite-node": "^2.1.0", "vitest": "^2.1.0" };
32
35
  const keywords = ["reference-manager", "csl-json", "bibliography", "pandoc", "citation"];
33
36
  const author = "";
34
37
  const license = "MIT";
@@ -92,7 +95,7 @@ function parseFilename(filename) {
92
95
  ext: extWithoutDot
93
96
  };
94
97
  }
95
- const RESERVED_ROLES = ["fulltext", "supplement", "notes", "draft"];
98
+ const RESERVED_ROLES = ["fulltext", "supplement", "notes", "draft", "archive"];
96
99
  function isReservedRole(role) {
97
100
  return RESERVED_ROLES.includes(role);
98
101
  }
@@ -7394,7 +7397,7 @@ function extractRefText(item) {
7394
7397
  }
7395
7398
  return text2;
7396
7399
  }
7397
- function extractDoi(item) {
7400
+ function extractDoi$1(item) {
7398
7401
  for (const link of qa(item, "a")) {
7399
7402
  const href = link.getAttribute("href") ?? "";
7400
7403
  const doiMatch = href.match(/doi\.org\/(.+)/);
@@ -7408,7 +7411,7 @@ function parseReferences(root) {
7408
7411
  for (const item of qa(root, ".ltx_bibitem")) {
7409
7412
  const id = item.getAttribute("id") ?? `ref${refs.length + 1}`;
7410
7413
  const text2 = extractRefText(item);
7411
- const doi = extractDoi(item);
7414
+ const doi = extractDoi$1(item);
7412
7415
  const ref = { id, text: text2 };
7413
7416
  if (doi)
7414
7417
  ref.doi = doi;
@@ -11644,11 +11647,54 @@ function detectSingleIdentifier(input) {
11644
11647
  if (isIsbn(input)) {
11645
11648
  return "isbn";
11646
11649
  }
11650
+ if (extractPubmedId(input) !== null) {
11651
+ return "pmid";
11652
+ }
11647
11653
  if (isPmid(input)) {
11648
11654
  return "pmid";
11649
11655
  }
11656
+ if (isUrl(input)) {
11657
+ return "url";
11658
+ }
11650
11659
  return "unknown";
11651
11660
  }
11661
+ const PUBMED_URL_PATTERNS = [
11662
+ // https://pubmed.ncbi.nlm.nih.gov/{PMID}/
11663
+ {
11664
+ pattern: /^https?:\/\/pubmed\.ncbi\.nlm\.nih\.gov\/(\d+)\/?$/,
11665
+ extract: (m) => m[1]
11666
+ },
11667
+ // https://www.ncbi.nlm.nih.gov/pubmed/{PMID}
11668
+ {
11669
+ pattern: /^https?:\/\/(?:www\.)?ncbi\.nlm\.nih\.gov\/pubmed\/(\d+)\/?$/,
11670
+ extract: (m) => m[1]
11671
+ },
11672
+ // https://pmc.ncbi.nlm.nih.gov/articles/PMC{ID}/
11673
+ {
11674
+ pattern: /^https?:\/\/(?:www\.)?pmc\.ncbi\.nlm\.nih\.gov\/articles\/PMC(\d+)\/?$/,
11675
+ extract: (m) => `PMC${m[1]}`
11676
+ }
11677
+ ];
11678
+ function extractPubmedId(input) {
11679
+ for (const { pattern, extract } of PUBMED_URL_PATTERNS) {
11680
+ const match = pattern.exec(input);
11681
+ if (match) {
11682
+ return extract(match);
11683
+ }
11684
+ }
11685
+ return null;
11686
+ }
11687
+ function isUrl(input) {
11688
+ if (!input) return false;
11689
+ const lower = input.toLowerCase();
11690
+ if (!lower.startsWith("http://") && !lower.startsWith("https://")) {
11691
+ return false;
11692
+ }
11693
+ if (isDoi(input)) return false;
11694
+ if (isArxiv(input)) return false;
11695
+ if (extractPubmedId(input) !== null) return false;
11696
+ return true;
11697
+ }
11652
11698
  function isDoi(input) {
11653
11699
  for (const prefix of DOI_URL_PREFIXES) {
11654
11700
  if (input.toLowerCase().startsWith(prefix.toLowerCase())) {
@@ -12306,6 +12352,447 @@ function isEmptyFormat(content, format) {
12306
12352
  }
12307
12353
  return false;
12308
12354
  }
12355
+ class BrowserNotFoundError extends Error {
12356
+ constructor(cause) {
12357
+ const message = [
12358
+ "Browser not found. URL import requires Chrome or Chromium.",
12359
+ "",
12360
+ " Install one of the following:",
12361
+ "",
12362
+ " Google Chrome: https://www.google.com/chrome/",
12363
+ " Chromium: sudo apt install chromium-browser (Ubuntu/Debian)",
12364
+ " brew install --cask chromium (macOS)",
12365
+ "",
12366
+ " Or specify the path manually:",
12367
+ "",
12368
+ " [url]",
12369
+ ' browser_path = "/path/to/chrome"'
12370
+ ].join("\n");
12371
+ super(message, { cause });
12372
+ this.name = "BrowserNotFoundError";
12373
+ }
12374
+ }
12375
+ async function launchBrowser(config) {
12376
+ let chromium;
12377
+ try {
12378
+ ({ chromium } = await import("playwright-core"));
12379
+ } catch {
12380
+ throw new BrowserNotFoundError(
12381
+ new Error("playwright-core is not installed. Run: npm install playwright-core")
12382
+ );
12383
+ }
12384
+ try {
12385
+ const launchOptions = {
12386
+ headless: true
12387
+ };
12388
+ if (config.browserPath) {
12389
+ launchOptions.executablePath = config.browserPath;
12390
+ } else {
12391
+ launchOptions.channel = "chrome";
12392
+ }
12393
+ return await chromium.launch(launchOptions);
12394
+ } catch (error) {
12395
+ throw new BrowserNotFoundError(error instanceof Error ? error : void 0);
12396
+ }
12397
+ }
12398
+ async function captureMhtml(page) {
12399
+ const cdp = await page.context().newCDPSession(page);
12400
+ try {
12401
+ const { data } = await cdp.send("Page.captureSnapshot", { format: "mhtml" });
12402
+ return data;
12403
+ } finally {
12404
+ await cdp.detach();
12405
+ }
12406
+ }
12407
+ async function captureHtml(page) {
12408
+ return page.evaluate("document.documentElement.outerHTML");
12409
+ }
12410
+ async function createArchive(page, format) {
12411
+ if (format === "mhtml") {
12412
+ const data2 = await captureMhtml(page);
12413
+ return { data: data2, extension: "mhtml" };
12414
+ }
12415
+ const data = await captureHtml(page);
12416
+ return { data, extension: "html" };
12417
+ }
12418
+ async function extractContent(page) {
12419
+ return page.evaluate(`
12420
+ (() => {
12421
+ let content = null;
12422
+ try {
12423
+ if (typeof Readability !== "undefined") {
12424
+ const reader = new Readability(document.cloneNode(true));
12425
+ const article = reader.parse();
12426
+ content = article ? article.content : null;
12427
+ }
12428
+ } catch (e) {}
12429
+ return {
12430
+ content: content,
12431
+ fullHtml: document.documentElement.outerHTML,
12432
+ };
12433
+ })()
12434
+ `);
12435
+ }
12436
+ function htmlToMarkdown(html2) {
12437
+ const turndown = new TurndownService({
12438
+ headingStyle: "atx",
12439
+ codeBlockStyle: "fenced"
12440
+ });
12441
+ turndown.use(gfm);
12442
+ return turndown.turndown(html2);
12443
+ }
12444
+ async function generateFulltext(page) {
12445
+ const extracted = await extractContent(page);
12446
+ const html2 = extracted.content ?? extracted.fullHtml;
12447
+ if (!html2) {
12448
+ return "";
12449
+ }
12450
+ return htmlToMarkdown(html2);
12451
+ }
12452
+ const SCHEMA_TYPE_TO_CSL = {
12453
+ Legislation: "legislation",
12454
+ LegislationObject: "legislation",
12455
+ Report: "report",
12456
+ Article: "article",
12457
+ ScholarlyArticle: "article-journal",
12458
+ NewsArticle: "article-newspaper",
12459
+ WebPage: "webpage"
12460
+ };
12461
+ function parseDate(raw) {
12462
+ const cleaned = raw.replace(/\//g, "-");
12463
+ const parts = cleaned.split("-").map(Number);
12464
+ if (parts.length >= 1 && !Number.isNaN(parts[0])) {
12465
+ return { "date-parts": [parts.filter((p) => !Number.isNaN(p))] };
12466
+ }
12467
+ return void 0;
12468
+ }
12469
+ function isEmptyAuthor(a) {
12470
+ return !a.family && !a.given && !a.literal;
12471
+ }
12472
+ function parseName(name2) {
12473
+ const trimmed = name2.trim();
12474
+ if (!trimmed) return { literal: "" };
12475
+ if (trimmed.includes(",")) {
12476
+ const parts = trimmed.split(",");
12477
+ const family = parts[0] ?? "";
12478
+ const given = parts.slice(1).join(",").trim();
12479
+ const result = { family: family.trim() };
12480
+ if (given) result.given = given;
12481
+ return result;
12482
+ }
12483
+ const spaceIdx = trimmed.lastIndexOf(" ");
12484
+ if (spaceIdx > 0) {
12485
+ return {
12486
+ given: trimmed.slice(0, spaceIdx),
12487
+ family: trimmed.slice(spaceIdx + 1)
12488
+ };
12489
+ }
12490
+ return { literal: trimmed };
12491
+ }
12492
+ const PRIORITY_SCHEMA_TYPES = /* @__PURE__ */ new Set([
12493
+ "Article",
12494
+ "ScholarlyArticle",
12495
+ "NewsArticle",
12496
+ "Report",
12497
+ "Legislation",
12498
+ "LegislationObject"
12499
+ ]);
12500
+ function isObj(v) {
12501
+ return v != null && typeof v === "object" && !Array.isArray(v);
12502
+ }
12503
+ function findInGraph(graph) {
12504
+ for (const item of graph) {
12505
+ if (!isObj(item)) continue;
12506
+ const t = item["@type"];
12507
+ if (typeof t === "string" && PRIORITY_SCHEMA_TYPES.has(t)) return item;
12508
+ }
12509
+ for (const item of graph) {
12510
+ if (isObj(item) && item.name) return item;
12511
+ }
12512
+ return void 0;
12513
+ }
12514
+ function findBestJsonLdItem(jsonLdArray) {
12515
+ for (const entry of jsonLdArray) {
12516
+ if (!isObj(entry)) continue;
12517
+ if (Array.isArray(entry["@graph"])) {
12518
+ const found = findInGraph(entry["@graph"]);
12519
+ if (found) return found;
12520
+ }
12521
+ if (entry["@type"]) return entry;
12522
+ }
12523
+ return void 0;
12524
+ }
12525
+ function extractJsonLdAuthor(rawAuthor) {
12526
+ const authors = Array.isArray(rawAuthor) ? rawAuthor : [rawAuthor];
12527
+ return authors.filter(isObj).map((a) => {
12528
+ const name2 = typeof a.name === "string" ? a.name : "";
12529
+ if (a["@type"] === "Organization") return { literal: name2 };
12530
+ return parseName(name2);
12531
+ });
12532
+ }
12533
+ function extractJsonLdPublisher(pub) {
12534
+ if (typeof pub === "string") return pub;
12535
+ if (isObj(pub) && typeof pub.name === "string") return pub.name;
12536
+ return void 0;
12537
+ }
12538
+ function extractJsonLdDate(item) {
12539
+ if (typeof item.datePublished === "string") return item.datePublished;
12540
+ if (typeof item.legislationDate === "string") return item.legislationDate;
12541
+ return void 0;
12542
+ }
12543
+ function extractDoi(value) {
12544
+ if (typeof value !== "string") return void 0;
12545
+ const normalized = value.replace(/^https?:\/\/(dx\.)?doi\.org\//, "");
12546
+ if (normalized.startsWith("10.")) return normalized;
12547
+ return void 0;
12548
+ }
12549
+ function setDateField(fields, dateStr) {
12550
+ if (!dateStr) return;
12551
+ const parsed = parseDate(dateStr);
12552
+ if (parsed) fields.issued = parsed;
12553
+ }
12554
+ function extractFromJsonLd(jsonLdArray) {
12555
+ const item = findBestJsonLdItem(jsonLdArray);
12556
+ if (!item) return {};
12557
+ const fields = {};
12558
+ if (typeof item.name === "string" && item.name) fields.title = item.name;
12559
+ if (typeof item["@type"] === "string") {
12560
+ const mapped = SCHEMA_TYPE_TO_CSL[item["@type"]];
12561
+ if (mapped) fields.type = mapped;
12562
+ }
12563
+ if (item.author) {
12564
+ const authors = extractJsonLdAuthor(item.author);
12565
+ if (authors.length) fields.author = authors;
12566
+ }
12567
+ setDateField(fields, extractJsonLdDate(item));
12568
+ const doi = extractDoi(item.identifier);
12569
+ if (doi) fields.DOI = doi;
12570
+ const pub = extractJsonLdPublisher(item.publisher);
12571
+ if (pub) fields.publisher = pub;
12572
+ if (typeof item.description === "string" && item.description) fields.abstract = item.description;
12573
+ return fields;
12574
+ }
12575
+ function extractFromCitation(citation) {
12576
+ const fields = {};
12577
+ if (typeof citation.citation_title === "string" && citation.citation_title) {
12578
+ fields.title = citation.citation_title;
12579
+ }
12580
+ const rawAuthors = citation.citation_author;
12581
+ if (rawAuthors) {
12582
+ const authorList = Array.isArray(rawAuthors) ? rawAuthors : [rawAuthors];
12583
+ const parsed = authorList.map((a) => parseName(a)).filter((a) => !isEmptyAuthor(a));
12584
+ if (parsed.length) fields.author = parsed;
12585
+ }
12586
+ const dateStr = (typeof citation.citation_date === "string" ? citation.citation_date : void 0) || (typeof citation.citation_publication_date === "string" ? citation.citation_publication_date : void 0);
12587
+ setDateField(fields, dateStr);
12588
+ if (typeof citation.citation_doi === "string" && citation.citation_doi) {
12589
+ fields.DOI = citation.citation_doi;
12590
+ }
12591
+ if (typeof citation.citation_journal_title === "string" && citation.citation_journal_title) {
12592
+ fields["container-title"] = citation.citation_journal_title;
12593
+ }
12594
+ return fields;
12595
+ }
12596
+ function dcString(val) {
12597
+ if (typeof val === "string") return val;
12598
+ if (Array.isArray(val) && val.length > 0) return val[0];
12599
+ return void 0;
12600
+ }
12601
+ function extractFromDublinCore(dc) {
12602
+ const fields = {};
12603
+ const title = dcString(dc["DC.title"]);
12604
+ if (title) fields.title = title;
12605
+ const rawCreator = dc["DC.creator"];
12606
+ if (rawCreator) {
12607
+ const creators = Array.isArray(rawCreator) ? rawCreator : [rawCreator];
12608
+ const parsed = creators.map((c) => parseName(c)).filter((a) => !isEmptyAuthor(a));
12609
+ if (parsed.length) fields.author = parsed;
12610
+ }
12611
+ setDateField(fields, dcString(dc["DC.date"]));
12612
+ const publisher = dcString(dc["DC.publisher"]);
12613
+ if (publisher) fields.publisher = publisher;
12614
+ const description2 = dcString(dc["DC.description"]);
12615
+ if (description2) fields.abstract = description2;
12616
+ const doi = extractDoi(dcString(dc["DC.identifier"]));
12617
+ if (doi) fields.DOI = doi;
12618
+ return fields;
12619
+ }
12620
+ function extractFromOpenGraph(og) {
12621
+ const fields = {};
12622
+ if (og["og:title"]) fields.title = og["og:title"];
12623
+ if (og["og:description"]) fields.abstract = og["og:description"];
12624
+ return fields;
12625
+ }
12626
+ const SIMPLE_KEYS = ["title", "type", "DOI", "container-title", "publisher", "abstract"];
12627
+ function mergeFields(...sources) {
12628
+ const merged = {};
12629
+ for (const key of SIMPLE_KEYS) {
12630
+ const source = sources.find((s) => s[key]);
12631
+ if (source?.[key]) merged[key] = source[key];
12632
+ }
12633
+ const authorSource = sources.find((s) => s.author?.length);
12634
+ if (authorSource?.author) merged.author = authorSource.author;
12635
+ const issuedSource = sources.find((s) => s.issued);
12636
+ if (issuedSource?.issued) merged.issued = issuedSource.issued;
12637
+ return merged;
12638
+ }
12639
+ async function extractMetadata(page) {
12640
+ const rawMeta = await page.evaluate(`
12641
+ (() => {
12642
+ const jsonLd = [...document.querySelectorAll('script[type="application/ld+json"]')]
12643
+ .map(el => { try { return JSON.parse(el.textContent || ""); } catch { return null; } })
12644
+ .filter(Boolean);
12645
+
12646
+ const citationMulti = {};
12647
+ for (const el of document.querySelectorAll('meta[name^="citation_"]')) {
12648
+ const name = el.getAttribute("name") || "";
12649
+ const content = el.getAttribute("content") || "";
12650
+ if (!citationMulti[name]) citationMulti[name] = [];
12651
+ citationMulti[name].push(content);
12652
+ }
12653
+ const citation = {};
12654
+ for (const [key, values] of Object.entries(citationMulti)) {
12655
+ citation[key] = values.length === 1 ? values[0] : values;
12656
+ }
12657
+
12658
+ const dcMulti = {};
12659
+ for (const el of document.querySelectorAll('meta[name^="DC."]')) {
12660
+ const name = el.getAttribute("name") || "";
12661
+ const content = el.getAttribute("content") || "";
12662
+ if (!dcMulti[name]) dcMulti[name] = [];
12663
+ dcMulti[name].push(content);
12664
+ }
12665
+ const dc = {};
12666
+ for (const [key, values] of Object.entries(dcMulti)) {
12667
+ dc[key] = values.length === 1 ? values[0] : values;
12668
+ }
12669
+ const og = Object.fromEntries(
12670
+ [...document.querySelectorAll('meta[property^="og:"]')]
12671
+ .map(el => [el.getAttribute("property"), el.getAttribute("content") || ""])
12672
+ .filter(([k]) => k));
12673
+ return { jsonLd, citation, dc, og, title: document.title };
12674
+ })()
12675
+ `);
12676
+ const pageUrl = page.url();
12677
+ const jsonLdFields = extractFromJsonLd(rawMeta.jsonLd);
12678
+ const citationFields = extractFromCitation(rawMeta.citation);
12679
+ const dcFields = extractFromDublinCore(rawMeta.dc);
12680
+ const ogFields = extractFromOpenGraph(rawMeta.og);
12681
+ const htmlFields = {};
12682
+ if (rawMeta.title) htmlFields.title = rawMeta.title;
12683
+ const merged = mergeFields(jsonLdFields, citationFields, dcFields, ogFields, htmlFields);
12684
+ const now = /* @__PURE__ */ new Date();
12685
+ const accessed = {
12686
+ "date-parts": [[now.getFullYear(), now.getMonth() + 1, now.getDate()]]
12687
+ };
12688
+ const item = {
12689
+ id: "",
12690
+ type: merged.type || "webpage",
12691
+ title: merged.title || pageUrl,
12692
+ URL: pageUrl,
12693
+ accessed
12694
+ };
12695
+ if (merged.author?.length) item.author = merged.author;
12696
+ if (merged.issued) item.issued = merged.issued;
12697
+ if (merged.DOI) item.DOI = merged.DOI;
12698
+ if (merged["container-title"]) item["container-title"] = merged["container-title"];
12699
+ if (merged.publisher) item.publisher = merged.publisher;
12700
+ if (merged.abstract) item.abstract = merged.abstract;
12701
+ return item;
12702
+ }
12703
+ function getReadabilityPath() {
12704
+ const require2 = createRequire(import.meta.url);
12705
+ return require2.resolve("@mozilla/readability/Readability.js");
12706
+ }
12707
+ let readabilityScriptCache;
12708
+ function getReadabilityScript() {
12709
+ if (readabilityScriptCache === void 0) {
12710
+ readabilityScriptCache = readFileSync(getReadabilityPath(), "utf-8");
12711
+ }
12712
+ return readabilityScriptCache;
12713
+ }
12714
+ async function processPage(page, url, options) {
12715
+ const warnings = [];
12716
+ await page.goto(url, {
12717
+ waitUntil: "domcontentloaded",
12718
+ timeout: options.timeout * 1e3
12719
+ });
12720
+ await page.waitForLoadState("networkidle", { timeout: 2e3 }).catch(() => {
12721
+ });
12722
+ try {
12723
+ const readabilityScript = getReadabilityScript();
12724
+ await page.addScriptTag({ content: readabilityScript });
12725
+ } catch (error) {
12726
+ const msg = error instanceof Error ? error.message : String(error);
12727
+ warnings.push(`Readability injection failed: ${msg}`);
12728
+ }
12729
+ const item = await extractMetadata(page);
12730
+ const fulltext = await generateFulltext(page);
12731
+ let archive;
12732
+ if (!options.noArchive) {
12733
+ try {
12734
+ archive = await createArchive(page, options.archiveFormat);
12735
+ } catch (error) {
12736
+ const msg = error instanceof Error ? error.message : String(error);
12737
+ warnings.push(`Archive creation failed: ${msg}`);
12738
+ }
12739
+ }
12740
+ return { item, fulltext, archive, warnings };
12741
+ }
12742
+ function resolvePageOptions(options) {
12743
+ return {
12744
+ archiveFormat: options.archiveFormat ?? options.urlConfig.archiveFormat,
12745
+ noArchive: options.noArchive ?? false,
12746
+ timeout: options.urlConfig.timeout
12747
+ };
12748
+ }
12749
+ async function fetchUrl(url, options) {
12750
+ const browser = await launchBrowser(options.urlConfig);
12751
+ try {
12752
+ const page = await browser.newPage();
12753
+ try {
12754
+ return await processPage(page, url, resolvePageOptions(options));
12755
+ } finally {
12756
+ await page.close();
12757
+ }
12758
+ } finally {
12759
+ await browser.close();
12760
+ }
12761
+ }
12762
+ async function fetchUrls(urls, options) {
12763
+ const results = /* @__PURE__ */ new Map();
12764
+ if (urls.length === 0) {
12765
+ return results;
12766
+ }
12767
+ let browser;
12768
+ try {
12769
+ browser = await launchBrowser(options.urlConfig);
12770
+ } catch (error) {
12771
+ for (const url of urls) {
12772
+ results.set(url, error instanceof Error ? error : new Error(String(error)));
12773
+ }
12774
+ return results;
12775
+ }
12776
+ const pageOptions = resolvePageOptions(options);
12777
+ try {
12778
+ for (const url of urls) {
12779
+ try {
12780
+ const page = await browser.newPage();
12781
+ try {
12782
+ const result = await processPage(page, url, pageOptions);
12783
+ results.set(url, result);
12784
+ } finally {
12785
+ await page.close();
12786
+ }
12787
+ } catch (error) {
12788
+ results.set(url, error instanceof Error ? error : new Error(String(error)));
12789
+ }
12790
+ }
12791
+ } finally {
12792
+ await browser.close();
12793
+ }
12794
+ return results;
12795
+ }
12309
12796
  function classifyIdentifiers(identifiers) {
12310
12797
  const pmids = [];
12311
12798
  const dois = [];
@@ -12642,10 +13129,104 @@ async function processFile(filePath, options) {
12642
13129
  ];
12643
13130
  }
12644
13131
  }
13132
+ async function processUrlInput(url, options) {
13133
+ if (!options.urlConfig) {
13134
+ return {
13135
+ success: false,
13136
+ error: "URL import requires browser configuration. Set [url] section in config.",
13137
+ source: url,
13138
+ reason: "validation_error"
13139
+ };
13140
+ }
13141
+ try {
13142
+ const result = await fetchUrl(url, {
13143
+ urlConfig: options.urlConfig,
13144
+ archiveFormat: options.archiveFormat,
13145
+ noArchive: options.noArchive
13146
+ });
13147
+ return {
13148
+ success: true,
13149
+ item: result.item,
13150
+ source: url,
13151
+ urlData: {
13152
+ fulltext: result.fulltext,
13153
+ archive: result.archive,
13154
+ warnings: result.warnings
13155
+ }
13156
+ };
13157
+ } catch (error) {
13158
+ const message = error instanceof Error ? error.message : String(error);
13159
+ return {
13160
+ success: false,
13161
+ error: message,
13162
+ source: url,
13163
+ reason: "fetch_error"
13164
+ };
13165
+ }
13166
+ }
13167
+ async function processUrlInputs(urls, options) {
13168
+ if (!options.urlConfig) {
13169
+ return urls.map((url) => ({
13170
+ success: false,
13171
+ error: "URL import requires browser configuration. Set [url] section in config.",
13172
+ source: url,
13173
+ reason: "validation_error"
13174
+ }));
13175
+ }
13176
+ if (urls.length === 1) {
13177
+ const url = urls[0];
13178
+ if (url) return [await processUrlInput(url, options)];
13179
+ return [];
13180
+ }
13181
+ const fetchResults = await fetchUrls(urls, {
13182
+ urlConfig: options.urlConfig,
13183
+ archiveFormat: options.archiveFormat,
13184
+ noArchive: options.noArchive
13185
+ });
13186
+ return urls.map((url) => {
13187
+ const result = fetchResults.get(url);
13188
+ if (!result) {
13189
+ return {
13190
+ success: false,
13191
+ error: "URL was not processed",
13192
+ source: url,
13193
+ reason: "fetch_error"
13194
+ };
13195
+ }
13196
+ if (result instanceof Error) {
13197
+ return {
13198
+ success: false,
13199
+ error: result.message,
13200
+ source: url,
13201
+ reason: "fetch_error"
13202
+ };
13203
+ }
13204
+ return {
13205
+ success: true,
13206
+ item: result.item,
13207
+ source: url,
13208
+ urlData: {
13209
+ fulltext: result.fulltext,
13210
+ archive: result.archive,
13211
+ warnings: result.warnings
13212
+ }
13213
+ };
13214
+ });
13215
+ }
12645
13216
  async function processIdentifiers(inputs, options) {
12646
13217
  const results = [];
12647
13218
  const validIdentifiers = [];
13219
+ const urlInputs = [];
12648
13220
  for (const input of inputs) {
13221
+ const pubmedId = extractPubmedId(input);
13222
+ if (pubmedId !== null) {
13223
+ validIdentifiers.push(pubmedId);
13224
+ continue;
13225
+ }
13226
+ if (isUrl(input)) {
13227
+ urlInputs.push(input);
13228
+ continue;
13229
+ }
12649
13230
  const isValidPmid = isPmid(input);
12650
13231
  const isValidDoi = isDoi(input);
12651
13232
  const isValidIsbn = isIsbn(input);
@@ -12666,6 +13247,10 @@ async function processIdentifiers(inputs, options) {
12666
13247
  const fetchResult = await importFromIdentifiers(validIdentifiers, options);
12667
13248
  results.push(...fetchResult.results);
12668
13249
  }
13250
+ if (urlInputs.length > 0) {
13251
+ const urlResults = await processUrlInputs(urlInputs, options);
13252
+ results.push(...urlResults);
13253
+ }
12669
13254
  return results;
12670
13255
  }
12671
13256
  async function importFromInputs(inputs, options) {
@@ -12736,17 +13321,40 @@ async function addReferences(inputs, library, options) {
12736
13321
  );
12737
13322
  if (processed.type === "failed") {
12738
13323
  failed.push(processed.item);
12739
- } else if (processed.type === "skipped") {
13324
+ continue;
13325
+ }
13326
+ if (processed.type === "skipped") {
12740
13327
  skipped.push(processed.item);
12741
- } else {
12742
- added.push(processed.item);
13328
+ continue;
12743
13329
  }
13330
+ if (result.success && result.urlData) {
13331
+ await handleUrlData(processed.item, result.urlData, library, options.attachmentsDirectory);
13332
+ }
13333
+ added.push(processed.item);
12744
13334
  }
12745
13335
  if (added.length > 0) {
12746
13336
  await library.save();
12747
13337
  }
12748
13338
  return { added, failed, skipped };
12749
13339
  }
13340
+ async function handleUrlData(addedItem, urlData, library, attachmentsDirectory) {
13341
+ if (urlData.warnings.length > 0) {
13342
+ addedItem.warnings = [...urlData.warnings];
13343
+ }
13344
+ if (attachmentsDirectory) {
13345
+ try {
13346
+ await saveUrlData(addedItem.id, urlData, library, attachmentsDirectory);
13347
+ } catch (error) {
13348
+ const message = `Failed to save URL data for ${addedItem.id}: ${error instanceof Error ? error.message : String(error)}`;
13349
+ process.stderr.write(`${message}
13350
+ `);
13351
+ if (!addedItem.warnings) {
13352
+ addedItem.warnings = [];
13353
+ }
13354
+ addedItem.warnings.push(message);
13355
+ }
13356
+ }
13357
+ }
12750
13358
  function buildImportOptions(options) {
12751
13359
  const importOptions = {};
12752
13360
  if (options.format !== void 0) {
@@ -12758,6 +13366,15 @@ function buildImportOptions(options) {
12758
13366
  if (options.stdinContent !== void 0) {
12759
13367
  importOptions.stdinContent = options.stdinContent;
12760
13368
  }
13369
+ if (options.urlConfig !== void 0) {
13370
+ importOptions.urlConfig = options.urlConfig;
13371
+ }
13372
+ if (options.archiveFormat !== void 0) {
13373
+ importOptions.archiveFormat = options.archiveFormat;
13374
+ }
13375
+ if (options.noArchive !== void 0) {
13376
+ importOptions.noArchive = options.noArchive;
13377
+ }
12761
13378
  return importOptions;
12762
13379
  }
12763
13380
  async function processImportResult(result, existingItems, addedIds, force, library) {
@@ -12803,6 +13420,52 @@ async function processImportResult(result, existingItems, addedIds, force, libra
12803
13420
  }
12804
13421
  return { type: "added", item: addedItem };
12805
13422
  }
13423
+ async function saveUrlData(id, urlData, library, attachmentsDirectory) {
13424
+ if (urlData.fulltext) {
13425
+ let tempDir;
13426
+ try {
13427
+ tempDir = mkdtempSync(join(tmpdir(), "refmgr-url-ft-"));
13428
+ const fulltextPath = join(tempDir, "fulltext.md");
13429
+ writeFileSync(fulltextPath, urlData.fulltext, "utf-8");
13430
+ await addAttachment(library, {
13431
+ identifier: id,
13432
+ filePath: fulltextPath,
13433
+ role: "fulltext",
13434
+ move: true,
13435
+ force: true,
13436
+ idType: "id",
13437
+ attachmentsDirectory
13438
+ });
13439
+ } finally {
13440
+ if (tempDir) {
13441
+ await rm(tempDir, { recursive: true, force: true }).catch(() => {
13442
+ });
13443
+ }
13444
+ }
13445
+ }
13446
+ if (urlData.archive) {
13447
+ let tempDir;
13448
+ try {
13449
+ tempDir = mkdtempSync(join(tmpdir(), "refmgr-url-ar-"));
13450
+ const archivePath = join(tempDir, `archive.${urlData.archive.extension}`);
13451
+ writeFileSync(archivePath, urlData.archive.data, "utf-8");
13452
+ await addAttachment(library, {
13453
+ identifier: id,
13454
+ filePath: archivePath,
13455
+ role: "archive",
13456
+ move: true,
13457
+ force: false,
13458
+ idType: "id",
13459
+ attachmentsDirectory
13460
+ });
13461
+ } finally {
13462
+ if (tempDir) {
13463
+ await rm(tempDir, { recursive: true, force: true }).catch(() => {
13464
+ });
13465
+ }
13466
+ }
13467
+ }
13468
+ }
12806
13469
  function generateSuffix(index) {
12807
13470
  const alphabet = "abcdefghijklmnopqrstuvwxyz";
12808
13471
  let suffix = "";
@@ -12873,7 +13536,7 @@ function createAddRoute(library, config) {
12873
13536
  }
12874
13537
  const CHECK_CONCURRENCY = 5;
12875
13538
  async function checkReferences(library, options) {
12876
- const { checkReference } = await import("./checker-kVM4S67y.js");
13539
+ const { checkReference } = await import("./checker-D_kpqqRi.js");
12877
13540
  const save = options.save !== false;
12878
13541
  const skipDays = options.skipDays ?? 7;
12879
13542
  const items = await resolveItems(library, options);
@@ -13566,4 +14229,4 @@ export {
13566
14229
  createServer as y,
13567
14230
  fetch$1 as z
13568
14231
  };
13569
- //# sourceMappingURL=index-B4-i4PrU.js.map
14232
+ //# sourceMappingURL=index-CgOvxl5f.js.map