dsh-happy-reader 0.3.3 → 0.3.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 ADDED
@@ -0,0 +1,55 @@
1
+ # 版本记录
2
+
3
+ 版本策略:插件版本与 DSH 客户端版本绑定。`package.json.engines.dsh` 是安装/更新时的
4
+ 硬门槛;客户端 `apply()` 里还有一次运行时闸门,手动 `file:///` 安装也会被拦住。
5
+ 旧的 DSH 用户继续装旧的插件版本,不追求“一个版本通吃”。
6
+
7
+ | 插件版本 | DSH 范围 | 状态 |
8
+ |---|---|---|
9
+ | 0.3.4 | `>=0.2.2-rc.2 <0.2.3` | 2026-11-12 已发布 |
10
+ | 0.3.3 / 0.3.2 | DSH 0.2.2-rc.1 / rc.2 | 已发布 |
11
+ | 0.3.0 / 0.3.1 | DSH 0.2.2-rc.1 | 已发布 |
12
+ | 0.2.0 – 0.2.7 | DSH 0.2.2 早期 | 已发布,保留 tag |
13
+
14
+ ## 0.3.4(2026-11-12 发布)
15
+
16
+ - 适配 DSH 0.2.2-rc.2;`engines.dsh` 收紧到 `>=0.2.2-rc.2 <0.2.3`,并加运行时版本闸门。
17
+ - 默认快捷键改为 `Ctrl/⌘+Alt+I`(隐藏输入框)/ `Ctrl/⌘+Alt+T`(隐藏上边栏)。
18
+ - 复制监听先看 `e.defaultPrevented`:官方或其它插件先处理时主动让行。
19
+ - macOS:面板与 Aa 加 `data-window-drag-exclude`(单个按钮不需要
20
+ `data-window-drag-controls`;仅基础声明,未实机验证)。
21
+ - 面板稳定性:最大化/还原/全屏切换时只按新视口夹取当前落点,不再重跑自动候选;
22
+ 拖动 Aa 不再带着已打开的面板一起移动;未拖动过的 Aa 在窗口变化后仍按右下 16px 锚定。
23
+
24
+ ## 0.3.3
25
+
26
+ - 修复混合选区被整段退回原生:代码块↔列表/正文、表头↔列表、正文↔表格边界、表格↔表格。
27
+ - 首尾代码块补语言;边界表补完整表头/整行;有序列表中段续接真实序号;
28
+ 嵌套列表子项不再输出 `- -`;任务项 checkbox 状态补回。
29
+ - 代码横幅/复制按钮的 UI 选区交还原生复制。
30
+
31
+ ## 0.3.2
32
+
33
+ - 通过官方 `ctx.shortcuts` 注册“隐藏输入框 / 隐藏上边栏”,面板显示官方键帽提示。
34
+ - 隐藏输入区改用 `visibility:hidden` 预防聚焦,官方“引用文本”不再带动会话滚动。
35
+ - 代码围栏从 DSH 横幅读取语言;表格/列表结构补全;浅色主题面板白底深字。
36
+ - 面板高度锁定、滚动区顶部跳过 `scrollHeight` 读取、宽度观察器延后一帧。
37
+
38
+ ## 0.3.1
39
+
40
+ - 修复行内代码选区复制外壳(`Elf64_Rela` 不再被转义成 `Elf64\_Rela`)。
41
+ - 记录官方原生右键“复制”与全局 copy 拦截的冲突(见 TODO)。
42
+
43
+ ## 0.3.0
44
+
45
+ - 界面迁移到官方 `shell.overlay` / `settings.general.item` 槽位;文案走官方 locale;
46
+ 开关符合 `role="switch"`;取色只用官方主题变量。
47
+ - 正文字体三组可折叠卡片(正文/代码/终端),中英分设字体,官方代码/终端字体与字号同步。
48
+ - 官方「通用设置」增加中文/西文两行正文字体;浮动面板与设置页双向同步。
49
+ - 宽度扩展、全屏补偿、引用滚动修复、热重载清理与禁用即恢复。
50
+
51
+ ## 0.2.0 – 0.2.7
52
+
53
+ 公开版本重命名后的迭代:面板开关与底部设置栏、滚动条贴右缘、隐藏上边栏收敛为
54
+ 会话头正向判据、行距/字距、面板拖动/拉高、失败回退与安装/卸载清理。逐版本提交见
55
+ GitHub tag `v0.2.0` – `v0.2.7`。
package/README.md CHANGED
@@ -128,8 +128,8 @@
128
128
  ### 10. 快捷键(官方通用设置)
