Saltar al contenido principal
Z

dsh-plugin-sticky-wall

zmengle/dsh-plugin-sticky-wall

Sidebar sticky-note wall with colour-coded notes, checklists, folding, a trash drawer that restores deleted notes, drag and resize, and five sticky_wall_* model tools for writing and restoring notes from chat.

Instalar

dsh plugin --profile web add github:zmengle/dsh-plugin-sticky-wall

README

便签墙 · dsh-plugin-sticky-wall

DSH 侧栏里的一块便签墙:随手贴、随手拖、能手写清单,删了也找得回来。 模型也能往上贴——说一句「记到便签墙」,纸条就真的贴上去了。

dsh plugin add dsh-plugin-sticky-wall

重启 DSH,侧栏多一个便签图标。没有配置文件、没有登录、不联网,数据就在 ~/.dsh/sticky-wall.json。

便签墙

它和自带的待办(todo_write)有什么区别? 待办是「我有几件事要做」;便签墙是「先记一下」—— 一块可以随手摆的桌面。要清单也没问题:行首敲 - 或 1. 当场就变成清单。

  • 四种行格式混排(正文 / 待办 / 无序 / 有序),点方框就打勾
  • 8 种纸色、可选横线纸、可拖大小、可折叠成两行
  • 删除进回收站(30 天 / 500 张),能撤销、能恢复
  • 导出 / 导入备份,换电脑能搬走;数据位置可配置
  • 模型 5 个工具:sticky_wall_list / add / update / remove / restore

一张清单便签

更多截图

折叠(折叠时也能直接选词)

回收站抽屉

提示条不会互相顶掉

功能

  • 侧栏一个图标,主面板一面墙:注册 sidebar.panellist(id = sticky-wall)+ main 同名 key, 点图标切到主面板,和 QQ 机器人面板同一套机制。
  • 一行一种格式:正文 / ☐ 待办 / • 无序 / 1. 有序,四种混排。 待办点方框即「已完成」(打勾 + 删除线),抬头显示 1/3 进度。 行首敲「- 」「1. 」「- [ ] 」直接变成对应格式;悬停纸条底部还有四个格式按钮, 作用在「当前这一行」上,点一下就能切换,再点「≡」回到普通正文。
  • 横线纸可选:抬头那个「带横线的纸」图标,点一下在横线纸 / 空白纸之间切换,逐张便签独立记。 横线按 14px/22px 的行距画在每行下沿,字正好落在线上;纸高按整行增长,永远不会切半行。
  • 纸的质感:8 种纸色(亮纸 + 深墨)+ 细颗粒噪声 + 左上柔光 + 下沿压暗 + 8px 圆角(纸面内容按圆角裁切)+ 内外两层阴影。不做倾斜——旋转层里的文字会丢次像素抗锯齿, 看着发虚,所以纸是摆正的。
  • 可调大小:右下角的斜线手柄(悬停出现)拖一下就能改大小——宽 196–560px, 高按 22px 的整行增减,双击手柄回默认;窄到 240px 以下时底部调色盘自动收起,把位置让给行格式按钮。
  • 一键折叠:抬头最左边那个「∨」把纸条收成两行(底部渐隐 + 一个「展开」小胶囊),点正文或再点一次「∧」展开。 折叠只截高度、不动宽度——宽度是你拖出来的,不该被自动改掉。折叠状态跟着便签存。 折叠时正文是只读的,但可以直接选词复制:拖选不会把纸展开(只有不带选区的点击才展开)。
  • 回收站:删除(胶头 🗑、清空墙面、模型工具都算)只是把便签移进右侧回收站,不会立刻销毁; 工具栏右边的「回收站」(带数量)点开是抽屉:逐张「恢复」或「彻底删除」,也可以一次清空回收站。 抽屉默认收起,图钉可固定(固定后点别处不会自动收起,状态记在本地);删除后 toast 里还有「撤销」直达; Esc 也能关抽屉(正在写便签时 Esc 优先给输入框)。 恢复时如果原来那块地方已经被别的纸占了(重叠超过自身面积 20%),会自动挪到第一个空格子并提示。
  • 随手摆放:按住纸头(含六点抓手的那条)拖到任意位置,落点按百分比存, 面板宽窄变了也不会散架。双击空白处即在点击处新建一张。
  • 就地编辑:每行都是一个输入框,点进去直接改;输入 0.65s 后自动落盘, 失焦 / ⌘/Ctrl+Enter / Esc 都会立即保存。回车拆行、行首退格并回上一行、↑↓ 跨行移动。
  • 纸色 / 置顶 / 删除:悬停纸条出现调色圆点;置顶的纸永远在最上层(顶部一道墨色压边)。
  • 搜索 / 只看置顶 / 整理 / 清空:搜索按正文过滤;「整理」是流式重排(见下); 清空需要点两次。
  • 整理 = 对齐,不是重排:按你摆的位置把便签聚成行(y 差 56px 内算同一行), 行内保持你的左右顺序、统一左对齐、间距 16px;行与行按「这一行最高 + 16px」往下排, 左边距 18px。不会因为某张纸大就把它排到后面去——宽纸还待在它那一行里。 一行真的比面板宽时,先把行内间距压到 8px,仍放不下才把这一行末尾的几张挪到下一行。
  • 跨会话持久化:$DSH_HOME/sticky-wall.json(默认 ~/.dsh/sticky-wall.json),原子写、写失败会备份原文件。
  • 备份与迁移:工具栏齿轮里能导出 JSON(含回收站)、导入时可选合并或整体覆盖;设置面板还显示版本、 条数、数据大小和路径。
  • 和模型联动:5 个工具,说一句「把这条记到便签墙」就会真的贴上; 模型贴来的 Markdown(- [ ] / - / 1. )会自动解析成复选框和列表; 模型贴的便签 6 秒内自动出现在墙面上(面板可见时轮询)。

