dsh-calendar 0.10.3 → 0.10.4
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 +6 -0
- package/README.en.md +5 -0
- package/README.md +5 -0
- package/docs/VALIDATION.md +2 -0
- package/docs/validation/0.10.4.md +11 -0
- package/lib/client.js +24 -19
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
[历史英文记录](docs/CHANGELOG.en.md)
|
|
6
6
|
|
|
7
|
+
## 0.10.4 (2026-10-17)
|
|
8
|
+
|
|
9
|
+
- 日程与连接表单采用每个实例独立的字段标识,点击标签可聚焦输入框,并提供正确的可访问名称。
|
|
10
|
+
- 修复英文服务商选项残留中文;首次连接摘要尚在加载时暂缓打开连接编辑器,避免快速点击看到空白配置。
|
|
11
|
+
- 在官方 DSH 0.2.1-alpha.5 的隔离网页版检查本机 CalDAV 新建日程、连接测试、中英文表单及 390px 布局。
|
|
12
|
+
|
|
7
13
|
## 0.10.3 (2026-10-05)
|
|
8
14
|
|
|
9
15
|
- 修复窄窗口中日历悬浮按钮遮挡聊天发送按钮的问题;浮层宽度计入内边距,并限制在视口内,避免左侧内容被裁切。
|
package/README.en.md
CHANGED
|
@@ -8,6 +8,11 @@ View, create and reschedule CalDAV events from DSH.
|
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/dsh-calendar) [](https://www.npmjs.com/package/dsh-calendar)
|
|
10
10
|
|
|
11
|
+
Feedback and contributions are welcome: report [issues](https://github.com/STARDUSTLC666/dsh-calendar/issues) or submit [pull requests](https://github.com/STARDUSTLC666/dsh-calendar/pulls).
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
See the [0.10.4 validation record](docs/validation/0.10.4.md) for DSH `0.2.1-alpha.5` browser checks and the remaining limits.
|
|
15
|
+
|
|
11
16
|
## What it does
|
|
12
17
|
|
|
13
18
|
- Month, week and agenda views with drag and keyboard rescheduling.
|
package/README.md
CHANGED
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/dsh-calendar) [](https://www.npmjs.com/package/dsh-calendar)
|
|
10
10
|
|
|
11
|
+
欢迎使用,遇到问题或有改进建议,请提交 [issues](https://github.com/STARDUSTLC666/dsh-calendar/issues) 和 [PR](https://github.com/STARDUSTLC666/dsh-calendar/pulls)。
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
DSH `0.2.1-alpha.5` 的本轮检查与未验收范围见 [0.10.4 验收记录](docs/validation/0.10.4.md)。
|
|
15
|
+
|
|
11
16
|
## 功能
|
|
12
17
|
|
|
13
18
|
- 月、周与议程视图,支持拖拽和键盘改期。
|
package/docs/VALIDATION.md
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# 0.10.4 · DSH alpha.5 网页验收
|
|
2
|
+
|
|
3
|
+
宿主为公开 npm `@deepseek-ai/dsh@0.2.1-alpha.5`,Windows Node 24,系统 Chrome 无头浏览器。测试数据与 DSH profile 均位于 E 盘的隔离目录。
|
|
4
|
+
|
|
5
|
+
- 194 项 Windows 自动测试通过。
|
|
6
|
+
- 日程标题、日期、起止时间、重复、地点及备注可通过标签定位;实际点击日期标签后输入框获得焦点。
|
|
7
|
+
- 新建日程经真实 HTTP CalDAV 客户端写入本机合成服务并重新读取;连接设置可测试该服务。
|
|
8
|
+
- 英文服务商显示 Custom CalDAV,表单字段有英文名称;390px 英文弹窗保持在视口内。
|
|
9
|
+
- 连接摘要请求完成前,连接入口保持禁用,避免编辑器以空配置初始化。
|
|
10
|
+
|
|
11
|
+
未使用外部 CalDAV 账号,未操作桌面原生窗口;本机合成服务通过不等于云端账号验收。
|
package/lib/client.js
CHANGED
|
@@ -10,7 +10,7 @@ var module = { exports: {} }; var exports = module.exports;
|
|
|
10
10
|
"use strict";
|
|
11
11
|
|
|
12
12
|
const React = require("react");
|
|
13
|
-
const { useState, useEffect, useMemo, useRef, useCallback } = React;
|
|
13
|
+
const { useState, useEffect, useMemo, useRef, useCallback, useId } = React;
|
|
14
14
|
const h = React.createElement;
|
|
15
15
|
|
|
16
16
|
// Resolve against the Harness document base, including any reverse-proxy prefix.
|
|
@@ -183,6 +183,7 @@ const UI = {
|
|
|
183
183
|
"conn.title": "CalDAV 连接设置",
|
|
184
184
|
"conn.intro": "测试连接不会修改日历。连接成功后,点击“保存并启用”。",
|
|
185
185
|
"conn.provider": "服务商",
|
|
186
|
+
"provider.custom": "自建 CalDAV",
|
|
186
187
|
"conn.caldavUrl": "日历集合 URL",
|
|
187
188
|
"conn.username": "账号",
|
|
188
189
|
"conn.password": "密码 / 应用专用密码",
|
|
@@ -278,6 +279,7 @@ const UI = {
|
|
|
278
279
|
"conn.title": "CalDAV connection",
|
|
279
280
|
"conn.intro": "Testing the connection does not change your calendar. Once it succeeds, click Save and enable.",
|
|
280
281
|
"conn.provider": "Provider",
|
|
282
|
+
"provider.custom": "Custom CalDAV",
|
|
281
283
|
"conn.caldavUrl": "Calendar collection URL",
|
|
282
284
|
"conn.username": "Account",
|
|
283
285
|
"conn.password": "Password / app-specific password",
|
|
@@ -1310,6 +1312,7 @@ function asPseudoEvent(draft) {
|
|
|
1310
1312
|
|
|
1311
1313
|
function EventForm(props) {
|
|
1312
1314
|
const { draft, events, onCancel, onSubmit, busy, error } = props;
|
|
1315
|
+
const formId = useId();
|
|
1313
1316
|
const [form, setForm] = useState(draft);
|
|
1314
1317
|
useEffect(() => { setForm(draft); }, [draft.uid, draft.date, draft.startTime, draft.summary]);
|
|
1315
1318
|
const patch = (fields) => setForm((prev) => Object.assign({}, prev, fields));
|
|
@@ -1321,21 +1324,21 @@ function EventForm(props) {
|
|
|
1321
1324
|
const body = h("div", null, [
|
|
1322
1325
|
error ? h("div", { className: "dshc-alert error" }, error) : null,
|
|
1323
1326
|
h("div", { className: "dshc-field" }, [
|
|
1324
|
-
h("label",
|
|
1325
|
-
h(Txt, { value: form.summary, autoFocus: true, onChange: (e) => patch({ summary: e.target.value }), placeholder: t("form.newTitle") })
|
|
1327
|
+
h("label", { htmlFor: formId + "-summary" }, t("field.summary")),
|
|
1328
|
+
h(Txt, { id: formId + "-summary", value: form.summary, autoFocus: true, onChange: (e) => patch({ summary: e.target.value }), placeholder: t("form.newTitle") })
|
|
1326
1329
|
]),
|
|
1327
1330
|
h("div", { className: "dshc-field" }, [
|
|
1328
|
-
h("label",
|
|
1329
|
-
h(Txt, { type: "date", value: form.date, onChange: (e) => patch({ date: e.target.value }) })
|
|
1331
|
+
h("label", { htmlFor: formId + "-date" }, t("field.date")),
|
|
1332
|
+
h(Txt, { id: formId + "-date", type: "date", value: form.date, onChange: (e) => patch({ date: e.target.value }) })
|
|
1330
1333
|
]),
|
|
1331
1334
|
form.allDay === true ? null : h("div", { className: "dshc-two" }, [
|
|
1332
|
-
h("div", { className: "dshc-field" }, [h("label",
|
|
1333
|
-
h("div", { className: "dshc-field" }, [h("label",
|
|
1335
|
+
h("div", { className: "dshc-field" }, [h("label", { htmlFor: formId + "-start" }, t("field.start")), h(Txt, { id: formId + "-start", type: "time", value: form.startTime, onChange: (e) => patch({ startTime: e.target.value }) })]),
|
|
1336
|
+
h("div", { className: "dshc-field" }, [h("label", { htmlFor: formId + "-end" }, t("field.end")), h(Txt, { id: formId + "-end", type: "time", value: form.endTime, onChange: (e) => patch({ endTime: e.target.value }) })])
|
|
1334
1337
|
]),
|
|
1335
1338
|
h("div", { className: "dshc-field" }, [h(Chk, { checked: form.allDay === true, label: t("field.allDay"), onChange: (next) => patch({ allDay: next }) })]),
|
|
1336
1339
|
h("div", { className: "dshc-field" }, [
|
|
1337
|
-
h("label",
|
|
1338
|
-
h("select", { className: "dshc-input", value: form.repeat, onChange: (e) => patch({ repeat: e.target.value }) }, [
|
|
1340
|
+
h("label", { htmlFor: formId + "-repeat" }, t("field.repeat")),
|
|
1341
|
+
h("select", { id: formId + "-repeat", className: "dshc-input", value: form.repeat, onChange: (e) => patch({ repeat: e.target.value }) }, [
|
|
1339
1342
|
h("option", { value: "", key: "none" }, t("repeat.none")),
|
|
1340
1343
|
h("option", { value: "daily", key: "d" }, t("repeat.daily")),
|
|
1341
1344
|
h("option", { value: "weekly", key: "w" }, t("repeat.weekly")),
|
|
@@ -1345,13 +1348,13 @@ function EventForm(props) {
|
|
|
1345
1348
|
])
|
|
1346
1349
|
]),
|
|
1347
1350
|
form.repeat === "custom" ? h("div", { className: "dshc-field" }, [
|
|
1348
|
-
h("label",
|
|
1349
|
-
h(Txt, { value: form.rrule, placeholder: "FREQ=WEEKLY;COUNT=10", onChange: (e) => patch({ rrule: e.target.value }) })
|
|
1351
|
+
h("label", { htmlFor: formId + "-rrule" }, "RRULE"),
|
|
1352
|
+
h(Txt, { id: formId + "-rrule", value: form.rrule, placeholder: "FREQ=WEEKLY;COUNT=10", onChange: (e) => patch({ rrule: e.target.value }) })
|
|
1350
1353
|
]) : null,
|
|
1351
|
-
h("div", { className: "dshc-field" }, [h("label",
|
|
1354
|
+
h("div", { className: "dshc-field" }, [h("label", { htmlFor: formId + "-location" }, t("field.location")), h(Txt, { id: formId + "-location", value: form.location, onChange: (e) => patch({ location: e.target.value }) })]),
|
|
1352
1355
|
h("div", { className: "dshc-field" }, [
|
|
1353
|
-
h("label",
|
|
1354
|
-
h("textarea", { className: "dshc-input", value: form.description, onChange: (e) => patch({ description: e.target.value }) })
|
|
1356
|
+
h("label", { htmlFor: formId + "-description" }, t("field.description")),
|
|
1357
|
+
h("textarea", { id: formId + "-description", className: "dshc-input", value: form.description, onChange: (e) => patch({ description: e.target.value }) })
|
|
1355
1358
|
]),
|
|
1356
1359
|
conflicts.length > 0 ? h("div", { className: "dshc-alert warn" }, [
|
|
1357
1360
|
h("div", null, [
|
|
@@ -1479,6 +1482,7 @@ function connectionSummary(connection) {
|
|
|
1479
1482
|
*/
|
|
1480
1483
|
function ConnectionForm(props) {
|
|
1481
1484
|
const { connection, onClose, onSaved } = props;
|
|
1485
|
+
const formId = useId();
|
|
1482
1486
|
const [form, setForm] = useState(() => connectionDraftOf(connection));
|
|
1483
1487
|
const [busy, setBusy] = useState("");
|
|
1484
1488
|
const [result, setResult] = useState(null);
|
|
@@ -1533,10 +1537,10 @@ function ConnectionForm(props) {
|
|
|
1533
1537
|
? h("div", { className: "dshc-alert warn" }, t("conn.noSettings"))
|
|
1534
1538
|
: null,
|
|
1535
1539
|
h("div", { className: "dshc-field" }, [
|
|
1536
|
-
h("label",
|
|
1537
|
-
h("select", { className: "dshc-input", "aria-label": t("conn.provider"), value: form.provider, onChange: (e) => patch({ provider: e.target.value }) },
|
|
1540
|
+
h("label", { htmlFor: formId + "-provider" }, t("conn.provider")),
|
|
1541
|
+
h("select", { id: formId + "-provider", className: "dshc-input", "aria-label": t("conn.provider"), value: form.provider, onChange: (e) => patch({ provider: e.target.value }) },
|
|
1538
1542
|
[h("option", { value: "", key: "__none" }, "— " + t("conn.needProvider") + " —")].concat(
|
|
1539
|
-
PROVIDER_NAMES.map((name) => h("option", { value: name, key: name }, name === "custom" ? "
|
|
1543
|
+
PROVIDER_NAMES.map((name) => h("option", { value: name, key: name }, name === "custom" ? t("provider.custom") : name === "icloud" ? "iCloud" : name === "nextcloud" ? "Nextcloud" : "Google"))
|
|
1540
1544
|
))
|
|
1541
1545
|
]),
|
|
1542
1546
|
PROVIDER_HINT[form.provider] ? h("div", { className: "dshc-sub", style: { marginBottom: "8px" } }, t(PROVIDER_HINT[form.provider])) : null,
|
|
@@ -1554,8 +1558,9 @@ function ConnectionForm(props) {
|
|
|
1554
1558
|
|| (key === "refreshToken" && connection.hasRefreshToken === true));
|
|
1555
1559
|
const hintKey = FIELD_HINT[form.provider] ? FIELD_HINT[form.provider][key] : undefined;
|
|
1556
1560
|
return h("div", { className: "dshc-field", key }, [
|
|
1557
|
-
h("label",
|
|
1561
|
+
h("label", { htmlFor: formId + "-" + key }, t(FIELD_LABEL[key])),
|
|
1558
1562
|
h(Txt, {
|
|
1563
|
+
id: formId + "-" + key,
|
|
1559
1564
|
type: secret ? "password" : "text",
|
|
1560
1565
|
"aria-label": t(FIELD_LABEL[key]),
|
|
1561
1566
|
value: form[key] || "",
|
|
@@ -2016,7 +2021,7 @@ function CalendarPanel(props) {
|
|
|
2016
2021
|
h(Btn, { key: "prev", onClick: () => step(-1), "aria-label": t("action.prev") }, "\u2039"),
|
|
2017
2022
|
h(Btn, { key: "today", onClick: () => setAnchor(new Date()) }, t("action.today")),
|
|
2018
2023
|
h(Btn, { key: "next", onClick: () => step(1), "aria-label": t("action.next") }, "\u203a"),
|
|
2019
|
-
h(Btn, { key: "conn", onClick: () => setConnOpen(true) }, t("conn.button")),
|
|
2024
|
+
h(Btn, { key: "conn", disabled: configured === null, onClick: () => setConnOpen(true) }, t("conn.button")),
|
|
2020
2025
|
h(Btn, { key: "import", disabled: demo === true || importBusy, onClick: () => setImportOpen(value => !value) }, t("import.open")),
|
|
2021
2026
|
demo === true
|
|
2022
2027
|
? h(Btn, { key: "exitdemo", onClick: () => setDemo(false) }, t("action.exitDemo"))
|