@aleph-alpha/chat-kit 6.1.1 → 6.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/dist/{CkHistory.vue_vue_type_script_setup_true_lang-Cr8vF94e.js → CkHistory.vue_vue_type_script_setup_true_lang-gV-lp0ZB.js} +345 -164
- package/dist/adapters/responses-api.js +1 -1
- package/dist/chat-kit.css +111 -28
- package/dist/code-palette-DAJUTnR-.js +21598 -0
- package/dist/components/base/CkCodeEditor/CkCodeEditor.stories.d.ts +43 -0
- package/dist/components/base/CkCodeEditor/CkCodeEditor.stories.d.ts.map +1 -0
- package/dist/components/base/CkCodeEditor/CkCodeEditor.vue.d.ts +39 -0
- package/dist/components/base/CkCodeEditor/CkCodeEditor.vue.d.ts.map +1 -0
- package/dist/components/base/CkCodeEditor/createCodeMirror.d.ts +44 -0
- package/dist/components/base/CkCodeEditor/createCodeMirror.d.ts.map +1 -0
- package/dist/components/base/CkCodeEditor/index.d.ts +3 -0
- package/dist/components/base/CkCodeEditor/index.d.ts.map +1 -0
- package/dist/components/base/CkCodeEditor/types.d.ts +129 -0
- package/dist/components/base/CkCodeEditor/types.d.ts.map +1 -0
- package/dist/components/base/CkMilkdownEditor/CkMilkdownEditor.stories.d.ts +6 -0
- package/dist/components/base/CkMilkdownEditor/CkMilkdownEditor.stories.d.ts.map +1 -1
- package/dist/components/base/CkMilkdownEditor/CkMilkdownEditor.vue.d.ts +1 -0
- package/dist/components/base/CkMilkdownEditor/CkMilkdownEditor.vue.d.ts.map +1 -1
- package/dist/components/base/CkMilkdownEditor/createCrepeEditor.d.ts +1 -0
- package/dist/components/base/CkMilkdownEditor/createCrepeEditor.d.ts.map +1 -1
- package/dist/components/base/CkMilkdownEditor/types.d.ts +8 -0
- package/dist/components/base/CkMilkdownEditor/types.d.ts.map +1 -1
- package/dist/components/base/index.d.ts +2 -0
- package/dist/components/base/index.d.ts.map +1 -1
- package/dist/components/index.d.ts +2 -2
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +27 -26
- package/dist/composables/index.js +1 -1
- package/dist/createCodeMirror-BkV6FEPn.js +101 -0
- package/dist/{createCrepeEditor-CFbvSH3s.js → createCrepeEditor-Ci5FHHIV.js} +1434 -24739
- package/dist/handlers/index.js +2 -2
- package/dist/handlers/useChatKitLabels.d.ts +6 -0
- package/dist/handlers/useChatKitLabels.d.ts.map +1 -1
- package/dist/{components/base/CkMilkdownEditor/codeMirrorTheme.d.ts → helpers/codemirror/theme.d.ts} +1 -1
- package/dist/helpers/codemirror/theme.d.ts.map +1 -0
- package/dist/helpers/index.d.ts +1 -0
- package/dist/helpers/index.d.ts.map +1 -1
- package/dist/helpers/markdownToPlainText.d.ts +18 -0
- package/dist/helpers/markdownToPlainText.d.ts.map +1 -0
- package/dist/{index-BWvkps_G.js → index-B2tew8uf.js} +3 -3
- package/dist/{index-DhY_9bdS.js → index-BLVAx_f8.js} +2 -2
- package/dist/{index-B0ldhzaj.js → index-BUCqcTPA.js} +2 -2
- package/dist/{index-DdlzabmR.js → index-BhvbqYsK.js} +2 -2
- package/dist/{index-gzleljO6.js → index-BoUJGvvQ.js} +2 -2
- package/dist/{index-sKO1lHHC.js → index-BtY1uxn6.js} +3 -2
- package/dist/{index-BVOjyn3y.js → index-BvG6l44n.js} +3 -2
- package/dist/{index-BoqzHVNB.js → index-CB6xZVsf.js} +2 -2
- package/dist/{index-BaS8OW8V.js → index-CIF93PZF.js} +3 -3
- package/dist/index-CLdXUpL8.js +9560 -0
- package/dist/{index-DSq3gvqU.js → index-CN8d3yl1.js} +1 -1
- package/dist/{index-CRfGWVdQ.js → index-CYk7AB8M.js} +4 -4
- package/dist/{index-C72e57-5.js → index-CZFx44C7.js} +2 -2
- package/dist/{index-Tj3pzx-E.js → index-Cxs6kPho.js} +3 -3
- package/dist/index-D6ftdiLa.js +1825 -0
- package/dist/{index-CMJGuXap.js → index-DgCcFC3N.js} +3 -3
- package/dist/{index-DSU7cBGz.js → index-DhwsowEf.js} +2 -2
- package/dist/{index-C_gTb50u.js → index-Dl24CCB3.js} +4 -4
- package/dist/{index-BlHHEbV-.js → index-DqGDenO9.js} +2 -2
- package/dist/{index-naWlQpaL.js → index-DufAP1JF.js} +3 -2
- package/dist/{index-Dm6C1blN.js → index-DxTzaHLr.js} +4 -4
- package/dist/{index-A5rWjRtZ.js → index-MfXW4ZyN.js} +3 -2
- package/dist/{index-C6zYjwIQ.js → index-ZHg2R9iD.js} +3 -2
- package/dist/{index-ZeRS5rER.js → index-gYt3E3Cp.js} +2 -2
- package/dist/{index-Da3777l2.js → index-w2yzH7vs.js} +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -30
- package/dist/markdownToPlainText-BOtlQ_BY.js +34 -0
- package/dist/{useChatKitLabels-Ww1Q7xUr.js → useChatKitLabels-CatQSpfL.js} +881 -10428
- package/dist/{useMessageHandler-Tellov3w.js → useMessageHandler-nnsort0-.js} +1 -1
- package/package.json +5 -3
- package/src/components/base/CkCodeEditor/CkCodeEditor.spec.ts +223 -0
- package/src/components/base/CkCodeEditor/CkCodeEditor.stories.ts +311 -0
- package/src/components/base/CkCodeEditor/CkCodeEditor.vue +238 -0
- package/src/components/base/CkCodeEditor/createCodeMirror.ts +188 -0
- package/src/components/base/CkCodeEditor/index.ts +2 -0
- package/src/components/base/CkCodeEditor/types.ts +128 -0
- package/src/components/base/CkMilkdownEditor/CkMilkdownEditor.spec.ts +13 -0
- package/src/components/base/CkMilkdownEditor/CkMilkdownEditor.stories.ts +34 -0
- package/src/components/base/CkMilkdownEditor/CkMilkdownEditor.vue +3 -1
- package/src/components/base/CkMilkdownEditor/createCrepeEditor.ts +2 -1
- package/src/components/base/CkMilkdownEditor/milkdown-theme.css +39 -26
- package/src/components/base/CkMilkdownEditor/types.ts +8 -0
- package/src/components/base/index.ts +6 -0
- package/src/components/index.ts +4 -0
- package/src/handlers/useChatKitLabels.ts +6 -0
- package/src/helpers/codemirror/code-palette.css +49 -0
- package/src/{components/base/CkMilkdownEditor/codeMirrorTheme.ts → helpers/codemirror/theme.ts} +23 -12
- package/src/helpers/index.ts +1 -0
- package/src/helpers/markdownToPlainText.spec.ts +107 -0
- package/src/helpers/markdownToPlainText.ts +69 -0
- package/src/index.ts +5 -1
- package/dist/components/base/CkMilkdownEditor/codeMirrorTheme.d.ts.map +0 -1
- package/dist/generateId-BHf0W4Rq.js +0 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aleph-alpha/chat-kit",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"description": "Chat kit package for building chat interfaces with Vue",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -68,7 +68,9 @@
|
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
70
|
"@aleph-alpha/ui-library": "^6.0.0",
|
|
71
|
+
"@codemirror/commands": "^6.11.1",
|
|
71
72
|
"@codemirror/language": "^6.11.0",
|
|
73
|
+
"@codemirror/language-data": "^6.5.2",
|
|
72
74
|
"@codemirror/state": "^6.5.0",
|
|
73
75
|
"@codemirror/view": "^6.38.0",
|
|
74
76
|
"@lezer/highlight": "^1.2.0",
|
|
@@ -113,9 +115,9 @@
|
|
|
113
115
|
"vue": "^3.5.27",
|
|
114
116
|
"vue-tsc": "^3.2.4",
|
|
115
117
|
"wait-on": "9.0.3",
|
|
116
|
-
"@aleph-alpha/
|
|
118
|
+
"@aleph-alpha/prettier-config-frontend": "2.1.1",
|
|
117
119
|
"@aleph-alpha/tsconfig-frontend": "0.7.3",
|
|
118
|
-
"@aleph-alpha/
|
|
120
|
+
"@aleph-alpha/eslint-config-frontend": "0.7.1"
|
|
119
121
|
},
|
|
120
122
|
"scripts": {
|
|
121
123
|
"build": "vite build && vue-tsc --project tsconfig.build.json --declaration --emitDeclarationOnly --outDir dist",
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { render, screen, waitFor } from '@testing-library/vue';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import CkCodeEditor from './CkCodeEditor.vue';
|
|
4
|
+
import { resolveLanguage } from './createCodeMirror';
|
|
5
|
+
|
|
6
|
+
// CodeMirror measures its own layout on construction; jsdom has none of the
|
|
7
|
+
// geometry APIs it calls. The stubs stay in this spec rather than the global
|
|
8
|
+
// setup so every other spec keeps an unmodified jsdom.
|
|
9
|
+
const EMPTY_RECT = {
|
|
10
|
+
x: 0,
|
|
11
|
+
y: 0,
|
|
12
|
+
top: 0,
|
|
13
|
+
left: 0,
|
|
14
|
+
right: 0,
|
|
15
|
+
bottom: 0,
|
|
16
|
+
width: 0,
|
|
17
|
+
height: 0,
|
|
18
|
+
toJSON: () => ({}),
|
|
19
|
+
} as DOMRect;
|
|
20
|
+
|
|
21
|
+
beforeAll(() => {
|
|
22
|
+
Range.prototype.getBoundingClientRect = () => EMPTY_RECT;
|
|
23
|
+
Range.prototype.getClientRects = () =>
|
|
24
|
+
({
|
|
25
|
+
length: 0,
|
|
26
|
+
item: () => null,
|
|
27
|
+
*[Symbol.iterator]() {
|
|
28
|
+
// no rects
|
|
29
|
+
},
|
|
30
|
+
}) as unknown as DOMRectList;
|
|
31
|
+
Element.prototype.scrollIntoView ??= () => undefined;
|
|
32
|
+
document.elementFromPoint ??= () => null;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
async function renderEditor(
|
|
36
|
+
props: InstanceType<typeof CkCodeEditor>['$props'] = {},
|
|
37
|
+
) {
|
|
38
|
+
const utils = render(CkCodeEditor, { props });
|
|
39
|
+
// The editor loads lazily, so wait for the ready flag rather than the shell.
|
|
40
|
+
await waitFor(() =>
|
|
41
|
+
expect(
|
|
42
|
+
utils.container.querySelector('.ck-code-editor--ready .cm-editor'),
|
|
43
|
+
).toBeInTheDocument(),
|
|
44
|
+
);
|
|
45
|
+
return utils;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe('CkCodeEditor', () => {
|
|
49
|
+
it('renders the initial code', async () => {
|
|
50
|
+
const { container } = await renderEditor({
|
|
51
|
+
modelValue: 'const answer = 42;',
|
|
52
|
+
language: 'javascript',
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(container.querySelector('.cm-content')?.textContent).toContain(
|
|
56
|
+
'const answer = 42;',
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('emits the new document when the user types', async () => {
|
|
61
|
+
const { emitted } = await renderEditor({ modelValue: '' });
|
|
62
|
+
|
|
63
|
+
const content = document.querySelector('.cm-content') as HTMLElement;
|
|
64
|
+
await userEvent.click(content);
|
|
65
|
+
await userEvent.type(content, 'hi');
|
|
66
|
+
|
|
67
|
+
await waitFor(() => expect(emitted()['update:modelValue']).toBeTruthy());
|
|
68
|
+
const updates = emitted()['update:modelValue'] as string[][];
|
|
69
|
+
expect(updates.at(-1)?.[0]).toBe('hi');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('applies an external modelValue change without echoing it back', async () => {
|
|
73
|
+
const { container, rerender, emitted } = await renderEditor({
|
|
74
|
+
modelValue: 'one',
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
await rerender({ modelValue: 'two' });
|
|
78
|
+
|
|
79
|
+
await waitFor(() =>
|
|
80
|
+
expect(container.querySelector('.cm-content')?.textContent).toContain(
|
|
81
|
+
'two',
|
|
82
|
+
),
|
|
83
|
+
);
|
|
84
|
+
const updates = (emitted()['update:modelValue'] ?? []) as string[][];
|
|
85
|
+
expect(updates.map(([value]) => value)).not.toContain('two');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('drops contenteditable when readonly, and restores it', async () => {
|
|
89
|
+
const { container, rerender } = await renderEditor({
|
|
90
|
+
modelValue: 'x',
|
|
91
|
+
readonly: true,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const content = () => container.querySelector('.cm-content');
|
|
95
|
+
expect(content()?.getAttribute('contenteditable')).toBe('false');
|
|
96
|
+
|
|
97
|
+
await rerender({ modelValue: 'x', readonly: false });
|
|
98
|
+
await waitFor(() =>
|
|
99
|
+
expect(content()?.getAttribute('contenteditable')).toBe('true'),
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('marks the root disabled and locks editing with `disabled`', async () => {
|
|
104
|
+
const { container } = await renderEditor({
|
|
105
|
+
modelValue: 'x',
|
|
106
|
+
disabled: true,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect(
|
|
110
|
+
container.querySelector('.ck-code-editor')?.getAttribute('aria-disabled'),
|
|
111
|
+
).toBe('true');
|
|
112
|
+
expect(
|
|
113
|
+
container.querySelector('.cm-content')?.getAttribute('contenteditable'),
|
|
114
|
+
).toBe('false');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('names the editable region for assistive tech', async () => {
|
|
118
|
+
const { container } = await renderEditor({
|
|
119
|
+
modelValue: 'x',
|
|
120
|
+
ariaLabel: 'Snippet',
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const textbox = container.querySelector('[role="textbox"]');
|
|
124
|
+
expect(textbox).toBe(container.querySelector('.cm-content'));
|
|
125
|
+
expect(textbox?.getAttribute('aria-label')).toBe('Snippet');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('shows the filename in the header when there is one', async () => {
|
|
129
|
+
await renderEditor({
|
|
130
|
+
modelValue: 'x',
|
|
131
|
+
language: 'python',
|
|
132
|
+
filename: 'main.py',
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
expect(screen.getByText('main.py')).toBeInTheDocument();
|
|
136
|
+
expect(screen.queryByText('python')).not.toBeInTheDocument();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('falls back to the language, and labels plain text with nothing', async () => {
|
|
140
|
+
const withLanguage = await renderEditor({
|
|
141
|
+
modelValue: 'x',
|
|
142
|
+
language: 'python',
|
|
143
|
+
});
|
|
144
|
+
expect(screen.getByText('python')).toBeInTheDocument();
|
|
145
|
+
withLanguage.unmount();
|
|
146
|
+
|
|
147
|
+
// `text` is the "no language" value, which UiCodeBlock also leaves blank.
|
|
148
|
+
await renderEditor({ modelValue: 'x', language: 'text' });
|
|
149
|
+
expect(screen.queryByText('text')).not.toBeInTheDocument();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('copies the current code through the injected writer', async () => {
|
|
153
|
+
const writeToClipboard = vi.fn().mockResolvedValue(undefined);
|
|
154
|
+
const { emitted } = await renderEditor({
|
|
155
|
+
modelValue: 'copy me',
|
|
156
|
+
writeToClipboard,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
await userEvent.click(screen.getByRole('button', { name: 'Copy code' }));
|
|
160
|
+
|
|
161
|
+
expect(writeToClipboard).toHaveBeenCalledWith('copy me');
|
|
162
|
+
await waitFor(() => expect(emitted().copy).toEqual([[{ success: true }]]));
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('reports a failed copy rather than throwing', async () => {
|
|
166
|
+
const writeToClipboard = vi.fn().mockRejectedValue(new Error('denied'));
|
|
167
|
+
const { emitted } = await renderEditor({
|
|
168
|
+
modelValue: 'x',
|
|
169
|
+
writeToClipboard,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
await userEvent.click(screen.getByRole('button', { name: 'Copy code' }));
|
|
173
|
+
|
|
174
|
+
await waitFor(() => expect(emitted().copy).toEqual([[{ success: false }]]));
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('hides the header when asked', async () => {
|
|
178
|
+
const { container } = await renderEditor({
|
|
179
|
+
modelValue: 'x',
|
|
180
|
+
language: 'python',
|
|
181
|
+
showHeader: false,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
expect(container.querySelector('button')).toBeNull();
|
|
185
|
+
expect(screen.queryByText('python')).not.toBeInTheDocument();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('toggles the line-number gutter', async () => {
|
|
189
|
+
const { container, rerender } = await renderEditor({
|
|
190
|
+
modelValue: 'a\nb',
|
|
191
|
+
showLineNumbers: true,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
expect(container.querySelector('.cm-lineNumbers')).toBeInTheDocument();
|
|
195
|
+
|
|
196
|
+
await rerender({ modelValue: 'a\nb', showLineNumbers: false });
|
|
197
|
+
await waitFor(() =>
|
|
198
|
+
expect(container.querySelector('.cm-lineNumbers')).toBeNull(),
|
|
199
|
+
);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('does not throw when unmounted before or after the editor is created', async () => {
|
|
203
|
+
const early = render(CkCodeEditor, { props: { modelValue: 'x' } });
|
|
204
|
+
expect(() => early.unmount()).not.toThrow();
|
|
205
|
+
|
|
206
|
+
const late = await renderEditor({ modelValue: 'y' });
|
|
207
|
+
expect(() => late.unmount()).not.toThrow();
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('resolveLanguage', () => {
|
|
212
|
+
it('resolves by name, alias and extension, ignoring case', async () => {
|
|
213
|
+
await expect(resolveLanguage('TypeScript')).resolves.toBeTruthy();
|
|
214
|
+
await expect(resolveLanguage('ts')).resolves.toBeTruthy();
|
|
215
|
+
await expect(resolveLanguage('py')).resolves.toBeTruthy();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('returns null for plain text and unknown languages', async () => {
|
|
219
|
+
await expect(resolveLanguage('text')).resolves.toBeNull();
|
|
220
|
+
await expect(resolveLanguage('')).resolves.toBeNull();
|
|
221
|
+
await expect(resolveLanguage('not-a-language')).resolves.toBeNull();
|
|
222
|
+
});
|
|
223
|
+
});
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { ref } from 'vue';
|
|
2
|
+
import { UiCodeBlock } from '@aleph-alpha/ui-library';
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
4
|
+
import CkCodeEditor from './CkCodeEditor.vue';
|
|
5
|
+
import CkMarkdownRenderer from '../CkMarkdownRenderer/CkMarkdownRenderer.vue';
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof CkCodeEditor> = {
|
|
8
|
+
title: 'Base/CkCodeEditor',
|
|
9
|
+
component: CkCodeEditor,
|
|
10
|
+
tags: ['autodocs'],
|
|
11
|
+
argTypes: {
|
|
12
|
+
modelValue: { control: 'text', description: 'The code (`v-model`).' },
|
|
13
|
+
language: {
|
|
14
|
+
control: 'text',
|
|
15
|
+
description:
|
|
16
|
+
'Language id for highlighting and the header label, matched on names, aliases and extensions.',
|
|
17
|
+
},
|
|
18
|
+
filename: {
|
|
19
|
+
control: 'text',
|
|
20
|
+
description: 'Shown in the header instead of the language.',
|
|
21
|
+
},
|
|
22
|
+
maxHeight: {
|
|
23
|
+
control: 'text',
|
|
24
|
+
description: 'CSS length capping the scrollable area, e.g. `24rem`.',
|
|
25
|
+
},
|
|
26
|
+
placeholder: { control: 'text' },
|
|
27
|
+
readonly: { control: 'boolean' },
|
|
28
|
+
disabled: { control: 'boolean' },
|
|
29
|
+
showHeader: { control: 'boolean' },
|
|
30
|
+
showCopyButton: { control: 'boolean' },
|
|
31
|
+
showLineNumbers: { control: 'boolean' },
|
|
32
|
+
lineWrapping: { control: 'boolean' },
|
|
33
|
+
autofocus: { control: 'boolean' },
|
|
34
|
+
writeToClipboard: {
|
|
35
|
+
control: false,
|
|
36
|
+
description:
|
|
37
|
+
'Clipboard writer for tests or hosts that proxy clipboard access.',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export default meta;
|
|
43
|
+
type Story = StoryObj<typeof meta>;
|
|
44
|
+
|
|
45
|
+
const typescriptSample = [
|
|
46
|
+
'export interface Greeting {',
|
|
47
|
+
' name: string;',
|
|
48
|
+
' formal?: boolean;',
|
|
49
|
+
'}',
|
|
50
|
+
'',
|
|
51
|
+
'// Returns a greeting suited to the audience.',
|
|
52
|
+
'export function greet({ name, formal = false }: Greeting): string {',
|
|
53
|
+
' const opener = formal ? "Good day" : "Hello";',
|
|
54
|
+
' return `${opener}, ${name}!`;',
|
|
55
|
+
'}',
|
|
56
|
+
].join('\n');
|
|
57
|
+
|
|
58
|
+
const pythonSample = [
|
|
59
|
+
'from dataclasses import dataclass',
|
|
60
|
+
'',
|
|
61
|
+
'',
|
|
62
|
+
'@dataclass',
|
|
63
|
+
'class Greeting:',
|
|
64
|
+
' name: str',
|
|
65
|
+
' formal: bool = False',
|
|
66
|
+
'',
|
|
67
|
+
' def render(self) -> str:',
|
|
68
|
+
' opener = "Good day" if self.formal else "Hello"',
|
|
69
|
+
' return f"{opener}, {self.name}!"',
|
|
70
|
+
].join('\n');
|
|
71
|
+
|
|
72
|
+
const defaultTemplateSource = `<script setup lang="ts">
|
|
73
|
+
import { ref } from 'vue';
|
|
74
|
+
import { CkCodeEditor } from '@aleph-alpha/chat-kit';
|
|
75
|
+
|
|
76
|
+
const code = ref('export const answer = 42;');
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<template>
|
|
80
|
+
<CkCodeEditor v-model="code" language="typescript" aria-label="Code" />
|
|
81
|
+
</template>`;
|
|
82
|
+
|
|
83
|
+
const withFilenameTemplateSource = `<script setup lang="ts">
|
|
84
|
+
import { ref } from 'vue';
|
|
85
|
+
import { CkCodeEditor } from '@aleph-alpha/chat-kit';
|
|
86
|
+
|
|
87
|
+
const code = ref('print("hello")');
|
|
88
|
+
</script>
|
|
89
|
+
|
|
90
|
+
<template>
|
|
91
|
+
<CkCodeEditor
|
|
92
|
+
v-model="code"
|
|
93
|
+
language="python"
|
|
94
|
+
filename="main.py"
|
|
95
|
+
aria-label="main.py"
|
|
96
|
+
/>
|
|
97
|
+
</template>`;
|
|
98
|
+
|
|
99
|
+
const plainTextTemplateSource = `<script setup lang="ts">
|
|
100
|
+
import { ref } from 'vue';
|
|
101
|
+
import { CkCodeEditor } from '@aleph-alpha/chat-kit';
|
|
102
|
+
|
|
103
|
+
const notes = ref('');
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
<template>
|
|
107
|
+
<CkCodeEditor
|
|
108
|
+
v-model="notes"
|
|
109
|
+
placeholder="Paste your snippet…"
|
|
110
|
+
:show-line-numbers="false"
|
|
111
|
+
aria-label="Snippet"
|
|
112
|
+
/>
|
|
113
|
+
</template>`;
|
|
114
|
+
|
|
115
|
+
const readonlyTemplateSource = `<script setup lang="ts">
|
|
116
|
+
import { CkCodeEditor } from '@aleph-alpha/chat-kit';
|
|
117
|
+
|
|
118
|
+
const code = 'export const answer = 42;';
|
|
119
|
+
</script>
|
|
120
|
+
|
|
121
|
+
<template>
|
|
122
|
+
<CkCodeEditor :model-value="code" language="typescript" readonly aria-label="Code" />
|
|
123
|
+
</template>`;
|
|
124
|
+
|
|
125
|
+
const bareTemplateSource = `<script setup lang="ts">
|
|
126
|
+
import { ref } from 'vue';
|
|
127
|
+
import { CkCodeEditor } from '@aleph-alpha/chat-kit';
|
|
128
|
+
|
|
129
|
+
const code = ref('SELECT * FROM users;');
|
|
130
|
+
</script>
|
|
131
|
+
|
|
132
|
+
<template>
|
|
133
|
+
<CkCodeEditor
|
|
134
|
+
v-model="code"
|
|
135
|
+
language="sql"
|
|
136
|
+
:show-header="false"
|
|
137
|
+
aria-label="Query"
|
|
138
|
+
/>
|
|
139
|
+
</template>`;
|
|
140
|
+
|
|
141
|
+
const scrollingTemplateSource = `<script setup lang="ts">
|
|
142
|
+
import { ref } from 'vue';
|
|
143
|
+
import { CkCodeEditor } from '@aleph-alpha/chat-kit';
|
|
144
|
+
|
|
145
|
+
const code = ref(longFile);
|
|
146
|
+
</script>
|
|
147
|
+
|
|
148
|
+
<template>
|
|
149
|
+
<CkCodeEditor v-model="code" language="typescript" max-height="16rem" line-wrapping />
|
|
150
|
+
</template>`;
|
|
151
|
+
|
|
152
|
+
const matchesRendererTemplateSource = `<script setup lang="ts">
|
|
153
|
+
import { ref } from 'vue';
|
|
154
|
+
import { CkCodeEditor, CkMarkdownRenderer } from '@aleph-alpha/chat-kit';
|
|
155
|
+
import { UiCodeBlock } from '@aleph-alpha/ui-library';
|
|
156
|
+
|
|
157
|
+
const code = ref('export const answer = 42;');
|
|
158
|
+
const asMarkdown = computed(() => '\\\`\\\`\\\`typescript\\n' + code.value + '\\n\\\`\\\`\\\`');
|
|
159
|
+
</script>
|
|
160
|
+
|
|
161
|
+
<template>
|
|
162
|
+
<div class="text-content-on-surface-primary grid gap-6 text-base leading-7 md:grid-cols-2">
|
|
163
|
+
<CkCodeEditor v-model="code" language="typescript" aria-label="Editor" />
|
|
164
|
+
<CkMarkdownRenderer :content="asMarkdown">
|
|
165
|
+
<template #code-block="{ code: c, language }">
|
|
166
|
+
<UiCodeBlock :code="c" :language="language || 'text'" />
|
|
167
|
+
</template>
|
|
168
|
+
</CkMarkdownRenderer>
|
|
169
|
+
</div>
|
|
170
|
+
</template>`;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* A TypeScript snippet with the header, copy button and line numbers. Type in
|
|
174
|
+
* it — changes are emitted synchronously, with no debounce.
|
|
175
|
+
*/
|
|
176
|
+
export const Default: Story = {
|
|
177
|
+
render: (args) => ({
|
|
178
|
+
components: { CkCodeEditor },
|
|
179
|
+
setup() {
|
|
180
|
+
const code = ref(args.modelValue ?? '');
|
|
181
|
+
return { args, code };
|
|
182
|
+
},
|
|
183
|
+
template: `<CkCodeEditor v-bind="args" v-model="code" aria-label="Code" />`,
|
|
184
|
+
}),
|
|
185
|
+
args: { modelValue: typescriptSample, language: 'typescript' },
|
|
186
|
+
parameters: { docs: { source: { code: defaultTemplateSource } } },
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* `filename` replaces the language in the header, matching the prop of the same
|
|
191
|
+
* name on `UiCodeBlock`. The grammar still comes from `language`.
|
|
192
|
+
*/
|
|
193
|
+
export const WithFilename: Story = {
|
|
194
|
+
render: (args) => ({
|
|
195
|
+
components: { CkCodeEditor },
|
|
196
|
+
setup() {
|
|
197
|
+
const code = ref(args.modelValue ?? '');
|
|
198
|
+
return { args, code };
|
|
199
|
+
},
|
|
200
|
+
template: `<CkCodeEditor v-bind="args" v-model="code" aria-label="main.py" />`,
|
|
201
|
+
}),
|
|
202
|
+
args: { modelValue: pythonSample, language: 'python', filename: 'main.py' },
|
|
203
|
+
parameters: { docs: { source: { code: withFilenameTemplateSource } } },
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* With no language the header label is blank and nothing is highlighted, the
|
|
208
|
+
* same treatment `UiCodeBlock` gives plain text. Shown here with a placeholder
|
|
209
|
+
* and no gutter, as a lightweight snippet field.
|
|
210
|
+
*/
|
|
211
|
+
export const PlainText: Story = {
|
|
212
|
+
render: (args) => ({
|
|
213
|
+
components: { CkCodeEditor },
|
|
214
|
+
setup() {
|
|
215
|
+
const code = ref(args.modelValue ?? '');
|
|
216
|
+
return { args, code };
|
|
217
|
+
},
|
|
218
|
+
template: `<CkCodeEditor v-bind="args" v-model="code" aria-label="Snippet" />`,
|
|
219
|
+
}),
|
|
220
|
+
args: {
|
|
221
|
+
modelValue: '',
|
|
222
|
+
placeholder: 'Paste your snippet…',
|
|
223
|
+
showLineNumbers: false,
|
|
224
|
+
},
|
|
225
|
+
parameters: { docs: { source: { code: plainTextTemplateSource } } },
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* `readonly` keeps the code selectable and copyable but drops the caret and the
|
|
230
|
+
* active-line band. Use it while a request is in flight.
|
|
231
|
+
*/
|
|
232
|
+
export const Readonly: Story = {
|
|
233
|
+
args: {
|
|
234
|
+
modelValue: typescriptSample,
|
|
235
|
+
language: 'typescript',
|
|
236
|
+
readonly: true,
|
|
237
|
+
},
|
|
238
|
+
parameters: { docs: { source: { code: readonlyTemplateSource } } },
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* `showHeader: false` gives a bare editing surface, for forms that already
|
|
243
|
+
* label the field themselves.
|
|
244
|
+
*/
|
|
245
|
+
export const WithoutHeader: Story = {
|
|
246
|
+
args: {
|
|
247
|
+
modelValue: 'SELECT id, email\nFROM users\nWHERE active = true;',
|
|
248
|
+
language: 'sql',
|
|
249
|
+
showHeader: false,
|
|
250
|
+
},
|
|
251
|
+
parameters: { docs: { source: { code: bareTemplateSource } } },
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* `maxHeight` caps the scrollable area and `lineWrapping` folds long lines
|
|
256
|
+
* instead of scrolling sideways.
|
|
257
|
+
*/
|
|
258
|
+
export const ScrollingAndWrapping: Story = {
|
|
259
|
+
render: (args) => ({
|
|
260
|
+
components: { CkCodeEditor },
|
|
261
|
+
setup() {
|
|
262
|
+
const code = ref(args.modelValue ?? '');
|
|
263
|
+
return { args, code };
|
|
264
|
+
},
|
|
265
|
+
template: `<CkCodeEditor v-bind="args" v-model="code" aria-label="Long file" />`,
|
|
266
|
+
}),
|
|
267
|
+
args: {
|
|
268
|
+
modelValue: [
|
|
269
|
+
typescriptSample,
|
|
270
|
+
'',
|
|
271
|
+
'// A deliberately long line that has to wrap rather than scroll sideways, so you can see how lineWrapping behaves in a narrow column.',
|
|
272
|
+
'',
|
|
273
|
+
pythonSample,
|
|
274
|
+
].join('\n'),
|
|
275
|
+
language: 'typescript',
|
|
276
|
+
maxHeight: '16rem',
|
|
277
|
+
lineWrapping: true,
|
|
278
|
+
},
|
|
279
|
+
parameters: { docs: { source: { code: scrollingTemplateSource } } },
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Parity check: the editor on the left, the same snippet rendered through
|
|
284
|
+
* `CkMarkdownRenderer` and `UiCodeBlock` on the right. Border, radius, header,
|
|
285
|
+
* font and syntax colours should read as one component in both themes.
|
|
286
|
+
*/
|
|
287
|
+
export const MatchesTheRenderer: Story = {
|
|
288
|
+
render: (args) => ({
|
|
289
|
+
components: { CkCodeEditor, CkMarkdownRenderer, UiCodeBlock },
|
|
290
|
+
setup() {
|
|
291
|
+
const code = ref(args.modelValue ?? '');
|
|
292
|
+
return { args, code };
|
|
293
|
+
},
|
|
294
|
+
template: `
|
|
295
|
+
<div class="text-content-on-surface-primary grid gap-6 text-base leading-7 md:grid-cols-2">
|
|
296
|
+
<CkCodeEditor v-bind="args" v-model="code" aria-label="Editor" />
|
|
297
|
+
<CkMarkdownRenderer :content="'\\\`\\\`\\\`typescript\\n' + code + '\\n\\\`\\\`\\\`'">
|
|
298
|
+
<template #code-block="{ code: c, language }">
|
|
299
|
+
<UiCodeBlock :code="c" :language="language || 'text'" />
|
|
300
|
+
</template>
|
|
301
|
+
</CkMarkdownRenderer>
|
|
302
|
+
</div>
|
|
303
|
+
`,
|
|
304
|
+
}),
|
|
305
|
+
args: {
|
|
306
|
+
modelValue: typescriptSample,
|
|
307
|
+
language: 'typescript',
|
|
308
|
+
showLineNumbers: false,
|
|
309
|
+
},
|
|
310
|
+
parameters: { docs: { source: { code: matchesRendererTemplateSource } } },
|
|
311
|
+
};
|