交互一览

操作效果
点侧栏「便签墙」图标切到便签墙主面板
点「新建便签」新建并聚焦第一行
双击墙面空白处在该处新建一张
按住纸头拖动移动便签(松手保存)
回车在当前行下方拆出一行(同格式)
行首退格列表/待办行先退回正文;空行则并到上一行
敲「- 」「1. 」「- [ ] 」当前行变成无序 / 有序 / 待办
点待办方框打勾(已完成,带删除线)
悬停 → 底部四个格式按钮把当前行切成 正文(三条线)/ 待办(方框+勾)/ 无序(圆点)/ 有序(衬线「1」)
悬停 → 右下角斜线手柄拖动改大小(宽 196–560px、高按整行);双击回默认
悬停 → 抬头 ∨ / ∧折叠成两行 / 展开(点正文也能展开)
悬停 → 抬头「带横线的纸」图标横线纸开 / 关
悬停 → 调色圆点换纸色
悬停 → 抬头图钉 / 垃圾桶置顶 / 移进回收站(可撤销)
工具栏「回收站」(带数量)打开右侧抽屉(图钉可固定,状态记在本地)
回收站 → 恢复 / 彻底删除放回墙面(最上层)/ 真正销毁
墙面右下 ↑ / ↓一键到最上 / 最下(只有内容超出一屏时才出现,到头的那个变灰)
「只看置顶」筛选只看置顶便签(和抬头那个「置顶」不是一回事)
「整理」 / 「清空」对齐排布 / 连点两次清空(清空也进回收站)
工具栏最右的「设置」版本、条数、数据大小、存储路径、导出 / 导入备份
页脚右侧的路径数据文件位置,点一下复制路径

备份与迁移

数据只在本机一个 JSON 里(默认 ~/.dsh/sticky-wall.json),所以给了两条正经出口:

  • 导出备份:设置面板里点一下,下载 sticky-wall-20261107-0216.json—— 墙上 + 回收站全都在,勾选状态、纸色、位置、尺寸一个不落。
  • 导入备份:选文件后先告诉你要导入几张,再让你选:
    • 合并进来:文件里的便签加到墙上,现有的不动(id 撞了会自动换新 id);
    • 覆盖现有:整体替换(含回收站),要点两次确认。

设置与备份

换电脑就是「在旧机器上导出 → 拷文件 → 新机器导入」,或者直接把 sticky-wall.json 拷过去。 想放到别的位置(比如同步盘)见下面的「配置」。

