dsh-schedule-calendar 1.0.0 → 1.0.1

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/README.md CHANGED
@@ -29,7 +29,7 @@ DSH 桌面端的侧边栏「日程助手」插件:城市天气(定点刷新
29
29
  - **黄历(宜 / 忌)**:今日头部在星期右侧显示农历与当日宜忌(本地黄历算法,按日固定),可在设置里开关。
30
30
  - **农历月历**:月视图显示农历与节气、法定节假日;周末 / 个人休息日 / 法定假期 / 补班上班 四类背景色可区分,且可在设置里自定义颜色;**点击某天即可查看 / 添加当天日程**(也支持右键加日程);**开背景图时格子自动半透明**以免挡住壁纸,不开背景则保持原配色。
31
31
  - **日程**:增删改,字段含标题、日期、时间、备注、提前提醒;重复支持**不重复 / 每日 / 每周 / 每月 / 每年 / 农历每年(生日纪念)**;记录创建时间;列表按下次发生排序,**本周期已过发灰、未到的高亮**。
32
- - **工作待办**:增删改与完成状态切换,可设**截止日期**,未完成按截止日排序、逾期红色「⚠ 逾期」,已完成显示完成时间。
32
+ - **工作待办**:增删改与完成状态切换,可设**截止日期**;未完成列表支持按**时间 / 标题**排序并在**升序 / 降序**间切换(默认时间升序 —— 截止越近越在上,无截止日期排最后),逾期红色「⚠ 逾期」,已完成按完成时间倒序(最近完成的在上)。
33
33
  - **日报 / 周报**:日报可「一键生成(自动统计当日日程 / 未完成任务 / 已完成)」,支持按日期区间**批量一键生成**;周报自动汇总本周日报与完成情况,也可手动修改并保存。
34
34
  - **星座运势(可开关)**:在「设置」里为每人填写生日(可选**阳历 / 农历 + 年份**,自动识别星座);「今日」底部显示星座图案与每日气运(综合 / 恋爱 / 事业 / 财运 / 幸运色 / 幸运数字),并给出**星座配对**(最配 TOP3 + 与其余 11 星座的相处评分);整块可在设置里开关,并已做**紧凑折叠**,展开才占版面。
35
35
  - **好运签到**:今日顶部 Buff 条,每日签到随机抽取「鸿运当头 / 日进斗金 / 暴富」等好运词并附健康提醒,连续签到累计天数。
@@ -38,7 +38,7 @@ DSH 桌面端的侧边栏「日程助手」插件:城市天气(定点刷新
38
38
  - **左侧栏入口**:在原生左侧栏脚部注入「日程助手」按钮,一键打开右侧面板;也可从右侧「新建标签页」选择「日程助手」。
39
39
  - **外观(壁纸文件夹轮播)**:设置里可填一个壁纸文件夹路径(jpg / png / webp / gif),插件读取文件夹内图片自动轮播为面板背景;支持**顺序 / 随机不重复洗牌**两种播放方式、切换间隔、透明度、磨砂玻璃开关,**文字颜色可设为自动 / 深色字 / 浅色字 / 自定义取色**,深浅色自动适配;开背景图时日历格子自动半透明。
40
40
  - **健康 / 趣味提醒**:久坐、喝水(科学饮水量按体重估算)、摸鱼 / 护眼、听音乐四条内置提醒,可改间隔与时段、可增删、可恢复默认。**到点由宿主端扫描**(不依赖界面是否打开):在 DSH 客户端界面**最上层浮出自绘的胶囊卡片**(`document.body` 固定层,z-index 10500,**与侧边栏是否展开无关**),**停留时间可在设置里设 3~60 秒(默认 6 秒)**,**弹出位置用左右 / 上下两个百分比滑块调**,**点一下胶囊立即关闭**;也可**同时推送 Windows 系统通知**(独立开关,窗口最小化也能看到),还可选择自动弹出侧边栏定位到日程助手,各开关可单独关闭;设置里每条提醒都有 **▶ 手动预览**(只预览效果,不消耗正式计时)。
41
- - **邮件(IMAP 收信 + SMTP 发信,QQ 邮箱实测通过)**:**多账号**,每个账号独立授权码、存本机凭据库,按账号设定的间隔定时收信。收件箱 **七个页签**(全部 / 未读 / 星标 / 垃圾 / 垃圾箱 / 已发送 / 草稿),**未读与已读底色区分**,支持关键词搜索、点开看全文(自动标已读)、收藏(星标)/ 标记垃圾 / 删除(本地 + IMAP 双向尽力同步)/ 还原;正文里的网址显示为蓝色可点,**用系统默认浏览器打开**。写信支持**多附件(含中文文件名)**与群发(逐封限速),带**草稿箱**;**附件收发都支持** —— 收信附件落盘后可预览、「↗ 用系统默认应用打开」(发信仅纯文本正文,不支持 HTML 排版)。账号信息卡显示服务器 / 检查间隔 / 上次检查 / 未读统计。
41
+ - **邮件(IMAP 收信 + SMTP 发信,QQ 邮箱实测通过)**:**多账号**,每个账号独立授权码、存本机凭据库,按账号设定的间隔定时收信。收件箱 **七个页签**(全部 / 未读 / 星标 / 垃圾 / 垃圾箱 / 已发送 / 草稿),**未读与已读底色区分**,支持关键词搜索、点开看全文(自动标已读)、收藏(星标)/ 标记垃圾 / 删除(本地 + IMAP 双向尽力同步)/ 还原;正文里的网址显示为蓝色可点,**用系统默认浏览器打开**。写信支持**多附件(含中文文件名)**与群发(逐封限速),带**草稿箱**;**附件收发都支持** —— 收信附件落盘后可预览、「↗ 用系统默认应用打开」(发信仅纯文本正文,不支持 HTML 排版)。账号信息卡显示服务器 / 检查间隔 / 上次检查 / 未读统计。列表支持按**时间 / 标题**排序(升序 / 降序可切,**默认按收到时间降序 —— 最新邮件在最上面**)。
42
42
  - **日程生日上锁与解锁**:日历视图的生日纪念可上锁保护隐私,解锁后显示备注内容;支持自动排列。
43
43
  - **设置分栏**:设置页按 班制 / 城市 / 提醒 / 外观 / 数据 / 关于 六个二级分栏,不挤在一页。
44
44
  - **数据随包走**:全部数据为本地 JSON(插件包内 `data/store.json`),不上传云端,便于整包打包 / 备份。
package/lib/client.js CHANGED
@@ -16,7 +16,7 @@ window.__ModuleLoader__.load({
16
16
  const { useState, useEffect, useRef, useMemo, useCallback } = React;
17
17
 
18
18
  const NAME = "dsh-schedule-calendar";
19
- const VERSION = "1.0.0";
19
+ const VERSION = "1.0.1";
20
20
  const REPO_URL = "https://github.com/loyalchiiina/dsh-schedule-calendar";
21
21
  const ROUTE = "/schedule-calendar";
22
22
 
@@ -179,6 +179,22 @@ const REPO_URL = "https://github.com/loyalchiiina/dsh-schedule-calendar";
179
179
  }
180
180
 
181
181
  /* ============================ 数据 ============================ */
182
+ /* 待办排序:by = time(按截止日期,空值排最后)| title;dir = asc | desc */
183
+ function sortTodos(list, opt) {
184
+ const o = opt || {};
185
+ const by = o.by === "title" ? "title" : "time";
186
+ const dir = o.dir === "desc" ? "desc" : "asc";
187
+ const arr = (list || []).slice();
188
+ arr.sort((a, b) => {
189
+ let r;
190
+ if (by === "title") r = String(a.text || "").localeCompare(String(b.text || ""), "zh-Hans-CN");
191
+ else r = String(a.dueDate || "9999-12-31").localeCompare(String(b.dueDate || "9999-12-31"));
192
+ if (r === 0) r = String(a.createdAt || "").localeCompare(String(b.createdAt || ""));
193
+ return dir === "desc" ? -r : r;
194
+ });
195
+ return arr;
196
+ }
197
+
182
198
  function normalize(s) {
183
199
  s = s || {};
184
200
  s.version = s.version || 1;
@@ -196,6 +212,12 @@ const REPO_URL = "https://github.com/loyalchiiina/dsh-schedule-calendar";
196
212
  s.settings.reminderSeconds = Math.min(60, Math.max(3, Number(s.settings.reminderSeconds) || 6));
197
213
  s.settings.capsX = Number.isFinite(Number(s.settings.capsX)) ? Math.min(100, Math.max(0, Math.round(Number(s.settings.capsX)))) : 92;
198
214
  s.settings.capsY = Number.isFinite(Number(s.settings.capsY)) ? Math.min(100, Math.max(0, Math.round(Number(s.settings.capsY)))) : 92;
215
+ /* 待办排序:by=time(按截止日期,空值排最后)/ title;dir=asc/desc。默认 time+asc = 截止越近越在上 */
216
+ var _ts = s.settings.todosSort || {};
217
+ s.settings.todosSort = { by: _ts.by === "title" ? "title" : "time", dir: _ts.dir === "desc" ? "desc" : "asc" };
218
+ /* 邮件列表排序:by=time(按收到时间)/ title;dir=asc/desc。默认 time+desc = 最新邮件在最上 */
219
+ var _ms = s.settings.mailSort || {};
220
+ s.settings.mailSort = { by: _ms.by === "title" ? "title" : "time", dir: _ms.dir === "asc" ? "asc" : "desc" };
199
221
  s.settings.update = Object.assign({ autoCheck: false, lastCheck: 0, lastLatest: "" }, s.settings.update || {});
200
222
  s.settings.showHoroscope = s.settings.showHoroscope !== false;
201
223
  s.weatherCities = Array.isArray(s.weatherCities) ? s.weatherCities : [];
@@ -885,7 +907,7 @@ function ensureStyles() {
885
907
  const signObj = signId ? SIGNS_MAP[signId] : null;
886
908
  const hz = useHoroscope(signId);
887
909
  const occ = occurrencesForDate(tk, store.events, lunarToday);
888
- const todos = store.todos.filter((t) => !t.done).slice(0, 6);
910
+ const todos = sortTodos(store.todos.filter((t) => !t.done), (store.settings && store.settings.todosSort) || null).slice(0, 6);
889
911
  const hol = holidays[tk];
890
912
  const l = lunarToday && lunarToday[tk];
891
913
  return h("div", null,
@@ -1234,14 +1256,19 @@ function ensureStyles() {
1234
1256
  const [txt, setTxt] = useState("");
1235
1257
  const [due, setDue] = useState("");
1236
1258
  const add = () => { const t = txt.trim(); if (!t) return; update((s) => ({ ...s, todos: [{ id: uid(), text: t, done: false, dueDate: due || "", createdAt: new Date().toISOString(), doneAt: null }].concat(s.todos) })); setTxt(""); setDue(""); };
1237
- const active = store.todos.filter((t) => !t.done).slice().sort((a, b) => String(a.dueDate || "9999").localeCompare(String(b.dueDate || "9999")));
1238
- const done = store.todos.filter((t) => t.done);
1259
+ const tSort = (store.settings && store.settings.todosSort) || { by: "time", dir: "asc" };
1260
+ const setTSort = (patch) => update((s) => ({ ...s, settings: { ...s.settings, todosSort: Object.assign({ by: "time", dir: "asc" }, (s.settings && s.settings.todosSort) || {}, patch) } }));
1261
+ const active = sortTodos(store.todos.filter((t) => !t.done), tSort);
1262
+ const done = store.todos.filter((t) => t.done).slice().sort((a, b) => String(b.doneAt || "").localeCompare(String(a.doneAt || "")));
1239
1263
  return h("div", null,
1240
1264
  h("div", { className: "dsc-row", style: { marginBottom: 6, flexWrap: "wrap" } },
1241
1265
  h("input", { className: "dsc-input", style: { flex: 1, minWidth: 140 }, placeholder: "添加待办…", value: txt, onChange: (e) => setTxt(e.target.value), onKeyDown: (e) => { if (e.key === "Enter") add(); } }),
1242
1266
  h("input", { className: "dsc-input", type: "date", title: "截止日期(可选)", value: due, onChange: (e) => setDue(e.target.value), style: { width: 140 } }),
1243
1267
  h(Btn, { kind: "pri", onClick: add }, "添加")),
1244
- h("div", { className: "dsc-sec-h" }, "未完成 (" + active.length + ")"),
1268
+ h("div", { className: "dsc-sec-h" }, "未完成 (" + active.length + ")",
1269
+ h("span", { className: "sp" },
1270
+ h(Btn, { kind: tSort.by === "time" ? "mini pri" : "mini ghost", title: "按截止时间排序(点一下切换升/降;时间越近越在上)", onClick: () => setTSort({ by: "time", dir: tSort.by === "time" && tSort.dir === "asc" ? "desc" : "asc" }) }, "时间" + (tSort.by === "time" ? (tSort.dir === "asc" ? " ↑" : " ↓") : "")),
1271
+ h(Btn, { kind: tSort.by === "title" ? "mini pri" : "mini ghost", title: "按标题排序(点一下切换升/降)", onClick: () => setTSort({ by: "title", dir: tSort.by === "title" && tSort.dir === "asc" ? "desc" : "asc" }) }, "标题" + (tSort.by === "title" ? (tSort.dir === "asc" ? " ↑" : " ↓") : "")))),
1245
1272
  h("div", { className: "dsc-card", style: { padding: 0 } }, active.length ? active.map((t) => h(TodoRow, { key: t.id, todo: t, store, update })) : h("div", { className: "dsc-empty" }, "太棒了,全部完成")),
1246
1273
  done.length ? h("div", { style: { marginTop: 12 } },
1247
1274
  h("div", { className: "dsc-sec-h" }, "已完成 (" + done.length + ")", h("span", { className: "sp" }, h(Btn, { kind: "mini ghost", onClick: () => update((s) => ({ ...s, todos: s.todos.filter((t) => !t.done) })) }, "清空已完成"))),
@@ -1992,6 +2019,19 @@ function ensureStyles() {
1992
2019
  return hay.indexOf(k) >= 0;
1993
2020
  });
1994
2021
 
2022
+ /* 列表排序:时间(收到时间,默认降序=最新在最上)或标题(主题) */
2023
+ const mSort = (store.settings && store.settings.mailSort) || { by: "time", dir: "desc" };
2024
+ const setMSort = (patch) => update((s2) => ({ ...s2, settings: { ...s2.settings, mailSort: Object.assign({ by: "time", dir: "desc" }, (s2.settings && s2.settings.mailSort) || {}, patch) } }));
2025
+ const sorted = filtered.slice().sort((a, b) => {
2026
+ let r;
2027
+ if (mSort.by === "title") r = String(a.subject || "(无主题)").localeCompare(String(b.subject || "(无主题)"), "zh-Hans-CN");
2028
+ else {
2029
+ r = String(a.date || "").localeCompare(String(b.date || ""));
2030
+ if (r === 0) r = Number(a.seq || 0) - Number(b.seq || 0);
2031
+ }
2032
+ return mSort.dir === "desc" ? -r : r;
2033
+ });
2034
+
1995
2035
  const openMail = useCallback(async (m) => {
1996
2036
  setSel(m);
1997
2037
  setBodyText(m.text || m.preview || m.snippet || "");
@@ -2187,7 +2227,10 @@ function ensureStyles() {
2187
2227
  // ── 列表(草稿箱单独渲染:本地 store.drafts,不进邮件列表)──
2188
2228
  (tab === "draft"
2189
2229
  ? h("div", { className: "dsc-sec-h" }, "草稿箱(" + drafts.length + " 封)")
2190
- : h("div", { className: "dsc-sec-h" }, (tab === "sent" ? "已发送" : tab === "unread" ? "未读邮件" : "收件箱") + "(" + filtered.length + " 封)")),
2230
+ : h("div", { className: "dsc-sec-h" }, (tab === "sent" ? "已发送" : tab === "unread" ? "未读邮件" : "收件箱") + "(" + filtered.length + " 封)",
2231
+ h("span", { className: "sp" },
2232
+ h(Btn, { kind: mSort.by === "time" ? "mini pri" : "mini ghost", title: "按收到时间排序(点一下切换升/降;默认最新在最上)", onClick: () => setMSort({ by: "time", dir: mSort.by === "time" && mSort.dir === "desc" ? "asc" : "desc" }) }, "时间" + (mSort.by === "time" ? (mSort.dir === "desc" ? " ↓" : " ↑") : "")),
2233
+ h(Btn, { kind: mSort.by === "title" ? "mini pri" : "mini ghost", title: "按主题排序(点一下切换升/降)", onClick: () => setMSort({ by: "title", dir: mSort.by === "title" && mSort.dir === "asc" ? "desc" : "asc" }) }, "标题" + (mSort.by === "title" ? (mSort.dir === "asc" ? " ↑" : " ↓") : ""))))),
2191
2234
  (tab === "draft"
2192
2235
  ? (drafts.length
2193
2236
  ? drafts.map((d) => h("div", { key: d.id, className: "dsc-item", style: { cursor: "pointer" }, onClick: () => openDraft(d) },
@@ -2196,7 +2239,7 @@ function ensureStyles() {
2196
2239
  h("div", { className: "m" }, "发给 " + (d.to || "(空)") + " · " + timeStr(d.at) + ((d.attachments || []).length ? " · 附件 " + d.attachments.length + " 个(需重选)" : ""))),
2197
2240
  h("span", { title: "删除草稿", onClick: (e) => { e.stopPropagation(); delDraft(d.id); setMsg("已删除草稿"); }, style: { cursor: "pointer", color: "var(--sub)", fontSize: 14, padding: "0 6px", flex: "0 0 auto" } }, "×")))
2198
2241
  : h("div", { className: "dsc-card dsc-empty" }, "草稿箱是空的:写信时点「存草稿」就存到这里"))
2199
- : (filtered.length ? filtered.map((m, i) => {
2242
+ : (sorted.length ? sorted.map((m, i) => {
2200
2243
  const from = (m.from && (m.from.name || m.from.address)) || m.fromAddress || "未知发件人";
2201
2244
  const isUnread = m.folder !== "sent" && m.seen !== true;
2202
2245
  // 标记位按「收藏 / 垃圾 / 垃圾箱 / 未读 / 附件」顺序排,一眼能分清状态
package/lib/index.js CHANGED
@@ -27,7 +27,7 @@ import { execFile } from "node:child_process";
27
27
  export const name = "dsh-schedule-calendar";
28
28
  export const inject = ["webServer"];
29
29
 
30
- const VERSION = "1.0.0";
30
+ const VERSION = "1.0.1";
31
31
  const ROUTE_PREFIX = "/schedule-calendar";
32
32
  const PKG_NAME = name;
33
33
  const REGISTRY = "https://registry.npmmirror.com";
package/lib/mail/index.js CHANGED
@@ -34,7 +34,7 @@ import { sendMail } from "./smtp.js";
34
34
 
35
35
  export { PLUGIN_ID };
36
36
 
37
- const MODULE_VERSION = "1.0.0";
37
+ const MODULE_VERSION = "1.0.1";
38
38
  const LOG_PREFIX = "[dsh-schedule-calendar] mail";
39
39
  const MAIL_BASE = "/schedule-calendar/mail"; // 全路径 base(容错用)
40
40
  // 附件上限:单封 8MB/个、合计 20MB(base64 会膨胀 4/3,故请求体上限给到 28MB)
@@ -202,7 +202,7 @@ export class AccountWatcher {
202
202
  // QQ 邮箱识别客户端:发 IMAP ID 命令(部分账号不发会被拒/异常)。
203
203
  try {
204
204
  if (typeof client.id === "function") {
205
- await client.id({ name: "dsh-schedule-calendar", version: "1.0.0", vendor: "deepseek-harness" });
205
+ await client.id({ name: "dsh-schedule-calendar", version: "1.0.1", vendor: "deepseek-harness" });
206
206
  }
207
207
  } catch (e) {
208
208
  this.log("ID command skipped: " + String((e && e.message) || e));
@@ -501,7 +501,7 @@ export async function testAccount(account, authCode) {
501
501
  });
502
502
  await client.connect();
503
503
  try {
504
- if (typeof client.id === "function") await client.id({ name: "dsh-schedule-calendar", version: "1.0.0" });
504
+ if (typeof client.id === "function") await client.id({ name: "dsh-schedule-calendar", version: "1.0.1" });
505
505
  } catch {
506
506
  /* ignore */
507
507
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-schedule-calendar",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "侧边栏日程中心(dsh-better-sidebar companion):今日一屏(多城市天气按 8:30/12:00/17:30 定点刷新 + 黄历 + 可折叠星座运势)、农历月历、日程与待办、日报周报与签到、按人设置班制与上下班倒计时;多账号邮件客户端(IMAP 收信 + SMTP 发信,附件收发、群发、草稿箱、收藏/垃圾/垃圾箱/已读未读);久坐 / 喝水 / 护眼 / 听音乐提醒弹面板内胶囊(停留秒数与位置可调)并可同时推 Windows 通知;壁纸轮播(顺序或随机洗牌、透明度与磨砂)与界面文字颜色可调。数据只存本机。",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",