dsh-cite 0.4.1 → 0.5.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/CHANGELOG.md CHANGED
@@ -4,6 +4,14 @@
4
4
 
5
5
  [历史英文记录](docs/CHANGELOG.en.md)
6
6
 
7
+ ## 0.5.0(2026-10-17)
8
+
9
+ - 设置面板顶部新增「首次使用」分步引导:一次只讲一步,支持上一步/下一步/跳过,看完记在本机不再出现。
10
+ - 引导内容与该插件的实际用法一致;可在开发者工具里清掉 `dsh-first-run:dsh-cite:v1` 重新查看。
11
+ ## 0.4.2 (2026-10-05)
12
+
13
+ - 保留 DOI 内合法的成对括号,正确剥除正文外层标点;Crossref 响应读取也受取消与超时控制。
14
+
7
15
  ## 0.4.1 (2026-10-03)
8
16
 
9
17
  - 修复含多个出版社名称的 BibTeX 导出后再导入失败,保留出版社名称及原始来源。41 项 Windows 测试与真实下载文件的再导入通过。
package/README.en.md CHANGED
@@ -2,9 +2,13 @@
2
2
 
3
3
  [中文](README.md)
4
4
 
5
+ ![dsh-cite whale girl plugin cover](https://raw.githubusercontent.com/STARDUSTLC666/dsh-cite/master/assets/cover-whale-girl.png)
6
+
5
7
  Organize DOI lists or BibTeX into a reviewable local library, then export references and BibTeX.
6
8
 
7
- [![npm](https://img.shields.io/npm/v/dsh-cite)](https://www.npmjs.com/package/dsh-cite) [![downloads](https://img.shields.io/npm/dm/dsh-cite)](https://www.npmjs.com/package/dsh-cite)
9
+ [![npm](https://img.shields.io/npm/v/dsh-cite)](https://www.npmjs.com/package/dsh-cite) [![downloads](https://raw.githubusercontent.com/STARDUSTLC666/dsh-suite/npm-downloads/assets/dsh-cite-downloads.svg)](https://www.npmjs.com/package/dsh-cite)
10
+
11
+ Feedback and contributions are welcome: report [issues](https://github.com/STARDUSTLC666/dsh-cite/issues) or submit [pull requests](https://github.com/STARDUSTLC666/dsh-cite/pulls).
8
12
 
9
13
  ## What it does
10
14
 
package/README.md CHANGED
@@ -2,9 +2,13 @@
2
2
 
3
3
  [English](README.en.md)
4
4
 
5
+ ![dsh-cite 鲸鱼娘插件封面](https://raw.githubusercontent.com/STARDUSTLC666/dsh-cite/master/assets/cover-whale-girl.png)
6
+
5
7
  把 DOI 列表或 BibTeX 整理成可核对的本地文献库,再导出参考文献与 BibTeX。
6
8
 
7
- [![npm](https://img.shields.io/npm/v/dsh-cite)](https://www.npmjs.com/package/dsh-cite) [![downloads](https://img.shields.io/npm/dm/dsh-cite)](https://www.npmjs.com/package/dsh-cite)
9
+ [![npm](https://img.shields.io/npm/v/dsh-cite)](https://www.npmjs.com/package/dsh-cite) [![downloads](https://raw.githubusercontent.com/STARDUSTLC666/dsh-suite/npm-downloads/assets/dsh-cite-downloads.svg)](https://www.npmjs.com/package/dsh-cite)
10
+
11
+ 欢迎使用,遇到问题或有改进建议,请提交 [issues](https://github.com/STARDUSTLC666/dsh-cite/issues) 和 [PR](https://github.com/STARDUSTLC666/dsh-cite/pulls)。
8
12
 
9
13
  ## 功能
10
14
 
@@ -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
@@ -2,6 +2,10 @@
2
2
 
3
3
  [返回简介](../README.md) · [更新记录](../CHANGELOG.md) · [验证记录](VALIDATION.md)
4
4
 
5
+ ## 本次改进
6
+
7
+ 可直接查询带成对括号的 DOI。网络或响应读取超时会给出明确错误;取消后不继续等待。文献库离线导入与外部 Crossref 检索分别验收。
8
+
5
9
  ## 文献工作台
6
10
 
7
11
  1. 打开「设置 → Cite」,选择 DOI 列表或 BibTeX。DOI 每行一条,也可上传 `.bib` 文件。
@@ -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: requestSignal(cfg.timeoutMs, 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: requestSignal(cfg.timeoutMs, 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
- const clean = match.replace(/[.,;:)\]}'">]+$/, '').trim();
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.1",
3
+ "version": "0.5.0",
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": { "inject": ["@deepseek-ai/dsh-client-ui-settings"], "platform": "web" },
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
  }