模型工具

工具用途
sticky_wall_list读墙上的便签(id、纸色、时间、正文;清单会带 - [ ] / 1. 标记),可按 pinned/query 过滤;回收站里的不列
sticky_wall_add贴一张新便签(text 必填,支持 Markdown 清单,color、pinned 可选)
sticky_wall_update改正文 / 纸色 / 置顶(正文会重新解析成行,勾选状态会重置;回收站里的便签要先恢复)
sticky_wall_remove移到回收站(软删除,可用 sticky_wall_restore 恢复)
sticky_wall_restore把回收站里的便签恢复回墙面(恢复后在最上层)

返回文本里带 [n-xxxxxx] 这样的短 id,后续 update/remove 用它。

数据与隐私

  • 全部数据只存在本机(默认 ~/.dsh/sticky-wall.json,路径可配置,见下),不上传、不联网。
  • 文件坏了不会静默重写:启动时如果 JSON 解析失败,原文件会被改名备份成 sticky-wall.json.corrupt-<时间戳>,面板里也会弹一条提示告诉你备份路径——保证「数据消失」是可追的, 而不是被下一次保存覆盖掉。
  • 改动的 HTTP 回包是等落盘之后才发的,所以面板上的「已保存 HH:MM」是真的写进了磁盘。
  • 一张便签 = lines[](每行 kind/text/done)+ 纸色/位置/置顶/横线开关 + 尺寸(w、minLines)
    • 折叠状态(collapsed)+ 回收站标记(trashed、deletedAt); text 是从行派生的可读副本,给搜索和工具用——所以老数据(只有 text)能直接读, 下次保存时自动升级成行结构。
  • 回收站是同一份数组里的软删除(打标记),所以「恢复」是无损的:位置、尺寸、纸色、勾选状态全都还在。 容量 500 张 / 30 天:超期在加载时清掉;超过 500 张则在删除那一刻挤掉最旧的一张并打日志 ——「丢」只发生在看得见的删除动作上,读盘时绝不裁剪(老版本会按 100 截断,清空后重启会静默丢便签)。
  • 上限:500 张便签、每张 200 行 / 每行 1000 字 / 合计 4000 字;超出是截断(不是拒绝), 但不会悄悄发生:保存回包带 truncated: true,面板会弹一句「内容超出上限,已截断」。 回收站里的不占墙面名额(各算各的 500)。
  • 首次加载会在文件缺失时放两张欢迎便签,之后不再写入任何默认内容。

配置:把数据放到别处

默认落在 $DSH_HOME/sticky-wall.json(一般就是 ~/.dsh/sticky-wall.json)。 想换位置就改插件配置里的 storePath,然后重启 DSH:

# cordis.patch.yml
- insert:
    - id: sticky-wall
      name: dsh-plugin-sticky-wall
      config:
        storePath: D:/notes/sticky-wall.json   # 绝对路径、~/notes/...、或相对 $DSH_HOME 的路径都行
  • 换路径时如果新文件还不存在、而默认文件在,会先把默认文件复制过去,不会让老便签凭空消失。
  • 当前实际用的路径显示在面板页脚右侧(…/.dsh/sticky-wall.json),点一下复制;改过配置会高亮成主题色。
  • 这个配置只在宿主侧生效,改完需要重启 DSH(和其它宿主改动一样)。

结构与实现要点

