dsh-cite 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +4 -0
- package/README.en.md +3 -1
- package/README.md +3 -1
- package/docs/CHANGELOG.en.md +4 -0
- package/docs/USAGE.en.md +4 -0
- package/docs/USAGE.md +4 -0
- package/lib/citation-errors.js +2 -0
- package/lib/crossref.js +34 -9
- package/package.json +7 -2
package/CHANGELOG.md
CHANGED
package/README.en.md
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
[中文](README.md)
|
|
4
4
|
|
|
5
|
+

|
|
6
|
+
|
|
5
7
|
Organize DOI lists or BibTeX into a reviewable local library, then export references and BibTeX.
|
|
6
8
|
|
|
7
|
-
[](https://www.npmjs.com/package/dsh-cite) [](https://www.npmjs.com/package/dsh-cite) [](https://www.npmjs.com/package/dsh-cite)
|
|
8
10
|
|
|
9
11
|
## What it does
|
|
10
12
|
|
package/README.md
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README.en.md)
|
|
4
4
|
|
|
5
|
+

|
|
6
|
+
|
|
5
7
|
把 DOI 列表或 BibTeX 整理成可核对的本地文献库,再导出参考文献与 BibTeX。
|
|
6
8
|
|
|
7
|
-
[](https://www.npmjs.com/package/dsh-cite) [](https://www.npmjs.com/package/dsh-cite) [](https://www.npmjs.com/package/dsh-cite)
|
|
8
10
|
|
|
9
11
|
## 功能
|
|
10
12
|
|
package/docs/CHANGELOG.en.md
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
These English notes preserve the earlier translations. The main changelog contains the consolidated version history.
|
|
6
6
|
|
|
7
|
+
## 0.4.2 (2026-10-05)
|
|
8
|
+
|
|
9
|
+
- Preserve balanced parentheses inside DOIs while removing surrounding prose punctuation. Crossref response parsing respects cancellation and timeouts.
|
|
10
|
+
|
|
7
11
|
## 0.4.1 (2026-10-03)
|
|
8
12
|
|
|
9
13
|
Fixes re-importing exported BibTeX containing publisher lists. Publisher names and original sources remain intact. All 41 Windows tests and re-importing the actual browser download pass.
|
package/docs/USAGE.en.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
[Overview](../README.en.md) · [Changelog](../CHANGELOG.md) · [Validation](VALIDATION.md)
|
|
4
4
|
|
|
5
|
+
## Current improvements
|
|
6
|
+
|
|
7
|
+
DOIs with balanced parentheses can be queried directly. Transport or parsing timeouts are explicit, and cancellation stops waiting. Offline library import and external Crossref lookup are verified separately.
|
|
8
|
+
|
|
5
9
|
## Citation workbench
|
|
6
10
|
|
|
7
11
|
1. Open **Settings → Cite**. Paste one DOI per line, paste BibTeX, or upload a `.bib` file.
|
package/docs/USAGE.md
CHANGED
package/lib/citation-errors.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** English UI messages; bibliographic content and original source text stay unchanged. */
|
|
2
2
|
export function englishCitationError(raw) {
|
|
3
|
+
if (/^Crossref.*超时/.test(raw))
|
|
4
|
+
return 'Crossref timed out. Retry later; your local entries were preserved.';
|
|
3
5
|
if (/^Crossref 请求失败:/.test(raw))
|
|
4
6
|
return raw.replace(/^Crossref 请求失败:/, 'Crossref request failed: ');
|
|
5
7
|
if (/^DOI 在 Crossref 中不存在:/.test(raw))
|
package/lib/crossref.js
CHANGED
|
@@ -67,19 +67,32 @@ function requestSignal(timeoutMs, signal) {
|
|
|
67
67
|
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
68
68
|
return signal === undefined ? timeoutSignal : AbortSignal.any([signal, timeoutSignal]);
|
|
69
69
|
}
|
|
70
|
+
function withAbort(value, signal) {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const abort = () => reject(signal.reason);
|
|
73
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
74
|
+
value.then(result => { signal.removeEventListener('abort', abort); resolve(result); }, error => { signal.removeEventListener('abort', abort); reject(error); });
|
|
75
|
+
if (signal.aborted)
|
|
76
|
+
abort();
|
|
77
|
+
});
|
|
78
|
+
}
|
|
70
79
|
/** 按 DOI 查询 Crossref 并归一化。 */
|
|
71
80
|
export async function lookupDoi(doi, cfg, fetchImpl, signal) {
|
|
72
81
|
const clean = assertDoi(doi);
|
|
82
|
+
signal?.throwIfAborted();
|
|
83
|
+
const deadline = requestSignal(cfg.timeoutMs, signal);
|
|
73
84
|
const fetcher = fetchImpl ?? globalThis.fetch;
|
|
74
85
|
let response;
|
|
75
86
|
try {
|
|
76
|
-
response = await fetcher(CROSSREF_API + encodeURIComponent(clean), {
|
|
87
|
+
response = await withAbort(fetcher(CROSSREF_API + encodeURIComponent(clean), {
|
|
77
88
|
headers: makeHeaders(cfg),
|
|
78
|
-
signal:
|
|
79
|
-
});
|
|
89
|
+
signal: deadline,
|
|
90
|
+
}), deadline);
|
|
80
91
|
}
|
|
81
92
|
catch (error) {
|
|
82
93
|
signal?.throwIfAborted();
|
|
94
|
+
if (deadline.aborted)
|
|
95
|
+
throw new Error('Crossref 请求超时,请稍后重试;本地文献已保留。');
|
|
83
96
|
const code = error instanceof Error && typeof error.cause?.code === 'string' && /^[A-Z0-9_]+$/.test(error.cause.code) ? ' (' + error.cause.code + ')' : '';
|
|
84
97
|
throw new Error('Crossref 请求失败:' + (error instanceof Error ? error.message : String(error)) + code);
|
|
85
98
|
}
|
|
@@ -90,9 +103,12 @@ export async function lookupDoi(doi, cfg, fetchImpl, signal) {
|
|
|
90
103
|
}
|
|
91
104
|
let payload;
|
|
92
105
|
try {
|
|
93
|
-
payload = await response.json();
|
|
106
|
+
payload = await withAbort(response.json(), deadline);
|
|
94
107
|
}
|
|
95
108
|
catch {
|
|
109
|
+
signal?.throwIfAborted();
|
|
110
|
+
if (deadline.aborted)
|
|
111
|
+
throw new Error('Crossref 响应读取超时,请稍后重试;本地文献已保留。');
|
|
96
112
|
throw new Error('Crossref 响应不是合法 JSON。');
|
|
97
113
|
}
|
|
98
114
|
const message = record(payload).message;
|
|
@@ -106,28 +122,35 @@ export async function lookupDoi(doi, cfg, fetchImpl, signal) {
|
|
|
106
122
|
/** 按题录文本检索 Crossref,返回归一化结果。 */
|
|
107
123
|
export async function searchWorks(query, limit, cfg, fetchImpl, signal) {
|
|
108
124
|
const q = query.trim();
|
|
125
|
+
signal?.throwIfAborted();
|
|
126
|
+
const deadline = requestSignal(cfg.timeoutMs, signal);
|
|
109
127
|
if (q === '')
|
|
110
128
|
throw new Error('检索词不能为空。');
|
|
111
129
|
const url = CROSSREF_API + '?query.bibliographic=' + encodeURIComponent(q) + '&rows=' + String(limit);
|
|
112
130
|
const fetcher = fetchImpl ?? globalThis.fetch;
|
|
113
131
|
let response;
|
|
114
132
|
try {
|
|
115
|
-
response = await fetcher(url, {
|
|
133
|
+
response = await withAbort(fetcher(url, {
|
|
116
134
|
headers: makeHeaders(cfg),
|
|
117
|
-
signal:
|
|
118
|
-
});
|
|
135
|
+
signal: deadline,
|
|
136
|
+
}), deadline);
|
|
119
137
|
}
|
|
120
138
|
catch (error) {
|
|
121
139
|
signal?.throwIfAborted();
|
|
140
|
+
if (deadline.aborted)
|
|
141
|
+
throw new Error('Crossref 请求超时,请稍后重试;本地文献已保留。');
|
|
122
142
|
throw new Error('Crossref 检索失败:' + (error instanceof Error ? error.message : String(error)));
|
|
123
143
|
}
|
|
124
144
|
if (!response.ok)
|
|
125
145
|
throw new Error('Crossref 返回 HTTP ' + response.status + ',无法完成检索。');
|
|
126
146
|
let payload;
|
|
127
147
|
try {
|
|
128
|
-
payload = await response.json();
|
|
148
|
+
payload = await withAbort(response.json(), deadline);
|
|
129
149
|
}
|
|
130
150
|
catch {
|
|
151
|
+
signal?.throwIfAborted();
|
|
152
|
+
if (deadline.aborted)
|
|
153
|
+
throw new Error('Crossref 响应读取超时,请稍后重试;本地文献已保留。');
|
|
131
154
|
throw new Error('Crossref 响应不是合法 JSON。');
|
|
132
155
|
}
|
|
133
156
|
const items = record(payload).message;
|
|
@@ -142,7 +165,9 @@ export function extractDois(text) {
|
|
|
142
165
|
const seen = new Set();
|
|
143
166
|
const result = [];
|
|
144
167
|
for (const match of text.match(pattern) ?? []) {
|
|
145
|
-
|
|
168
|
+
let clean = match.replace(/[.,;:\]}'">]+$/, '').trim();
|
|
169
|
+
while (clean.endsWith(')') && (clean.match(/\)/g)?.length ?? 0) > (clean.match(/\(/g)?.length ?? 0))
|
|
170
|
+
clean = clean.slice(0, -1);
|
|
146
171
|
if (seen.has(clean.toLowerCase()))
|
|
147
172
|
continue;
|
|
148
173
|
seen.add(clean.toLowerCase());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-cite",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "批量导入 DOI / BibTeX,预览去重、补全文献元数据并导出引文。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -29,7 +29,12 @@
|
|
|
29
29
|
"prepublishOnly": "pnpm run build"
|
|
30
30
|
},
|
|
31
31
|
"dsh": {
|
|
32
|
-
"client": {
|
|
32
|
+
"client": {
|
|
33
|
+
"inject": [
|
|
34
|
+
"@deepseek-ai/dsh-client-ui-settings"
|
|
35
|
+
],
|
|
36
|
+
"platform": "web"
|
|
37
|
+
},
|
|
33
38
|
"bundle": {
|
|
34
39
|
"patch": "./cordis.patch.yml"
|
|
35
40
|
}
|