dsh-calendar 0.6.0 → 0.7.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 +7 -0
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/lib/client.js +38 -7
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0(2026-09-19)
|
|
4
|
+
|
|
5
|
+
- **新增**:面板**记住上次用的视图**(月/周/议程,存 localStorage;隐私模式写不进去就退回「月」),下次打开不再固定跳回月视图。
|
|
6
|
+
- **新增**:周视图**默认落在 07:00**(半夜那几格基本没人看,但需要时还能往上滚,不裁剪任何事件)。
|
|
7
|
+
- **适配窄容器**:面板在设置页里比对话页浮层窄得多,现在用容器查询自适应 —— 窄的时候隐藏芯片里的时间前缀(让日程标题露出来)、隐藏重复的面板标题、收紧工具栏间距。
|
|
8
|
+
- **修复**:月视图的格子由 `<button>` 改为 `<div role="button" tabIndex=0>`。按钮不能嵌套按钮(芯片本身是按钮),一旦同格有多条日程,浏览器会把外层按钮提前闭合、把芯片挤成网格的兄弟节点;现在结构合法,键盘 Enter/空格仍可新建,并加了回归测试(月视图里不允许出现 `button`)。
|
|
9
|
+
|
|
3
10
|
## 0.6.0(2026-09-19)
|
|
4
11
|
|
|
5
12
|
- **新增设置页日历面板**:月 / 周 / 议程三种视图;点日程开右侧详情(时间、时长、重复规则的人话、地点、备注、uid),可直接改标题时间、加重复、删除(二次确认);新建支持全天 / 地点 / 备注 / RRULE;保存前做冲突检测。
|
package/README.en.md
CHANGED
|
@@ -171,7 +171,7 @@ The stable event identifier `uid` is the CalDAV href (full object URL); `calenda
|
|
|
171
171
|
|
|
172
172
|
Once installed, DSH grows a「Calendar」section in Settings; the chat page also gets a small 📅 button in the bottom-right that opens the same panel (non-modal, Esc closes).
|
|
173
173
|
|
|
174
|
-
- **Three views**: month (6×7 grid, today highlighted, click a cell to create), week (24-hour time grid, overlapping events side by side) and agenda (grouped by day).
|
|
174
|
+
- **Three views**: month (6×7 grid, today highlighted, click a cell to create), week (24-hour time grid starting at 07:00, overlapping events side by side) and agenda (grouped by day). The panel **remembers the view you used last**.
|
|
175
175
|
- **Click to edit**: open any event in the right-hand drawer — time, duration, a human-readable repeat rule (`FREQ=WEEKLY;COUNT=6` → "weekly (6 times)"), location, notes, one-click uid copy — then edit or delete (deletion asks twice).
|
|
176
176
|
- **Create**: title, date, start/end, all-day, location, notes, repeat (daily/weekly/monthly/yearly, or a raw RRULE).
|
|
177
177
|
- **Conflict notice**: before saving, overlapping events are listed and you are asked whether to continue.
|
package/README.md
CHANGED
|
@@ -197,7 +197,7 @@ Nextcloud / 自定义 Basic 服务:检查账号、密码或服务要求的应
|
|
|
197
197
|
|
|
198
198
|
装好后,DSH 的「设置 → 日历」里会多出一节面板;对话页右下角还有一个 📅 小按钮,点开是同一个面板(非模态,Esc 关闭)。
|
|
199
199
|
|
|
200
|
-
- **三种视图**:月(6×7 网格,今天高亮,点格子直接新建)、周(24
|
|
200
|
+
- **三种视图**:月(6×7 网格,今天高亮,点格子直接新建)、周(24 小时时间网格,重叠日程并排显示,默认落在 07:00)、议程(按天分组,最像「接下来要干什么」)。面板会**记住你上次用的视图**。
|
|
201
201
|
- **点开就能改**:点任意日程打开右侧详情 —— 时间、时长、重复规则的人话(`FREQ=WEEKLY;COUNT=6` → 「每周(共 6 次)」)、地点、备注、uid 一键复制;接着「编辑」或「删除」(删除要二次确认)。
|
|
202
202
|
- **新建**:标题、日期、开始/结束、全天、地点、备注、重复(每天/每周/每月/每年,或直接写 RRULE)。
|
|
203
203
|
- **冲突提醒**:保存前若与已有日程重叠,会把重叠的几条列出来问你是否继续。
|
package/lib/client.js
CHANGED
|
@@ -20,10 +20,28 @@ const VIEWS = ["month", "week", "agenda"];
|
|
|
20
20
|
|
|
21
21
|
const WEEKDAYS_ZH = ["一", "二", "三", "四", "五", "六", "日"];
|
|
22
22
|
|
|
23
|
+
/** 上次用的视图:用户下次打开面板不该又跳回「月」。localStorage 不可用(隐私模式)就退回 default。 */
|
|
24
|
+
const VIEW_STORAGE_KEY = "dsh-calendar:view";
|
|
25
|
+
function loadStoredView() {
|
|
26
|
+
try {
|
|
27
|
+
const stored = window.localStorage.getItem(VIEW_STORAGE_KEY);
|
|
28
|
+
return VIEWS.indexOf(stored) === -1 ? "month" : stored;
|
|
29
|
+
} catch (error) {
|
|
30
|
+
return "month";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function storeView(view) {
|
|
34
|
+
try { window.localStorage.setItem(VIEW_STORAGE_KEY, view); } catch (error) { /* 写不进去就算了 */ }
|
|
35
|
+
}
|
|
36
|
+
|
|
23
37
|
/* @ui-css-start */
|
|
24
38
|
const CSS = [
|
|
25
39
|
".dshc-root{color:var(--dsw-alias-label-primary,#111);font-size:13px;line-height:1.5}",
|
|
26
|
-
|
|
40
|
+
// 面板可能出现在两个宽度差很大的容器里(对话页浮层 ~760px、设置页更窄)。
|
|
41
|
+
// 用容器查询按「面板自己的宽度」适配:窄的时候藏掉时间前缀与重复的标题,
|
|
42
|
+
// 让芯片至少能把日程标题露出来。
|
|
43
|
+
".dshc-shell{background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.08));border-radius:14px;padding:14px 16px 16px;container-type:inline-size}",
|
|
44
|
+
"@container (max-width: 620px){.dshc-title{display:none}.dshc-chip .t{display:none}.dshc-toolbar{gap:6px}.dshc-monthhead{min-width:0}}",
|
|
27
45
|
".dshc-toolbar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:12px}",
|
|
28
46
|
".dshc-title{font-size:15px;font-weight:600;margin:0}",
|
|
29
47
|
".dshc-sub{color:var(--dsw-alias-label-tertiary,#6b7280);font-size:12px}",
|
|
@@ -41,7 +59,7 @@ const CSS = [
|
|
|
41
59
|
".dshc-monthhead{text-align:center;font-size:13px;font-weight:600;min-width:104px}",
|
|
42
60
|
".dshc-grid{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:6px}",
|
|
43
61
|
".dshc-dow{text-align:center;font-size:11px;color:var(--dsw-alias-label-tertiary,#9ca3af);padding:2px 0 4px}",
|
|
44
|
-
".dshc-cell{min-height:88px;border-radius:10px;padding:5px 5px 6px;background:var(--dsw-alias-bg-layer-2,rgba(0,0,0,.03));border:1px solid transparent;cursor:pointer;text-align:left;font:inherit;color:inherit;transition:background .12s,border-color .12s;overflow:hidden}",
|
|
62
|
+
".dshc-cell{display:block;width:100%;min-height:88px;box-sizing:border-box;border-radius:10px;padding:5px 5px 6px;background:var(--dsw-alias-bg-layer-2,rgba(0,0,0,.03));border:1px solid transparent;cursor:pointer;text-align:left;font:inherit;color:inherit;transition:background .12s,border-color .12s;overflow:hidden}",
|
|
45
63
|
".dshc-cell:hover{background:var(--dsw-alias-bg-mask-3,rgba(0,0,0,.07))}",
|
|
46
64
|
".dshc-cell.out{opacity:.45}",
|
|
47
65
|
".dshc-cell.today{border-color:var(--dsw-alias-state-business-primary,#2563eb)}",
|
|
@@ -584,11 +602,15 @@ function MonthView(props) {
|
|
|
584
602
|
const list = sortForDisplay(byDay[key] || []);
|
|
585
603
|
const shown = list.slice(0, 3);
|
|
586
604
|
const rest = list.length - shown.length;
|
|
587
|
-
|
|
605
|
+
// 格子必须是 div 而不是 button:里面的日程芯片本身是按钮,而按钮不能嵌套按钮 ——
|
|
606
|
+
// 嵌套时浏览器会把外层按钮提前闭合,芯片就被挤到网格外面去了(真被用户抓到过)。
|
|
607
|
+
cells.push(h("div", {
|
|
588
608
|
key: key,
|
|
589
|
-
|
|
609
|
+
role: "button",
|
|
610
|
+
tabIndex: 0,
|
|
590
611
|
className: "dshc-cell" + (day.getMonth() !== anchor.getMonth() ? " out" : "") + (key === today ? " today" : ""),
|
|
591
612
|
onClick: () => onCreateAt(day),
|
|
613
|
+
onKeyDown: (e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onCreateAt(day); } },
|
|
592
614
|
title: t("action.new")
|
|
593
615
|
}, [
|
|
594
616
|
h("div", { className: "dshc-daynum" }, [key === today ? h("b", null, String(day.getDate())) : h("span", null, String(day.getDate()))]),
|
|
@@ -613,6 +635,14 @@ function WeekView(props) {
|
|
|
613
635
|
const allDay = sortForDisplay(events.filter((e) => e.allDay));
|
|
614
636
|
const timed = events.filter((e) => !e.allDay);
|
|
615
637
|
|
|
638
|
+
// 默认落在 07:00:半夜那几格基本没人看,但需要时还能往上滚(不裁剪任何事件)。
|
|
639
|
+
const scroller = useRef(null);
|
|
640
|
+
useEffect(() => {
|
|
641
|
+
const node = scroller.current;
|
|
642
|
+
if (node === null || node === undefined) return;
|
|
643
|
+
node.scrollTop = 7 * HOUR_PX;
|
|
644
|
+
}, [dayKey(anchor)]);
|
|
645
|
+
|
|
616
646
|
const hoursColumn = h("div", { key: "__hours" }, [h("div", { className: "dshc-dayhead", key: "h" }, "\u00a0")].concat(
|
|
617
647
|
Array.from({ length: 24 }, (_, hour) => h("div", { className: "dshc-hour", key: "h" + hour }, hour === 0 ? "" : pad2(hour) + ":00"))
|
|
618
648
|
));
|
|
@@ -664,7 +694,7 @@ function WeekView(props) {
|
|
|
664
694
|
allDay.length > 0
|
|
665
695
|
? h("div", { style: { marginBottom: "8px" } }, allDay.map((event) => h(Row, { key: event.uid, event, onClick: onPick })))
|
|
666
696
|
: null,
|
|
667
|
-
h("div", { className: "dshc-week" }, [hoursColumn].concat(dayColumns))
|
|
697
|
+
h("div", { className: "dshc-week", ref: scroller }, [hoursColumn].concat(dayColumns))
|
|
668
698
|
]);
|
|
669
699
|
}
|
|
670
700
|
|
|
@@ -1119,7 +1149,7 @@ function ConnectionForm(props) {
|
|
|
1119
1149
|
|
|
1120
1150
|
function CalendarPanel(props) {
|
|
1121
1151
|
const compact = props.compact === true;
|
|
1122
|
-
const [view, setView] = useState(
|
|
1152
|
+
const [view, setView] = useState(loadStoredView);
|
|
1123
1153
|
const [anchor, setAnchor] = useState(() => new Date());
|
|
1124
1154
|
const [demo, setDemo] = useState(false);
|
|
1125
1155
|
const [reloadToken, setReloadToken] = useState(0);
|
|
@@ -1185,7 +1215,8 @@ function CalendarPanel(props) {
|
|
|
1185
1215
|
const toolbar = h("div", { className: "dshc-toolbar" }, [
|
|
1186
1216
|
h("h3", { className: "dshc-title", key: "title" }, t("section.title")),
|
|
1187
1217
|
h("div", { className: "dshc-tabs", key: "tabs" }, VIEWS.map((name) => h("button", {
|
|
1188
|
-
key: name, type: "button", className: "dshc-tab" + (view === name ? " on" : ""),
|
|
1218
|
+
key: name, type: "button", className: "dshc-tab" + (view === name ? " on" : ""),
|
|
1219
|
+
onClick: () => { setView(name); storeView(name); }
|
|
1189
1220
|
}, t("view." + name)))),
|
|
1190
1221
|
h("span", { className: "dshc-spacer", key: "spacer" }),
|
|
1191
1222
|
h("span", { className: "dshc-monthhead", key: "period" }, title),
|