dsh-plugin-sticky-wall/
├── package.json        # dsh.bundle.patch + dsh.client.platform=web + exports["./client"]
├── cordis.patch.yml    # 插入一行:id=sticky-wall, name=dsh-plugin-sticky-wall
├── index.js            # Host 半侧:行模型 / 存储 / HTTP 端点 / 5 个模型工具(零第三方依赖)
├── client.js           # Client 半侧:ModuleLoader bundle,两处 slot 注册 + 整面墙的 UI
└── docs/               # 截图
  • 双面插件:Host 起 webServer 前缀路由 /api/dsh-sticky-wall/*,Client 同源 fetch 读写, 不碰 typert/remote,也就没有版本适配问题。
  • 端点:/snapshot、/upsert、/remove(软删除)、/restore、/purge、/purge-all、/clear(也是软删除)、 GET /export(导出整份数据)、POST /import(mode=merge|replace,上限 8MB)。 除了 upsert 的 saved/skipped/full,回包统一带 { notes, trash, trashCount, colors, store },客户端一份合并逻辑就能覆盖所有动作。
  • 导入的两条语义:merge 只加不删、撞 id 自动换新 id(防止覆盖现有内容); replace 整体替换(含回收站),UI 上要点两次确认。
  • 两条瘦身通道(回收站和墙都可能很大,别每次全量搬):
    • /snapshot?trash=0 只回 trashCount,明细留空。客户端轮询用它,条数变了才补拉一次明细(抽屉打开时也会补)。
    • /upsert 带 light: true 时不回整面墙(保存回包实测 1.1MB → 几 KB)。客户端保存只等 ack,本地就是权威。
  • 写盘:全量 JSON 原子写(tmp + rename),但内容没变就跳过;写失败会作废「已落盘」标记,下次同样的内容还会再写。
  • 同源校验:每个请求都过一道 Sec-Fetch-Site / Origin 检查,跨站直接 403;POST 只接受 application/json (跨站的「简单请求」只能用 text/plain,这一条把它们全挡掉)。这样本机别的页面就没法偷偷改/清你的便签了 —— 顺手也挡住了 CSRF 那类玩法(实测:本机 4398 端口的页面写入被拒,数据 0 变化)。
  • 层级:便签 z-index ≤ 40000(100 + z,置顶 +20000)→ 滚动按钮 43000 → 抽屉遮罩 44000 → 抽屉 44100 → toast 50000。 抽屉和遮罩都挂在新加的 .sw-main(工具栏以下那一块)里,所以既盖得住便签,又不会糊住工具栏和页脚。
  • 未知字段原样保留:normalizeNote 只收敛已知字段,其余键(跳过 __proto__ 之类)原样写回。 这样以后客户端只加 UI 字段时,不用再改宿主、也不用重启。
  • 折叠:collapsed 布尔值存进便签;CSS 用 max-height: calc(lh*2 + 上下内边距) 截断 + mask-image 渐隐, 底部菜单同时 display: none。正文不是 pointer-events: none(那样选不了文字), 而是折叠时 readOnly + body 的 onClick 里判断 window.getSelection():有选区(用户在选词)就不展开。
  • 回收站抽屉的「固定/收起」偏好存在 localStorage(dsh-sticky-wall:trash),不占便签数据。
  • client.js 是手写的 ModuleLoader bundle(window.__ModuleLoader__.load({ id, factory })), 只 require('react')——平台种子表提供,不需要构建步骤,改完刷新页面即生效。
  • 渲染性能:每张便签 React.memo,只收 note + focusId 两个 prop,不订阅 store; 打字时只有正在编辑的那一张重渲染。实测 200 张便签下从 42.7ms/键降到 6.7ms/键(20 张 4.5ms/键)。
  • 行模型:lines: [{ id, kind, text, done }],kind ∈ text|todo|ul|ol。 有序列表按「连续 ol 段」在前端重新编号,所以增删行不用重写编号; textFromLines() / linesFromText() 在两端各有一份同构实现,保证工具写入与老数据解析一致。
  • 横线是 background 画的,不是额外元素: .sw-body 用 linear-gradient 画 1px、background-size: 100% 22px, background-position-y = 行距 + 上内边距,所以第一条横线正好是第一行的下沿; 每个行盒高度都吸附到 22px 的整数倍,横线不会被切半。改行距只需同步 LINE_H(JS)与 --sw-lh(CSS),NOTE_W 决定网格列数。
  • 写入的并发安全:每次本地改动会给该便签打一个自增 revision;保存回包返回时 只有 revision 没变才清 dirty,否则保留本地版本(避免「勾选的同时旧保存回包把状态盖回去」)。
  • 尺寸:w(px,196–560)与 minLines(1–30,按 22px 行高换算)。 拖手柄时只改元素的内联宽高与 --sw-min-lines(实时预览),松手才写回 store; 高度用 min-height 表达,所以便签永远「至少这么高、内容多了自己长」。
  • 窄便签自适应:.sw-note 开了 container-type: inline-size, 容器查询在 ≤240px 时收起调色盘;纸用 overflow: hidden + 8px 圆角,横线会被圆角自然裁掉。
  • 整理的量尺:直接读 DOM(offsetWidth/offsetHeight)拿每张纸的真实尺寸, 被搜索过滤掉、量不到的才用 行数 × 22 + 头尾 估算;先按现有 y 聚行、行内按 x 排序, 再统一落位。整定是幂等的:连点两次结果一样(第一次排完就已经是对齐状态)。 排完若内容高于画布,把画布 min-height 撑到 内容高 / 0.84, 既让点阵随之延长,也保证最后一行换算成百分比后不超过宿主侧 88% 的 y 上限。
  • 图标:全部是 24 网格的 path,不用文字字形(≡ ☐ • 1. 那种会受字体影响、基线与重量都不齐)。 描边按尺寸取:size >= 24 用 1.6,更小的用 2(12px 时正好 1px 实心线);圆头圆角。 行格式四个按钮是 18px 图标配 22px 按钮,抬头四个小图标 13px 配 20px 按钮; 小尺寸的图形都做了减法(垃圾桶去掉内部竖线、横线纸只画两条线、抓手点放大到 1.5), 免得 12–13px 时糊成一团。
  • 纸面质感全在 CSS:SVG feTurbulence 噪声(data URI)+ 柔光 + 下沿压暗 + 内外阴影, 文字不做变形,保证 1x 屏上也是清楚的。
  • 颜色一律走 DSH 主题令牌(--dsw-alias-*),纸色是固定的亮纸深墨, 所以浅色/深色主题下都像「桌上的纸」而不是「界面里的卡片」;横线与复选框描边是 color-mix(in srgb, currentColor 17~58%, transparent),跟着纸的墨色走。
  • 样式通过 data-plugin + data-plugin-css 标记注入;热替换时若旧标签还在, installStyles() 会比较内容并原地改写,避免新样式被幂等判断挡掉。

安装 / 卸载

# 从 npm 装(推荐)
dsh plugin add dsh-plugin-sticky-wall

# 从源码装(跑最新 main)
dsh plugin add git+https://github.com/zmengle/dsh-plugin-sticky-wall.git

# 本地目录
dsh plugin add D:/path/to/dsh-plugin-sticky-wall

# 卸载(不会删你的便签文件)
dsh plugin remove dsh-plugin-sticky-wall

有多个 profile 时,按 dsh plugin --help 的写法带上 --profile <名字>。

装完重启 dsh web,侧栏就会出现便签图标。要求 DSH >=0.2.1-rc.4 <0.3.0(engines.dsh 已声明: 小版本内行为稳定,但不跨 0.3 猜兼容——不满足的版本会被插件管理器直接拦住,而不是装上一个空白面板)。

已知限制

  • 便签是自由摆放的:窗口变得很窄时可能出现重叠,用「整理」重排即可。
  • 高度只有「最少几行」这一个维度:内容比它高时纸会自己长,所以拖高只会变高、不会把内容裁掉。
  • 列表只有一层,不支持嵌套;每行一个格式,不混排(正文行里不能再嵌入列表)。
  • 轮询间隔 6 秒,多窗口同开时另一边的改动最多滞后一个周期。
  • 工具改正文会重建行,勾选状态会丢;要保留勾选请只在 UI 里改。
  • 删除有回收站兜底(30 天 / 500 张),但「彻底删除」和「清空回收站」不可撤销。
  • 写盘是整文件重写:实测 50 张(94KB)一次保存往返约 3.4ms,顶到上限 500 张 + 100 张回收站(1.1MB)约 8–9ms (都是「轻回包」下的数字,机器不同会有出入)——够用,但再往上(几千张)就该换成增量格式或 SQLite 了。 这也是「单文件 + 全量写」这个设计的真实上限。
  • 不做富文本/附件——便签就该是纯文本。
  • 鉴权沿用 DSH 自身的本地回环边界:接口做了同源校验(跨站 403、非 JSON 415), 但同一台机器上的进程(脚本、其它本地程序)仍然能直接读写那个 JSON 文件——这是本地应用的固有边界。

更新日志

见 CHANGELOG.md。

Plugins relacionados