dsh-file-activity 0.5.10 → 0.5.13
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 +18 -0
- package/README.md +3 -0
- package/lib/client.js +239 -2
- package/lib/client.src.js +3 -0
- package/lib/parts/apply.part.js +10 -0
- package/lib/parts/i18n.part.js +5 -0
- package/lib/parts/rightbar-width.part.js +176 -0
- package/lib/parts/settings.part.js +38 -1
- package/lib/parts/styles.part.js +7 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
本文件记录 dsh-file-activity 的所有版本变更。格式遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
|
4
4
|
|
|
5
|
+
## [0.5.13] - 2026-09-25
|
|
6
|
+
|
|
7
|
+
### 变更
|
|
8
|
+
|
|
9
|
+
- fix: 适配宿主 0.1.7-rc.2 的事件名与席位契约,修正失效文档断言
|
|
10
|
+
|
|
11
|
+
## [0.5.12] - 2026-09-18
|
|
12
|
+
|
|
13
|
+
### 变更
|
|
14
|
+
|
|
15
|
+
- feat(file-activity): 右侧边栏默认宽度配置(默认 20%)
|
|
16
|
+
|
|
17
|
+
## [0.5.11] - 2026-09-18
|
|
18
|
+
|
|
19
|
+
### 变更
|
|
20
|
+
|
|
21
|
+
- feat(file-activity): 右侧边栏默认宽度配置(默认 20%)
|
|
22
|
+
|
|
5
23
|
## [0.5.10] - 2026-09-16
|
|
6
24
|
|
|
7
25
|
### 变更
|
package/README.md
CHANGED
|
@@ -52,6 +52,9 @@ dsh plugin --profile web add link:<仓库路径>/plugins/dsh-file-activity
|
|
|
52
52
|
| 配置项 | 默认 | 作用 |
|
|
53
53
|
| ------------------------------------------------ | ---- | -------------------------------------------------------------------------- |
|
|
54
54
|
| 会话开始时自动打开(**设置 → 插件 → 文件活动**) | 开 | 每个会话首次打开时自动显示本页;关闭后仍可从侧边栏「新标签页」菜单手动打开 |
|
|
55
|
+
| 右侧边栏默认宽度(**设置 → 插件 → 文件活动**) | 20% | 启动时按此比例展开右侧边栏(10%–70%);手动拖拽过宽度后本次运行不再覆盖 |
|
|
56
|
+
|
|
57
|
+
> 宽度偏好只在你**尚未拖拽**时生效(宿主自己给的 45% 默认值也算「已拖拽」之外的空档)。宿主右侧栏下限为 300px,窗口窄于约 1500px 时 20% 会被抬高到 300px——这是宿主 clamp,非本插件行为。
|
|
55
58
|
|
|
56
59
|
## 相关文档
|
|
57
60
|
|
package/lib/client.js
CHANGED
|
@@ -46,6 +46,8 @@ window.__ModuleLoader__.load({
|
|
|
46
46
|
const PREVIEW_ID = 'dsh-file-activity-preview'
|
|
47
47
|
const AUTO_OPEN_KEY = 'dsh-file-activity:auto-opened:'
|
|
48
48
|
const AUTO_OPEN_PREF_KEY = 'dsh-file-activity:autoOpen'
|
|
49
|
+
// Right column default width ratio in percent (issue #384).
|
|
50
|
+
const RIGHTBAR_PREF_KEY = 'dsh-file-activity:rightbarWidth'
|
|
49
51
|
const POLL_MS = 6000
|
|
50
52
|
|
|
51
53
|
// ── parts (injected by scripts/build.mjs; keep this exact order — the
|
|
@@ -104,6 +106,11 @@ const strings = {
|
|
|
104
106
|
isZh()
|
|
105
107
|
? '每个会话首次打开时自动显示本页;关闭后仍可从侧边栏右上角的「新标签页」手动打开。'
|
|
106
108
|
: 'Shows this page once per session. When off, open it from the sidebar new-tab control.',
|
|
109
|
+
rightbarWidthLabel: () => (isZh() ? '右侧边栏默认宽度' : 'Right sidebar default width'),
|
|
110
|
+
rightbarWidthHint: () =>
|
|
111
|
+
isZh()
|
|
112
|
+
? '启动时右侧边栏按此比例展开(10%–70%)。手动拖拽过宽度后不再覆盖;宿主最小宽度 300px,窄窗口下实际会更宽。'
|
|
113
|
+
: 'Opens the right sidebar at this ratio (10%–70%). A manual drag wins for the rest of the run; the host floor is 300px, so narrow windows land wider.',
|
|
107
114
|
tabUnavailable: () => (isZh() ? '侧边栏扩展点不可用' : 'Sidebar extension point unavailable'),
|
|
108
115
|
tabUnavailableHint: () =>
|
|
109
116
|
isZh()
|
|
@@ -986,7 +993,7 @@ const STYLES = `
|
|
|
986
993
|
border:1px solid var(--dsw-alias-state-error-primary); background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent); }
|
|
987
994
|
.dfa-degraded-title { font:var(--dsw-font-xs-13); color:var(--dsw-alias-state-error-primary); }
|
|
988
995
|
.dfa-degraded-hint { margin-top:2px; font:var(--dsw-font-xxs-12); opacity:.8; }
|
|
989
|
-
/* 设置页(settings.plugins.tab
|
|
996
|
+
/* 设置页(settings.plugins.tab):开关行 + 百分比输入行(issue #384)。 */
|
|
990
997
|
.dfa-set { display:flex; flex-direction:column; gap:10px; padding:10px 2px; }
|
|
991
998
|
.dfa-set-title { font:var(--dsw-font-sm-14); }
|
|
992
999
|
.dfa-set-row { display:flex; align-items:center; justify-content:space-between; gap:12px; cursor:pointer; }
|
|
@@ -994,6 +1001,12 @@ const STYLES = `
|
|
|
994
1001
|
.dfa-set-label { font:var(--dsw-font-xs-13); }
|
|
995
1002
|
.dfa-set-hint { font:var(--dsw-font-xxs-12); opacity:.75; }
|
|
996
1003
|
.dfa-set-switch { flex:none; width:16px; height:16px; accent-color:var(--dsw-alias-accent); cursor:pointer; }
|
|
1004
|
+
.dfa-set-row-static { cursor:default; }
|
|
1005
|
+
.dfa-set-field { flex:none; display:inline-flex; align-items:center; gap:4px; cursor:default; }
|
|
1006
|
+
.dfa-set-number { box-sizing:border-box; width:60px; padding:4px 6px; text-align:right; color:inherit;
|
|
1007
|
+
background:transparent; border:1px solid var(--dsw-alias-border-l1); border-radius:6px; font:var(--dsw-font-xs-13); }
|
|
1008
|
+
.dfa-set-number:focus { outline:none; border-color:var(--dsw-alias-accent); }
|
|
1009
|
+
.dfa-set-unit { font:var(--dsw-font-xs-13); opacity:.75; }
|
|
997
1010
|
/* 浮窗正文:Markdown / 代码 / 纯文本 / 图片四种渲染体。 */
|
|
998
1011
|
.dfa-fp-md { height:100%; overflow:auto; padding:2px 4px; }
|
|
999
1012
|
.dfa-fp-code { height:100%; overflow:auto; }
|
|
@@ -1798,6 +1811,183 @@ function PdfPreview({ src, download, title }) {
|
|
|
1798
1811
|
)
|
|
1799
1812
|
}
|
|
1800
1813
|
|
|
1814
|
+
'use strict'
|
|
1815
|
+
// ── right column default width (issue #384) ───────────────────────────
|
|
1816
|
+
//
|
|
1817
|
+
// Why this half exists: the host hardcodes the right column's first-open width
|
|
1818
|
+
// at 45% of the frame (ui-layout columns.ts RIGHTBAR_DEFAULT_RATIO) and
|
|
1819
|
+
// persists nothing, and it exposes NO width face on ctx.layout or
|
|
1820
|
+
// ctx.sidebarRight. The one reachable channel is the layout entry's declared
|
|
1821
|
+
// store seat: ctx.slots.entries('root') yields the ui-layout registration,
|
|
1822
|
+
// whose store.create() returns the very instance AppFrame subscribes to (the
|
|
1823
|
+
// framework itself goes through handle.create()).
|
|
1824
|
+
//
|
|
1825
|
+
// Semantics = DEFAULT, not override: the host fills layoutInfo.rightbar with
|
|
1826
|
+
// the 45% value on the panel's first opening (stores.ts rightbar ??= …), so
|
|
1827
|
+
// `rightbar === null` means "nothing opened or dragged it in this run". We
|
|
1828
|
+
// write ONLY then; a value the user dragged is never touched.
|
|
1829
|
+
//
|
|
1830
|
+
// Every contract mismatch (no entry, factory-shaped store, no create(), a
|
|
1831
|
+
// throwing create()/getSnapshot(), no setRightbar, an unexpected snapshot) is
|
|
1832
|
+
// a SILENT skip with at most one debug line. Breaking the tab, the auto-open
|
|
1833
|
+
// or the preview over a nicety like this would be far worse than doing
|
|
1834
|
+
// nothing, so the whole path is detect-and-degrade.
|
|
1835
|
+
/** Allowed ratio range in percent; the ceiling matches the host's own cap (70%). */
|
|
1836
|
+
const RIGHTBAR_RATIO_MIN = 10
|
|
1837
|
+
const RIGHTBAR_RATIO_MAX = 70
|
|
1838
|
+
/** Ratio used when nothing valid is stored: the issue #384 default. */
|
|
1839
|
+
const RIGHTBAR_RATIO_DEFAULT = 20
|
|
1840
|
+
/** One-shot skip log gate, so a churning root seat cannot flood the console. */
|
|
1841
|
+
let rightbarSkipLogged = false
|
|
1842
|
+
/** Debug-only skip report; never throws, never warns (degradation is silent). */
|
|
1843
|
+
function logRightbarSkip(reason, error) {
|
|
1844
|
+
if (rightbarSkipLogged) return
|
|
1845
|
+
rightbarSkipLogged = true
|
|
1846
|
+
try {
|
|
1847
|
+
if (typeof console !== 'undefined' && typeof console.debug === 'function') {
|
|
1848
|
+
console.debug('[dsh-file-activity] 右侧边栏默认宽度未应用: ' + reason, error ?? '')
|
|
1849
|
+
}
|
|
1850
|
+
} catch {
|
|
1851
|
+
// logging must never break activation
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
/** Whether a raw value is a usable percent ratio (finite, inside the range). */
|
|
1855
|
+
function rightbarRatioValid(value) {
|
|
1856
|
+
const ratio = Number(value)
|
|
1857
|
+
return Number.isFinite(ratio) && ratio >= RIGHTBAR_RATIO_MIN && ratio <= RIGHTBAR_RATIO_MAX
|
|
1858
|
+
}
|
|
1859
|
+
/** Persisted ratio, falling back to the default for missing/illegal values. */
|
|
1860
|
+
function storedRightbarRatio() {
|
|
1861
|
+
let raw
|
|
1862
|
+
try {
|
|
1863
|
+
raw = window.localStorage.getItem(RIGHTBAR_PREF_KEY)
|
|
1864
|
+
} catch {
|
|
1865
|
+
return RIGHTBAR_RATIO_DEFAULT
|
|
1866
|
+
}
|
|
1867
|
+
if (raw === null || !rightbarRatioValid(raw)) return RIGHTBAR_RATIO_DEFAULT
|
|
1868
|
+
return Number(raw)
|
|
1869
|
+
}
|
|
1870
|
+
/** Persist a ratio; an illegal value is rejected instead of poisoning storage. */
|
|
1871
|
+
function saveRightbarRatio(value) {
|
|
1872
|
+
if (!rightbarRatioValid(value)) return false
|
|
1873
|
+
try {
|
|
1874
|
+
window.localStorage.setItem(RIGHTBAR_PREF_KEY, String(Number(value)))
|
|
1875
|
+
} catch {
|
|
1876
|
+
// storage unavailable (private mode): the control still shows the value
|
|
1877
|
+
}
|
|
1878
|
+
return true
|
|
1879
|
+
}
|
|
1880
|
+
/**
|
|
1881
|
+
* The entry's store seat in HANDLE form (create() present), or null.
|
|
1882
|
+
* A factory-shaped seat is deliberately not guessed at: calling it would build
|
|
1883
|
+
* a second, unsubscribed instance instead of the one AppFrame renders.
|
|
1884
|
+
* @param entry - one 'root' seat snapshot.
|
|
1885
|
+
* @returns the handle-shaped seat, or null.
|
|
1886
|
+
*/
|
|
1887
|
+
function rightbarStoreSeat(entry) {
|
|
1888
|
+
const store = entry && entry.store
|
|
1889
|
+
if (!store || typeof store !== 'object') return null
|
|
1890
|
+
if (typeof store.create !== 'function') return null
|
|
1891
|
+
return store
|
|
1892
|
+
}
|
|
1893
|
+
/**
|
|
1894
|
+
* The instance's layoutInfo when it is writable, or null.
|
|
1895
|
+
* @param instance - handle-created store instance.
|
|
1896
|
+
* @returns layoutInfo, or null when the shape is foreign or a width exists.
|
|
1897
|
+
*/
|
|
1898
|
+
function rightbarWritableInfo(instance) {
|
|
1899
|
+
if (typeof instance.getSnapshot !== 'function') return null
|
|
1900
|
+
const snapshot = instance.getSnapshot()
|
|
1901
|
+
const info = snapshot && snapshot.layoutInfo
|
|
1902
|
+
if (!info || typeof info !== 'object') return null
|
|
1903
|
+
// NULL is the only green light: any number means this run already owns a
|
|
1904
|
+
// width (the host's own 45% first-open default, or a user drag).
|
|
1905
|
+
if (info.rightbar !== null) return null
|
|
1906
|
+
return info
|
|
1907
|
+
}
|
|
1908
|
+
/**
|
|
1909
|
+
* Turn a handle-created instance into a write target, or null.
|
|
1910
|
+
* @param instance - handle-created store instance.
|
|
1911
|
+
* @returns the write target, or null.
|
|
1912
|
+
*/
|
|
1913
|
+
function rightbarTargetFromInstance(instance) {
|
|
1914
|
+
if (!instance) return null
|
|
1915
|
+
const info = rightbarWritableInfo(instance)
|
|
1916
|
+
if (!info) return null
|
|
1917
|
+
if (!instance.actions || typeof instance.actions.setRightbar !== 'function') return null
|
|
1918
|
+
return { setRightbar: instance.actions.setRightbar, info }
|
|
1919
|
+
}
|
|
1920
|
+
/**
|
|
1921
|
+
* Resolve the ui-layout instance behind the 'root' seat, or null when the host
|
|
1922
|
+
* contract does not match. The snapshot shape is the test: writing the width
|
|
1923
|
+
* goes through exactly that shape, so a shape we cannot read is a channel we
|
|
1924
|
+
* must not use.
|
|
1925
|
+
* @param ctx - client root context.
|
|
1926
|
+
* @returns the write target, or null (caller stays silent).
|
|
1927
|
+
*/
|
|
1928
|
+
function rightbarLayoutTarget(ctx) {
|
|
1929
|
+
const slots = ctx && ctx.slots
|
|
1930
|
+
if (!slots || typeof slots.entries !== 'function') {
|
|
1931
|
+
logRightbarSkip('ctx.slots.entries 不可用')
|
|
1932
|
+
return null
|
|
1933
|
+
}
|
|
1934
|
+
const entries = slots.entries('root') || []
|
|
1935
|
+
for (const entry of entries) {
|
|
1936
|
+
try {
|
|
1937
|
+
const target = rightbarTargetFromInstance(rightbarStoreSeat(entry)?.create?.())
|
|
1938
|
+
if (target) return target
|
|
1939
|
+
} catch (error) {
|
|
1940
|
+
logRightbarSkip('读取 root 席位 store 失败', error)
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
logRightbarSkip('未找到可写的 ui-layout root 席位')
|
|
1944
|
+
return null
|
|
1945
|
+
}
|
|
1946
|
+
/**
|
|
1947
|
+
* Apply the configured default width once, if the panel has no width yet.
|
|
1948
|
+
* @param ctx - client root context.
|
|
1949
|
+
*/
|
|
1950
|
+
function applyRightbarDefaultWidth(ctx) {
|
|
1951
|
+
try {
|
|
1952
|
+
const target = rightbarLayoutTarget(ctx)
|
|
1953
|
+
if (!target) return
|
|
1954
|
+
// The snapshot's own viewportWidth is the value the host's clamp uses, so
|
|
1955
|
+
// prefer it; window.innerWidth only covers a snapshot that omits it.
|
|
1956
|
+
const viewport = Number.isFinite(target.info.viewportWidth) ? target.info.viewportWidth : window.innerWidth
|
|
1957
|
+
if (!Number.isFinite(viewport)) {
|
|
1958
|
+
logRightbarSkip('viewportWidth 不可用')
|
|
1959
|
+
return
|
|
1960
|
+
}
|
|
1961
|
+
const px = Math.round(viewport * (storedRightbarRatio() / 100))
|
|
1962
|
+
if (!Number.isFinite(px) || px <= 0) {
|
|
1963
|
+
logRightbarSkip('计算出的宽度非法')
|
|
1964
|
+
return
|
|
1965
|
+
}
|
|
1966
|
+
target.setRightbar(px)
|
|
1967
|
+
} catch (error) {
|
|
1968
|
+
logRightbarSkip('应用宽度失败', error)
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
/**
|
|
1972
|
+
* Install the preference: apply once at activation, then re-resolve the
|
|
1973
|
+
* instance after a 'root' seat change (HMR / remount must not keep a stale
|
|
1974
|
+
* instance). The returned disposer releases the subscription with the fiber.
|
|
1975
|
+
* @param ctx - client root context.
|
|
1976
|
+
* @returns the subscription disposer, when one was taken.
|
|
1977
|
+
*/
|
|
1978
|
+
function installRightbarDefaultWidth(ctx) {
|
|
1979
|
+
applyRightbarDefaultWidth(ctx)
|
|
1980
|
+
const slots = ctx && ctx.slots
|
|
1981
|
+
if (!slots || typeof slots.subscribe !== 'function') return undefined
|
|
1982
|
+
try {
|
|
1983
|
+
const dispose = slots.subscribe('root', () => applyRightbarDefaultWidth(ctx))
|
|
1984
|
+
return typeof dispose === 'function' ? () => dispose() : undefined
|
|
1985
|
+
} catch (error) {
|
|
1986
|
+
logRightbarSkip('订阅 root 席位失败', error)
|
|
1987
|
+
return undefined
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
|
|
1801
1991
|
'use strict'
|
|
1802
1992
|
// ── settings tab (replaces 迁移前的 settings.pluginToggles) ────
|
|
1803
1993
|
//
|
|
@@ -1843,9 +2033,40 @@ function settingsRow(label, hint, checked, onChange) {
|
|
|
1843
2033
|
}),
|
|
1844
2034
|
)
|
|
1845
2035
|
}
|
|
1846
|
-
/**
|
|
2036
|
+
/** One labeled percent row (issue #384: the right column's default width). */
|
|
2037
|
+
function settingsPercentRow(label, hint, value, onCommit) {
|
|
2038
|
+
return createElement(
|
|
2039
|
+
'div',
|
|
2040
|
+
{ className: 'dfa-set-row dfa-set-row-static' },
|
|
2041
|
+
createElement(
|
|
2042
|
+
'span',
|
|
2043
|
+
{ className: 'dfa-set-text' },
|
|
2044
|
+
createElement('span', { className: 'dfa-set-label' }, label),
|
|
2045
|
+
createElement('span', { className: 'dfa-set-hint' }, hint),
|
|
2046
|
+
),
|
|
2047
|
+
createElement(
|
|
2048
|
+
'span',
|
|
2049
|
+
{ className: 'dfa-set-field' },
|
|
2050
|
+
createElement('input', {
|
|
2051
|
+
type: 'number',
|
|
2052
|
+
className: 'dfa-set-number',
|
|
2053
|
+
min: RIGHTBAR_RATIO_MIN,
|
|
2054
|
+
max: RIGHTBAR_RATIO_MAX,
|
|
2055
|
+
step: 1,
|
|
2056
|
+
value,
|
|
2057
|
+
onChange: (event) => onCommit(event?.target?.value),
|
|
2058
|
+
}),
|
|
2059
|
+
createElement('span', { className: 'dfa-set-unit' }, '%'),
|
|
2060
|
+
),
|
|
2061
|
+
)
|
|
2062
|
+
}
|
|
2063
|
+
/**
|
|
2064
|
+
* Settings panel body: the auto-open switch, the right-sidebar default width,
|
|
2065
|
+
* and their explanatory hints.
|
|
2066
|
+
*/
|
|
1847
2067
|
function FileActivitySettings() {
|
|
1848
2068
|
const [enabled, setEnabled] = useState(autoOpenEnabled)
|
|
2069
|
+
const [ratio, setRatio] = useState(storedRightbarRatio)
|
|
1849
2070
|
return createElement(
|
|
1850
2071
|
'div',
|
|
1851
2072
|
{ className: 'dfa-set', 'data-dfa-settings': '1' },
|
|
@@ -1854,6 +2075,12 @@ function FileActivitySettings() {
|
|
|
1854
2075
|
setAutoOpenEnabled(next)
|
|
1855
2076
|
setEnabled(next)
|
|
1856
2077
|
}),
|
|
2078
|
+
settingsPercentRow(strings.rightbarWidthLabel(), strings.rightbarWidthHint(), ratio, (raw) => {
|
|
2079
|
+
// An illegal / out-of-range edit is rejected outright: the control keeps
|
|
2080
|
+
// showing the stored preference instead of a value we would not apply.
|
|
2081
|
+
if (!saveRightbarRatio(raw)) return
|
|
2082
|
+
setRatio(Number(raw))
|
|
2083
|
+
}),
|
|
1857
2084
|
)
|
|
1858
2085
|
}
|
|
1859
2086
|
/** Register the settings tab into the settings section's list seat. */
|
|
@@ -1902,6 +2129,10 @@ function registerTabType(ctx) {
|
|
|
1902
2129
|
// place (host GuideBody → actions.openTab(kind, { replaceTab: true })).
|
|
1903
2130
|
guide: [
|
|
1904
2131
|
{
|
|
2132
|
+
// Required by the host guide-entry contract: without it the host hands
|
|
2133
|
+
// `entryId: undefined` to `sidebar.right.tab.guide.entry` and the
|
|
2134
|
+
// per-type duplicate-id check silently stops working.
|
|
2135
|
+
id: TAB_ID,
|
|
1905
2136
|
order: 20,
|
|
1906
2137
|
title: () => strings.title(),
|
|
1907
2138
|
description: () => strings.guideDescription(),
|
|
@@ -1967,6 +2198,12 @@ exports.apply = function apply(ctx) {
|
|
|
1967
2198
|
registerPreviewOverlay(ctx, dataStore)
|
|
1968
2199
|
registerSettingsTab(ctx)
|
|
1969
2200
|
guarded('previews', () => registerDocumentPreviews(ctx))
|
|
2201
|
+
// Right column default width (issue #384) BEFORE the auto-open below: the
|
|
2202
|
+
// host fills its own 45% default the moment the panel first opens
|
|
2203
|
+
// (stores.ts `rightbar ??= …`), so whoever writes first wins. Auto-opening
|
|
2204
|
+
// our tab opens that panel — applying the width afterwards would always
|
|
2205
|
+
// find a non-null preference and never take effect.
|
|
2206
|
+
ctx.effect(() => installRightbarDefaultWidth(ctx), 'dsh-file-activity: rightbar default width')
|
|
1970
2207
|
// sidebar operations → host record route
|
|
1971
2208
|
ctx.effect(() => installFetchInterceptor(), 'dsh-file-activity: sidebar fetch observation')
|
|
1972
2209
|
// auto-open once per session (default on)
|
package/lib/client.src.js
CHANGED
|
@@ -46,6 +46,8 @@ window.__ModuleLoader__.load({
|
|
|
46
46
|
const PREVIEW_ID = 'dsh-file-activity-preview'
|
|
47
47
|
const AUTO_OPEN_KEY = 'dsh-file-activity:auto-opened:'
|
|
48
48
|
const AUTO_OPEN_PREF_KEY = 'dsh-file-activity:autoOpen'
|
|
49
|
+
// Right column default width ratio in percent (issue #384).
|
|
50
|
+
const RIGHTBAR_PREF_KEY = 'dsh-file-activity:rightbarWidth'
|
|
49
51
|
const POLL_MS = 6000
|
|
50
52
|
|
|
51
53
|
// ── parts (injected by scripts/build.mjs; keep this exact order — the
|
|
@@ -64,6 +66,7 @@ window.__ModuleLoader__.load({
|
|
|
64
66
|
__PART_VIEW__
|
|
65
67
|
__PART_PREVIEW_DATA__
|
|
66
68
|
__PART_PREVIEW__
|
|
69
|
+
__PART_RIGHTBAR_WIDTH__
|
|
67
70
|
__PART_SETTINGS__
|
|
68
71
|
__PART_APPLY__
|
|
69
72
|
|
package/lib/parts/apply.part.js
CHANGED
|
@@ -30,6 +30,10 @@ function registerTabType(ctx) {
|
|
|
30
30
|
// place (host GuideBody → actions.openTab(kind, { replaceTab: true })).
|
|
31
31
|
guide: [
|
|
32
32
|
{
|
|
33
|
+
// Required by the host guide-entry contract: without it the host hands
|
|
34
|
+
// `entryId: undefined` to `sidebar.right.tab.guide.entry` and the
|
|
35
|
+
// per-type duplicate-id check silently stops working.
|
|
36
|
+
id: TAB_ID,
|
|
33
37
|
order: 20,
|
|
34
38
|
title: () => strings.title(),
|
|
35
39
|
description: () => strings.guideDescription(),
|
|
@@ -95,6 +99,12 @@ exports.apply = function apply(ctx) {
|
|
|
95
99
|
registerPreviewOverlay(ctx, dataStore)
|
|
96
100
|
registerSettingsTab(ctx)
|
|
97
101
|
guarded('previews', () => registerDocumentPreviews(ctx))
|
|
102
|
+
// Right column default width (issue #384) BEFORE the auto-open below: the
|
|
103
|
+
// host fills its own 45% default the moment the panel first opens
|
|
104
|
+
// (stores.ts `rightbar ??= …`), so whoever writes first wins. Auto-opening
|
|
105
|
+
// our tab opens that panel — applying the width afterwards would always
|
|
106
|
+
// find a non-null preference and never take effect.
|
|
107
|
+
ctx.effect(() => installRightbarDefaultWidth(ctx), 'dsh-file-activity: rightbar default width')
|
|
98
108
|
// sidebar operations → host record route
|
|
99
109
|
ctx.effect(() => installFetchInterceptor(), 'dsh-file-activity: sidebar fetch observation')
|
|
100
110
|
// auto-open once per session (default on)
|
package/lib/parts/i18n.part.js
CHANGED
|
@@ -52,6 +52,11 @@ const strings = {
|
|
|
52
52
|
isZh()
|
|
53
53
|
? '每个会话首次打开时自动显示本页;关闭后仍可从侧边栏右上角的「新标签页」手动打开。'
|
|
54
54
|
: 'Shows this page once per session. When off, open it from the sidebar new-tab control.',
|
|
55
|
+
rightbarWidthLabel: () => (isZh() ? '右侧边栏默认宽度' : 'Right sidebar default width'),
|
|
56
|
+
rightbarWidthHint: () =>
|
|
57
|
+
isZh()
|
|
58
|
+
? '启动时右侧边栏按此比例展开(10%–70%)。手动拖拽过宽度后不再覆盖;宿主最小宽度 300px,窄窗口下实际会更宽。'
|
|
59
|
+
: 'Opens the right sidebar at this ratio (10%–70%). A manual drag wins for the rest of the run; the host floor is 300px, so narrow windows land wider.',
|
|
55
60
|
tabUnavailable: () => (isZh() ? '侧边栏扩展点不可用' : 'Sidebar extension point unavailable'),
|
|
56
61
|
tabUnavailableHint: () =>
|
|
57
62
|
isZh()
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
// ── right column default width (issue #384) ───────────────────────────
|
|
3
|
+
//
|
|
4
|
+
// Why this half exists: the host hardcodes the right column's first-open width
|
|
5
|
+
// at 45% of the frame (ui-layout columns.ts RIGHTBAR_DEFAULT_RATIO) and
|
|
6
|
+
// persists nothing, and it exposes NO width face on ctx.layout or
|
|
7
|
+
// ctx.sidebarRight. The one reachable channel is the layout entry's declared
|
|
8
|
+
// store seat: ctx.slots.entries('root') yields the ui-layout registration,
|
|
9
|
+
// whose store.create() returns the very instance AppFrame subscribes to (the
|
|
10
|
+
// framework itself goes through handle.create()).
|
|
11
|
+
//
|
|
12
|
+
// Semantics = DEFAULT, not override: the host fills layoutInfo.rightbar with
|
|
13
|
+
// the 45% value on the panel's first opening (stores.ts rightbar ??= …), so
|
|
14
|
+
// `rightbar === null` means "nothing opened or dragged it in this run". We
|
|
15
|
+
// write ONLY then; a value the user dragged is never touched.
|
|
16
|
+
//
|
|
17
|
+
// Every contract mismatch (no entry, factory-shaped store, no create(), a
|
|
18
|
+
// throwing create()/getSnapshot(), no setRightbar, an unexpected snapshot) is
|
|
19
|
+
// a SILENT skip with at most one debug line. Breaking the tab, the auto-open
|
|
20
|
+
// or the preview over a nicety like this would be far worse than doing
|
|
21
|
+
// nothing, so the whole path is detect-and-degrade.
|
|
22
|
+
/** Allowed ratio range in percent; the ceiling matches the host's own cap (70%). */
|
|
23
|
+
const RIGHTBAR_RATIO_MIN = 10
|
|
24
|
+
const RIGHTBAR_RATIO_MAX = 70
|
|
25
|
+
/** Ratio used when nothing valid is stored: the issue #384 default. */
|
|
26
|
+
const RIGHTBAR_RATIO_DEFAULT = 20
|
|
27
|
+
/** One-shot skip log gate, so a churning root seat cannot flood the console. */
|
|
28
|
+
let rightbarSkipLogged = false
|
|
29
|
+
/** Debug-only skip report; never throws, never warns (degradation is silent). */
|
|
30
|
+
function logRightbarSkip(reason, error) {
|
|
31
|
+
if (rightbarSkipLogged) return
|
|
32
|
+
rightbarSkipLogged = true
|
|
33
|
+
try {
|
|
34
|
+
if (typeof console !== 'undefined' && typeof console.debug === 'function') {
|
|
35
|
+
console.debug('[dsh-file-activity] 右侧边栏默认宽度未应用: ' + reason, error ?? '')
|
|
36
|
+
}
|
|
37
|
+
} catch {
|
|
38
|
+
// logging must never break activation
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Whether a raw value is a usable percent ratio (finite, inside the range). */
|
|
42
|
+
function rightbarRatioValid(value) {
|
|
43
|
+
const ratio = Number(value)
|
|
44
|
+
return Number.isFinite(ratio) && ratio >= RIGHTBAR_RATIO_MIN && ratio <= RIGHTBAR_RATIO_MAX
|
|
45
|
+
}
|
|
46
|
+
/** Persisted ratio, falling back to the default for missing/illegal values. */
|
|
47
|
+
function storedRightbarRatio() {
|
|
48
|
+
let raw
|
|
49
|
+
try {
|
|
50
|
+
raw = window.localStorage.getItem(RIGHTBAR_PREF_KEY)
|
|
51
|
+
} catch {
|
|
52
|
+
return RIGHTBAR_RATIO_DEFAULT
|
|
53
|
+
}
|
|
54
|
+
if (raw === null || !rightbarRatioValid(raw)) return RIGHTBAR_RATIO_DEFAULT
|
|
55
|
+
return Number(raw)
|
|
56
|
+
}
|
|
57
|
+
/** Persist a ratio; an illegal value is rejected instead of poisoning storage. */
|
|
58
|
+
function saveRightbarRatio(value) {
|
|
59
|
+
if (!rightbarRatioValid(value)) return false
|
|
60
|
+
try {
|
|
61
|
+
window.localStorage.setItem(RIGHTBAR_PREF_KEY, String(Number(value)))
|
|
62
|
+
} catch {
|
|
63
|
+
// storage unavailable (private mode): the control still shows the value
|
|
64
|
+
}
|
|
65
|
+
return true
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The entry's store seat in HANDLE form (create() present), or null.
|
|
69
|
+
* A factory-shaped seat is deliberately not guessed at: calling it would build
|
|
70
|
+
* a second, unsubscribed instance instead of the one AppFrame renders.
|
|
71
|
+
* @param entry - one 'root' seat snapshot.
|
|
72
|
+
* @returns the handle-shaped seat, or null.
|
|
73
|
+
*/
|
|
74
|
+
function rightbarStoreSeat(entry) {
|
|
75
|
+
const store = entry && entry.store
|
|
76
|
+
if (!store || typeof store !== 'object') return null
|
|
77
|
+
if (typeof store.create !== 'function') return null
|
|
78
|
+
return store
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The instance's layoutInfo when it is writable, or null.
|
|
82
|
+
* @param instance - handle-created store instance.
|
|
83
|
+
* @returns layoutInfo, or null when the shape is foreign or a width exists.
|
|
84
|
+
*/
|
|
85
|
+
function rightbarWritableInfo(instance) {
|
|
86
|
+
if (typeof instance.getSnapshot !== 'function') return null
|
|
87
|
+
const snapshot = instance.getSnapshot()
|
|
88
|
+
const info = snapshot && snapshot.layoutInfo
|
|
89
|
+
if (!info || typeof info !== 'object') return null
|
|
90
|
+
// NULL is the only green light: any number means this run already owns a
|
|
91
|
+
// width (the host's own 45% first-open default, or a user drag).
|
|
92
|
+
if (info.rightbar !== null) return null
|
|
93
|
+
return info
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Turn a handle-created instance into a write target, or null.
|
|
97
|
+
* @param instance - handle-created store instance.
|
|
98
|
+
* @returns the write target, or null.
|
|
99
|
+
*/
|
|
100
|
+
function rightbarTargetFromInstance(instance) {
|
|
101
|
+
if (!instance) return null
|
|
102
|
+
const info = rightbarWritableInfo(instance)
|
|
103
|
+
if (!info) return null
|
|
104
|
+
if (!instance.actions || typeof instance.actions.setRightbar !== 'function') return null
|
|
105
|
+
return { setRightbar: instance.actions.setRightbar, info }
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Resolve the ui-layout instance behind the 'root' seat, or null when the host
|
|
109
|
+
* contract does not match. The snapshot shape is the test: writing the width
|
|
110
|
+
* goes through exactly that shape, so a shape we cannot read is a channel we
|
|
111
|
+
* must not use.
|
|
112
|
+
* @param ctx - client root context.
|
|
113
|
+
* @returns the write target, or null (caller stays silent).
|
|
114
|
+
*/
|
|
115
|
+
function rightbarLayoutTarget(ctx) {
|
|
116
|
+
const slots = ctx && ctx.slots
|
|
117
|
+
if (!slots || typeof slots.entries !== 'function') {
|
|
118
|
+
logRightbarSkip('ctx.slots.entries 不可用')
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
121
|
+
const entries = slots.entries('root') || []
|
|
122
|
+
for (const entry of entries) {
|
|
123
|
+
try {
|
|
124
|
+
const target = rightbarTargetFromInstance(rightbarStoreSeat(entry)?.create?.())
|
|
125
|
+
if (target) return target
|
|
126
|
+
} catch (error) {
|
|
127
|
+
logRightbarSkip('读取 root 席位 store 失败', error)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
logRightbarSkip('未找到可写的 ui-layout root 席位')
|
|
131
|
+
return null
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Apply the configured default width once, if the panel has no width yet.
|
|
135
|
+
* @param ctx - client root context.
|
|
136
|
+
*/
|
|
137
|
+
function applyRightbarDefaultWidth(ctx) {
|
|
138
|
+
try {
|
|
139
|
+
const target = rightbarLayoutTarget(ctx)
|
|
140
|
+
if (!target) return
|
|
141
|
+
// The snapshot's own viewportWidth is the value the host's clamp uses, so
|
|
142
|
+
// prefer it; window.innerWidth only covers a snapshot that omits it.
|
|
143
|
+
const viewport = Number.isFinite(target.info.viewportWidth) ? target.info.viewportWidth : window.innerWidth
|
|
144
|
+
if (!Number.isFinite(viewport)) {
|
|
145
|
+
logRightbarSkip('viewportWidth 不可用')
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
const px = Math.round(viewport * (storedRightbarRatio() / 100))
|
|
149
|
+
if (!Number.isFinite(px) || px <= 0) {
|
|
150
|
+
logRightbarSkip('计算出的宽度非法')
|
|
151
|
+
return
|
|
152
|
+
}
|
|
153
|
+
target.setRightbar(px)
|
|
154
|
+
} catch (error) {
|
|
155
|
+
logRightbarSkip('应用宽度失败', error)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Install the preference: apply once at activation, then re-resolve the
|
|
160
|
+
* instance after a 'root' seat change (HMR / remount must not keep a stale
|
|
161
|
+
* instance). The returned disposer releases the subscription with the fiber.
|
|
162
|
+
* @param ctx - client root context.
|
|
163
|
+
* @returns the subscription disposer, when one was taken.
|
|
164
|
+
*/
|
|
165
|
+
function installRightbarDefaultWidth(ctx) {
|
|
166
|
+
applyRightbarDefaultWidth(ctx)
|
|
167
|
+
const slots = ctx && ctx.slots
|
|
168
|
+
if (!slots || typeof slots.subscribe !== 'function') return undefined
|
|
169
|
+
try {
|
|
170
|
+
const dispose = slots.subscribe('root', () => applyRightbarDefaultWidth(ctx))
|
|
171
|
+
return typeof dispose === 'function' ? () => dispose() : undefined
|
|
172
|
+
} catch (error) {
|
|
173
|
+
logRightbarSkip('订阅 root 席位失败', error)
|
|
174
|
+
return undefined
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -43,9 +43,40 @@ function settingsRow(label, hint, checked, onChange) {
|
|
|
43
43
|
}),
|
|
44
44
|
)
|
|
45
45
|
}
|
|
46
|
-
/**
|
|
46
|
+
/** One labeled percent row (issue #384: the right column's default width). */
|
|
47
|
+
function settingsPercentRow(label, hint, value, onCommit) {
|
|
48
|
+
return createElement(
|
|
49
|
+
'div',
|
|
50
|
+
{ className: 'dfa-set-row dfa-set-row-static' },
|
|
51
|
+
createElement(
|
|
52
|
+
'span',
|
|
53
|
+
{ className: 'dfa-set-text' },
|
|
54
|
+
createElement('span', { className: 'dfa-set-label' }, label),
|
|
55
|
+
createElement('span', { className: 'dfa-set-hint' }, hint),
|
|
56
|
+
),
|
|
57
|
+
createElement(
|
|
58
|
+
'span',
|
|
59
|
+
{ className: 'dfa-set-field' },
|
|
60
|
+
createElement('input', {
|
|
61
|
+
type: 'number',
|
|
62
|
+
className: 'dfa-set-number',
|
|
63
|
+
min: RIGHTBAR_RATIO_MIN,
|
|
64
|
+
max: RIGHTBAR_RATIO_MAX,
|
|
65
|
+
step: 1,
|
|
66
|
+
value,
|
|
67
|
+
onChange: (event) => onCommit(event?.target?.value),
|
|
68
|
+
}),
|
|
69
|
+
createElement('span', { className: 'dfa-set-unit' }, '%'),
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Settings panel body: the auto-open switch, the right-sidebar default width,
|
|
75
|
+
* and their explanatory hints.
|
|
76
|
+
*/
|
|
47
77
|
function FileActivitySettings() {
|
|
48
78
|
const [enabled, setEnabled] = useState(autoOpenEnabled)
|
|
79
|
+
const [ratio, setRatio] = useState(storedRightbarRatio)
|
|
49
80
|
return createElement(
|
|
50
81
|
'div',
|
|
51
82
|
{ className: 'dfa-set', 'data-dfa-settings': '1' },
|
|
@@ -54,6 +85,12 @@ function FileActivitySettings() {
|
|
|
54
85
|
setAutoOpenEnabled(next)
|
|
55
86
|
setEnabled(next)
|
|
56
87
|
}),
|
|
88
|
+
settingsPercentRow(strings.rightbarWidthLabel(), strings.rightbarWidthHint(), ratio, (raw) => {
|
|
89
|
+
// An illegal / out-of-range edit is rejected outright: the control keeps
|
|
90
|
+
// showing the stored preference instead of a value we would not apply.
|
|
91
|
+
if (!saveRightbarRatio(raw)) return
|
|
92
|
+
setRatio(Number(raw))
|
|
93
|
+
}),
|
|
57
94
|
)
|
|
58
95
|
}
|
|
59
96
|
/** Register the settings tab into the settings section's list seat. */
|
package/lib/parts/styles.part.js
CHANGED
|
@@ -116,7 +116,7 @@ const STYLES = `
|
|
|
116
116
|
border:1px solid var(--dsw-alias-state-error-primary); background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 8%, transparent); }
|
|
117
117
|
.dfa-degraded-title { font:var(--dsw-font-xs-13); color:var(--dsw-alias-state-error-primary); }
|
|
118
118
|
.dfa-degraded-hint { margin-top:2px; font:var(--dsw-font-xxs-12); opacity:.8; }
|
|
119
|
-
/* 设置页(settings.plugins.tab
|
|
119
|
+
/* 设置页(settings.plugins.tab):开关行 + 百分比输入行(issue #384)。 */
|
|
120
120
|
.dfa-set { display:flex; flex-direction:column; gap:10px; padding:10px 2px; }
|
|
121
121
|
.dfa-set-title { font:var(--dsw-font-sm-14); }
|
|
122
122
|
.dfa-set-row { display:flex; align-items:center; justify-content:space-between; gap:12px; cursor:pointer; }
|
|
@@ -124,6 +124,12 @@ const STYLES = `
|
|
|
124
124
|
.dfa-set-label { font:var(--dsw-font-xs-13); }
|
|
125
125
|
.dfa-set-hint { font:var(--dsw-font-xxs-12); opacity:.75; }
|
|
126
126
|
.dfa-set-switch { flex:none; width:16px; height:16px; accent-color:var(--dsw-alias-accent); cursor:pointer; }
|
|
127
|
+
.dfa-set-row-static { cursor:default; }
|
|
128
|
+
.dfa-set-field { flex:none; display:inline-flex; align-items:center; gap:4px; cursor:default; }
|
|
129
|
+
.dfa-set-number { box-sizing:border-box; width:60px; padding:4px 6px; text-align:right; color:inherit;
|
|
130
|
+
background:transparent; border:1px solid var(--dsw-alias-border-l1); border-radius:6px; font:var(--dsw-font-xs-13); }
|
|
131
|
+
.dfa-set-number:focus { outline:none; border-color:var(--dsw-alias-accent); }
|
|
132
|
+
.dfa-set-unit { font:var(--dsw-font-xs-13); opacity:.75; }
|
|
127
133
|
/* 浮窗正文:Markdown / 代码 / 纯文本 / 图片四种渲染体。 */
|
|
128
134
|
.dfa-fp-md { height:100%; overflow:auto; padding:2px 4px; }
|
|
129
135
|
.dfa-fp-code { height:100%; overflow:auto; }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-file-activity",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.13",
|
|
4
4
|
"description": "DSH 侧边栏文件活动插件:按 LRU 记录文件读取/新增/修改事件(最近访问),按绝对路径树形统计(文件统计),点击文件浮窗预览(代码高亮/Markdown/图片/PDF)。DSH web plugin: file activity tracker — LRU recent-access list plus per-file read/create/modify counts in a folder tree, with floating preview.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|