@huanlin/dsh-plugin-input-history 0.2.0 → 0.3.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/README.md +115 -115
- package/lib/client.js +641 -641
- package/lib/types/client/HistoryDock.d.ts +43 -43
- package/lib/types/client/HistoryDock.js +187 -187
- package/lib/types/client/dom.d.ts +81 -81
- package/lib/types/client/dom.js +142 -142
- package/lib/types/client/index.d.ts +34 -34
- package/lib/types/client/index.js +62 -62
- package/lib/types/index.d.ts +24 -24
- package/lib/types/index.js +25 -25
- package/package.json +6 -6
package/lib/types/client/dom.js
CHANGED
|
@@ -1,142 +1,142 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DOM helpers for the Lexical composer surface.
|
|
3
|
-
*
|
|
4
|
-
* The DSH composer's text surface is a Lexical-bound contenteditable div,
|
|
5
|
-
* not a textarea: plugins cannot obtain a React ref or a slot-currency
|
|
6
|
-
* handle to it, and writing text goes through `inputActions.setDraft` (the
|
|
7
|
-
* public machine action), not the DOM. What remains DOM-bound is geometry
|
|
8
|
-
* and focus: locating the editable the keystroke targeted, detecting an
|
|
9
|
-
* open trigger menu, and deciding whether the collapsed caret sits on the
|
|
10
|
-
* first/last visual line of a multi-line draft.
|
|
11
|
-
*
|
|
12
|
-
* All markers queried here are internal to `@deepseek-ai/dsh-client-ui-conversation`
|
|
13
|
-
* (`InputBar.tsx` / `ComposerContentEditable.tsx`) or
|
|
14
|
-
* `@deepseek-ai/dsh-client-ui-input-trigger` (`MenuView.tsx`); they are
|
|
15
|
-
* stable but undocumented, and the locators below are the single point to
|
|
16
|
-
* update if upstream changes them.
|
|
17
|
-
*
|
|
18
|
-
* @module @huanlin/dsh-plugin-input-history/client/dom
|
|
19
|
-
*/
|
|
20
|
-
/**
|
|
21
|
-
* Pure decision over caret geometry: where a caret resting at `caretTop`
|
|
22
|
-
* sits relative to the box whose visual line tops are `lineTops` (ascending,
|
|
23
|
-
* one entry per visual line, viewport coordinates).
|
|
24
|
-
*
|
|
25
|
-
* @param caretTop - viewport `top` of the collapsed caret's box.
|
|
26
|
-
* @param lineTops - viewport `top` of each visual line, ascending.
|
|
27
|
-
* @param tolerance - px slop absorbing subpixel rounding between the caret
|
|
28
|
-
* rect and its line's rect.
|
|
29
|
-
* @returns the boundary flags; an empty `lineTops` (empty editable) is
|
|
30
|
-
* treated as a single virtual line, so both flags are true.
|
|
31
|
-
*/
|
|
32
|
-
export function boundaryFromLineTops(caretTop, lineTops, tolerance) {
|
|
33
|
-
if (lineTops.length === 0)
|
|
34
|
-
return { atFirstLine: true, atLastLine: true };
|
|
35
|
-
return {
|
|
36
|
-
atFirstLine: caretTop <= lineTops[0] + tolerance,
|
|
37
|
-
atLastLine: caretTop >= lineTops[lineTops.length - 1] - tolerance,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Locate the DSH composer editable the event targeted.
|
|
42
|
-
*
|
|
43
|
-
* Walks from the event target up to the closest `[data-composer-card]`
|
|
44
|
-
* ancestor, queries the `[data-composer-input]` contenteditable inside it,
|
|
45
|
-
* and confirms the target sits inside that editable (keystrokes on the
|
|
46
|
-
* card's buttons and chrome do not navigate history). Returns `null` when
|
|
47
|
-
* the target is not inside the composer editable.
|
|
48
|
-
*
|
|
49
|
-
* @param from - the event target (or any node inside the composer editable).
|
|
50
|
-
* @returns the editable element, or `null` when not found.
|
|
51
|
-
*/
|
|
52
|
-
export function findComposerEditable(from) {
|
|
53
|
-
if (typeof document === 'undefined')
|
|
54
|
-
return null;
|
|
55
|
-
if (from === null || !(from instanceof Element))
|
|
56
|
-
return null;
|
|
57
|
-
const card = from.closest('[data-composer-card]');
|
|
58
|
-
if (card === null)
|
|
59
|
-
return null;
|
|
60
|
-
const editable = card.querySelector('[data-composer-input]');
|
|
61
|
-
if (editable === null)
|
|
62
|
-
return null;
|
|
63
|
-
return editable.contains(from) ? editable : null;
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Detect an open trigger (slash-command / @-mention) menu inside the
|
|
67
|
-
* composer card that owns `editable`.
|
|
68
|
-
*
|
|
69
|
-
* While the menu is open, ArrowUp/ArrowDown move the highlighted row and
|
|
70
|
-
* must not recall history. The menu renders inside the same
|
|
71
|
-
* `[data-composer-card]` as the editable and carries the stable
|
|
72
|
-
* `data-trigger-menu` marker.
|
|
73
|
-
*
|
|
74
|
-
* @param editable - the composer editable element.
|
|
75
|
-
* @returns the menu element, or `null` when no menu is open.
|
|
76
|
-
*/
|
|
77
|
-
export function findTriggerMenu(editable) {
|
|
78
|
-
const card = editable.closest('[data-composer-card]');
|
|
79
|
-
return card === null ? null : card.querySelector('[data-trigger-menu]');
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Decide the collapsed caret's line boundary inside the composer editable.
|
|
83
|
-
*
|
|
84
|
-
* Compares the caret's viewport box against the editable content's visual
|
|
85
|
-
* line boxes (`Range.getClientRects()` yields one rect per line fragment;
|
|
86
|
-
* fragments of the same visual line share a top within subpixel slop, so
|
|
87
|
-
* tops are deduped with a 2px threshold). A non-collapsed selection and a
|
|
88
|
-
* geometry-less environment (headless/jsdom) both return `null`, which the
|
|
89
|
-
* caller must treat as "do not navigate".
|
|
90
|
-
*
|
|
91
|
-
* @param editable - the composer editable element.
|
|
92
|
-
* @param tolerance - px slop between the caret rect and its line rect
|
|
93
|
-
* (defaults to 4px).
|
|
94
|
-
* @returns the boundary flags, or `null` when they cannot be determined.
|
|
95
|
-
*/
|
|
96
|
-
export function caretLineBoundary(editable, tolerance = 4) {
|
|
97
|
-
const selection = window.getSelection();
|
|
98
|
-
if (selection === null || selection.rangeCount === 0)
|
|
99
|
-
return null;
|
|
100
|
-
if (!selection.isCollapsed)
|
|
101
|
-
return null;
|
|
102
|
-
const caretTop = caretTopOf(selection);
|
|
103
|
-
if (caretTop === null)
|
|
104
|
-
return null;
|
|
105
|
-
const lineTops = contentLineTops(editable);
|
|
106
|
-
if (lineTops === null)
|
|
107
|
-
return null;
|
|
108
|
-
return boundaryFromLineTops(caretTop, lineTops, tolerance);
|
|
109
|
-
}
|
|
110
|
-
/** Viewport `top` of the collapsed caret's box, or `null` when unmeasurable. */
|
|
111
|
-
function caretTopOf(selection) {
|
|
112
|
-
const rects = selection.getRangeAt(0).getClientRects();
|
|
113
|
-
for (let i = 0; i < rects.length; i++) {
|
|
114
|
-
const rect = rects[i];
|
|
115
|
-
if (rect.height === 0 && rect.width === 0)
|
|
116
|
-
continue;
|
|
117
|
-
return rect.top;
|
|
118
|
-
}
|
|
119
|
-
// Some engines report a zero-box collapsed caret; the anchor's element
|
|
120
|
-
// box is the line the caret sits on (the same ruler InputBar's reveal uses).
|
|
121
|
-
const anchor = selection.anchorNode;
|
|
122
|
-
const el = anchor instanceof HTMLElement ? anchor : anchor?.parentElement;
|
|
123
|
-
return el === undefined || el === null ? null : el.getBoundingClientRect().top;
|
|
124
|
-
}
|
|
125
|
-
/** Ascending, deduped tops of the editable content's visual lines; `null` without geometry. Empty for an empty editable. */
|
|
126
|
-
function contentLineTops(editable) {
|
|
127
|
-
const range = document.createRange();
|
|
128
|
-
range.selectNodeContents(editable);
|
|
129
|
-
const rects = range.getClientRects();
|
|
130
|
-
const tops = [];
|
|
131
|
-
for (let i = 0; i < rects.length; i++) {
|
|
132
|
-
const rect = rects[i];
|
|
133
|
-
if (rect.height === 0 && rect.width === 0)
|
|
134
|
-
continue;
|
|
135
|
-
const top = rect.top;
|
|
136
|
-
// Rects come in document order; fragments of one visual line differ by
|
|
137
|
-
// subpixel amounts, real lines by a full line height.
|
|
138
|
-
if (tops.length === 0 || Math.abs(top - tops[tops.length - 1]) > 2)
|
|
139
|
-
tops.push(top);
|
|
140
|
-
}
|
|
141
|
-
return tops;
|
|
142
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* DOM helpers for the Lexical composer surface.
|
|
3
|
+
*
|
|
4
|
+
* The DSH composer's text surface is a Lexical-bound contenteditable div,
|
|
5
|
+
* not a textarea: plugins cannot obtain a React ref or a slot-currency
|
|
6
|
+
* handle to it, and writing text goes through `inputActions.setDraft` (the
|
|
7
|
+
* public machine action), not the DOM. What remains DOM-bound is geometry
|
|
8
|
+
* and focus: locating the editable the keystroke targeted, detecting an
|
|
9
|
+
* open trigger menu, and deciding whether the collapsed caret sits on the
|
|
10
|
+
* first/last visual line of a multi-line draft.
|
|
11
|
+
*
|
|
12
|
+
* All markers queried here are internal to `@deepseek-ai/dsh-client-ui-conversation`
|
|
13
|
+
* (`InputBar.tsx` / `ComposerContentEditable.tsx`) or
|
|
14
|
+
* `@deepseek-ai/dsh-client-ui-input-trigger` (`MenuView.tsx`); they are
|
|
15
|
+
* stable but undocumented, and the locators below are the single point to
|
|
16
|
+
* update if upstream changes them.
|
|
17
|
+
*
|
|
18
|
+
* @module @huanlin/dsh-plugin-input-history/client/dom
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Pure decision over caret geometry: where a caret resting at `caretTop`
|
|
22
|
+
* sits relative to the box whose visual line tops are `lineTops` (ascending,
|
|
23
|
+
* one entry per visual line, viewport coordinates).
|
|
24
|
+
*
|
|
25
|
+
* @param caretTop - viewport `top` of the collapsed caret's box.
|
|
26
|
+
* @param lineTops - viewport `top` of each visual line, ascending.
|
|
27
|
+
* @param tolerance - px slop absorbing subpixel rounding between the caret
|
|
28
|
+
* rect and its line's rect.
|
|
29
|
+
* @returns the boundary flags; an empty `lineTops` (empty editable) is
|
|
30
|
+
* treated as a single virtual line, so both flags are true.
|
|
31
|
+
*/
|
|
32
|
+
export function boundaryFromLineTops(caretTop, lineTops, tolerance) {
|
|
33
|
+
if (lineTops.length === 0)
|
|
34
|
+
return { atFirstLine: true, atLastLine: true };
|
|
35
|
+
return {
|
|
36
|
+
atFirstLine: caretTop <= lineTops[0] + tolerance,
|
|
37
|
+
atLastLine: caretTop >= lineTops[lineTops.length - 1] - tolerance,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Locate the DSH composer editable the event targeted.
|
|
42
|
+
*
|
|
43
|
+
* Walks from the event target up to the closest `[data-composer-card]`
|
|
44
|
+
* ancestor, queries the `[data-composer-input]` contenteditable inside it,
|
|
45
|
+
* and confirms the target sits inside that editable (keystrokes on the
|
|
46
|
+
* card's buttons and chrome do not navigate history). Returns `null` when
|
|
47
|
+
* the target is not inside the composer editable.
|
|
48
|
+
*
|
|
49
|
+
* @param from - the event target (or any node inside the composer editable).
|
|
50
|
+
* @returns the editable element, or `null` when not found.
|
|
51
|
+
*/
|
|
52
|
+
export function findComposerEditable(from) {
|
|
53
|
+
if (typeof document === 'undefined')
|
|
54
|
+
return null;
|
|
55
|
+
if (from === null || !(from instanceof Element))
|
|
56
|
+
return null;
|
|
57
|
+
const card = from.closest('[data-composer-card]');
|
|
58
|
+
if (card === null)
|
|
59
|
+
return null;
|
|
60
|
+
const editable = card.querySelector('[data-composer-input]');
|
|
61
|
+
if (editable === null)
|
|
62
|
+
return null;
|
|
63
|
+
return editable.contains(from) ? editable : null;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Detect an open trigger (slash-command / @-mention) menu inside the
|
|
67
|
+
* composer card that owns `editable`.
|
|
68
|
+
*
|
|
69
|
+
* While the menu is open, ArrowUp/ArrowDown move the highlighted row and
|
|
70
|
+
* must not recall history. The menu renders inside the same
|
|
71
|
+
* `[data-composer-card]` as the editable and carries the stable
|
|
72
|
+
* `data-trigger-menu` marker.
|
|
73
|
+
*
|
|
74
|
+
* @param editable - the composer editable element.
|
|
75
|
+
* @returns the menu element, or `null` when no menu is open.
|
|
76
|
+
*/
|
|
77
|
+
export function findTriggerMenu(editable) {
|
|
78
|
+
const card = editable.closest('[data-composer-card]');
|
|
79
|
+
return card === null ? null : card.querySelector('[data-trigger-menu]');
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Decide the collapsed caret's line boundary inside the composer editable.
|
|
83
|
+
*
|
|
84
|
+
* Compares the caret's viewport box against the editable content's visual
|
|
85
|
+
* line boxes (`Range.getClientRects()` yields one rect per line fragment;
|
|
86
|
+
* fragments of the same visual line share a top within subpixel slop, so
|
|
87
|
+
* tops are deduped with a 2px threshold). A non-collapsed selection and a
|
|
88
|
+
* geometry-less environment (headless/jsdom) both return `null`, which the
|
|
89
|
+
* caller must treat as "do not navigate".
|
|
90
|
+
*
|
|
91
|
+
* @param editable - the composer editable element.
|
|
92
|
+
* @param tolerance - px slop between the caret rect and its line rect
|
|
93
|
+
* (defaults to 4px).
|
|
94
|
+
* @returns the boundary flags, or `null` when they cannot be determined.
|
|
95
|
+
*/
|
|
96
|
+
export function caretLineBoundary(editable, tolerance = 4) {
|
|
97
|
+
const selection = window.getSelection();
|
|
98
|
+
if (selection === null || selection.rangeCount === 0)
|
|
99
|
+
return null;
|
|
100
|
+
if (!selection.isCollapsed)
|
|
101
|
+
return null;
|
|
102
|
+
const caretTop = caretTopOf(selection);
|
|
103
|
+
if (caretTop === null)
|
|
104
|
+
return null;
|
|
105
|
+
const lineTops = contentLineTops(editable);
|
|
106
|
+
if (lineTops === null)
|
|
107
|
+
return null;
|
|
108
|
+
return boundaryFromLineTops(caretTop, lineTops, tolerance);
|
|
109
|
+
}
|
|
110
|
+
/** Viewport `top` of the collapsed caret's box, or `null` when unmeasurable. */
|
|
111
|
+
function caretTopOf(selection) {
|
|
112
|
+
const rects = selection.getRangeAt(0).getClientRects();
|
|
113
|
+
for (let i = 0; i < rects.length; i++) {
|
|
114
|
+
const rect = rects[i];
|
|
115
|
+
if (rect.height === 0 && rect.width === 0)
|
|
116
|
+
continue;
|
|
117
|
+
return rect.top;
|
|
118
|
+
}
|
|
119
|
+
// Some engines report a zero-box collapsed caret; the anchor's element
|
|
120
|
+
// box is the line the caret sits on (the same ruler InputBar's reveal uses).
|
|
121
|
+
const anchor = selection.anchorNode;
|
|
122
|
+
const el = anchor instanceof HTMLElement ? anchor : anchor?.parentElement;
|
|
123
|
+
return el === undefined || el === null ? null : el.getBoundingClientRect().top;
|
|
124
|
+
}
|
|
125
|
+
/** Ascending, deduped tops of the editable content's visual lines; `null` without geometry. Empty for an empty editable. */
|
|
126
|
+
function contentLineTops(editable) {
|
|
127
|
+
const range = document.createRange();
|
|
128
|
+
range.selectNodeContents(editable);
|
|
129
|
+
const rects = range.getClientRects();
|
|
130
|
+
const tops = [];
|
|
131
|
+
for (let i = 0; i < rects.length; i++) {
|
|
132
|
+
const rect = rects[i];
|
|
133
|
+
if (rect.height === 0 && rect.width === 0)
|
|
134
|
+
continue;
|
|
135
|
+
const top = rect.top;
|
|
136
|
+
// Rects come in document order; fragments of one visual line differ by
|
|
137
|
+
// subpixel amounts, real lines by a full line height.
|
|
138
|
+
if (tops.length === 0 || Math.abs(top - tops[tops.length - 1]) > 2)
|
|
139
|
+
tops.push(top);
|
|
140
|
+
}
|
|
141
|
+
return tops;
|
|
142
|
+
}
|
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dsh-plugin-input-history — browser half.
|
|
3
|
-
*
|
|
4
|
-
* One registration: the `conversation.composer.dock` list slot (id
|
|
5
|
-
* `dsh-plugin-input-history`, order 100) mounts the invisible dock entry
|
|
6
|
-
* that owns both plugin behaviors — prompt-history collection from the
|
|
7
|
-
* Chat target's user/steering nodes, and the capture-phase document
|
|
8
|
-
* keydown listener that navigates the composer draft through
|
|
9
|
-
* `inputActions.setDraft`. See [HistoryDock.tsx](./HistoryDock.tsx) for
|
|
10
|
-
* the data flow; the dock is session-scoped, so in hero/blank mode the
|
|
11
|
-
* plugin is dormant (no input machine exists there to drive).
|
|
12
|
-
*
|
|
13
|
-
* History is collected from `user` and `steering` chat nodes of any active
|
|
14
|
-
* session, persisted to `localStorage` (FIFO, 500 entries), and shared
|
|
15
|
-
* across all sessions in the same browser profile.
|
|
16
|
-
*
|
|
17
|
-
* @module @huanlin/dsh-plugin-input-history/client
|
|
18
|
-
*/
|
|
19
|
-
import type { Context } from '@deepseek-ai/cordis';
|
|
20
|
-
import { type InputHistoryKey } from './locales.ts';
|
|
21
|
-
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
22
|
-
interface LocaleNamespaceMap {
|
|
23
|
-
/** The dock's aria-label + future settings row copy. */
|
|
24
|
-
'dsh-plugin-input-history': InputHistoryKey;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
/** Required services: slots + locale. */
|
|
28
|
-
export declare const inject: string[];
|
|
29
|
-
/**
|
|
30
|
-
* Client plugin body: register the dock + locale dictionaries.
|
|
31
|
-
*
|
|
32
|
-
* @param ctx - client root context.
|
|
33
|
-
*/
|
|
34
|
-
export declare function apply(ctx: Context): void;
|
|
1
|
+
/**
|
|
2
|
+
* dsh-plugin-input-history — browser half.
|
|
3
|
+
*
|
|
4
|
+
* One registration: the `conversation.composer.dock` list slot (id
|
|
5
|
+
* `dsh-plugin-input-history`, order 100) mounts the invisible dock entry
|
|
6
|
+
* that owns both plugin behaviors — prompt-history collection from the
|
|
7
|
+
* Chat target's user/steering nodes, and the capture-phase document
|
|
8
|
+
* keydown listener that navigates the composer draft through
|
|
9
|
+
* `inputActions.setDraft`. See [HistoryDock.tsx](./HistoryDock.tsx) for
|
|
10
|
+
* the data flow; the dock is session-scoped, so in hero/blank mode the
|
|
11
|
+
* plugin is dormant (no input machine exists there to drive).
|
|
12
|
+
*
|
|
13
|
+
* History is collected from `user` and `steering` chat nodes of any active
|
|
14
|
+
* session, persisted to `localStorage` (FIFO, 500 entries), and shared
|
|
15
|
+
* across all sessions in the same browser profile.
|
|
16
|
+
*
|
|
17
|
+
* @module @huanlin/dsh-plugin-input-history/client
|
|
18
|
+
*/
|
|
19
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
20
|
+
import { type InputHistoryKey } from './locales.ts';
|
|
21
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
22
|
+
interface LocaleNamespaceMap {
|
|
23
|
+
/** The dock's aria-label + future settings row copy. */
|
|
24
|
+
'dsh-plugin-input-history': InputHistoryKey;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** Required services: slots + locale. */
|
|
28
|
+
export declare const inject: string[];
|
|
29
|
+
/**
|
|
30
|
+
* Client plugin body: register the dock + locale dictionaries.
|
|
31
|
+
*
|
|
32
|
+
* @param ctx - client root context.
|
|
33
|
+
*/
|
|
34
|
+
export declare function apply(ctx: Context): void;
|
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dsh-plugin-input-history — browser half.
|
|
3
|
-
*
|
|
4
|
-
* One registration: the `conversation.composer.dock` list slot (id
|
|
5
|
-
* `dsh-plugin-input-history`, order 100) mounts the invisible dock entry
|
|
6
|
-
* that owns both plugin behaviors — prompt-history collection from the
|
|
7
|
-
* Chat target's user/steering nodes, and the capture-phase document
|
|
8
|
-
* keydown listener that navigates the composer draft through
|
|
9
|
-
* `inputActions.setDraft`. See [HistoryDock.tsx](./HistoryDock.tsx) for
|
|
10
|
-
* the data flow; the dock is session-scoped, so in hero/blank mode the
|
|
11
|
-
* plugin is dormant (no input machine exists there to drive).
|
|
12
|
-
*
|
|
13
|
-
* History is collected from `user` and `steering` chat nodes of any active
|
|
14
|
-
* session, persisted to `localStorage` (FIFO, 500 entries), and shared
|
|
15
|
-
* across all sessions in the same browser profile.
|
|
16
|
-
*
|
|
17
|
-
* @module @huanlin/dsh-plugin-input-history/client
|
|
18
|
-
*/
|
|
19
|
-
import { HistoryDock } from "./HistoryDock.js";
|
|
20
|
-
import { en, NS, zh } from "./locales.js";
|
|
21
|
-
import { dicts } from "./dictionaries.js";
|
|
22
|
-
/** Required services: slots + locale. */
|
|
23
|
-
export const inject = ['slots', 'locale'];
|
|
24
|
-
/**
|
|
25
|
-
* Client plugin body: register the dock + locale dictionaries.
|
|
26
|
-
*
|
|
27
|
-
* @param ctx - client root context.
|
|
28
|
-
*/
|
|
29
|
-
export function apply(ctx) {
|
|
30
|
-
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-plugin-input-history: dictionaries');
|
|
31
|
-
// better-locale override: register the 19-language dicts so a selected
|
|
32
|
-
// override language (with DSH on 'en') replaces the plugin's copy. The
|
|
33
|
-
// service is optional — no better-locale, no dicts.
|
|
34
|
-
// Activation-order-safe: re-check ctx.get('betterLocale') on every locale
|
|
35
|
-
// revision bump (better-locale bumps on activation + override switch).
|
|
36
|
-
ctx.effect(() => {
|
|
37
|
-
let dispose;
|
|
38
|
-
const sync = () => {
|
|
39
|
-
dispose?.();
|
|
40
|
-
dispose = undefined;
|
|
41
|
-
const store = ctx.get('betterLocale');
|
|
42
|
-
if (store !== undefined) {
|
|
43
|
-
dispose = store.register(NS, dicts);
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
sync();
|
|
47
|
-
const unsubscribe = ctx.locale.subscribe(sync);
|
|
48
|
-
return () => {
|
|
49
|
-
unsubscribe();
|
|
50
|
-
dispose?.();
|
|
51
|
-
};
|
|
52
|
-
}, 'dsh-plugin-input-history: better-locale override dicts');
|
|
53
|
-
// The dock collects history from the Chat target's nodes and attaches the
|
|
54
|
-
// capture-phase keydown listener. It is session-scoped; in hero/blank mode
|
|
55
|
-
// it is unmounted and the plugin is dormant (no input machine exists there).
|
|
56
|
-
ctx.slots.inject('conversation.composer.dock', () => ctx.slots.register({
|
|
57
|
-
name: 'conversation.composer.dock',
|
|
58
|
-
id: 'dsh-plugin-input-history',
|
|
59
|
-
order: 100,
|
|
60
|
-
locale: NS,
|
|
61
|
-
}, HistoryDock));
|
|
62
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* dsh-plugin-input-history — browser half.
|
|
3
|
+
*
|
|
4
|
+
* One registration: the `conversation.composer.dock` list slot (id
|
|
5
|
+
* `dsh-plugin-input-history`, order 100) mounts the invisible dock entry
|
|
6
|
+
* that owns both plugin behaviors — prompt-history collection from the
|
|
7
|
+
* Chat target's user/steering nodes, and the capture-phase document
|
|
8
|
+
* keydown listener that navigates the composer draft through
|
|
9
|
+
* `inputActions.setDraft`. See [HistoryDock.tsx](./HistoryDock.tsx) for
|
|
10
|
+
* the data flow; the dock is session-scoped, so in hero/blank mode the
|
|
11
|
+
* plugin is dormant (no input machine exists there to drive).
|
|
12
|
+
*
|
|
13
|
+
* History is collected from `user` and `steering` chat nodes of any active
|
|
14
|
+
* session, persisted to `localStorage` (FIFO, 500 entries), and shared
|
|
15
|
+
* across all sessions in the same browser profile.
|
|
16
|
+
*
|
|
17
|
+
* @module @huanlin/dsh-plugin-input-history/client
|
|
18
|
+
*/
|
|
19
|
+
import { HistoryDock } from "./HistoryDock.js";
|
|
20
|
+
import { en, NS, zh } from "./locales.js";
|
|
21
|
+
import { dicts } from "./dictionaries.js";
|
|
22
|
+
/** Required services: slots + locale. */
|
|
23
|
+
export const inject = ['slots', 'locale'];
|
|
24
|
+
/**
|
|
25
|
+
* Client plugin body: register the dock + locale dictionaries.
|
|
26
|
+
*
|
|
27
|
+
* @param ctx - client root context.
|
|
28
|
+
*/
|
|
29
|
+
export function apply(ctx) {
|
|
30
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-plugin-input-history: dictionaries');
|
|
31
|
+
// better-locale override: register the 19-language dicts so a selected
|
|
32
|
+
// override language (with DSH on 'en') replaces the plugin's copy. The
|
|
33
|
+
// service is optional — no better-locale, no dicts.
|
|
34
|
+
// Activation-order-safe: re-check ctx.get('betterLocale') on every locale
|
|
35
|
+
// revision bump (better-locale bumps on activation + override switch).
|
|
36
|
+
ctx.effect(() => {
|
|
37
|
+
let dispose;
|
|
38
|
+
const sync = () => {
|
|
39
|
+
dispose?.();
|
|
40
|
+
dispose = undefined;
|
|
41
|
+
const store = ctx.get('betterLocale');
|
|
42
|
+
if (store !== undefined) {
|
|
43
|
+
dispose = store.register(NS, dicts);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
sync();
|
|
47
|
+
const unsubscribe = ctx.locale.subscribe(sync);
|
|
48
|
+
return () => {
|
|
49
|
+
unsubscribe();
|
|
50
|
+
dispose?.();
|
|
51
|
+
};
|
|
52
|
+
}, 'dsh-plugin-input-history: better-locale override dicts');
|
|
53
|
+
// The dock collects history from the Chat target's nodes and attaches the
|
|
54
|
+
// capture-phase keydown listener. It is session-scoped; in hero/blank mode
|
|
55
|
+
// it is unmounted and the plugin is dormant (no input machine exists there).
|
|
56
|
+
ctx.slots.inject('conversation.composer.dock', () => ctx.slots.register({
|
|
57
|
+
name: 'conversation.composer.dock',
|
|
58
|
+
id: 'dsh-plugin-input-history',
|
|
59
|
+
order: 100,
|
|
60
|
+
locale: NS,
|
|
61
|
+
}, HistoryDock));
|
|
62
|
+
}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dsh-plugin-input-history — host plugin entry.
|
|
3
|
-
*
|
|
4
|
-
* Client-only plugin: the host half has no runtime work. The browser half
|
|
5
|
-
* (`./client`) registers an invisible entry in `conversation.composer.dock`
|
|
6
|
-
* that collects prompt history from the Chat target's user/steering nodes
|
|
7
|
-
* and implements terminal-style history navigation (ArrowUp/ArrowDown) over
|
|
8
|
-
* the composer's Lexical surface through `inputActions.setDraft`.
|
|
9
|
-
*
|
|
10
|
-
* History is collected from `user` and `steering` chat nodes of any active
|
|
11
|
-
* session, persisted to `localStorage` (FIFO, capacity 500), and shared
|
|
12
|
-
* across all sessions in the same browser profile.
|
|
13
|
-
*
|
|
14
|
-
* @module @huanlin/dsh-plugin-input-history
|
|
15
|
-
*/
|
|
16
|
-
import type { Context } from '@deepseek-ai/cordis';
|
|
17
|
-
export declare const name = "dsh-plugin-input-history";
|
|
18
|
-
export declare const inject: string[];
|
|
19
|
-
/**
|
|
20
|
-
* Host apply — no-op. The history navigation is a pure client-side UI
|
|
21
|
-
* contribution; no host-side resources are used.
|
|
22
|
-
* @param _ctx - host context (unused).
|
|
23
|
-
*/
|
|
24
|
-
export declare function apply(_ctx: Context): void;
|
|
1
|
+
/**
|
|
2
|
+
* dsh-plugin-input-history — host plugin entry.
|
|
3
|
+
*
|
|
4
|
+
* Client-only plugin: the host half has no runtime work. The browser half
|
|
5
|
+
* (`./client`) registers an invisible entry in `conversation.composer.dock`
|
|
6
|
+
* that collects prompt history from the Chat target's user/steering nodes
|
|
7
|
+
* and implements terminal-style history navigation (ArrowUp/ArrowDown) over
|
|
8
|
+
* the composer's Lexical surface through `inputActions.setDraft`.
|
|
9
|
+
*
|
|
10
|
+
* History is collected from `user` and `steering` chat nodes of any active
|
|
11
|
+
* session, persisted to `localStorage` (FIFO, capacity 500), and shared
|
|
12
|
+
* across all sessions in the same browser profile.
|
|
13
|
+
*
|
|
14
|
+
* @module @huanlin/dsh-plugin-input-history
|
|
15
|
+
*/
|
|
16
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
17
|
+
export declare const name = "dsh-plugin-input-history";
|
|
18
|
+
export declare const inject: string[];
|
|
19
|
+
/**
|
|
20
|
+
* Host apply — no-op. The history navigation is a pure client-side UI
|
|
21
|
+
* contribution; no host-side resources are used.
|
|
22
|
+
* @param _ctx - host context (unused).
|
|
23
|
+
*/
|
|
24
|
+
export declare function apply(_ctx: Context): void;
|
package/lib/types/index.js
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dsh-plugin-input-history — host plugin entry.
|
|
3
|
-
*
|
|
4
|
-
* Client-only plugin: the host half has no runtime work. The browser half
|
|
5
|
-
* (`./client`) registers an invisible entry in `conversation.composer.dock`
|
|
6
|
-
* that collects prompt history from the Chat target's user/steering nodes
|
|
7
|
-
* and implements terminal-style history navigation (ArrowUp/ArrowDown) over
|
|
8
|
-
* the composer's Lexical surface through `inputActions.setDraft`.
|
|
9
|
-
*
|
|
10
|
-
* History is collected from `user` and `steering` chat nodes of any active
|
|
11
|
-
* session, persisted to `localStorage` (FIFO, capacity 500), and shared
|
|
12
|
-
* across all sessions in the same browser profile.
|
|
13
|
-
*
|
|
14
|
-
* @module @huanlin/dsh-plugin-input-history
|
|
15
|
-
*/
|
|
16
|
-
export const name = 'dsh-plugin-input-history';
|
|
17
|
-
export const inject = [];
|
|
18
|
-
/**
|
|
19
|
-
* Host apply — no-op. The history navigation is a pure client-side UI
|
|
20
|
-
* contribution; no host-side resources are used.
|
|
21
|
-
* @param _ctx - host context (unused).
|
|
22
|
-
*/
|
|
23
|
-
export function apply(_ctx) {
|
|
24
|
-
// Client-only plugin: all work happens in src/client/index.ts.
|
|
25
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* dsh-plugin-input-history — host plugin entry.
|
|
3
|
+
*
|
|
4
|
+
* Client-only plugin: the host half has no runtime work. The browser half
|
|
5
|
+
* (`./client`) registers an invisible entry in `conversation.composer.dock`
|
|
6
|
+
* that collects prompt history from the Chat target's user/steering nodes
|
|
7
|
+
* and implements terminal-style history navigation (ArrowUp/ArrowDown) over
|
|
8
|
+
* the composer's Lexical surface through `inputActions.setDraft`.
|
|
9
|
+
*
|
|
10
|
+
* History is collected from `user` and `steering` chat nodes of any active
|
|
11
|
+
* session, persisted to `localStorage` (FIFO, capacity 500), and shared
|
|
12
|
+
* across all sessions in the same browser profile.
|
|
13
|
+
*
|
|
14
|
+
* @module @huanlin/dsh-plugin-input-history
|
|
15
|
+
*/
|
|
16
|
+
export const name = 'dsh-plugin-input-history';
|
|
17
|
+
export const inject = [];
|
|
18
|
+
/**
|
|
19
|
+
* Host apply — no-op. The history navigation is a pure client-side UI
|
|
20
|
+
* contribution; no host-side resources are used.
|
|
21
|
+
* @param _ctx - host context (unused).
|
|
22
|
+
*/
|
|
23
|
+
export function apply(_ctx) {
|
|
24
|
+
// Client-only plugin: all work happens in src/client/index.ts.
|
|
25
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@huanlin/dsh-plugin-input-history",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -45,11 +45,11 @@
|
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
48
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.
|
|
49
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.
|
|
50
|
-
"@deepseek-ai/dsh-client-ui-chat": "^0.1.
|
|
51
|
-
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.
|
|
52
|
-
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.
|
|
48
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.5-rc.1",
|
|
49
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.5-rc.1",
|
|
50
|
+
"@deepseek-ai/dsh-client-ui-chat": "^0.1.5-rc.1",
|
|
51
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.5-rc.1",
|
|
52
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.5-rc.1",
|
|
53
53
|
"react": "^18.2.0",
|
|
54
54
|
"react-dom": "^18.2.0"
|
|
55
55
|
},
|