mirror of
https://github.com/OFFIS-ESC/constellation-analyzer
synced 2026-01-27 07:43:41 +00:00
feat: add PubMed and software citation format support
Extends bibliography smart import capabilities with additional citation.js plugins: - @citation-js/plugin-pubmed: Support for PubMed IDs (PMID/PMCID) - @citation-js/plugin-software-formats: Support for software citations (CFF, GitHub, npm, Zenodo) Updates smart-parser.ts to recognize and validate Zenodo DOI format (10.5281/zenodo.xxxxx). Improves input type detection for better user feedback. Includes code formatting improvements to bibliographyStore.ts for consistency. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
14ccb2da5b
commit
ef16b9d060
4 changed files with 169 additions and 86 deletions
106
package-lock.json
generated
106
package-lock.json
generated
|
|
@ -12,7 +12,9 @@
|
||||||
"@citation-js/plugin-bibtex": "^0.7.18",
|
"@citation-js/plugin-bibtex": "^0.7.18",
|
||||||
"@citation-js/plugin-csl": "^0.7.18",
|
"@citation-js/plugin-csl": "^0.7.18",
|
||||||
"@citation-js/plugin-doi": "^0.7.18",
|
"@citation-js/plugin-doi": "^0.7.18",
|
||||||
|
"@citation-js/plugin-pubmed": "^0.3.0",
|
||||||
"@citation-js/plugin-ris": "^0.7.18",
|
"@citation-js/plugin-ris": "^0.7.18",
|
||||||
|
"@citation-js/plugin-software-formats": "^0.6.1",
|
||||||
"@emotion/react": "^11.11.3",
|
"@emotion/react": "^11.11.3",
|
||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.0",
|
||||||
"@mui/icons-material": "^5.15.10",
|
"@mui/icons-material": "^5.15.10",
|
||||||
|
|
@ -377,6 +379,18 @@
|
||||||
"@citation-js/core": "^0.7.0"
|
"@citation-js/core": "^0.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@citation-js/plugin-cff": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@citation-js/plugin-cff/-/plugin-cff-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-tLjTgsfzNOdQWGn5mNc2NAaydHnlRucSERoyAXLN7u0BQBfp7j5zwdxCmxcQD/N7hH3fpDKMG+qDzbqpJuKyNA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@citation-js/date": "^0.5.0",
|
||||||
|
"@citation-js/plugin-yaml": "^0.6.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@citation-js/plugin-csl": {
|
"node_modules/@citation-js/plugin-csl": {
|
||||||
"version": "0.7.18",
|
"version": "0.7.18",
|
||||||
"resolved": "https://registry.npmjs.org/@citation-js/plugin-csl/-/plugin-csl-0.7.18.tgz",
|
"resolved": "https://registry.npmjs.org/@citation-js/plugin-csl/-/plugin-csl-0.7.18.tgz",
|
||||||
|
|
@ -406,6 +420,41 @@
|
||||||
"@citation-js/core": "^0.7.0"
|
"@citation-js/core": "^0.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@citation-js/plugin-github": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@citation-js/plugin-github/-/plugin-github-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-1ZeSgQ5AoYsa8n2acVooUeRk76oA8rLszYNBjzj5z6MPa11BZlQJ9O+Gy4tHjlImvsENLbLPx5f8/V1VHXaCfQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@citation-js/date": "^0.5.0",
|
||||||
|
"@citation-js/name": "^0.4.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@citation-js/plugin-npm": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@citation-js/plugin-npm/-/plugin-npm-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-rojJA+l/p2KBpDoY+8n0YfNyQO1Aw03fQR5BN+gXD1LNAP1V+8wqvdPsaHnzPsrhrd4ZXDR7ch/Nk0yynPkJ3Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@citation-js/date": "^0.5.0",
|
||||||
|
"@citation-js/name": "^0.4.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@citation-js/plugin-pubmed": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@citation-js/plugin-pubmed/-/plugin-pubmed-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-E3l83VP5UnTh6lLJaTaNBIkNgIx3U6IjDNf7j5l4geBOaOwDIhkl9X+Ss33/MMNEIMNDsBoodoMJTZ8Cq+C/ug==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@citation-js/core": ">=0.5.1 <=0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@citation-js/plugin-ris": {
|
"node_modules/@citation-js/plugin-ris": {
|
||||||
"version": "0.7.18",
|
"version": "0.7.18",
|
||||||
"resolved": "https://registry.npmjs.org/@citation-js/plugin-ris/-/plugin-ris-0.7.18.tgz",
|
"resolved": "https://registry.npmjs.org/@citation-js/plugin-ris/-/plugin-ris-0.7.18.tgz",
|
||||||
|
|
@ -421,6 +470,44 @@
|
||||||
"@citation-js/core": "^0.7.0"
|
"@citation-js/core": "^0.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@citation-js/plugin-software-formats": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@citation-js/plugin-software-formats/-/plugin-software-formats-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-BDF9rqi56K0hoTgYTVANCFVRSbWKC9V06Uap7oa8SjqCTgnHJAy8t/F3NxsyYPPG+zmRsLW9VNbcIsJOl0eu/w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@citation-js/plugin-cff": "^0.6.1",
|
||||||
|
"@citation-js/plugin-github": "^0.6.1",
|
||||||
|
"@citation-js/plugin-npm": "^0.6.1",
|
||||||
|
"@citation-js/plugin-yaml": "^0.6.1",
|
||||||
|
"@citation-js/plugin-zenodo": "^0.6.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@citation-js/plugin-yaml": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@citation-js/plugin-yaml/-/plugin-yaml-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-XEVVks1cJTqRbjy+nmthfw/puR6NwRB3fyJWi1tX13UYXlkhP/h45nsv4zjgLLGekdcMHQvhad9MAYunOftGKA==",
|
||||||
|
"dependencies": {
|
||||||
|
"js-yaml": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@citation-js/plugin-zenodo": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@citation-js/plugin-zenodo/-/plugin-zenodo-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-bUybENHoZqJ6gheUqgkumjI+mu+fA2bg6VoniDmZTb7Qng9iEpi+IWEAR26/vBE0gK0EWrJjczyDW3HCwrhvVw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@citation-js/date": "^0.5.0",
|
||||||
|
"@citation-js/name": "^0.4.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@emotion/babel-plugin": {
|
"node_modules/@emotion/babel-plugin": {
|
||||||
"version": "11.13.5",
|
"version": "11.13.5",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
|
||||||
|
|
@ -2103,6 +2190,7 @@
|
||||||
"version": "18.3.26",
|
"version": "18.3.26",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.26.tgz",
|
||||||
"integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==",
|
"integrity": "sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==",
|
||||||
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
|
|
@ -2436,8 +2524,7 @@
|
||||||
"node_modules/argparse": {
|
"node_modules/argparse": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/array-union": {
|
"node_modules/array-union": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
|
|
@ -3784,7 +3871,6 @@
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": "^2.0.1"
|
"argparse": "^2.0.1"
|
||||||
},
|
},
|
||||||
|
|
@ -5402,20 +5488,6 @@
|
||||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/yaml": {
|
|
||||||
"version": "2.8.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz",
|
|
||||||
"integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
|
|
||||||
"dev": true,
|
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
|
||||||
"yaml": "bin.mjs"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/yocto-queue": {
|
"node_modules/yocto-queue": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,9 @@
|
||||||
"@citation-js/plugin-bibtex": "^0.7.18",
|
"@citation-js/plugin-bibtex": "^0.7.18",
|
||||||
"@citation-js/plugin-csl": "^0.7.18",
|
"@citation-js/plugin-csl": "^0.7.18",
|
||||||
"@citation-js/plugin-doi": "^0.7.18",
|
"@citation-js/plugin-doi": "^0.7.18",
|
||||||
|
"@citation-js/plugin-pubmed": "^0.3.0",
|
||||||
"@citation-js/plugin-ris": "^0.7.18",
|
"@citation-js/plugin-ris": "^0.7.18",
|
||||||
|
"@citation-js/plugin-software-formats": "^0.6.1",
|
||||||
"@emotion/react": "^11.11.3",
|
"@emotion/react": "^11.11.3",
|
||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.0",
|
||||||
"@mui/icons-material": "^5.15.10",
|
"@mui/icons-material": "^5.15.10",
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,20 @@
|
||||||
import { create } from 'zustand';
|
import { create } from "zustand";
|
||||||
// @ts-expect-error - citation.js doesn't have TypeScript definitions
|
// @ts-expect-error - citation.js doesn't have TypeScript definitions
|
||||||
import { Cite } from '@citation-js/core';
|
import { Cite } from "@citation-js/core";
|
||||||
// Load plugins
|
// Load plugins
|
||||||
import '@citation-js/plugin-csl';
|
import "@citation-js/plugin-csl";
|
||||||
import '@citation-js/plugin-doi';
|
import "@citation-js/plugin-doi";
|
||||||
import '@citation-js/plugin-bibtex';
|
import "@citation-js/plugin-bibtex";
|
||||||
import '@citation-js/plugin-ris';
|
import "@citation-js/plugin-ris";
|
||||||
|
import "@citation-js/plugin-pubmed";
|
||||||
|
import "@citation-js/plugin-software-formats";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
BibliographyReference,
|
BibliographyReference,
|
||||||
CSLReference,
|
CSLReference,
|
||||||
ReferenceAppMetadata,
|
ReferenceAppMetadata,
|
||||||
BibliographySettings
|
BibliographySettings,
|
||||||
} from '../types/bibliography';
|
} from "../types/bibliography";
|
||||||
|
|
||||||
interface BibliographyStore {
|
interface BibliographyStore {
|
||||||
// State
|
// State
|
||||||
|
|
@ -43,18 +45,22 @@ interface BibliographyStore {
|
||||||
setSettings: (settings: BibliographySettings) => void;
|
setSettings: (settings: BibliographySettings) => void;
|
||||||
|
|
||||||
// Citation.js powered operations
|
// Citation.js powered operations
|
||||||
formatReference: (id: string, style?: string, format?: 'html' | 'text') => string;
|
formatReference: (
|
||||||
formatBibliography: (style?: string, format?: 'html' | 'text') => string;
|
id: string,
|
||||||
|
style?: string,
|
||||||
|
format?: "html" | "text",
|
||||||
|
) => string;
|
||||||
|
formatBibliography: (style?: string, format?: "html" | "text") => string;
|
||||||
parseInput: (input: string) => Promise<CSLReference[]>;
|
parseInput: (input: string) => Promise<CSLReference[]>;
|
||||||
exportAs: (format: 'bibtex' | 'ris' | 'json') => string;
|
exportAs: (format: "bibtex" | "ris" | "json") => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useBibliographyStore = create<BibliographyStore>((set, get) => ({
|
export const useBibliographyStore = create<BibliographyStore>((set, get) => ({
|
||||||
citeInstance: new Cite([]),
|
citeInstance: new Cite([]),
|
||||||
appMetadata: {},
|
appMetadata: {},
|
||||||
settings: {
|
settings: {
|
||||||
defaultStyle: 'apa',
|
defaultStyle: "apa",
|
||||||
sortOrder: 'author',
|
sortOrder: "author",
|
||||||
},
|
},
|
||||||
|
|
||||||
getReferences: () => {
|
getReferences: () => {
|
||||||
|
|
@ -62,7 +68,7 @@ export const useBibliographyStore = create<BibliographyStore>((set, get) => ({
|
||||||
const metadata = get().appMetadata;
|
const metadata = get().appMetadata;
|
||||||
|
|
||||||
// Merge CSL data with app metadata
|
// Merge CSL data with app metadata
|
||||||
return csl.map(ref => ({
|
return csl.map((ref) => ({
|
||||||
...ref,
|
...ref,
|
||||||
_app: metadata[ref.id],
|
_app: metadata[ref.id],
|
||||||
}));
|
}));
|
||||||
|
|
@ -70,7 +76,7 @@ export const useBibliographyStore = create<BibliographyStore>((set, get) => ({
|
||||||
|
|
||||||
getReferenceById: (id) => {
|
getReferenceById: (id) => {
|
||||||
const refs = get().getReferences();
|
const refs = get().getReferences();
|
||||||
return refs.find(ref => ref.id === id);
|
return refs.find((ref) => ref.id === id);
|
||||||
},
|
},
|
||||||
|
|
||||||
getCSLData: () => {
|
getCSLData: () => {
|
||||||
|
|
@ -81,7 +87,8 @@ export const useBibliographyStore = create<BibliographyStore>((set, get) => ({
|
||||||
const { citeInstance } = get();
|
const { citeInstance } = get();
|
||||||
|
|
||||||
// Generate ID if not provided
|
// Generate ID if not provided
|
||||||
const id = ref.id || `ref-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
const id =
|
||||||
|
ref.id || `ref-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
||||||
const fullRef = { ...ref, id };
|
const fullRef = { ...ref, id };
|
||||||
|
|
||||||
// Use citation.js add() method
|
// Use citation.js add() method
|
||||||
|
|
@ -89,7 +96,7 @@ export const useBibliographyStore = create<BibliographyStore>((set, get) => ({
|
||||||
|
|
||||||
// Add app metadata
|
// Add app metadata
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
set(state => ({
|
set((state) => ({
|
||||||
appMetadata: {
|
appMetadata: {
|
||||||
...state.appMetadata,
|
...state.appMetadata,
|
||||||
[id]: {
|
[id]: {
|
||||||
|
|
@ -109,15 +116,15 @@ export const useBibliographyStore = create<BibliographyStore>((set, get) => ({
|
||||||
const data = citeInstance.data as CSLReference[];
|
const data = citeInstance.data as CSLReference[];
|
||||||
|
|
||||||
// Find and update the reference
|
// Find and update the reference
|
||||||
const updatedData = data.map(ref =>
|
const updatedData = data.map((ref) =>
|
||||||
ref.id === id ? { ...ref, ...updates } : ref
|
ref.id === id ? { ...ref, ...updates } : ref,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Use citation.js set() method to replace all data
|
// Use citation.js set() method to replace all data
|
||||||
citeInstance.set(updatedData);
|
citeInstance.set(updatedData);
|
||||||
|
|
||||||
// Update metadata timestamp
|
// Update metadata timestamp
|
||||||
set(state => ({
|
set((state) => ({
|
||||||
appMetadata: {
|
appMetadata: {
|
||||||
...state.appMetadata,
|
...state.appMetadata,
|
||||||
[id]: {
|
[id]: {
|
||||||
|
|
@ -133,11 +140,11 @@ export const useBibliographyStore = create<BibliographyStore>((set, get) => ({
|
||||||
const data = citeInstance.data as CSLReference[];
|
const data = citeInstance.data as CSLReference[];
|
||||||
|
|
||||||
// Remove from citation.js
|
// Remove from citation.js
|
||||||
const filteredData = data.filter(ref => ref.id !== id);
|
const filteredData = data.filter((ref) => ref.id !== id);
|
||||||
citeInstance.set(filteredData);
|
citeInstance.set(filteredData);
|
||||||
|
|
||||||
// Remove metadata
|
// Remove metadata
|
||||||
set(state => {
|
set((state) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { [id]: _removed, ...rest } = state.appMetadata;
|
const { [id]: _removed, ...rest } = state.appMetadata;
|
||||||
return { appMetadata: rest };
|
return { appMetadata: rest };
|
||||||
|
|
@ -146,7 +153,7 @@ export const useBibliographyStore = create<BibliographyStore>((set, get) => ({
|
||||||
|
|
||||||
duplicateReference: (id) => {
|
duplicateReference: (id) => {
|
||||||
const original = get().getReferenceById(id);
|
const original = get().getReferenceById(id);
|
||||||
if (!original) return '';
|
if (!original) return "";
|
||||||
|
|
||||||
const newId = `ref-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
const newId = `ref-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
||||||
const duplicate = {
|
const duplicate = {
|
||||||
|
|
@ -173,7 +180,7 @@ export const useBibliographyStore = create<BibliographyStore>((set, get) => ({
|
||||||
const metadata: Record<string, ReferenceAppMetadata> = {};
|
const metadata: Record<string, ReferenceAppMetadata> = {};
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
|
|
||||||
refs.forEach(ref => {
|
refs.forEach((ref) => {
|
||||||
metadata[ref.id] = {
|
metadata[ref.id] = {
|
||||||
id: ref.id,
|
id: ref.id,
|
||||||
tags: [],
|
tags: [],
|
||||||
|
|
@ -193,9 +200,9 @@ export const useBibliographyStore = create<BibliographyStore>((set, get) => ({
|
||||||
|
|
||||||
// Add metadata for new references
|
// Add metadata for new references
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
set(state => {
|
set((state) => {
|
||||||
const newMetadata = { ...state.appMetadata };
|
const newMetadata = { ...state.appMetadata };
|
||||||
refs.forEach(ref => {
|
refs.forEach((ref) => {
|
||||||
if (!newMetadata[ref.id]) {
|
if (!newMetadata[ref.id]) {
|
||||||
newMetadata[ref.id] = {
|
newMetadata[ref.id] = {
|
||||||
id: ref.id,
|
id: ref.id,
|
||||||
|
|
@ -215,7 +222,7 @@ export const useBibliographyStore = create<BibliographyStore>((set, get) => ({
|
||||||
},
|
},
|
||||||
|
|
||||||
updateMetadata: (id, updates) => {
|
updateMetadata: (id, updates) => {
|
||||||
set(state => ({
|
set((state) => ({
|
||||||
appMetadata: {
|
appMetadata: {
|
||||||
...state.appMetadata,
|
...state.appMetadata,
|
||||||
[id]: {
|
[id]: {
|
||||||
|
|
@ -229,34 +236,34 @@ export const useBibliographyStore = create<BibliographyStore>((set, get) => ({
|
||||||
|
|
||||||
setSettings: (settings) => set({ settings }),
|
setSettings: (settings) => set({ settings }),
|
||||||
|
|
||||||
formatReference: (id, style, format = 'html') => {
|
formatReference: (id, style, format = "html") => {
|
||||||
const { citeInstance, settings } = get();
|
const { citeInstance, settings } = get();
|
||||||
const styleToUse = style || settings.defaultStyle;
|
const styleToUse = style || settings.defaultStyle;
|
||||||
|
|
||||||
// Find the reference
|
// Find the reference
|
||||||
const ref = (citeInstance.data as CSLReference[]).find(r => r.id === id);
|
const ref = (citeInstance.data as CSLReference[]).find((r) => r.id === id);
|
||||||
if (!ref) return '';
|
if (!ref) return "";
|
||||||
|
|
||||||
// Create temporary Cite instance for single reference
|
// Create temporary Cite instance for single reference
|
||||||
const cite = new Cite(ref);
|
const cite = new Cite(ref);
|
||||||
|
|
||||||
// Use citation.js format() method
|
// Use citation.js format() method
|
||||||
return cite.format('bibliography', {
|
return cite.format("bibliography", {
|
||||||
format: format,
|
format: format,
|
||||||
template: styleToUse,
|
template: styleToUse,
|
||||||
lang: 'en-US',
|
lang: "en-US",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
formatBibliography: (style, format = 'html') => {
|
formatBibliography: (style, format = "html") => {
|
||||||
const { citeInstance, settings } = get();
|
const { citeInstance, settings } = get();
|
||||||
const styleToUse = style || settings.defaultStyle;
|
const styleToUse = style || settings.defaultStyle;
|
||||||
|
|
||||||
// Use citation.js format() method on entire instance
|
// Use citation.js format() method on entire instance
|
||||||
return citeInstance.format('bibliography', {
|
return citeInstance.format("bibliography", {
|
||||||
format: format,
|
format: format,
|
||||||
template: styleToUse,
|
template: styleToUse,
|
||||||
lang: 'en-US',
|
lang: "en-US",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -267,8 +274,8 @@ export const useBibliographyStore = create<BibliographyStore>((set, get) => ({
|
||||||
const cite = await Cite.async(input);
|
const cite = await Cite.async(input);
|
||||||
return cite.data as CSLReference[];
|
return cite.data as CSLReference[];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to parse input:', error);
|
console.error("Failed to parse input:", error);
|
||||||
throw new Error('Could not parse citation data');
|
throw new Error("Could not parse citation data");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -277,14 +284,14 @@ export const useBibliographyStore = create<BibliographyStore>((set, get) => ({
|
||||||
|
|
||||||
// Use citation.js format() method
|
// Use citation.js format() method
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case 'bibtex':
|
case "bibtex":
|
||||||
return citeInstance.format('bibtex');
|
return citeInstance.format("bibtex");
|
||||||
case 'ris':
|
case "ris":
|
||||||
return citeInstance.format('ris');
|
return citeInstance.format("ris");
|
||||||
case 'json':
|
case "json":
|
||||||
return JSON.stringify(citeInstance.data, null, 2);
|
return JSON.stringify(citeInstance.data, null, 2);
|
||||||
default:
|
default:
|
||||||
return '';
|
return "";
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
@ -295,8 +302,8 @@ export const clearBibliographyForDocumentSwitch = () => {
|
||||||
citeInstance: new Cite([]),
|
citeInstance: new Cite([]),
|
||||||
appMetadata: {},
|
appMetadata: {},
|
||||||
settings: {
|
settings: {
|
||||||
defaultStyle: 'apa',
|
defaultStyle: "apa",
|
||||||
sortOrder: 'author',
|
sortOrder: "author",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,21 @@
|
||||||
// @ts-expect-error - citation.js doesn't have TypeScript definitions
|
// @ts-expect-error - citation.js doesn't have TypeScript definitions
|
||||||
import { Cite } from '@citation-js/core';
|
import { Cite } from "@citation-js/core";
|
||||||
import type { CSLReference } from '../../types/bibliography';
|
import type { CSLReference } from "../../types/bibliography";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse any citation input using citation.js
|
* Parse any citation input using citation.js
|
||||||
* Handles: DOI, URL, BibTeX, RIS, ISBN, PubMed ID, etc.
|
* Handles: DOI, URL, BibTeX, RIS, ISBN, PubMed ID, etc.
|
||||||
*/
|
*/
|
||||||
export const parseSmartInput = async (input: string): Promise<CSLReference[]> => {
|
export const parseSmartInput = async (
|
||||||
|
input: string,
|
||||||
|
): Promise<CSLReference[]> => {
|
||||||
try {
|
try {
|
||||||
const cite = await Cite.async(input);
|
const cite = await Cite.async(input);
|
||||||
return cite.data as CSLReference[];
|
return cite.data as CSLReference[];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Could not parse input: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
throw new Error(
|
||||||
|
`Could not parse input: ${error instanceof Error ? error.message : "Unknown error"}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -46,9 +50,8 @@ export const isValidCitationInput = (input: string): boolean => {
|
||||||
// Wikidata ID (Q followed by numbers)
|
// Wikidata ID (Q followed by numbers)
|
||||||
if (/^(wikidata:)?Q\d+$/i.test(trimmed)) return true;
|
if (/^(wikidata:)?Q\d+$/i.test(trimmed)) return true;
|
||||||
|
|
||||||
// Zenodo ID or URL
|
// Zenodo DOI (10.5281/zenodo.xxxxx)
|
||||||
if (/^zenodo\.\d+$/i.test(trimmed)) return true;
|
if (/^10\.5281\/zenodo\.\d+/i.test(trimmed)) return true;
|
||||||
if (/zenodo\.org\/record\/\d+/i.test(trimmed)) return true;
|
|
||||||
|
|
||||||
// CFF (Citation File Format) - YAML-based
|
// CFF (Citation File Format) - YAML-based
|
||||||
if (/^cff-version:/m.test(trimmed)) return true;
|
if (/^cff-version:/m.test(trimmed)) return true;
|
||||||
|
|
@ -62,17 +65,16 @@ export const isValidCitationInput = (input: string): boolean => {
|
||||||
export const getInputTypeHint = (input: string): string => {
|
export const getInputTypeHint = (input: string): string => {
|
||||||
const trimmed = input.trim();
|
const trimmed = input.trim();
|
||||||
|
|
||||||
if (/^10\.\d{4,}\//.test(trimmed)) return 'DOI';
|
if (/^10\.5281\/zenodo\.\d+/i.test(trimmed)) return "Zenodo DOI";
|
||||||
if (/zenodo\.org\/record\/\d+/i.test(trimmed)) return 'Zenodo URL';
|
if (/^10\.\d{4,}\//.test(trimmed)) return "DOI";
|
||||||
if (/^zenodo\.\d+$/i.test(trimmed)) return 'Zenodo ID';
|
if (/^https?:\/\//.test(trimmed)) return "URL";
|
||||||
if (/^https?:\/\//.test(trimmed)) return 'URL';
|
if (/^@\w+\{/.test(trimmed)) return "BibTeX";
|
||||||
if (/^@\w+\{/.test(trimmed)) return 'BibTeX';
|
if (/^TY\s+-\s+/m.test(trimmed)) return "RIS Format";
|
||||||
if (/^TY\s+-\s+/m.test(trimmed)) return 'RIS Format';
|
if (/^PMID:/i.test(trimmed)) return "PubMed ID (PMID)";
|
||||||
if (/^PMID:/i.test(trimmed)) return 'PubMed ID (PMID)';
|
if (/^PMC\d+/i.test(trimmed)) return "PubMed Central ID (PMCID)";
|
||||||
if (/^PMC\d+/i.test(trimmed)) return 'PubMed Central ID (PMCID)';
|
if (/^ISBN/i.test(trimmed)) return "ISBN";
|
||||||
if (/^ISBN/i.test(trimmed)) return 'ISBN';
|
if (/^(wikidata:)?Q\d+$/i.test(trimmed)) return "Wikidata ID";
|
||||||
if (/^(wikidata:)?Q\d+$/i.test(trimmed)) return 'Wikidata ID';
|
if (/^cff-version:/m.test(trimmed)) return "Citation File Format (CFF)";
|
||||||
if (/^cff-version:/m.test(trimmed)) return 'Citation File Format (CFF)';
|
|
||||||
|
|
||||||
return 'Unknown';
|
return "Unknown";
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue