dsh-sidebar-marks 0.1.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 +14 -0
- package/LICENSE +21 -0
- package/README.md +224 -0
- package/README_EN.md +95 -0
- package/cordis.patch.yml +12 -0
- package/icon.svg +10 -0
- package/index.mjs +33 -0
- package/lib/client.js +1143 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +59 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,1143 @@
|
|
|
1
|
+
// dsh-sidebar-marks — browser half.
|
|
2
|
+
//
|
|
3
|
+
// 给 DSH 左侧边栏的每个对话打标记:**整行背景的淡色底** 与 **左侧或最右侧的彩色圆点**,
|
|
4
|
+
// 另可选每条对话独立的标题字号。未标记的对话与原生界面完全一致。
|
|
5
|
+
//
|
|
6
|
+
// 约束与做法(全部来自 DSH 0.2.0-rc.1 的真实契约,见 README「落地依据」):
|
|
7
|
+
// 1. 本文件按 dsh-client-modules 的 lazy-CJS 模型编写:顶层只允许注册 factory,
|
|
8
|
+
// 任何副作用都必须留在 factory 闭包内、由 apply() 在 materialization 时触发。
|
|
9
|
+
// 2. `id` 必须等于 package.json 的 name,否则浏览器侧永远 materialize 不出来。
|
|
10
|
+
// 3. 模块对象用平铺导出(exports.apply / exports.inject),不要 export default:
|
|
11
|
+
// loader 的 unwrapExports 会用 default 整个替换命名空间,写在一起的 inject 会静默丢失。
|
|
12
|
+
// 4. 左侧圆点走官方插槽 `sidebar.session.row.leading`(标题左侧 16×20 的格子,与 DSH
|
|
13
|
+
// 状态点共用;状态点在 / 已归档 / blank 行时该格子不渲染 → 该圆点自动消失,其余标记不受影响)。
|
|
14
|
+
// 5. 淡色底与圆点作用于整行,插槽表达不了,因此直接给行元素打属性:
|
|
15
|
+
// 行的稳定钩子 `div[role="treeitem"][data-row-key="session:<id>"]`;选择器里不出现
|
|
16
|
+
// 任何会随构建变化的 CSS-module 哈希类名。
|
|
17
|
+
// 6. 注入样式表必须自己打 `data-plugin` / `data-plugin-css`,否则可能被后一个
|
|
18
|
+
// materialize 的插件「认领」,并在它卸载时被删掉。
|
|
19
|
+
|
|
20
|
+
window.__ModuleLoader__.load({
|
|
21
|
+
id: 'dsh-sidebar-marks',
|
|
22
|
+
factory: (require) => {
|
|
23
|
+
var module = { exports: {} };
|
|
24
|
+
var exports = module.exports;
|
|
25
|
+
|
|
26
|
+
var React = require('react');
|
|
27
|
+
|
|
28
|
+
// ======================================================================
|
|
29
|
+
// 一、常量
|
|
30
|
+
// ======================================================================
|
|
31
|
+
|
|
32
|
+
var STORAGE_KEY = 'dsh.sidebar-marks.v1';
|
|
33
|
+
var STYLE_TAG_ID = 'dsh-sidebar-marks/style';
|
|
34
|
+
var GLOBAL_KEY = '__dshSidebarMarks';
|
|
35
|
+
var ROW_PREFIX = 'session:';
|
|
36
|
+
/** 打开「当前对话标记面板」的快捷键:⌘/Ctrl + Shift + M。 */
|
|
37
|
+
var HOTKEY = { key: 'm', mod: true, shift: true };
|
|
38
|
+
|
|
39
|
+
/** 6 个预设色;每个都带浅色/深色两套值。自定义色号则在运行时算出深色变体。 */
|
|
40
|
+
var COLORS = {
|
|
41
|
+
blue: { label: '蓝 · 主线', light: '#4176e6', dark: '#7aaaff' },
|
|
42
|
+
green: { label: '绿 · 完成', light: '#22c55e', dark: '#4ed17e' },
|
|
43
|
+
amber: { label: '琥珀 · 待跟进', light: '#f59e0b', dark: '#f7ad31' },
|
|
44
|
+
red: { label: '红 · 紧急', light: '#ef4444', dark: '#f25a5a' },
|
|
45
|
+
violet: { label: '紫 · 个人', light: '#8b5cf6', dark: '#a78bfa' },
|
|
46
|
+
slate: { label: '灰蓝 · 归档', light: '#61666b', dark: '#adb2b8' }
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** 整行淡色底的浓度:浅色主题下的百分比。深色主题自动 +DARK_BOOST 个点(同一个数字在深色下更淡)。 */
|
|
50
|
+
var FILLS = [
|
|
51
|
+
{ value: 8, label: '8%' },
|
|
52
|
+
{ value: 10, label: '10%' },
|
|
53
|
+
{ value: 12, label: '12%' },
|
|
54
|
+
{ value: 16, label: '16%' },
|
|
55
|
+
{ value: 22, label: '22%' },
|
|
56
|
+
{ value: 0, label: '不要' }
|
|
57
|
+
];
|
|
58
|
+
/** 深色主题下自动补的浓度差值(百分点)。 */
|
|
59
|
+
var DARK_BOOST = 4;
|
|
60
|
+
|
|
61
|
+
/** 圆点位置:最右侧 / 标题左侧 / 两侧 / 不要。 */
|
|
62
|
+
var DOTS = [
|
|
63
|
+
{ value: 'right', label: '最右侧' },
|
|
64
|
+
{ value: 'left', label: '标题左侧' },
|
|
65
|
+
{ value: 'both', label: '两侧' },
|
|
66
|
+
{ value: 'none', label: '不要' }
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
/** 标题字号;inherit = 保持 DSH 原样(14px)。 */
|
|
70
|
+
var SIZES = [
|
|
71
|
+
{ value: 'inherit', label: '原样' },
|
|
72
|
+
{ value: 'xs', px: 12, label: '12' },
|
|
73
|
+
{ value: 's', px: 13, label: '13' },
|
|
74
|
+
{ value: 'm', px: 14, label: '14' },
|
|
75
|
+
{ value: 'l', px: 15, label: '15' },
|
|
76
|
+
{ value: 'xl', px: 16, label: '16' }
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
/** 一条新标记的初始形状:整行 10% 淡色底 + 最右侧圆点,颜色蓝。 */
|
|
80
|
+
var DEFAULT_MARK = { color: 'blue', fill: 10, dots: 'right', size: 'inherit', weight: 400 };
|
|
81
|
+
|
|
82
|
+
// ----------------------------------------------------------------------
|
|
83
|
+
// 文案:跟随 DSH 自己的界面语言
|
|
84
|
+
// dsh-client-locale 会把当前语言写到 <html lang>,所以这里读它即可,
|
|
85
|
+
// 不必依赖 locale 服务(插件保持零硬依赖);读不到就退回 navigator.language。
|
|
86
|
+
// ----------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
var STRINGS = {
|
|
89
|
+
zh: {
|
|
90
|
+
mark: '标记…',
|
|
91
|
+
editMark: '编辑标记…',
|
|
92
|
+
panelTitle: '对话标记',
|
|
93
|
+
previewTitle: '这条对话会变成这样',
|
|
94
|
+
previewTime: '刚刚',
|
|
95
|
+
color: '颜色',
|
|
96
|
+
picker: '自定义颜色',
|
|
97
|
+
hex: '自定义色号',
|
|
98
|
+
fill: '整行淡色底浓度',
|
|
99
|
+
fillDark: '(深色主题自动 +4 个点)',
|
|
100
|
+
dots: '圆点',
|
|
101
|
+
size: '标题字号',
|
|
102
|
+
hint: '快捷键 {key} 打开当前对话的标记面板;也可以直接右键任意对话行。',
|
|
103
|
+
clear: '清除标记',
|
|
104
|
+
close: '关闭',
|
|
105
|
+
done: '完成',
|
|
106
|
+
none: '无',
|
|
107
|
+
sizeKeep: '原样',
|
|
108
|
+
fillOff: '不要',
|
|
109
|
+
dotRight: '最右侧',
|
|
110
|
+
dotLeft: '标题左侧',
|
|
111
|
+
dotBoth: '两侧',
|
|
112
|
+
dotNone: '不要',
|
|
113
|
+
colorNames: {
|
|
114
|
+
blue: '蓝 · 主线', green: '绿 · 完成', amber: '琥珀 · 待跟进',
|
|
115
|
+
red: '红 · 紧急', violet: '紫 · 个人', slate: '灰蓝 · 归档'
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
en: {
|
|
119
|
+
mark: 'Mark…',
|
|
120
|
+
editMark: 'Edit mark…',
|
|
121
|
+
panelTitle: 'Conversation mark',
|
|
122
|
+
previewTitle: 'This row will look like this',
|
|
123
|
+
previewTime: 'now',
|
|
124
|
+
color: 'Color',
|
|
125
|
+
picker: 'Custom color',
|
|
126
|
+
hex: 'Hex code',
|
|
127
|
+
fill: 'Row tint strength',
|
|
128
|
+
fillDark: ' (+4 pts in dark theme)',
|
|
129
|
+
dots: 'Dot',
|
|
130
|
+
size: 'Title size',
|
|
131
|
+
hint: 'Press {key} to mark the conversation you are viewing, or right-click any row.',
|
|
132
|
+
clear: 'Clear mark',
|
|
133
|
+
close: 'Close',
|
|
134
|
+
done: 'Done',
|
|
135
|
+
none: 'None',
|
|
136
|
+
sizeKeep: 'Keep as is',
|
|
137
|
+
fillOff: 'Off',
|
|
138
|
+
dotRight: 'Right end',
|
|
139
|
+
dotLeft: 'Left of title',
|
|
140
|
+
dotBoth: 'Both',
|
|
141
|
+
dotNone: 'Off',
|
|
142
|
+
colorNames: {
|
|
143
|
+
blue: 'Blue · main', green: 'Green · done', amber: 'Amber · follow up',
|
|
144
|
+
red: 'Red · urgent', violet: 'Violet · personal', slate: 'Slate · archived'
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/** 当前界面语言:'zh' 或 'en'。 */
|
|
150
|
+
function currentLang() {
|
|
151
|
+
var lang = '';
|
|
152
|
+
try {
|
|
153
|
+
lang = (document.documentElement && document.documentElement.lang) || '';
|
|
154
|
+
if (!lang) lang = (typeof navigator !== 'undefined' && navigator.language) || '';
|
|
155
|
+
} catch (err) {
|
|
156
|
+
lang = '';
|
|
157
|
+
}
|
|
158
|
+
return String(lang).toLowerCase().indexOf('zh') === 0 ? 'zh' : 'en';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** 取一条界面文案;缺键回退英文,再缺就原样返回 key。 */
|
|
162
|
+
function t(key, params) {
|
|
163
|
+
var table = STRINGS[currentLang()] || STRINGS.en;
|
|
164
|
+
var text = table[key];
|
|
165
|
+
if (text === undefined) text = STRINGS.en[key];
|
|
166
|
+
if (text === undefined) return key;
|
|
167
|
+
if (params) {
|
|
168
|
+
Object.keys(params).forEach(function (name) {
|
|
169
|
+
text = text.split('{' + name + '}').join(String(params[name]));
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return text;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** 颜色的可读名字(跟随界面语言)。 */
|
|
176
|
+
function colorName(color) {
|
|
177
|
+
var table = (STRINGS[currentLang()] || STRINGS.en).colorNames || {};
|
|
178
|
+
return table[color] || (STRINGS.en.colorNames[color] || color);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ----------------------------------------------------------------------
|
|
182
|
+
// 颜色:预设色号之外还接受任意 #rgb / #rrggbb 自定义色
|
|
183
|
+
// ----------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
var HEX_RE = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
186
|
+
|
|
187
|
+
function isHexColor(value) {
|
|
188
|
+
return typeof value === 'string' && HEX_RE.test(value.trim());
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** `#abc` -> `#aabbcc`,统一小写。 */
|
|
192
|
+
function normalizeHex(value) {
|
|
193
|
+
var hex = String(value).trim().toLowerCase();
|
|
194
|
+
if (hex.length === 4) hex = '#' + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3];
|
|
195
|
+
return hex;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function hexToRgb(hex) {
|
|
199
|
+
var h = normalizeHex(hex).slice(1);
|
|
200
|
+
return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function toHex2(n) {
|
|
204
|
+
var v = Math.max(0, Math.min(255, Math.round(n))).toString(16);
|
|
205
|
+
return v.length === 1 ? '0' + v : v;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** 把颜色朝某个目标色混 `ratio`(0–1)。 */
|
|
209
|
+
function mixHex(hex, targetHex, ratio) {
|
|
210
|
+
var a = hexToRgb(hex);
|
|
211
|
+
var b = hexToRgb(targetHex);
|
|
212
|
+
return '#' + [0, 1, 2].map(function (i) {
|
|
213
|
+
return toHex2(a[i] + (b[i] - a[i]) * ratio);
|
|
214
|
+
}).join('');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** 相对亮度(0–1,sRGB 加权,够用就行)。 */
|
|
218
|
+
function luminance(hex) {
|
|
219
|
+
var rgb = hexToRgb(hex);
|
|
220
|
+
return (0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2]) / 255;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** 自定义色在深色主题下的变体:本来就够亮就原样用,否则朝白色提亮 28%(与预设色的明暗差大致相当)。 */
|
|
224
|
+
function darkVariant(hex) {
|
|
225
|
+
var base = normalizeHex(hex);
|
|
226
|
+
return luminance(base) > 0.55 ? base : mixHex(base, '#ffffff', 0.28);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** 某个颜色值在指定主题下的实际色值;`theme` 取 'light' | 'dark'。 */
|
|
230
|
+
function colorFor(color, theme) {
|
|
231
|
+
if (typeof color !== 'string') return null;
|
|
232
|
+
var preset = COLORS[color];
|
|
233
|
+
if (preset) return theme === 'dark' ? preset.dark : preset.light;
|
|
234
|
+
if (!isHexColor(color)) return null;
|
|
235
|
+
return theme === 'dark' ? darkVariant(color) : normalizeHex(color);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** 颜色的可读名字(面板 / 调试用)。 */
|
|
239
|
+
function colorLabel(color) {
|
|
240
|
+
if (COLORS[color]) return colorName(color);
|
|
241
|
+
if (isHexColor(color)) return '自定义 ' + normalizeHex(color);
|
|
242
|
+
return '无颜色';
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** 颜色在浅色主题下的色值(预设色取其浅色值);没有颜色时返回 null。 */
|
|
246
|
+
function hexOf(color) {
|
|
247
|
+
if (COLORS[color]) return COLORS[color].light;
|
|
248
|
+
if (isHexColor(color)) return normalizeHex(color);
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** 给任意元素写标记用的自定义属性(行、圆点、菜单图标、面板预览共用)。 */
|
|
253
|
+
function applyDescriptor(node, descriptor) {
|
|
254
|
+
ROW_ATTRS.forEach(function (name) { node.removeAttribute(name); });
|
|
255
|
+
ROW_STYLE_PROPS.forEach(function (name) { node.style.removeProperty(name); });
|
|
256
|
+
if (!descriptor) return;
|
|
257
|
+
Object.keys(descriptor.attrs).forEach(function (name) { node.setAttribute(name, descriptor.attrs[name]); });
|
|
258
|
+
Object.keys(descriptor.style).forEach(function (name) { node.style.setProperty(name, descriptor.style[name]); });
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** 只写颜色相关的自定义属性(给插槽里的小圆点用)。 */
|
|
262
|
+
function applyColorVars(node, color) {
|
|
263
|
+
var light = colorFor(color, 'light');
|
|
264
|
+
if (!light) return;
|
|
265
|
+
node.style.setProperty('--dshmk-color-light', light);
|
|
266
|
+
node.style.setProperty('--dshmk-color-dark', colorFor(color, 'dark'));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ======================================================================
|
|
270
|
+
// 二、注入的样式表
|
|
271
|
+
// —— 只依赖 DSH 的稳定钩子:行上的 data-row-key、插槽里的 .dshmk-dot、
|
|
272
|
+
// 以及 body[data-ds-dark-theme] 这个官方深色主题开关。
|
|
273
|
+
// ======================================================================
|
|
274
|
+
|
|
275
|
+
var CSS = [
|
|
276
|
+
/* ---- 颜色:不再按预设名写死规则,改成读取元素上的两道自定义属性。
|
|
277
|
+
标记色既可能是预设名(蓝/绿/…),也可能是用户自定义的 #rrggbb,
|
|
278
|
+
所以由 JS 同时算出「浅色值 / 深色值」写进去,主题切换纯 CSS 完成。 ---- */
|
|
279
|
+
'[data-dshmk-color]{--dshmk-color:var(--dshmk-color-light,#4176e6)}',
|
|
280
|
+
'body[data-ds-dark-theme] [data-dshmk-color]{--dshmk-color:var(--dshmk-color-dark,var(--dshmk-color-light,#7aaaff))}',
|
|
281
|
+
|
|
282
|
+
/* ---- 只碰被标记的行;未标记的行一个字节都不动,保持原生观感 ---- */
|
|
283
|
+
'[data-row-key^="session:"][data-dshmk]{position:relative}',
|
|
284
|
+
|
|
285
|
+
/* ---- 整行淡色底:颜色 + 浓度混出一层半透明色,铺在行背景之上。
|
|
286
|
+
DSH 的 hover / 选中态写的是 `background` **简写**(会把 background-image 重置掉),
|
|
287
|
+
所以这里必须 !important;而 background-color 不受影响,hover 依然可见、颜色自然深一档。
|
|
288
|
+
浓度由 JS 写成两个自定义属性:浅色用 --dshmk-mix,深色用 --dshmk-mix-dark(自动 +4 个点)。 ---- */
|
|
289
|
+
'[data-row-key^="session:"][data-dshmk-fill="1"]{' +
|
|
290
|
+
'--dshmk-fill:color-mix(in srgb, var(--dshmk-color) var(--dshmk-mix,10%), transparent);' +
|
|
291
|
+
'background-image:linear-gradient(var(--dshmk-fill),var(--dshmk-fill))!important}',
|
|
292
|
+
'body[data-ds-dark-theme] [data-row-key^="session:"][data-dshmk-fill="1"]{' +
|
|
293
|
+
'--dshmk-fill:color-mix(in srgb, var(--dshmk-color) var(--dshmk-mix-dark,14%), transparent)}',
|
|
294
|
+
|
|
295
|
+
/* ---- 最右侧圆点:用背景绘制,不参与布局、不干扰 React,也不会被 hover 吃掉。
|
|
296
|
+
右侧预留 20px 内边距,时间文字自动左移,圆点落在距右边缘 14px 处。 ---- */
|
|
297
|
+
'[data-row-key^="session:"][data-dshmk-dots="right"],' +
|
|
298
|
+
'[data-row-key^="session:"][data-dshmk-dots="both"]{' +
|
|
299
|
+
'padding-right:20px!important;' +
|
|
300
|
+
'background-image:radial-gradient(circle 4px at calc(100% - 14px) 50%, var(--dshmk-color) 3.5px, transparent 4px)!important}',
|
|
301
|
+
/* 淡色底 + 圆点同时存在时,两层背景叠在一起(圆点在上,淡色底在下) */
|
|
302
|
+
'[data-row-key^="session:"][data-dshmk-fill="1"][data-dshmk-dots="right"],' +
|
|
303
|
+
'[data-row-key^="session:"][data-dshmk-fill="1"][data-dshmk-dots="both"]{' +
|
|
304
|
+
'background-image:radial-gradient(circle 4px at calc(100% - 14px) 50%, var(--dshmk-color) 3.5px, transparent 4px),' +
|
|
305
|
+
'linear-gradient(var(--dshmk-fill),var(--dshmk-fill))!important}',
|
|
306
|
+
|
|
307
|
+
/* ---- 标题字号 / 字重:只在显式选过时才覆盖(inherit 时一个字节都不写) ---- */
|
|
308
|
+
'[data-dshmk-size="xs"]{--dshmk-fs:12px}',
|
|
309
|
+
'[data-dshmk-size="s"]{--dshmk-fs:13px}',
|
|
310
|
+
'[data-dshmk-size="m"]{--dshmk-fs:14px}',
|
|
311
|
+
'[data-dshmk-size="l"]{--dshmk-fs:15px}',
|
|
312
|
+
'[data-dshmk-size="xl"]{--dshmk-fs:16px}',
|
|
313
|
+
'[data-dshmk-weight="500"]{--dshmk-fw:500}',
|
|
314
|
+
'[data-dshmk-weight="600"]{--dshmk-fw:600}',
|
|
315
|
+
'[data-row-key^="session:"][data-dshmk]:not([data-dshmk-size="inherit"]) > span:nth-child(2){font-size:var(--dshmk-fs,14px)}',
|
|
316
|
+
'[data-row-key^="session:"][data-dshmk][data-dshmk-weight="500"] > span:nth-child(2),' +
|
|
317
|
+
'[data-row-key^="session:"][data-dshmk][data-dshmk-weight="600"] > span:nth-child(2){font-weight:var(--dshmk-fw)}',
|
|
318
|
+
|
|
319
|
+
/* ---- 左侧圆点:由 React 插槽渲染在标题前的 16×20 格子里 ---- */
|
|
320
|
+
'.dshmk-dot{display:inline-flex;align-items:center;justify-content:center;flex:none;width:8px;height:8px;' +
|
|
321
|
+
'border-radius:50%;background:var(--dshmk-color);box-sizing:content-box}',
|
|
322
|
+
|
|
323
|
+
/* ---- 标记面板 ---- */
|
|
324
|
+
'.dshmk-scrim{position:fixed;inset:0;z-index:2147483000;background:var(--dsw-alias-bg-mask-1,rgba(0,0,0,.24));' +
|
|
325
|
+
'-webkit-app-region:no-drag}',
|
|
326
|
+
'.dshmk-panel{position:fixed;z-index:2147483001;left:50%;top:50%;transform:translate(-50%,-50%);width:344px;' +
|
|
327
|
+
'-webkit-app-region:no-drag;' +
|
|
328
|
+
'box-sizing:border-box;padding:16px 16px 12px;border-radius:var(--dsw-radius-lg,16px);' +
|
|
329
|
+
'border:.5px solid var(--dsw-alias-border-l3,rgba(0,0,0,.12));background:var(--dsw-alias-bg-layer-1,#fff);' +
|
|
330
|
+
'color:var(--dsw-alias-label-primary,#0f1115);box-shadow:0 18px 48px rgba(0,0,0,.24);font-size:13px;line-height:20px}',
|
|
331
|
+
'body[data-ds-dark-theme] .dshmk-panel{background:var(--dsw-static-neutral-bluish-875,#232324);' +
|
|
332
|
+
'color:var(--dsw-static-neutral-bluish-50,#f9fafb)}',
|
|
333
|
+
'.dshmk-panel h3{margin:0 0 2px;font-size:14px;font-weight:600;line-height:22px}',
|
|
334
|
+
'.dshmk-sub{color:var(--dsw-alias-label-tertiary,#81858c);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
335
|
+
'.dshmk-field{margin-top:12px}',
|
|
336
|
+
'.dshmk-field > label{display:block;margin-bottom:6px;color:var(--dsw-alias-label-secondary,#61666b);font-size:12px}',
|
|
337
|
+
'.dshmk-chips{display:flex;flex-wrap:wrap;gap:6px}',
|
|
338
|
+
'.dshmk-chip{cursor:pointer;padding:4px 9px;border-radius:8px;font:inherit;font-size:12px;line-height:18px;color:inherit;' +
|
|
339
|
+
'border:1px solid var(--dsw-alias-border-l3,rgba(0,0,0,.12));background:transparent}',
|
|
340
|
+
'.dshmk-chip[aria-pressed="true"]{border-color:var(--dsw-alias-state-business-primary,#4176e6);' +
|
|
341
|
+
'background:var(--dsw-alias-interactive-bg-hover-accent,rgba(38,49,72,.14))}',
|
|
342
|
+
'.dshmk-swatches{display:flex;gap:8px;align-items:center;flex-wrap:wrap}',
|
|
343
|
+
'.dshmk-sw{width:24px;height:24px;padding:0;border-radius:50%;cursor:pointer;border:2px solid transparent;background:var(--dshmk-color)}',
|
|
344
|
+
'.dshmk-sw[aria-pressed="true"]{border-color:var(--dsw-alias-label-primary,#0f1115)}',
|
|
345
|
+
'.dshmk-sw[data-color="none"]{background:transparent;border-color:var(--dsw-alias-border-l3,rgba(0,0,0,.12));' +
|
|
346
|
+
'color:var(--dsw-alias-label-tertiary,#81858c);font-size:10px}',
|
|
347
|
+
/* 自定义颜色:系统色盘 + 色号输入 */
|
|
348
|
+
'.dshmk-custom-row{display:flex;align-items:center;gap:8px;margin-top:8px}',
|
|
349
|
+
'.dshmk-picker{width:24px;height:24px;padding:0;border:2px solid var(--dsw-alias-border-l3,rgba(0,0,0,.12));' +
|
|
350
|
+
'border-radius:50%;background:transparent;cursor:pointer;appearance:none;-webkit-appearance:none;overflow:hidden}',
|
|
351
|
+
'.dshmk-picker[data-active="1"]{border-color:var(--dsw-alias-label-primary,#0f1115)}',
|
|
352
|
+
'.dshmk-picker::-webkit-color-swatch-wrapper{padding:0}',
|
|
353
|
+
'.dshmk-picker::-webkit-color-swatch{border:none;border-radius:50%}',
|
|
354
|
+
'.dshmk-picker::-moz-color-swatch{border:none;border-radius:50%}',
|
|
355
|
+
'.dshmk-hex{box-sizing:border-box;width:92px;padding:4px 8px;border-radius:8px;font:inherit;font-size:12px;color:inherit;' +
|
|
356
|
+
'background:transparent;border:1px solid var(--dsw-alias-border-l3,rgba(0,0,0,.12));font-family:ui-monospace,SFMono-Regular,Menlo,monospace}',
|
|
357
|
+
'.dshmk-hex::placeholder{color:var(--dsw-alias-label-caption,#adb2b8);font-family:inherit}',
|
|
358
|
+
'.dshmk-foot{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:14px}',
|
|
359
|
+
'.dshmk-btn{cursor:pointer;padding:6px 12px;border-radius:8px;font:inherit;font-size:12px;' +
|
|
360
|
+
'border:1px solid var(--dsw-alias-border-l3,rgba(0,0,0,.12));background:transparent;color:inherit}',
|
|
361
|
+
'.dshmk-btn[data-variant="primary"]{border-color:transparent;background:var(--dsw-alias-state-business-primary,#4176e6);color:#fff}',
|
|
362
|
+
'.dshmk-hint{margin-top:10px;color:var(--dsw-alias-label-tertiary,#81858c);font-size:11.5px;line-height:17px}',
|
|
363
|
+
|
|
364
|
+
/* ---- 面板里的实时预览行:结构与真实行一致,复用同一套标记规则 ---- */
|
|
365
|
+
'.dshmk-preview{margin-top:10px;padding:4px 4px 2px;border-radius:12px;' +
|
|
366
|
+
'background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06))}',
|
|
367
|
+
'.dshmk-preview-row{display:flex;align-items:center;gap:0;height:32px;padding:0 8px;border-radius:12px;' +
|
|
368
|
+
'color:var(--dsw-alias-label-primary,#0f1115);font-size:14px;line-height:20px}',
|
|
369
|
+
'.dshmk-preview-cell{width:16px;height:20px;flex:none;display:inline-flex;align-items:center;justify-content:center}',
|
|
370
|
+
'.dshmk-preview-title{flex:1;min-width:0;margin:0 6px 0 4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}',
|
|
371
|
+
'.dshmk-preview-time{flex:none;font-size:10px;color:var(--dsw-alias-label-tertiary,#81858c)}'
|
|
372
|
+
].join('\n');
|
|
373
|
+
|
|
374
|
+
// ======================================================================
|
|
375
|
+
// 三、状态与持久化
|
|
376
|
+
// ======================================================================
|
|
377
|
+
|
|
378
|
+
/** sessionId -> mark。内存里的唯一真相,DOM 只是它的投影。 */
|
|
379
|
+
var marks = Object.create(null);
|
|
380
|
+
var listeners = new Set();
|
|
381
|
+
var version = 0;
|
|
382
|
+
var saveTimer = null;
|
|
383
|
+
var deferred = [];
|
|
384
|
+
|
|
385
|
+
function bump() {
|
|
386
|
+
version += 1;
|
|
387
|
+
listeners.forEach(function (fn) {
|
|
388
|
+
try {
|
|
389
|
+
fn(version);
|
|
390
|
+
} catch (err) {
|
|
391
|
+
/* 单个订阅者出错不影响其它 */
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function subscribe(fn) {
|
|
397
|
+
listeners.add(fn);
|
|
398
|
+
return function () {
|
|
399
|
+
listeners.delete(fn);
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/** 把任意输入收敛成合法 mark;没有任何可见效果时返回 null(= 没有标记)。 */
|
|
404
|
+
function normalizeMark(input) {
|
|
405
|
+
if (!input || typeof input !== 'object') return null;
|
|
406
|
+
var out = {};
|
|
407
|
+
if (typeof input.color === 'string') {
|
|
408
|
+
if (COLORS[input.color]) out.color = input.color;
|
|
409
|
+
else if (isHexColor(input.color)) out.color = normalizeHex(input.color);
|
|
410
|
+
}
|
|
411
|
+
if (input.fill !== undefined && input.fill !== null && input.fill !== '') {
|
|
412
|
+
var f = Number(input.fill);
|
|
413
|
+
if (isFinite(f)) out.fill = Math.max(0, Math.min(30, Math.round(f)));
|
|
414
|
+
}
|
|
415
|
+
if (input.dots && DOTS.some(function (d) { return d.value === input.dots; })) out.dots = input.dots;
|
|
416
|
+
if (input.size && SIZES.some(function (s) { return s.value === input.size; })) out.size = input.size;
|
|
417
|
+
else if (input.size && SIZES.some(function (s) { return s.px === Number(input.size); })) {
|
|
418
|
+
out.size = SIZES.filter(function (s) { return s.px === Number(input.size); })[0].value;
|
|
419
|
+
}
|
|
420
|
+
if (Number(input.weight) === 500 || Number(input.weight) === 600) out.weight = Number(input.weight);
|
|
421
|
+
var paints = out.color && ((out.fill && out.fill > 0) || (out.dots && out.dots !== 'none'));
|
|
422
|
+
var resizes = out.size && out.size !== 'inherit';
|
|
423
|
+
return paints || resizes ? out : null;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function markOf(sessionId) {
|
|
427
|
+
return marks[sessionId] || null;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** 该对话生效的完整形状(未标记时返回一份默认形状,供面板预览)。 */
|
|
431
|
+
function markOrDefault(sessionId) {
|
|
432
|
+
var seed = {};
|
|
433
|
+
var k;
|
|
434
|
+
for (k in DEFAULT_MARK) seed[k] = DEFAULT_MARK[k];
|
|
435
|
+
var own = marks[sessionId];
|
|
436
|
+
if (own) for (k in own) seed[k] = own[k];
|
|
437
|
+
return seed;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function setMark(sessionId, patch, options) {
|
|
441
|
+
var next = normalizeMark(Object.assign({}, markOrDefault(sessionId), patch || {}));
|
|
442
|
+
if (next === null) delete marks[sessionId];
|
|
443
|
+
else marks[sessionId] = next;
|
|
444
|
+
if (!options || options.persist !== false) scheduleSave();
|
|
445
|
+
annotateRows();
|
|
446
|
+
bump();
|
|
447
|
+
return marks[sessionId] || null;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function clearMark(sessionId) {
|
|
451
|
+
delete marks[sessionId];
|
|
452
|
+
scheduleSave();
|
|
453
|
+
annotateRows();
|
|
454
|
+
bump();
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function serialize() {
|
|
458
|
+
return JSON.stringify({ version: 2, marks: marks });
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function parse(raw) {
|
|
462
|
+
if (!raw) return Object.create(null);
|
|
463
|
+
var data;
|
|
464
|
+
try {
|
|
465
|
+
data = JSON.parse(raw);
|
|
466
|
+
} catch (err) {
|
|
467
|
+
return Object.create(null);
|
|
468
|
+
}
|
|
469
|
+
var src = data && data.marks ? data.marks : {};
|
|
470
|
+
var out = Object.create(null);
|
|
471
|
+
Object.keys(src).forEach(function (id) {
|
|
472
|
+
var m = normalizeMark(src[id]);
|
|
473
|
+
if (m !== null) out[id] = m;
|
|
474
|
+
});
|
|
475
|
+
return out;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function storage() {
|
|
479
|
+
try {
|
|
480
|
+
return window.localStorage || null;
|
|
481
|
+
} catch (err) {
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function loadMarks() {
|
|
487
|
+
var ls = storage();
|
|
488
|
+
marks = parse(ls ? ls.getItem(STORAGE_KEY) : null);
|
|
489
|
+
return marks;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function saveMarks() {
|
|
493
|
+
var ls = storage();
|
|
494
|
+
if (!ls) return false;
|
|
495
|
+
try {
|
|
496
|
+
ls.setItem(STORAGE_KEY, serialize());
|
|
497
|
+
return true;
|
|
498
|
+
} catch (err) {
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/** 面板里每点一下都会改状态;写盘去抖,避免连续操作时狂写 localStorage。 */
|
|
504
|
+
function scheduleSave() {
|
|
505
|
+
if (saveTimer !== null) clearTimeout(saveTimer);
|
|
506
|
+
saveTimer = setTimeout(function () {
|
|
507
|
+
saveTimer = null;
|
|
508
|
+
saveMarks();
|
|
509
|
+
}, 200);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// ======================================================================
|
|
513
|
+
// 四、行标注引擎:把 marks 投影到 DOM 上
|
|
514
|
+
// ======================================================================
|
|
515
|
+
|
|
516
|
+
/** mark -> 行元素上应有的属性与自定义属性;无可见标记时返回 null。 */
|
|
517
|
+
function rowAttributes(mark) {
|
|
518
|
+
if (!mark) return null;
|
|
519
|
+
var hasColor = !!mark.color;
|
|
520
|
+
var fill = hasColor && Number(mark.fill) > 0 ? Math.round(Number(mark.fill)) : 0;
|
|
521
|
+
var dots = hasColor ? (mark.dots || 'none') : 'none';
|
|
522
|
+
var size = mark.size || 'inherit';
|
|
523
|
+
var weight = Number(mark.weight) === 500 || Number(mark.weight) === 600 ? Number(mark.weight) : 400;
|
|
524
|
+
if (fill === 0 && dots === 'none' && size === 'inherit') return null;
|
|
525
|
+
var attrs = { 'data-dshmk': '1' };
|
|
526
|
+
if (hasColor) attrs['data-dshmk-color'] = mark.color;
|
|
527
|
+
attrs['data-dshmk-fill'] = fill > 0 ? '1' : '0';
|
|
528
|
+
attrs['data-dshmk-dots'] = dots;
|
|
529
|
+
attrs['data-dshmk-size'] = size;
|
|
530
|
+
attrs['data-dshmk-weight'] = String(weight);
|
|
531
|
+
var style = {};
|
|
532
|
+
if (hasColor) {
|
|
533
|
+
style['--dshmk-color-light'] = colorFor(mark.color, 'light');
|
|
534
|
+
style['--dshmk-color-dark'] = colorFor(mark.color, 'dark');
|
|
535
|
+
}
|
|
536
|
+
if (fill > 0) {
|
|
537
|
+
style['--dshmk-mix'] = fill + '%';
|
|
538
|
+
style['--dshmk-mix-dark'] = Math.min(30, fill + DARK_BOOST) + '%';
|
|
539
|
+
}
|
|
540
|
+
return { attrs: attrs, style: style };
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
var ROW_ATTRS = ['data-dshmk', 'data-dshmk-color', 'data-dshmk-fill', 'data-dshmk-dots', 'data-dshmk-size', 'data-dshmk-weight'];
|
|
544
|
+
var ROW_STYLE_PROPS = ['--dshmk-mix', '--dshmk-mix-dark', '--dshmk-color-light', '--dshmk-color-dark'];
|
|
545
|
+
|
|
546
|
+
/** 行上的 session id(去掉 `session:` 前缀);不是对话行则返回 null。 */
|
|
547
|
+
function sessionIdOfRow(row) {
|
|
548
|
+
if (!row || typeof row.getAttribute !== 'function') return null;
|
|
549
|
+
var key = row.getAttribute('data-row-key') || '';
|
|
550
|
+
if (key.indexOf(ROW_PREFIX) !== 0) return null;
|
|
551
|
+
return key.slice(ROW_PREFIX.length);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function writeRow(row, descriptor) {
|
|
555
|
+
applyDescriptor(row, descriptor);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* 扫描(并重新标注)所有对话行。全量 + 幂等,所以「标记被清除」也能被纠正回来。
|
|
560
|
+
* @param root 默认 document。
|
|
561
|
+
* @returns 被标注的行数。
|
|
562
|
+
*/
|
|
563
|
+
function annotateRows(root) {
|
|
564
|
+
var host = root || (typeof document !== 'undefined' ? document : null);
|
|
565
|
+
if (!host || typeof host.querySelectorAll !== 'function') return 0;
|
|
566
|
+
var rows = host.querySelectorAll('[data-row-key^="' + ROW_PREFIX + '"]');
|
|
567
|
+
var marked = 0;
|
|
568
|
+
for (var i = 0; i < rows.length; i += 1) {
|
|
569
|
+
var id = sessionIdOfRow(rows[i]);
|
|
570
|
+
var descriptor = rowAttributes(marks[id]);
|
|
571
|
+
if (descriptor) marked += 1;
|
|
572
|
+
writeRow(rows[i], descriptor);
|
|
573
|
+
}
|
|
574
|
+
return marked;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// ======================================================================
|
|
578
|
+
// 五、插槽组件
|
|
579
|
+
// ======================================================================
|
|
580
|
+
|
|
581
|
+
function useVersion() {
|
|
582
|
+
var pair = React.useState(version);
|
|
583
|
+
var setV = pair[1];
|
|
584
|
+
React.useEffect(function () {
|
|
585
|
+
return subscribe(function (v) { setV(v); });
|
|
586
|
+
}, []);
|
|
587
|
+
return pair[0];
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/** 该对话是否要在标题左侧那格画圆点。 */
|
|
591
|
+
function leftDotVisible(mark) {
|
|
592
|
+
return !!(mark && mark.color && (mark.dots === 'left' || mark.dots === 'both'));
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/** 延迟取 ui-primitives:宿主没装它时退化成原生 button,插件不至于整个挂掉。 */
|
|
596
|
+
var primitives;
|
|
597
|
+
function menuItemComponent() {
|
|
598
|
+
if (primitives === undefined) {
|
|
599
|
+
try {
|
|
600
|
+
primitives = require('@deepseek-ai/dsh-client-ui-primitives');
|
|
601
|
+
} catch (err) {
|
|
602
|
+
primitives = null;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (primitives && primitives.MenuItemButton) return primitives.MenuItemButton;
|
|
606
|
+
return function FallbackMenuItem(props) {
|
|
607
|
+
return React.createElement('button', { type: 'button', role: 'menuitem', onClick: props.onSelect }, props.children);
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/** 左侧圆点:标题前 16×20 的格子。DSH 的状态点在场时本组件不会被挂载。 */
|
|
612
|
+
function LeadingDot(props) {
|
|
613
|
+
useVersion();
|
|
614
|
+
var mark = marks[props.sessionId];
|
|
615
|
+
if (!leftDotVisible(mark)) return null;
|
|
616
|
+
var light = colorFor(mark.color, 'light');
|
|
617
|
+
if (!light) return null;
|
|
618
|
+
return React.createElement('span', {
|
|
619
|
+
className: 'dshmk-dot',
|
|
620
|
+
'data-dshmk-color': mark.color,
|
|
621
|
+
style: { '--dshmk-color-light': light, '--dshmk-color-dark': colorFor(mark.color, 'dark') },
|
|
622
|
+
'aria-hidden': 'true',
|
|
623
|
+
title: '对话标记'
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/** 「…」菜单里的那一行文案。 */
|
|
628
|
+
function menuLabel(mark) {
|
|
629
|
+
return mark ? t('editMark') : t('mark');
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function MarkMenuItem(props) {
|
|
633
|
+
useVersion();
|
|
634
|
+
var pair = props.useMenuOpenState();
|
|
635
|
+
var setMenuOpen = pair[1];
|
|
636
|
+
var mark = marks[props.sessionId];
|
|
637
|
+
var menuLight = mark && mark.color ? colorFor(mark.color, 'light') : null;
|
|
638
|
+
var icon = React.createElement('span', {
|
|
639
|
+
className: 'dshmk-dot',
|
|
640
|
+
'data-dshmk-color': mark && mark.color ? mark.color : undefined,
|
|
641
|
+
style: menuLight
|
|
642
|
+
? { '--dshmk-color-light': menuLight, '--dshmk-color-dark': colorFor(mark.color, 'dark') }
|
|
643
|
+
: { background: 'transparent' }
|
|
644
|
+
});
|
|
645
|
+
return React.createElement(
|
|
646
|
+
menuItemComponent(),
|
|
647
|
+
{
|
|
648
|
+
icon: icon,
|
|
649
|
+
separatorBefore: true,
|
|
650
|
+
onSelect: function () {
|
|
651
|
+
setMenuOpen(false);
|
|
652
|
+
openPanel(props.sessionId, props.displayTitle);
|
|
653
|
+
}
|
|
654
|
+
},
|
|
655
|
+
menuLabel(mark)
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// ======================================================================
|
|
660
|
+
// 六、标记面板(纯 DOM,避免额外插槽契约)
|
|
661
|
+
// ======================================================================
|
|
662
|
+
|
|
663
|
+
var panel = null;
|
|
664
|
+
var panelState = null;
|
|
665
|
+
|
|
666
|
+
function el(tag, className, text) {
|
|
667
|
+
var node = document.createElement(tag);
|
|
668
|
+
if (className) node.className = className;
|
|
669
|
+
if (text !== undefined && text !== null) node.textContent = String(text);
|
|
670
|
+
return node;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function chip(labelText, pressed, onSelect) {
|
|
674
|
+
var b = el('button', 'dshmk-chip', labelText);
|
|
675
|
+
b.setAttribute('type', 'button');
|
|
676
|
+
b.setAttribute('aria-pressed', pressed ? 'true' : 'false');
|
|
677
|
+
b.addEventListener('click', onSelect);
|
|
678
|
+
return b;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function field(labelText) {
|
|
682
|
+
var wrap = el('div', 'dshmk-field');
|
|
683
|
+
wrap.appendChild(el('label', null, labelText));
|
|
684
|
+
return wrap;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function closePanel() {
|
|
688
|
+
if (panel && panel.root && panel.root.remove) panel.root.remove();
|
|
689
|
+
if (panel && panel.onKey) document.removeEventListener('keydown', panel.onKey);
|
|
690
|
+
panel = null;
|
|
691
|
+
panelState = null;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function openPanel(sessionId, displayTitle) {
|
|
695
|
+
if (!sessionId) return null;
|
|
696
|
+
closePanel();
|
|
697
|
+
panelState = { sessionId: sessionId, title: displayTitle || displayTitleOf(sessionId) || '(未命名对话)' };
|
|
698
|
+
var root = el('div', 'dshmk-scrim');
|
|
699
|
+
root.setAttribute('data-dshmk-panel', '1');
|
|
700
|
+
var card = el('div', 'dshmk-panel');
|
|
701
|
+
root.appendChild(card);
|
|
702
|
+
root.addEventListener('click', function () { closePanel(); });
|
|
703
|
+
card.addEventListener('click', function (event) {
|
|
704
|
+
if (event && event.stopPropagation) event.stopPropagation();
|
|
705
|
+
});
|
|
706
|
+
var onKey = function (event) {
|
|
707
|
+
if (event && (event.key === 'Escape' || event.key === 'Esc')) closePanel();
|
|
708
|
+
};
|
|
709
|
+
document.addEventListener('keydown', onKey);
|
|
710
|
+
panel = { root: root, card: card, onKey: onKey };
|
|
711
|
+
(document.body || document.documentElement).appendChild(root);
|
|
712
|
+
renderPanel();
|
|
713
|
+
return panel;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/** 从侧边栏行里读标题(行内第 2 个 span 就是标题,DSH 结构稳定)。 */
|
|
717
|
+
function displayTitleOf(sessionId) {
|
|
718
|
+
var row = findRow(sessionId);
|
|
719
|
+
if (!row || !row.children || row.children.length < 2) return '';
|
|
720
|
+
return String(row.children[1].textContent || '').trim();
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function findRow(sessionId) {
|
|
724
|
+
if (typeof document === 'undefined' || !document.querySelector) return null;
|
|
725
|
+
return document.querySelector('[data-row-key="' + ROW_PREFIX + sessionId + '"]');
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/** 预览行里的左侧圆点:颜色跟着标记走。 */
|
|
729
|
+
function fillPreviewCell(cell, mark) {
|
|
730
|
+
cell.textContent = '';
|
|
731
|
+
if (!leftDotVisible(mark)) return;
|
|
732
|
+
var dot = el('span', 'dshmk-dot');
|
|
733
|
+
dot.setAttribute('data-dshmk-color', mark.color);
|
|
734
|
+
applyColorVars(dot, mark.color);
|
|
735
|
+
cell.appendChild(dot);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/** 面板里的实时预览行:结构与真实行一致,因此复用同一套标记 CSS。 */
|
|
739
|
+
function previewRow(mark) {
|
|
740
|
+
var wrap = el('div', 'dshmk-preview');
|
|
741
|
+
var row = el('div', 'dshmk-preview-row');
|
|
742
|
+
row.setAttribute('data-row-key', ROW_PREFIX + '__preview');
|
|
743
|
+
applyDescriptor(row, rowAttributes(mark));
|
|
744
|
+
var cell = el('span', 'dshmk-preview-cell');
|
|
745
|
+
fillPreviewCell(cell, mark);
|
|
746
|
+
row.appendChild(cell);
|
|
747
|
+
row.appendChild(el('span', 'dshmk-preview-title', t('previewTitle')));
|
|
748
|
+
row.appendChild(el('span', 'dshmk-preview-time', t('previewTime')));
|
|
749
|
+
wrap.appendChild(row);
|
|
750
|
+
if (panel) { panel.previewRow = row; panel.previewCell = cell; }
|
|
751
|
+
return wrap;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/** 拖动系统色盘时的高频更新:只改预览行与色板选中态,不重建面板(重建会打断取色)。 */
|
|
755
|
+
function syncPanelLive() {
|
|
756
|
+
if (!panel || !panelState) return;
|
|
757
|
+
var mark = markOrDefault(panelState.sessionId);
|
|
758
|
+
if (panel.previewRow) applyDescriptor(panel.previewRow, rowAttributes(mark));
|
|
759
|
+
if (panel.previewCell) fillPreviewCell(panel.previewCell, mark);
|
|
760
|
+
var swatches = panel.card.querySelectorAll('[data-preset]');
|
|
761
|
+
for (var i = 0; i < swatches.length; i += 1) {
|
|
762
|
+
swatches[i].setAttribute('aria-pressed', swatches[i].getAttribute('data-preset') === mark.color ? 'true' : 'false');
|
|
763
|
+
}
|
|
764
|
+
var picker = panel.card.querySelector('.dshmk-picker');
|
|
765
|
+
if (picker && isHexColor(mark.color)) picker.value = normalizeHex(mark.color);
|
|
766
|
+
var hexInput = panel.card.querySelector('.dshmk-hex');
|
|
767
|
+
if (hexInput && isHexColor(mark.color) && document.activeElement !== hexInput) hexInput.value = normalizeHex(mark.color);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function renderPanel() {
|
|
771
|
+
if (!panel || !panelState) return;
|
|
772
|
+
var card = panel.card;
|
|
773
|
+
card.textContent = '';
|
|
774
|
+
var sessionId = panelState.sessionId;
|
|
775
|
+
var mark = markOrDefault(sessionId);
|
|
776
|
+
var committed = !!marks[sessionId];
|
|
777
|
+
|
|
778
|
+
card.appendChild(el('h3', null, t('panelTitle')));
|
|
779
|
+
card.appendChild(el('div', 'dshmk-sub', panelState.title + ' · ' + sessionId));
|
|
780
|
+
card.appendChild(previewRow(mark));
|
|
781
|
+
|
|
782
|
+
// 颜色
|
|
783
|
+
var colorField = field(t('color'));
|
|
784
|
+
var swatches = el('div', 'dshmk-swatches');
|
|
785
|
+
Object.keys(COLORS).forEach(function (key) {
|
|
786
|
+
var b = el('button', 'dshmk-sw');
|
|
787
|
+
b.setAttribute('type', 'button');
|
|
788
|
+
b.setAttribute('data-dshmk-color', key);
|
|
789
|
+
b.setAttribute('data-preset', key);
|
|
790
|
+
b.style.setProperty('--dshmk-color-light', COLORS[key].light);
|
|
791
|
+
b.style.setProperty('--dshmk-color-dark', COLORS[key].dark);
|
|
792
|
+
b.setAttribute('aria-pressed', mark.color === key ? 'true' : 'false');
|
|
793
|
+
b.title = colorName(key);
|
|
794
|
+
b.addEventListener('click', function () { setMark(sessionId, { color: key }); renderPanel(); });
|
|
795
|
+
swatches.appendChild(b);
|
|
796
|
+
});
|
|
797
|
+
var none = el('button', 'dshmk-sw', t('none'));
|
|
798
|
+
none.setAttribute('type', 'button');
|
|
799
|
+
none.setAttribute('data-color', 'none');
|
|
800
|
+
none.setAttribute('aria-pressed', mark.color ? 'false' : 'true');
|
|
801
|
+
none.addEventListener('click', function () { setMark(sessionId, { color: null }); renderPanel(); });
|
|
802
|
+
swatches.appendChild(none);
|
|
803
|
+
colorField.appendChild(swatches);
|
|
804
|
+
|
|
805
|
+
// 自定义颜色:色盘 + 色号
|
|
806
|
+
var customRow = el('div', 'dshmk-custom-row');
|
|
807
|
+
var customColor = hexOf(mark.color) || '#4176e6';
|
|
808
|
+
var picker = el('input', 'dshmk-picker');
|
|
809
|
+
picker.setAttribute('type', 'color');
|
|
810
|
+
picker.setAttribute('aria-label', t('picker'));
|
|
811
|
+
picker.value = customColor;
|
|
812
|
+
picker.title = t('picker');
|
|
813
|
+
if (isHexColor(mark.color)) picker.setAttribute('data-active', '1');
|
|
814
|
+
// 'input' 在系统色盘拖动时连续触发:只更新标记与预览,不重建面板(重建会打断取色)
|
|
815
|
+
picker.addEventListener('input', function () {
|
|
816
|
+
setMark(sessionId, { color: picker.value });
|
|
817
|
+
if (panel) syncPanelLive();
|
|
818
|
+
});
|
|
819
|
+
picker.addEventListener('change', function () {
|
|
820
|
+
setMark(sessionId, { color: picker.value });
|
|
821
|
+
renderPanel();
|
|
822
|
+
});
|
|
823
|
+
var hexInput = el('input', 'dshmk-hex');
|
|
824
|
+
hexInput.setAttribute('type', 'text');
|
|
825
|
+
hexInput.setAttribute('maxlength', '7');
|
|
826
|
+
hexInput.setAttribute('spellcheck', 'false');
|
|
827
|
+
hexInput.setAttribute('placeholder', '#8b5cf6');
|
|
828
|
+
hexInput.setAttribute('aria-label', t('hex'));
|
|
829
|
+
hexInput.value = isHexColor(mark.color) ? normalizeHex(mark.color) : '';
|
|
830
|
+
var applyHex = function () {
|
|
831
|
+
var raw = String(hexInput.value || '').trim();
|
|
832
|
+
if (raw && raw[0] !== '#') raw = '#' + raw;
|
|
833
|
+
if (isHexColor(raw)) {
|
|
834
|
+
setMark(sessionId, { color: normalizeHex(raw) });
|
|
835
|
+
renderPanel();
|
|
836
|
+
return true;
|
|
837
|
+
}
|
|
838
|
+
hexInput.value = isHexColor(mark.color) ? normalizeHex(mark.color) : '';
|
|
839
|
+
return false;
|
|
840
|
+
};
|
|
841
|
+
hexInput.addEventListener('change', applyHex);
|
|
842
|
+
hexInput.addEventListener('keydown', function (event) {
|
|
843
|
+
if (event && event.key === 'Enter') { event.preventDefault(); applyHex(); }
|
|
844
|
+
});
|
|
845
|
+
customRow.appendChild(picker);
|
|
846
|
+
customRow.appendChild(hexInput);
|
|
847
|
+
colorField.appendChild(customRow);
|
|
848
|
+
card.appendChild(colorField);
|
|
849
|
+
|
|
850
|
+
// 浓度(整行淡色底)
|
|
851
|
+
var fillField = field(t('fill') + (Number(mark.fill) > 0 ? t('fillDark') : ''));
|
|
852
|
+
var fillChips = el('div', 'dshmk-chips');
|
|
853
|
+
FILLS.forEach(function (f) {
|
|
854
|
+
fillChips.appendChild(chip(f.value === 0 ? t('fillOff') : f.label, Number(mark.fill || 0) === f.value, function () {
|
|
855
|
+
setMark(sessionId, { fill: f.value });
|
|
856
|
+
renderPanel();
|
|
857
|
+
}));
|
|
858
|
+
});
|
|
859
|
+
fillField.appendChild(fillChips);
|
|
860
|
+
card.appendChild(fillField);
|
|
861
|
+
|
|
862
|
+
// 圆点
|
|
863
|
+
var dotField = field(t('dots'));
|
|
864
|
+
var dotChips = el('div', 'dshmk-chips');
|
|
865
|
+
var DOT_LABELS = { right: 'dotRight', left: 'dotLeft', both: 'dotBoth', none: 'dotNone' };
|
|
866
|
+
DOTS.forEach(function (d) {
|
|
867
|
+
dotChips.appendChild(chip(t(DOT_LABELS[d.value] || 'dotNone'), (mark.dots || 'none') === d.value, function () {
|
|
868
|
+
setMark(sessionId, { dots: d.value });
|
|
869
|
+
renderPanel();
|
|
870
|
+
}));
|
|
871
|
+
});
|
|
872
|
+
dotField.appendChild(dotChips);
|
|
873
|
+
card.appendChild(dotField);
|
|
874
|
+
|
|
875
|
+
// 字号
|
|
876
|
+
var sizeField = field(t('size'));
|
|
877
|
+
var sizeChips = el('div', 'dshmk-chips');
|
|
878
|
+
SIZES.forEach(function (s) {
|
|
879
|
+
sizeChips.appendChild(chip(s.value === 'inherit' ? t('sizeKeep') : s.label, (mark.size || 'inherit') === s.value, function () {
|
|
880
|
+
setMark(sessionId, { size: s.value });
|
|
881
|
+
renderPanel();
|
|
882
|
+
}));
|
|
883
|
+
});
|
|
884
|
+
sizeField.appendChild(sizeChips);
|
|
885
|
+
card.appendChild(sizeField);
|
|
886
|
+
|
|
887
|
+
card.appendChild(el('div', 'dshmk-hint', t('hint', { key: isMac() ? '⌘⇧M' : 'Ctrl+Shift+M' })));
|
|
888
|
+
|
|
889
|
+
// 底部动作
|
|
890
|
+
var foot = el('div', 'dshmk-foot');
|
|
891
|
+
var left = el('div', 'dshmk-chips');
|
|
892
|
+
left.appendChild(chip(committed ? t('clear') : t('close'), false, function () {
|
|
893
|
+
if (committed) {
|
|
894
|
+
clearMark(sessionId);
|
|
895
|
+
renderPanel();
|
|
896
|
+
} else {
|
|
897
|
+
closePanel();
|
|
898
|
+
}
|
|
899
|
+
}));
|
|
900
|
+
var done = el('button', 'dshmk-btn', t('done'));
|
|
901
|
+
done.setAttribute('type', 'button');
|
|
902
|
+
done.setAttribute('data-variant', 'primary');
|
|
903
|
+
done.addEventListener('click', function () { closePanel(); });
|
|
904
|
+
foot.appendChild(left);
|
|
905
|
+
foot.appendChild(done);
|
|
906
|
+
card.appendChild(foot);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function isMac() {
|
|
910
|
+
try {
|
|
911
|
+
var ua = (typeof navigator !== 'undefined' && (navigator.platform || navigator.userAgent)) || '';
|
|
912
|
+
return /Mac|iPhone|iPad/.test(ua);
|
|
913
|
+
} catch (err) {
|
|
914
|
+
return false;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// ======================================================================
|
|
919
|
+
// 七、快捷键与右键
|
|
920
|
+
// ======================================================================
|
|
921
|
+
|
|
922
|
+
function isEditable(target) {
|
|
923
|
+
if (!target || !target.tagName) return false;
|
|
924
|
+
var tag = String(target.tagName).toLowerCase();
|
|
925
|
+
if (tag === 'input' || tag === 'textarea' || tag === 'select') return true;
|
|
926
|
+
return !!target.isContentEditable;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/** 当前打开的对话 = 侧边栏里 aria-selected="true" 的那一行。 */
|
|
930
|
+
function currentSessionId() {
|
|
931
|
+
if (typeof document === 'undefined' || !document.querySelector) return null;
|
|
932
|
+
var row = document.querySelector('[data-row-key^="' + ROW_PREFIX + '"][aria-selected="true"]');
|
|
933
|
+
return sessionIdOfRow(row);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
function onKeydown(event) {
|
|
937
|
+
if (!event || event.defaultPrevented) return;
|
|
938
|
+
// 输入法组字期间不抢键(Windows 中文输入法下 keydown 的 key 会是 "Process"、keyCode 229,
|
|
939
|
+
// 与 DSH 自己的 observeComposition 守卫保持一致)
|
|
940
|
+
if (event.isComposing || event.keyCode === 229) return;
|
|
941
|
+
if (String(event.key || '').toLowerCase() !== HOTKEY.key) return;
|
|
942
|
+
if (HOTKEY.mod && !(event.metaKey || event.ctrlKey)) return;
|
|
943
|
+
if (HOTKEY.shift && !event.shiftKey) return;
|
|
944
|
+
if (isEditable(event.target)) return;
|
|
945
|
+
if (event.preventDefault) event.preventDefault();
|
|
946
|
+
var id = currentSessionId();
|
|
947
|
+
if (id) openPanel(id);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
function onContextMenu(event) {
|
|
951
|
+
var target = event && event.target;
|
|
952
|
+
var row = target && target.closest ? target.closest('[data-row-key^="' + ROW_PREFIX + '"]') : null;
|
|
953
|
+
var id = sessionIdOfRow(row);
|
|
954
|
+
if (!id) return;
|
|
955
|
+
if (event.preventDefault) event.preventDefault();
|
|
956
|
+
openPanel(id);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// ======================================================================
|
|
960
|
+
// 八、装载 / 卸载
|
|
961
|
+
// ======================================================================
|
|
962
|
+
|
|
963
|
+
function injectStyle() {
|
|
964
|
+
if (document.querySelector('style[data-plugin-css="' + STYLE_TAG_ID + '"]')) return null;
|
|
965
|
+
var tag = document.createElement('style');
|
|
966
|
+
tag.dataset.plugin = 'dsh-sidebar-marks';
|
|
967
|
+
tag.dataset.pluginCss = STYLE_TAG_ID;
|
|
968
|
+
tag.textContent = CSS;
|
|
969
|
+
document.head.appendChild(tag);
|
|
970
|
+
return tag;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
var scanTimer = null;
|
|
974
|
+
var observer = null;
|
|
975
|
+
var lang = null;
|
|
976
|
+
|
|
977
|
+
function scheduleScan() {
|
|
978
|
+
if (scanTimer !== null) return;
|
|
979
|
+
scanTimer = setTimeout(function () {
|
|
980
|
+
scanTimer = null;
|
|
981
|
+
annotateRows();
|
|
982
|
+
}, 80);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
function startObserver() {
|
|
986
|
+
if (typeof MutationObserver === 'undefined') return null;
|
|
987
|
+
var target = document.body || document.documentElement;
|
|
988
|
+
if (!target) return null;
|
|
989
|
+
observer = new MutationObserver(function (records) {
|
|
990
|
+
var langChanged = false;
|
|
991
|
+
for (var i = 0; i < records.length; i += 1) {
|
|
992
|
+
if (records[i].type === 'attributes' && records[i].attributeName === 'lang') langChanged = true;
|
|
993
|
+
}
|
|
994
|
+
// 界面语言变了:菜单项与面板文案跟着换(locale 服务会改写 <html lang>)
|
|
995
|
+
if (langChanged && lang !== currentLang()) {
|
|
996
|
+
lang = currentLang();
|
|
997
|
+
if (panel) renderPanel();
|
|
998
|
+
bump();
|
|
999
|
+
}
|
|
1000
|
+
scheduleScan();
|
|
1001
|
+
});
|
|
1002
|
+
observer.observe(target, { childList: true, subtree: true });
|
|
1003
|
+
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['lang'] });
|
|
1004
|
+
return observer;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
function onStorage(event) {
|
|
1008
|
+
if (!event || event.key !== STORAGE_KEY) return;
|
|
1009
|
+
loadMarks();
|
|
1010
|
+
annotateRows();
|
|
1011
|
+
bump();
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
function install() {
|
|
1015
|
+
lang = currentLang();
|
|
1016
|
+
injectStyle();
|
|
1017
|
+
loadMarks();
|
|
1018
|
+
annotateRows();
|
|
1019
|
+
startObserver();
|
|
1020
|
+
document.addEventListener('keydown', onKeydown);
|
|
1021
|
+
document.addEventListener('contextmenu', onContextMenu);
|
|
1022
|
+
if (typeof window.addEventListener === 'function') window.addEventListener('storage', onStorage);
|
|
1023
|
+
// 首帧之后再补扫几次:React 挂载 / 会话切换后行节点会被重建。
|
|
1024
|
+
[120, 600, 2000].forEach(function (delay) {
|
|
1025
|
+
var t = setTimeout(function () { annotateRows(); }, delay);
|
|
1026
|
+
deferred.push(function () { clearTimeout(t); });
|
|
1027
|
+
});
|
|
1028
|
+
window[GLOBAL_KEY] = {
|
|
1029
|
+
get version() { return version; },
|
|
1030
|
+
get marks() { return JSON.parse(JSON.stringify(marks)); },
|
|
1031
|
+
set: setMark,
|
|
1032
|
+
clear: clearMark,
|
|
1033
|
+
rescan: annotateRows,
|
|
1034
|
+
open: openPanel,
|
|
1035
|
+
close: closePanel,
|
|
1036
|
+
current: currentSessionId,
|
|
1037
|
+
storageKey: STORAGE_KEY,
|
|
1038
|
+
colors: Object.keys(COLORS),
|
|
1039
|
+
hotkey: isMac() ? '⌘⇧M' : 'Ctrl+Shift+M'
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
function dispose() {
|
|
1044
|
+
deferred.forEach(function (fn) { fn(); });
|
|
1045
|
+
deferred = [];
|
|
1046
|
+
if (scanTimer !== null) { clearTimeout(scanTimer); scanTimer = null; }
|
|
1047
|
+
if (saveTimer !== null) { clearTimeout(saveTimer); saveTimer = null; }
|
|
1048
|
+
if (observer) { observer.disconnect(); observer = null; }
|
|
1049
|
+
closePanel();
|
|
1050
|
+
document.removeEventListener('keydown', onKeydown);
|
|
1051
|
+
document.removeEventListener('contextmenu', onContextMenu);
|
|
1052
|
+
if (typeof window.removeEventListener === 'function') window.removeEventListener('storage', onStorage);
|
|
1053
|
+
var tag = document.querySelector('style[data-plugin-css="' + STYLE_TAG_ID + '"]');
|
|
1054
|
+
if (tag && tag.remove) tag.remove();
|
|
1055
|
+
if (window[GLOBAL_KEY]) delete window[GLOBAL_KEY];
|
|
1056
|
+
listeners.clear();
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/**
|
|
1060
|
+
* 装载浏览器侧行为:注入样式、读标记、标注行、注册插槽、装快捷键。
|
|
1061
|
+
* @param ctx 客户端 root context(`inject: ['slots']` 保证 ctx.slots 存在)。
|
|
1062
|
+
*/
|
|
1063
|
+
function apply(ctx) {
|
|
1064
|
+
install();
|
|
1065
|
+
ctx.effect(function () { return dispose; }, 'sidebar-marks: styles, row annotations, panel, hotkey');
|
|
1066
|
+
|
|
1067
|
+
ctx.slots.inject('sidebar.session.row.leading', function () {
|
|
1068
|
+
return ctx.slots.register(
|
|
1069
|
+
{ name: 'sidebar.session.row.leading', id: 'dsh-sidebar-marks:dot', order: 100 },
|
|
1070
|
+
LeadingDot
|
|
1071
|
+
);
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
ctx.slots.inject('sidebar.workspaces.session.menu.item', function () {
|
|
1075
|
+
return ctx.slots.register(
|
|
1076
|
+
{ name: 'sidebar.workspaces.session.menu.item', id: 'dsh-sidebar-marks:mark-item', order: 350 },
|
|
1077
|
+
MarkMenuItem
|
|
1078
|
+
);
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
exports.apply = apply;
|
|
1083
|
+
exports.inject = ['slots'];
|
|
1084
|
+
|
|
1085
|
+
// 测试与调试用的内部视图(运行时不会用到)。
|
|
1086
|
+
exports.__internals = {
|
|
1087
|
+
STORAGE_KEY: STORAGE_KEY,
|
|
1088
|
+
STYLE_TAG_ID: STYLE_TAG_ID,
|
|
1089
|
+
GLOBAL_KEY: GLOBAL_KEY,
|
|
1090
|
+
HOTKEY: HOTKEY,
|
|
1091
|
+
CSS: CSS,
|
|
1092
|
+
COLORS: COLORS,
|
|
1093
|
+
STRINGS: STRINGS,
|
|
1094
|
+
t: t,
|
|
1095
|
+
currentLang: currentLang,
|
|
1096
|
+
colorName: colorName,
|
|
1097
|
+
FILLS: FILLS,
|
|
1098
|
+
DARK_BOOST: DARK_BOOST,
|
|
1099
|
+
isHexColor: isHexColor,
|
|
1100
|
+
normalizeHex: normalizeHex,
|
|
1101
|
+
colorFor: colorFor,
|
|
1102
|
+
colorLabel: colorLabel,
|
|
1103
|
+
hexOf: hexOf,
|
|
1104
|
+
darkVariant: darkVariant,
|
|
1105
|
+
mixHex: mixHex,
|
|
1106
|
+
applyColorVars: applyColorVars,
|
|
1107
|
+
syncPanelLive: syncPanelLive,
|
|
1108
|
+
DOTS: DOTS,
|
|
1109
|
+
SIZES: SIZES,
|
|
1110
|
+
DEFAULT_MARK: DEFAULT_MARK,
|
|
1111
|
+
normalizeMark: normalizeMark,
|
|
1112
|
+
markOf: markOf,
|
|
1113
|
+
markOrDefault: markOrDefault,
|
|
1114
|
+
setMark: setMark,
|
|
1115
|
+
clearMark: clearMark,
|
|
1116
|
+
rowAttributes: rowAttributes,
|
|
1117
|
+
sessionIdOfRow: sessionIdOfRow,
|
|
1118
|
+
writeRow: writeRow,
|
|
1119
|
+
annotateRows: annotateRows,
|
|
1120
|
+
leftDotVisible: leftDotVisible,
|
|
1121
|
+
menuLabel: menuLabel,
|
|
1122
|
+
serialize: serialize,
|
|
1123
|
+
parse: parse,
|
|
1124
|
+
loadMarks: loadMarks,
|
|
1125
|
+
saveMarks: saveMarks,
|
|
1126
|
+
getMarks: function () { return marks; },
|
|
1127
|
+
setMarks: function (next) { marks = next; },
|
|
1128
|
+
openPanel: openPanel,
|
|
1129
|
+
closePanel: closePanel,
|
|
1130
|
+
renderPanel: renderPanel,
|
|
1131
|
+
getPanel: function () { return panel; },
|
|
1132
|
+
currentSessionId: currentSessionId,
|
|
1133
|
+
isEditable: isEditable,
|
|
1134
|
+
onKeydown: onKeydown,
|
|
1135
|
+
onContextMenu: onContextMenu,
|
|
1136
|
+
install: install,
|
|
1137
|
+
dispose: dispose,
|
|
1138
|
+
getVersion: function () { return version; }
|
|
1139
|
+
};
|
|
1140
|
+
|
|
1141
|
+
return module.exports;
|
|
1142
|
+
}
|
|
1143
|
+
});
|