129
129
  - 通过官方 `ctx.shortcuts` 注册两条命令,自动出现在**设置 → 通用 → 快捷键**
130
130
  (官方“编辑快捷键”入口)里:**隐藏输入框**、**隐藏上边栏**
131
- - 桌面端默认键位:Windows/Linux `Ctrl+Shift+I` / `Ctrl+Shift+T`,
132
- macOS `⌘+Shift+I` / `⌘+Shift+T`;Web 端默认不绑定
131
+ - 桌面端默认键位:Windows/Linux `Ctrl+Alt+I` / `Ctrl+Alt+T`,
132
+ macOS `⌘+Option+I` / `⌘+Option+T`;Web 端默认不绑定
133
133
  - 在速查里可以直接录制、清除或恢复默认;如果默认键位被注册服务拒绝(与已注册
134
134
  命令冲突),插件会自动改为“未绑定”命令;若被固定操作占用,速查会标出冲突,
135
135
  仍可自行改键
@@ -167,9 +167,16 @@ dsh plugin --profile <profile> add dsh-happy-reader
167
167
 
168
168
  ## 兼容性
169
169
 
170
- - Windows + DSH 桌面端 0.2.2 开发验证(按官方客户端插件接口实现);macOS / Linux 未适配
171
- (面板避让的是 Windows 系统按钮条,字体候选为 Windows 常用字体)。
172
- - 依赖官方客户端界面结构,DSH 版本升级后可能需要跟随更新。
170
+ > 逐版本改动与 DSH 范围见 [CHANGELOG.md](CHANGELOG.md);旧版本继续保留在 npm 与 GitHub tag 上。
171
+
172
+ - **0.3.4**:DSH `0.2.2-rc.2` ≤ 版本 < `0.2.3`(`package.json.engines.dsh`);插件管理器
173
+ 会在安装/更新时拒绝旧 DSH,客户端挂载时也会再做一次版本闸门。
174
+ - **0.3.3 / 0.3.2**:DSH 0.2.2-rc.1 / rc.2;**0.3.0 / 0.3.1**:DSH 0.2.2-rc.1。
175
+ - 仍在旧 DSH 上的用户请显式安装对应旧版,例如
176
+ `dsh plugin --profile <profile> add dsh-happy-reader@0.3.3`;不要依赖 `latest` 自动升级。
177
+ - Windows + DSH 桌面端实机验证;macOS 已给面板与 Aa 加 `data-window-drag-exclude`
178
+ 基础兼容声明,但未实机验证;Linux 字体候选仍按 Windows 常用字体。
179
+ - 依赖官方客户端界面结构,DSH 版本升级后需要跟随发布对应插件版本。
173
180
  - 运行时按事件工作:无常驻定时轮询;宽度观察器只在面板打开或扩展宽度生效时挂载,
174
181
  纯高度变化不触发宽度逻辑;字体 token 晚就绪用有限次退避重试(成功后停止)。
175
182
 
package/lib/client.js CHANGED
@@ -21,7 +21,7 @@ window.__ModuleLoader__.load({
21
21
  var exports = module.exports
22
22
 
23
23
  // ── 常量 ────────────────────────────────────────────────────────────
24
- const VERSION = '0.3.3'
24
+ const VERSION = '0.3.4'
25
25
  const INSTANCE_TOKEN = {}
26
26
  const GROUPS_VERSION = 3
27
27
  const STATE_KEY = 'dsh.reader-boost.v1'
@@ -868,8 +868,32 @@ window.__ModuleLoader__.load({
868
868
  }
869
869
  }
870
870
 
871
+ /** 0.3.4 起明确声明 DSH 兼容范围;手动 file:/// 安装没有插件管理器兜底,
872
+ * 这里再做一次运行时闸门。未知版本不拦截,避免破坏开发/预览环境。 */
873
+ function dshVersionSupported(version) {
874
+ if (typeof version !== 'string' || !version) return true
875
+ const m = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(version)
876
+ if (!m) return true
877
+ const major = Number(m[1]), minor = Number(m[2]), patch = Number(m[3])
878
+ if (major !== 0 || minor !== 2 || patch !== 2) return false
879
+ if (!m[4]) return true
880
+ const pre = /^rc\.(\d+)$/.exec(m[4])
881
+ return !!(pre && Number(pre[1]) >= 2)
882
+ }
883
+
871
884
  function apply(ctx) {
872
885
  try {
886
+ const bootVersion = globalThis.__DSH_BOOT__ && globalThis.__DSH_BOOT__.release
887
+ ? globalThis.__DSH_BOOT__.release.version : undefined
888
+ if (!dshVersionSupported(bootVersion)) {
889
+ console.error('[dsh-happy-reader] 当前 DSH ' + bootVersion + ' 不在本插件支持范围(需 0.2.2-rc.2 至 0.2.2.x),已停止挂载。')
890
+ // 支持范围被收紧后热重载到本版本:旧实例不应继续留在页面上。
891
+ const staleDispose = window.__dsrbDispose
892
+ if (typeof staleDispose === 'function') {
893
+ try { staleDispose() } catch { /* 旧实例拆除失败不阻塞退出 */ }
894
+ }
895
+ return
896
+ }
873
897
  // 热重载:DSH 监视插件文件变化,会再次执行本客户端。先拆旧实例、再按 id
874
898
  // 清扫遗留节点、最后重建 —— 页面上运行的必然是最新代码。
875
899
  const prevDispose = window.__dsrbDispose
@@ -996,9 +1020,9 @@ window.__ModuleLoader__.load({
996
1020
  const shortcuts = sctx.shortcuts
997
1021
  if (!shortcuts || typeof shortcuts.register !== 'function') return
998
1022
  const desktopDefaults = (code) => ({
999
- 'desktop:macos': { code, modifiers: ['primary', 'shift'] },
1000
- 'desktop:windows': { code, modifiers: ['primary', 'shift'] },
1001
- 'desktop:linux': { code, modifiers: ['primary', 'shift'] },
1023
+ 'desktop:macos': { code, modifiers: ['primary', 'alt'] },
1024
+ 'desktop:windows': { code, modifiers: ['primary', 'alt'] },
1025
+ 'desktop:linux': { code, modifiers: ['primary', 'alt'] },
1002
1026
  })
1003
1027
  const command = (id, labelKey, aliases, code, action) => ({
1004
1028
  id,
@@ -1150,6 +1174,10 @@ window.__ModuleLoader__.load({
1150
1174
  fab.type = 'button'
1151
1175
  fab.title = t('fabTitle')
1152
1176
  fab.setAttribute('aria-label', t('fabTitle'))
1177
+ // 0.2.2-rc.1+ macOS:自定义浮层控件需显式声明窗口拖拽排除。
1178
+ // 单个按钮用 data-window-drag-exclude 即可;data-window-drag-controls 是
1179
+ // “容器内控件分别排除”的作用域,不应标在按钮自身。
1180
+ fab.setAttribute('data-window-drag-exclude', '')
1153
1181
  const [fx, fy] = clampPos(
1154
1182
  state.x ?? window.innerWidth - FAB_SIZE - 16,
1155
1183
  state.y ?? window.innerHeight - FAB_SIZE - 16,
@@ -1165,6 +1193,7 @@ window.__ModuleLoader__.load({
1165
1193
  panel.id = PANEL_ID
1166
1194
  panel.setAttribute('role', 'region')
1167
1195
  panel.setAttribute('aria-label', t('panelTitle'))
1196
+ panel.setAttribute('data-window-drag-exclude', '') // macOS:浮层可见矩形不参与窗口拖拽
1168
1197
 
1169
1198
  const headRow = el('div', 'dsrb-head')
1170
1199
  const title = el('h4', null, t('panelTitle'))
@@ -1476,9 +1505,11 @@ window.__ModuleLoader__.load({
1476
1505
  const titlebarH =
1477
1506
  Number.parseFloat(getComputedStyle(document.documentElement).getPropertyValue('--dsh-windows-titlebar-height')) || 40
1478
1507
  const CAP_W = 138
1479
- function placePanel(keepScroll) {
1508
+ function placePanel(keepScroll, keepPosition) {
1480
1509
  const scrollTop = typeof keepScroll === 'number' ? keepScroll : panelBody.scrollTop
1481
1510
  const r = fab.getBoundingClientRect()
1511
+ const currentLeft = Number.parseFloat(panel.style.left)
1512
+ const currentTop = Number.parseFloat(panel.style.top)
1482
1513
  const fixedH = typeof state.panelH === 'number'
1483
1514
  if (!fixedH) {
1484
1515
  panel.style.height = ''
@@ -1498,14 +1529,21 @@ window.__ModuleLoader__.load({
1498
1529
  const ph = Math.max(PANEL_MIN_H, Math.min(desired, vh - 2 * M - BORDER_Y))
1499
1530
  const clampX = (x) => Math.min(Math.max(M, x), Math.max(M, vw - pw - M))
1500
1531
  const clampY = (y, yMin) => Math.min(Math.max(yMin, y), Math.max(yMin, vh - ph - BORDER_Y - M))
1532
+ const yMinFor = (x) => CAP_H > 0 && x + pw > vw - CAP_W ? CAP_H : M
1501
1533
  let best = null
1502
- if (typeof state.panelX === 'number' && typeof state.panelY === 'number') {
1534
+ // 窗口最大化/还原/全屏切换时,面板若已经开着,保持当前视觉位置并只做视口夹取;
1535
+ // 不重新跑自动候选,避免面板在窗口状态变化时跳位。
1536
+ if (keepPosition && Number.isFinite(currentLeft) && Number.isFinite(currentTop)) {
1537
+ const sx = clampX(currentLeft)
1538
+ best = { x: sx, y: clampY(currentTop, yMinFor(sx)) }
1539
+ }
1540
+ if (!best && typeof state.panelX === 'number' && typeof state.panelY === 'number') {
1503
1541
  // 用户拖过位置 → 用记住的位置;仍做两件保护:避开右上角系统按钮条;
1504
1542
  // 若与按钮重叠(按钮可能后来被拖到附近)则放弃记忆、回自动布局
1505
1543
  const sx = clampX(state.panelX)
1506
- const sy = clampY(state.panelY, CAP_H > 0 && sx + pw > vw - CAP_W ? CAP_H : M)
1544
+ const sy = clampY(state.panelY, yMinFor(sx))
1507
1545
  const overlapsFab = sx < r.right + GAP && sx + pw > r.left - GAP && sy < r.bottom + GAP && sy + ph > r.top - GAP
1508
- if (overlapsFab) {
1546
+ if (overlapsFab && !keepPosition) {
1509
1547
  state.panelX = null
1510
1548
  state.panelY = null
1511
1549
  writeState({ panelX: null, panelY: null })
@@ -1525,8 +1563,7 @@ window.__ModuleLoader__.load({
1525
1563
  x < r.right + GAP && x + pw > r.left - GAP && y < r.bottom + GAP && y + ph > r.top - GAP
1526
1564
  for (const c of candidates) {
1527
1565
  const x = clampX(c.x)
1528
- const yMin = CAP_H > 0 && x + pw > vw - CAP_W ? CAP_H : M
1529
- const y = clampY(c.y, yMin)
1566
+ const y = clampY(c.y, yMinFor(x))
1530
1567
  if (!hits(x, y)) {
1531
1568
  best = { x, y }
1532
1569
  break
@@ -1703,8 +1740,9 @@ window.__ModuleLoader__.load({
1703
1740
  const root = document.documentElement
1704
1741
  if (on) root.setAttribute(FS_ATTR, '')
1705
1742
  else root.removeAttribute(FS_ATTR)
1706
- // 全屏切换会改变系统按钮条的有无 → 面板开着时当场重定位
1707
- if (state.open) placePanel()
1743
+ // 全屏切换会改变系统按钮条的有无 → 面板开着时夹取到新视口;
1744
+ // 保留用户当前看到的落点,不重新跑自动候选。
1745
+ if (state.open) placePanel(undefined, true)
1708
1746
  }
1709
1747
  function applyFullscreen() {
1710
1748
  // 进入全屏必须发生在用户手势内:本函数由开关 change / 整条点击同步触发,满足
@@ -2133,7 +2171,8 @@ window.__ModuleLoader__.load({
2133
2171
  drag = null
2134
2172
  if (wasDrag) {
2135
2173
  writeState({ x: fab.offsetLeft, y: fab.offsetTop })
2136
- if (state.open) placePanel()
2174
+ // 面板是独立浮层:拖动 Aa 时保持面板原位,不跟着按钮跑;
2175
+ // 下次打开面板再按新按钮位置自动布局。
2137
2176
  } else {
2138
2177
  togglePanel(!state.open)
2139
2178
  }
@@ -2622,6 +2661,7 @@ window.__ModuleLoader__.load({
2622
2661
  }
2623
2662
  function onCopy(e) {
2624
2663
  try {
2664
+ if (e.defaultPrevented) return // 尊重先注册/先执行的复制处理(含官方公式复制)
2625
2665
  const sel = document.getSelection()
2626
2666
  if (!sel || sel.isCollapsed || !sel.rangeCount) return
2627
2667
  const elOf = (n) => (n && n.nodeType === 3 ? n.parentElement : n)
@@ -2753,11 +2793,15 @@ window.__ModuleLoader__.load({
2753
2793
  document.addEventListener('keydown', onDocKeyDown)
2754
2794
 
2755
2795
  function onResize() {
2756
- const [x, y] = clampPos(fab.offsetLeft, fab.offsetTop, FAB_SIZE, FAB_SIZE)
2796
+ // 未拖动过:Aa 始终按“右下 16px”重新锚定;拖动过:按记住的绝对位置夹取。
2797
+ const baseX = state.x == null ? window.innerWidth - FAB_SIZE - 16 : state.x
2798
+ const baseY = state.y == null ? window.innerHeight - FAB_SIZE - 16 : state.y
2799
+ const [x, y] = clampPos(baseX, baseY, FAB_SIZE, FAB_SIZE)
2757
2800
  fab.style.left = x + 'px'
2758
2801
  fab.style.top = y + 'px'
2759
2802
  refreshWidthTargets()
2760
- if (state.open) placePanel()
2803
+ // 面板已开着:保持当前视觉位置并夹取视口,避免最大化/还原时跳位。
2804
+ if (state.open) placePanel(undefined, true)
2761
2805
  }
2762
2806
  window.addEventListener('resize', onResize)
2763
2807
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-happy-reader",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "description": "Happy Reader(阅读增强):隐藏输入框 / 上边栏、全屏切换、字号 10–40px、行距/字距微调、面板可拖动可拉高、复制为 Markdown(公式→LaTeX)、内容宽度扩展、中英文分别选正文字体 + 官方代码/终端字体与字号(仅列本机已装)—— 与官方设置协同,不冲突",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -34,6 +34,7 @@
34
34
  "lib",
35
35
  "cordis.patch.yml",
36
36
  "README.md",
37
+ "CHANGELOG.md",
37
38
  "LICENSE",
38
39
  "locale/*.json",
39
40
  "icon.svg"
@@ -46,5 +47,8 @@
46
47
  "platform": "web",
47
48
  "immediately": true
48
49
  }
50
+ },
51
+ "engines": {
52
+ "dsh": ">=0.2.2-rc.2 <0.2.3"
49
53
  }
50
54
  }