dsh-calendar 0.10.1 → 0.10.3

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.10.3 (2026-10-05)
8
+
9
+ - 修复窄窗口中日历悬浮按钮遮挡聊天发送按钮的问题;浮层宽度计入内边距,并限制在视口内,避免左侧内容被裁切。
10
+
11
+ ## 0.10.2 (2026-10-05)
12
+
13
+ - 严格验证真实 ISO 日期,拒绝不存在的日期、模糊地域格式与缺少时区的日期时间,防止日程悄悄改期。
14
+
7
15
  ## 0.10.1 (2026-10-03)
8
16
 
9
17
  - 修复语言切换时菜单和悬浮按钮不更新;日期、星期、重复规则、ICS 告警与连接引导支持英文。
package/README.en.md CHANGED
@@ -2,9 +2,11 @@
2
2
 
3
3
  [中文](README.md)
4
4
 
5
+ ![dsh-calendar whale girl plugin cover](https://raw.githubusercontent.com/STARDUSTLC666/dsh-calendar/main/assets/cover-whale-girl.png)
6
+
5
7
  View, create and reschedule CalDAV events from DSH.
6
8
 
7
- [![npm](https://img.shields.io/npm/v/dsh-calendar)](https://www.npmjs.com/package/dsh-calendar) [![downloads](https://img.shields.io/npm/dm/dsh-calendar)](https://www.npmjs.com/package/dsh-calendar)
9
+ [![npm](https://img.shields.io/npm/v/dsh-calendar)](https://www.npmjs.com/package/dsh-calendar) [![downloads](https://raw.githubusercontent.com/STARDUSTLC666/dsh-suite/npm-downloads/assets/dsh-calendar-downloads.svg)](https://www.npmjs.com/package/dsh-calendar)
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
+ ![dsh-calendar 鲸鱼娘插件封面](https://raw.githubusercontent.com/STARDUSTLC666/dsh-calendar/main/assets/cover-whale-girl.png)
6
+
5
7
  连接 CalDAV 日历,在 DSH 中查看、创建和调整日程。
6
8
 
7
- [![npm](https://img.shields.io/npm/v/dsh-calendar)](https://www.npmjs.com/package/dsh-calendar) [![downloads](https://img.shields.io/npm/dm/dsh-calendar)](https://www.npmjs.com/package/dsh-calendar)
9
+ [![npm](https://img.shields.io/npm/v/dsh-calendar)](https://www.npmjs.com/package/dsh-calendar) [![downloads](https://raw.githubusercontent.com/STARDUSTLC666/dsh-suite/npm-downloads/assets/dsh-calendar-downloads.svg)](https://www.npmjs.com/package/dsh-calendar)
8
10
 
9
11
  ## 功能
10
12
 
@@ -4,6 +4,14 @@
4
4
 
5
5
  These English notes preserve the earlier translations. The main changelog contains the consolidated version history.
6
6
 
7
+ ## 0.10.3 (2026-10-05)
8
+
9
+ - Move the calendar launcher clear of the chat send button in narrow windows. Include panel padding in its width and keep its bounds inside the viewport.
10
+
11
+ ## 0.10.2 (2026-10-05)
12
+
13
+ - Validate real ISO calendar dates, rejecting nonexistent dates, ambiguous locale formats and datetimes without a timezone.
14
+
7
15
  ## 0.10.0 (2026-10-03)
8
16
 
9
17
  - Import ICS files or pasted content with an explicit preview, selection and batch confirmation. Preserve recurrence rules, excluded dates, moved exceptions and timezone definitions.
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
+ Use YYYY-MM-DD for dates and ISO datetimes with Z or a UTC offset. Invalid dates fail before remote creation or updates. Existing events are preserved.
8
+
5
9
  ## Installation
6
10
 
7
11
  ```bash
package/docs/USAGE.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  [返回简介](../README.md) · [更新记录](../CHANGELOG.md) · [验证记录](VALIDATION.md)
4
4
 
5
+ ## 本次改进
6
+
7
+ 日期使用 YYYY-MM-DD;带时间使用带 Z 或 UTC 偏移的 ISO 格式。错误日期会在创建或更新远端日程前报错,原日程保留。
8
+
5
9
  ## 安装
6
10
 
7
11
  ```bash
@@ -6,6 +6,7 @@
6
6
 
7
7
  ## 当前版本
8
8
 
9
+ - **0.10.3 / 2026-10-05**:Windows 自动测试 194 通过。官方 npm Harness `0.2.1-alpha.1` 网页实际操作完成日程创建(隔离 CalDAV 服务),并在 424×612 窗口复测浮层与发送按钮:浮层在视口内,按钮区域无交集,聊天消息可正常发送。外部 CalDAV 账号和桌面原生窗口未重新验收。
9
10
  - [0.10.0:ICS 导入、失败重试与窄屏操作验收](validation/0.10.0.md)
10
11
 
11
12
  ## 原中文记录
package/lib/client.js CHANGED
@@ -1155,16 +1155,16 @@ function AgendaView(props) {
1155
1155
 
1156
1156
  // ───────────────────────────── 悬浮面板的样式补充 ─────────────────────────────
1157
1157
  const CSS_EXTRA = [
1158
- ".dshc-fab{position:fixed;right:18px;bottom:18px;z-index:55;width:38px;height:38px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-primary,#111);box-shadow:var(--dsw-elevation-prominent,0 6px 18px rgba(0,0,0,.18));cursor:pointer;display:flex;align-items:center;justify-content:center;opacity:.62;transition:opacity .15s,transform .15s}",
1158
+ ".dshc-fab{position:fixed;right:18px;bottom:88px;z-index:55;width:38px;height:38px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-primary,#111);box-shadow:var(--dsw-elevation-prominent,0 6px 18px rgba(0,0,0,.18));cursor:pointer;display:flex;align-items:center;justify-content:center;opacity:.62;transition:opacity .15s,transform .15s}",
1159
1159
  ".dshc-fab:hover{opacity:1;transform:translateY(-1px)}",
1160
1160
  ".dshc-fab.on{opacity:1;background:var(--dsw-alias-button-primary-fill,#2563eb);color:var(--dsw-alias-label-primary-foreground,#fff);border-color:transparent}",
1161
- ".dshc-float{position:fixed;right:18px;bottom:66px;z-index:56;width:min(760px,94vw);max-height:min(78vh,720px);overflow:auto;background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.1));border-radius:16px;box-shadow:var(--dsw-elevation-prominent,0 18px 48px rgba(0,0,0,.28));padding:14px}",
1161
+ ".dshc-float{position:fixed;right:18px;bottom:136px;z-index:56;box-sizing:border-box;width:min(760px,calc(100vw - 36px));max-height:min(calc(100vh - 160px),720px);overflow:auto;background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.1));border-radius:16px;box-shadow:var(--dsw-elevation-prominent,0 18px 48px rgba(0,0,0,.28));padding:14px}",
1162
1162
  ".dshc-float .dshc-shell{border:0;padding:0;background:transparent}",
1163
1163
  ".dshc-steps{margin:2px 0 10px;font-size:12px;color:var(--dsw-alias-label-tertiary,#6b7280)}",
1164
1164
  ".dshc-steps summary{cursor:pointer;color:var(--dsw-alias-state-business-primary,#2563eb)}",
1165
1165
  ".dshc-steps ol{margin:6px 0 0;padding-left:18px;line-height:1.6}",
1166
1166
  ".dshc-link{color:var(--dsw-alias-state-business-primary,#2563eb);text-decoration:none}",
1167
- ".dshc-toast{position:fixed;right:18px;bottom:112px;z-index:57;background:var(--dsw-alias-label-primary,#111);color:var(--dsw-alias-label-primary-foreground,#fff);border-radius:10px;padding:8px 12px;font-size:12px;box-shadow:var(--dsw-elevation-prominent,0 8px 20px rgba(0,0,0,.25))}"
1167
+ ".dshc-toast{position:fixed;right:18px;bottom:182px;z-index:57;background:var(--dsw-alias-label-primary,#111);color:var(--dsw-alias-label-primary-foreground,#fff);border-radius:10px;padding:8px 12px;font-size:12px;box-shadow:var(--dsw-elevation-prominent,0 8px 20px rgba(0,0,0,.25))}"
1168
1168
  ].join("");
1169
1169
 
1170
1170
  // ───────────────────────────── 数据 ─────────────────────────────
package/lib/ical.js CHANGED
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { randomUUID } from 'node:crypto';
8
8
  import ICAL from 'ical.js';
9
+ import { assertIsoTime } from './time.js';
9
10
  /** 展开重复事件超出迭代预算:显式报错,避免把「没走到窗口」静默当成「窗口内没有实例」。 */
10
11
  export class ExpansionLimitError extends Error {
11
12
  constructor(message) {
@@ -445,6 +446,7 @@ export function updateICalString(data, changes) {
445
446
  }
446
447
  /** 把 ISO 字符串解析成 ICAL.Time(全天 YYYY-MM-DD 或转 UTC 的定时时间)。 */
447
448
  function parseTime(value, allDay) {
449
+ assertIsoTime(value, '时间');
448
450
  if (allDay || isDateOnly(value)) {
449
451
  return ICAL.Time.fromDateString(value);
450
452
  }
package/lib/time.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ /** Validate calendar dates before Date/ical.js can silently normalize them. */
2
+ export declare const DATE_ONLY_PATTERN: RegExp;
3
+ export declare function assertIsoTime(value: string, label: string): void;
package/lib/time.js ADDED
@@ -0,0 +1,26 @@
1
+ /** Validate calendar dates before Date/ical.js can silently normalize them. */
2
+ export const DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
3
+ const DATETIME_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,9}))?)?(Z|[+-]\d{2}:\d{2})$/;
4
+ export function assertIsoTime(value, label) {
5
+ const dateOnly = DATE_ONLY_PATTERN.exec(value);
6
+ const parts = dateOnly ?? DATETIME_PATTERN.exec(value);
7
+ const invalid = () => new Error(label + ' 不是合法的 ISO 8601 时间:' + value + '。必须是真实存在的日期;定时事件须含时区,例如 2025-01-01T09:00:00+08:00 或 2025-01-01T09:00:00Z。');
8
+ if (!parts)
9
+ throw invalid();
10
+ const year = Number(parts[1]), month = Number(parts[2]), day = Number(parts[3]);
11
+ const leap = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
12
+ const days = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
13
+ if (year < 1 || month < 1 || month > 12 || day < 1 || day > days[month - 1])
14
+ throw invalid();
15
+ if (dateOnly)
16
+ return;
17
+ const hour = Number(parts[4]), minute = Number(parts[5]), second = Number(parts[6] ?? 0);
18
+ // ISO 8601 permits 24:00 only as midnight at the end of this valid date.
19
+ if (hour > 24 || minute > 59 || second > 59 || (hour === 24 && (minute !== 0 || second !== 0 || /[1-9]/.test(parts[7] ?? ''))))
20
+ throw invalid();
21
+ const zone = parts[8];
22
+ if (zone !== 'Z' && (Number(zone.slice(1, 3)) > 23 || Number(zone.slice(4)) > 59))
23
+ throw invalid();
24
+ if (Number.isNaN(new Date(value).getTime()))
25
+ throw invalid();
26
+ }
package/lib/tools.d.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { type CalendarConfig } from './config.js';
8
8
  import { type CalendarEvent } from './ical.js';
9
+ export { assertIsoTime } from './time.js';
9
10
  /** 模型可见的内容块。 */
10
11
  export interface ContentBlock {
11
12
  type: 'text';
@@ -29,7 +30,6 @@ export interface CalendarToolDefinition {
29
30
  }
30
31
  export declare function asRecord(args: unknown): Record<string, unknown>;
31
32
  export declare function optionalString(args: Record<string, unknown>, key: string): string | undefined;
32
- export declare function assertIsoTime(value: string, label: string): void;
33
33
  export declare function assertTimeRange(start: string, end: string): void;
34
34
  export declare function isoNoMillis(value: string): string;
35
35
  /** 按开始时间升序稳定排序(CalDAV 服务端返回顺序不保证稳定)。 */
package/lib/tools.js CHANGED
@@ -7,6 +7,8 @@
7
7
  import { CalendarService } from './caldav.js';
8
8
  import { CALENDAR_PROVIDERS, buildCaldavUrl, resolveAuthMethod, resolveConfig, resolveCredentials, validateOAuthUrl, } from './config.js';
9
9
  import { compileParameters } from './parameters.js';
10
+ import { assertIsoTime, DATE_ONLY_PATTERN } from './time.js';
11
+ export { assertIsoTime } from './time.js';
10
12
  const EVENT_SCHEMA = { type: 'object', additionalProperties: true };
11
13
  const TIMEOUT_MS = 60000;
12
14
  export function asRecord(args) {
@@ -48,28 +50,11 @@ function clampedInteger(args, key, fallback, min, max) {
48
50
  return fallback;
49
51
  return Math.min(max, Math.max(min, value));
50
52
  }
51
- const DATE_ONLY_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
52
- export function assertIsoTime(value, label) {
53
- const dateOnly = DATE_ONLY_PATTERN.exec(value);
54
- if (dateOnly !== null) {
55
- const year = Number(dateOnly[1]);
56
- const month = Number(dateOnly[2]);
57
- const day = Number(dateOnly[3]);
58
- const date = new Date(Date.UTC(year, month - 1, day));
59
- if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) {
60
- throw new Error(label + ' 不是合法的日期:' + value + '(YYYY-MM-DD 必须是真实存在的日期)。');
61
- }
62
- return;
63
- }
64
- if (Number.isNaN(new Date(value).getTime())) {
65
- throw new Error(label + ' 不是合法的 ISO 8601 时间,例如 2025-01-01T09:00:00+08:00 或 2025-01-01T09:00:00Z。');
66
- }
67
- }
68
53
  /** 日期/时间统一转 epoch 毫秒(date-only 按 UTC 零点),用于范围比较。 */
69
54
  function toEpochMs(value) {
70
55
  const dateOnly = DATE_ONLY_PATTERN.exec(value);
71
56
  if (dateOnly !== null) {
72
- return Date.UTC(Number(dateOnly[1]), Number(dateOnly[2]) - 1, Number(dateOnly[3]));
57
+ return new Date(value + 'T00:00:00Z').getTime();
73
58
  }
74
59
  return new Date(value).getTime();
75
60
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-calendar",
3
- "version": "0.10.1",
3
+ "version": "0.10.3",
4
4
  "description": "连接 CalDAV 日历,在 DSH 中查看、创建和调整日程。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",