@aleph-alpha/chat-kit 6.3.0 → 6.4.1
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-gV-lp0ZB.js → CkHistory.vue_vue_type_script_setup_true_lang-BvRIp3m6.js} +79 -16
- package/dist/adapters/responses-api.js +1 -1
- package/dist/chat-kit.css +81 -11
- package/dist/{code-palette-DAJUTnR-.js → code-palette-BWrZDi47.js} +156 -119
- package/dist/components/base/CkCodeEditor/CkCodeEditor.stories.d.ts +8 -1
- package/dist/components/base/CkCodeEditor/CkCodeEditor.stories.d.ts.map +1 -1
- package/dist/components/base/CkCodeEditor/CkCodeEditor.vue.d.ts +2 -0
- package/dist/components/base/CkCodeEditor/CkCodeEditor.vue.d.ts.map +1 -1
- package/dist/components/base/CkCodeEditor/badgeDecoration.d.ts +47 -0
- package/dist/components/base/CkCodeEditor/badgeDecoration.d.ts.map +1 -0
- package/dist/components/base/CkCodeEditor/createCodeMirror.d.ts +19 -1
- package/dist/components/base/CkCodeEditor/createCodeMirror.d.ts.map +1 -1
- package/dist/components/base/CkCodeEditor/types.d.ts +32 -0
- package/dist/components/base/CkCodeEditor/types.d.ts.map +1 -1
- package/dist/components/base/CkMarkdownRenderer/CkMarkdownRenderer.stories.d.ts +7 -0
- package/dist/components/base/CkMarkdownRenderer/CkMarkdownRenderer.stories.d.ts.map +1 -1
- package/dist/components/base/CkMarkdownRenderer/CkMarkdownRenderer.vue.d.ts +9 -0
- package/dist/components/base/CkMarkdownRenderer/CkMarkdownRenderer.vue.d.ts.map +1 -1
- package/dist/components/base/CkMarkdownRenderer/types.d.ts +13 -0
- package/dist/components/base/CkMarkdownRenderer/types.d.ts.map +1 -1
- package/dist/components/index.js +1 -1
- package/dist/composables/index.js +1 -1
- package/dist/createCodeMirror-BO-TWTzn.js +187 -0
- package/dist/{createCrepeEditor-Ci5FHHIV.js → createCrepeEditor-BGSlwmKn.js} +4 -4
- package/dist/handlers/index.js +2 -2
- package/dist/helpers/codemirror/theme.d.ts.map +1 -1
- package/dist/helpers/unified/remarkBreaks.d.ts +15 -0
- package/dist/helpers/unified/remarkBreaks.d.ts.map +1 -0
- package/dist/{index-w2yzH7vs.js → index--Yonjzfi.js} +3 -3
- package/dist/{index-CN8d3yl1.js → index-2YPxXIaf.js} +1 -1
- package/dist/{index-BoUJGvvQ.js → index-B4uqe-fL.js} +2 -2
- package/dist/{index-gYt3E3Cp.js → index-Bfo4U-Al.js} +2 -2
- package/dist/{index-BtY1uxn6.js → index-Bw39pbRL.js} +3 -3
- package/dist/{index-Dl24CCB3.js → index-CGEgwupi.js} +4 -4
- package/dist/{index-MfXW4ZyN.js → index-CZmdzLaU.js} +3 -3
- package/dist/{index-DgCcFC3N.js → index-CeaT_FBl.js} +3 -3
- package/dist/{index-BhvbqYsK.js → index-CefLTKmi.js} +2 -2
- package/dist/{index-CLdXUpL8.js → index-CfVHzirA.js} +5 -5
- package/dist/{index-BUCqcTPA.js → index-CrBanggA.js} +2 -2
- package/dist/{index-DufAP1JF.js → index-Cx5ThXzs.js} +3 -3
- package/dist/{index-CZFx44C7.js → index-D0DQjdsq.js} +2 -2
- package/dist/{index-DqGDenO9.js → index-DH9e_hqJ.js} +2 -2
- package/dist/{index-BvG6l44n.js → index-DTomeh-p.js} +3 -3
- package/dist/{index-ZHg2R9iD.js → index-DZGfsazg.js} +3 -3
- package/dist/{index-BLVAx_f8.js → index-DlLnTKv3.js} +2 -2
- package/dist/{index-Cxs6kPho.js → index-DqUiBDRo.js} +3 -3
- package/dist/{index-DhwsowEf.js → index-GDUM7K5V.js} +2 -2
- package/dist/{index-CB6xZVsf.js → index-Hro6EPZS.js} +2 -2
- package/dist/{index-D6ftdiLa.js → index-KgumaL1c.js} +1 -1
- package/dist/{index-CIF93PZF.js → index-TemHfU1M.js} +3 -3
- package/dist/{index-CYk7AB8M.js → index-g_Bs6dNr.js} +4 -4
- package/dist/{index-B2tew8uf.js → index-oO_X5wU0.js} +3 -3
- package/dist/{index-DxTzaHLr.js → index-ptbBQ53d.js} +4 -4
- package/dist/index.js +5 -5
- package/dist/{markdownToPlainText-BOtlQ_BY.js → markdownToPlainText-DN_G-I6g.js} +1 -1
- package/dist/{useChatKitLabels-CatQSpfL.js → useChatKitLabels-DxW0N5U-.js} +1 -1
- package/dist/{useMessageHandler-nnsort0-.js → useMessageHandler-Bkzrdgfj.js} +1 -1
- package/package.json +3 -3
- package/src/components/base/CkCodeEditor/CkCodeEditor.spec.ts +161 -1
- package/src/components/base/CkCodeEditor/CkCodeEditor.stories.ts +97 -1
- package/src/components/base/CkCodeEditor/CkCodeEditor.vue +135 -12
- package/src/components/base/CkCodeEditor/badgeDecoration.ts +173 -0
- package/src/components/base/CkCodeEditor/createCodeMirror.ts +54 -8
- package/src/components/base/CkCodeEditor/types.ts +38 -0
- package/src/components/base/CkMarkdownRenderer/CkMarkdownRenderer.spec.ts +40 -0
- package/src/components/base/CkMarkdownRenderer/CkMarkdownRenderer.stories.ts +31 -0
- package/src/components/base/CkMarkdownRenderer/CkMarkdownRenderer.vue +3 -0
- package/src/components/base/CkMarkdownRenderer/types.ts +13 -0
- package/src/components/base/CkMilkdownEditor/milkdown-theme.css +6 -3
- package/src/helpers/codemirror/code-palette.css +23 -3
- package/src/helpers/codemirror/theme.ts +44 -8
- package/src/helpers/unified/remarkBreaks.ts +32 -0
- package/dist/createCodeMirror-BkV6FEPn.js +0 -101
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { L as LRParser, E as ExternalTokenizer, C as ContextTracker } from "./index-
|
|
2
|
-
import {
|
|
3
|
-
import { defineCSSCompletionSource } from "./index-
|
|
1
|
+
import { L as LRParser, E as ExternalTokenizer, C as ContextTracker } from "./index-2YPxXIaf.js";
|
|
2
|
+
import { s as styleTags, t as tags, a as LanguageSupport, L as LRLanguage, f as foldNodeProp, g as foldInside, i as indentNodeProp, c as continuedIndent } from "./code-palette-BWrZDi47.js";
|
|
3
|
+
import { defineCSSCompletionSource } from "./index-CefLTKmi.js";
|
|
4
4
|
const indent = 168, dedent = 169, descendantOp = 170, InterpolationEnd = 1, InterpolationContinue = 2, Unit = 3, callee = 171, identifier = 172, VariableName = 4, queryIdentifier = 173, InterpolationStart = 5, newline = 174, blankLineStart = 175, eof = 176, whitespace = 177, LineComment = 6, Comment = 7, IndentedMixin = 8, IndentedInclude = 9, Dialect_indented = 0;
|
|
5
5
|
const space = [
|
|
6
6
|
9,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { L as LRParser, E as ExternalTokenizer } from "./index-
|
|
2
|
-
import {
|
|
1
|
+
import { L as LRParser, E as ExternalTokenizer } from "./index-2YPxXIaf.js";
|
|
2
|
+
import { s as styleTags, t as tags, a as LanguageSupport, L as LRLanguage, i as indentNodeProp, c as continuedIndent, f as foldNodeProp, g as foldInside } from "./code-palette-BWrZDi47.js";
|
|
3
3
|
const closureParamDelim = 1, tpOpen = 2, tpClose = 3, RawString = 4, Float = 5;
|
|
4
4
|
const _b = 98, _e = 101, _f = 102, _r = 114, _E = 69, Zero = 48, Dot = 46, Plus = 43, Minus = 45, Hash = 35, Quote = 34, Pipe = 124, LessThan = 60, GreaterThan = 62;
|
|
5
5
|
function isNum(ch) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { L as LRParser } from "./index-
|
|
2
|
-
import {
|
|
1
|
+
import { L as LRParser } from "./index-2YPxXIaf.js";
|
|
2
|
+
import { s as styleTags, t as tags, a as LanguageSupport, L as LRLanguage, i as indentNodeProp, c as continuedIndent, f as foldNodeProp, g as foldInside } from "./code-palette-BWrZDi47.js";
|
|
3
3
|
const jsonHighlighting = styleTags({
|
|
4
4
|
String: tags.string,
|
|
5
5
|
Number: tags.number,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { b as syntaxTree, T as Transaction, h as Text, S as StateEffect, j as codePointAt, d as EditorSelection, k as indentUnit, A as Annotation, D as Decoration, l as StateField, E as EditorView, P as Prec, m as keymap, n as codePointSize, R as RangeSet, o as showTooltip, V as ViewPlugin, q as logException, r as getTooltip, F as Facet, u as combineConfig, v as fromCodePoint, M as MapMode, W as WidgetType, w as RangeValue, x as Direction, C as CharCategory } from "./code-palette-BWrZDi47.js";
|
|
2
2
|
class CompletionContext {
|
|
3
3
|
/**
|
|
4
4
|
Create a new completion context. (Mostly useful for testing
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { defineCSSCompletionSource } from "./index-
|
|
3
|
-
import { L as LRParser, E as ExternalTokenizer } from "./index-
|
|
1
|
+
import { a as LanguageSupport, L as LRLanguage, i as indentNodeProp, c as continuedIndent, f as foldNodeProp, g as foldInside, s as styleTags, t as tags } from "./code-palette-BWrZDi47.js";
|
|
2
|
+
import { defineCSSCompletionSource } from "./index-CefLTKmi.js";
|
|
3
|
+
import { L as LRParser, E as ExternalTokenizer } from "./index-2YPxXIaf.js";
|
|
4
4
|
const descendantOp = 110, Unit = 1, openArgList = 2;
|
|
5
5
|
const space = [
|
|
6
6
|
9,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { L as LRLanguage,
|
|
2
|
-
import { html } from "./index-
|
|
3
|
-
import { javascriptLanguage } from "./index-
|
|
4
|
-
import { L as LRParser, E as ExternalTokenizer } from "./index-
|
|
1
|
+
import { L as LRLanguage, a as LanguageSupport, p as parseMixed, s as styleTags, t as tags } from "./code-palette-BWrZDi47.js";
|
|
2
|
+
import { html } from "./index-ptbBQ53d.js";
|
|
3
|
+
import { javascriptLanguage } from "./index-Cx5ThXzs.js";
|
|
4
|
+
import { L as LRParser, E as ExternalTokenizer } from "./index-2YPxXIaf.js";
|
|
5
5
|
const Text = 1, attributeContentSingle = 33, attributeContentDouble = 34, scriptAttributeContentSingle = 35, scriptAttributeContentDouble = 36;
|
|
6
6
|
const text = /* @__PURE__ */ new ExternalTokenizer((input) => {
|
|
7
7
|
let start = input.pos;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { E as EditorView,
|
|
2
|
-
import { html } from "./index-
|
|
3
|
-
import { L as LRParser, E as ExternalTokenizer } from "./index-
|
|
1
|
+
import { E as EditorView, d as EditorSelection, a as LanguageSupport, p as parseMixed, b as syntaxTree, L as LRLanguage, s as styleTags, t as tags, i as indentNodeProp, e as delimitedIndent, f as foldNodeProp } from "./code-palette-BWrZDi47.js";
|
|
2
|
+
import { html } from "./index-ptbBQ53d.js";
|
|
3
|
+
import { L as LRParser, E as ExternalTokenizer } from "./index-2YPxXIaf.js";
|
|
4
4
|
const interpolationStart = 1, tagStart = 2, endTagStart = 3, text = 180, endrawTagStart = 4, rawText = 181, endcommentTagStart = 5, commentText = 182, InlineComment = 6;
|
|
5
5
|
function wordChar(code) {
|
|
6
6
|
return code >= 65 && code <= 90 || code >= 97 && code <= 122;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { L as LRParser, E as ExternalTokenizer, C as ContextTracker } from "./index-
|
|
2
|
-
import {
|
|
3
|
-
import { cssLanguage, css } from "./index-
|
|
4
|
-
import { typescriptLanguage, jsxLanguage, tsxLanguage, javascriptLanguage, javascript } from "./index-
|
|
1
|
+
import { L as LRParser, E as ExternalTokenizer, C as ContextTracker } from "./index-2YPxXIaf.js";
|
|
2
|
+
import { p as parseMixed, s as styleTags, t as tags, E as EditorView, b as syntaxTree, d as EditorSelection, a as LanguageSupport, L as LRLanguage, i as indentNodeProp, f as foldNodeProp, ag as bracketMatchingHandle } from "./code-palette-BWrZDi47.js";
|
|
3
|
+
import { cssLanguage, css } from "./index-CefLTKmi.js";
|
|
4
|
+
import { typescriptLanguage, jsxLanguage, tsxLanguage, javascriptLanguage, javascript } from "./index-Cx5ThXzs.js";
|
|
5
5
|
const scriptText = 55, StartCloseScriptTag = 1, styleText = 56, StartCloseStyleTag = 2, textareaText = 57, StartCloseTextareaTag = 3, EndTag = 4, SelfClosingEndTag = 5, StartTag = 6, StartScriptTag = 7, StartStyleTag = 8, StartTextareaTag = 9, StartSelfClosingTag = 10, StartCloseTag = 11, NoMatchStartCloseTag = 12, MismatchedStartCloseTag = 13, missingCloseTag = 58, IncompleteTag = 14, IncompleteCloseTag = 15, commentContent$1 = 59, Element = 21, TagName = 23, Attribute = 24, AttributeName = 25, AttributeValue = 27, UnquotedAttributeValue = 28, ScriptText = 29, StyleText = 32, TextareaText = 35, OpenTag = 37, CloseTag = 38, Dialect_noMatch = 0, Dialect_selfClosing = 1;
|
|
6
6
|
const selfClosers$1 = {
|
|
7
7
|
area: true,
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { u } from "./useMessageHandler-
|
|
2
|
-
import { C, d, p, u as u2, b } from "./useChatKitLabels-
|
|
3
|
-
import { S, c, d as d2, f, u as u3, a, b as b2, e } from "./index-
|
|
4
|
-
import { _, a as a2, b as b3, c as c2, C as C2, d as d3, e as e2, f as f2, g, h, i, j, k, l, m, n, o, p as p2, q, r, s, t, u as u4, v, w, x, y, z, A, B, D, E } from "./CkHistory.vue_vue_type_script_setup_true_lang-
|
|
5
|
-
import { m as m2 } from "./markdownToPlainText-
|
|
1
|
+
import { u } from "./useMessageHandler-Bkzrdgfj.js";
|
|
2
|
+
import { C, d, p, u as u2, b } from "./useChatKitLabels-DxW0N5U-.js";
|
|
3
|
+
import { S, c, d as d2, f, u as u3, a, b as b2, e } from "./index-D0DQjdsq.js";
|
|
4
|
+
import { _, a as a2, b as b3, c as c2, C as C2, d as d3, e as e2, f as f2, g, h, i, j, k, l, m, n, o, p as p2, q, r, s, t, u as u4, v, w, x, y, z, A, B, D, E } from "./CkHistory.vue_vue_type_script_setup_true_lang-BvRIp3m6.js";
|
|
5
|
+
import { m as m2 } from "./markdownToPlainText-DN_G-I6g.js";
|
|
6
6
|
export {
|
|
7
7
|
C as CHAT_KIT_LABELS,
|
|
8
8
|
_ as CkAgentMessage,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { u as unified, r as remarkParse, a as remarkGfm } from "./index-
|
|
1
|
+
import { u as unified, r as remarkParse, a as remarkGfm } from "./index-CfVHzirA.js";
|
|
2
2
|
function generateId() {
|
|
3
3
|
return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : String(Date.now());
|
|
4
4
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { computed, toValue, inject, provide } from "vue";
|
|
2
2
|
import { p as purify } from "./katex.min-md74IFlY.js";
|
|
3
|
-
import { b as asciiAlphanumeric, o as ok, l as longestStreak,
|
|
3
|
+
import { b as asciiAlphanumeric, o as ok, l as longestStreak, c as factorySpace, m as markdownLineEnding, v as visit, d as convert, e as visitParents, S as SKIP, g as ccount, u as unified, r as remarkParse, a as remarkGfm, f as findAndReplace } from "./index-CfVHzirA.js";
|
|
4
4
|
function normalizeUri(value) {
|
|
5
5
|
const result = [];
|
|
6
6
|
let index = -1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aleph-alpha/chat-kit",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.4.1",
|
|
4
4
|
"description": "Chat kit package for building chat interfaces with Vue",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -115,9 +115,9 @@
|
|
|
115
115
|
"vue": "^3.5.27",
|
|
116
116
|
"vue-tsc": "^3.2.4",
|
|
117
117
|
"wait-on": "9.0.3",
|
|
118
|
+
"@aleph-alpha/eslint-config-frontend": "0.7.1",
|
|
118
119
|
"@aleph-alpha/prettier-config-frontend": "2.1.1",
|
|
119
|
-
"@aleph-alpha/tsconfig-frontend": "0.7.3"
|
|
120
|
-
"@aleph-alpha/eslint-config-frontend": "0.7.1"
|
|
120
|
+
"@aleph-alpha/tsconfig-frontend": "0.7.3"
|
|
121
121
|
},
|
|
122
122
|
"scripts": {
|
|
123
123
|
"build": "vite build && vue-tsc --project tsconfig.build.json --declaration --emitDeclarationOnly --outDir dist",
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { render, screen, waitFor } from '@testing-library/vue';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
3
|
import CkCodeEditor from './CkCodeEditor.vue';
|
|
4
|
-
import { resolveLanguage } from './createCodeMirror';
|
|
4
|
+
import { detectLineSeparator, resolveLanguage } from './createCodeMirror';
|
|
5
|
+
import { matchBadgeRanges } from './badgeDecoration';
|
|
5
6
|
|
|
6
7
|
// CodeMirror measures its own layout on construction; jsdom has none of the
|
|
7
8
|
// geometry APIs it calls. The stubs stay in this spec rather than the global
|
|
@@ -199,6 +200,124 @@ describe('CkCodeEditor', () => {
|
|
|
199
200
|
);
|
|
200
201
|
});
|
|
201
202
|
|
|
203
|
+
it('draws matched references as pills while the document keeps the text', async () => {
|
|
204
|
+
const { container } = await renderEditor({
|
|
205
|
+
modelValue: 'use @collection:alpha here',
|
|
206
|
+
badge: {
|
|
207
|
+
pattern: /@collection:([\w-]+)/g,
|
|
208
|
+
render: (m: RegExpExecArray) => ({ label: m[1] as string }),
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const pill = container.querySelector('.ck-code-badge');
|
|
213
|
+
expect(pill?.textContent).toBe('alpha');
|
|
214
|
+
// The raw reference is what a consumer reads back, not the label.
|
|
215
|
+
expect(container.querySelector('.cm-content')?.textContent).not.toContain(
|
|
216
|
+
'@collection:alpha',
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('leaves a match alone when the rule returns null', async () => {
|
|
221
|
+
const { container } = await renderEditor({
|
|
222
|
+
modelValue: '@collection:alpha and @collection:skip',
|
|
223
|
+
badge: {
|
|
224
|
+
pattern: /@collection:([\w-]+)/g,
|
|
225
|
+
render: (m: RegExpExecArray) =>
|
|
226
|
+
m[1] === 'skip' ? null : { label: m[1] as string },
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const pills = [...container.querySelectorAll('.ck-code-badge')].map(
|
|
231
|
+
(e) => e.textContent,
|
|
232
|
+
);
|
|
233
|
+
expect(pills).toEqual(['alpha']);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('keeps Windows line endings instead of rewriting them', async () => {
|
|
237
|
+
const crlf = 'alpha\r\nbeta\r\ngamma';
|
|
238
|
+
const { emitted } = await renderEditor({ modelValue: crlf });
|
|
239
|
+
|
|
240
|
+
const content = document.querySelector('.cm-content') as HTMLElement;
|
|
241
|
+
await userEvent.click(content);
|
|
242
|
+
await userEvent.type(content, '!');
|
|
243
|
+
|
|
244
|
+
await waitFor(() => expect(emitted()['update:modelValue']).toBeTruthy());
|
|
245
|
+
const updates = emitted()['update:modelValue'] as string[][];
|
|
246
|
+
const value = updates.at(-1)?.[0] ?? '';
|
|
247
|
+
// Every original separator survives, rather than being flattened to \n.
|
|
248
|
+
expect(value).toContain('alpha\r\nbeta\r\ngamma');
|
|
249
|
+
expect(value).not.toContain('alpha\nbeta');
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('leaves a Unix document on Unix endings', async () => {
|
|
253
|
+
const { emitted } = await renderEditor({ modelValue: 'alpha\nbeta' });
|
|
254
|
+
|
|
255
|
+
const content = document.querySelector('.cm-content') as HTMLElement;
|
|
256
|
+
await userEvent.click(content);
|
|
257
|
+
await userEvent.type(content, '!');
|
|
258
|
+
|
|
259
|
+
await waitFor(() => expect(emitted()['update:modelValue']).toBeTruthy());
|
|
260
|
+
const updates = emitted()['update:modelValue'] as string[][];
|
|
261
|
+
expect(updates.at(-1)?.[0]).not.toContain('\r');
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('wraps the code in the design system fade mask', async () => {
|
|
265
|
+
const { container } = await renderEditor({ modelValue: 'a\nb' });
|
|
266
|
+
|
|
267
|
+
const mask = container.querySelector('.ui-fade-mask');
|
|
268
|
+
expect(mask).toBeInTheDocument();
|
|
269
|
+
expect(mask?.querySelector('.ck-code-editor__host')).toBeInTheDocument();
|
|
270
|
+
// jsdom reports no layout, so nothing scrolls and the bands collapse.
|
|
271
|
+
expect(
|
|
272
|
+
(mask as HTMLElement).style.getPropertyValue('--ui-fade-mask-height'),
|
|
273
|
+
).toBe('0px');
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('omits the fade mask when `scrollFade` is off', async () => {
|
|
277
|
+
const { container } = await renderEditor({
|
|
278
|
+
modelValue: 'a\nb',
|
|
279
|
+
scrollFade: false,
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
expect(container.querySelector('.ui-fade-mask')).toBeNull();
|
|
283
|
+
expect(
|
|
284
|
+
container.querySelector('.ck-code-editor__host'),
|
|
285
|
+
).toBeInTheDocument();
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// CodeMirror reports focus changes from a 10ms timeout, so a test asserting
|
|
289
|
+
// that no event followed has to outlast it.
|
|
290
|
+
const settleFocus = () => new Promise((resolve) => setTimeout(resolve, 30));
|
|
291
|
+
|
|
292
|
+
it('keeps focus while it moves between the code and the header', async () => {
|
|
293
|
+
const { emitted } = await renderEditor({ modelValue: 'x' });
|
|
294
|
+
|
|
295
|
+
const content = document.querySelector('.cm-content') as HTMLElement;
|
|
296
|
+
await userEvent.click(content);
|
|
297
|
+
await waitFor(() => expect(emitted().focus).toBeTruthy());
|
|
298
|
+
|
|
299
|
+
// The copy button is inside the component, so the editor has not been left.
|
|
300
|
+
await userEvent.click(screen.getByRole('button', { name: 'Copy code' }));
|
|
301
|
+
await settleFocus();
|
|
302
|
+
expect(emitted().blur).toBeUndefined();
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('emits `blur` once focus leaves the component entirely', async () => {
|
|
306
|
+
const outside = document.createElement('button');
|
|
307
|
+
document.body.append(outside);
|
|
308
|
+
const { emitted } = await renderEditor({ modelValue: 'x' });
|
|
309
|
+
|
|
310
|
+
const content = document.querySelector('.cm-content') as HTMLElement;
|
|
311
|
+
await userEvent.click(content);
|
|
312
|
+
await userEvent.click(outside);
|
|
313
|
+
|
|
314
|
+
await waitFor(() => expect(emitted().blur).toHaveLength(1));
|
|
315
|
+
// And exactly once, not again from CodeMirror's own focus tracking.
|
|
316
|
+
await settleFocus();
|
|
317
|
+
expect(emitted().blur).toHaveLength(1);
|
|
318
|
+
outside.remove();
|
|
319
|
+
});
|
|
320
|
+
|
|
202
321
|
it('does not throw when unmounted before or after the editor is created', async () => {
|
|
203
322
|
const early = render(CkCodeEditor, { props: { modelValue: 'x' } });
|
|
204
323
|
expect(() => early.unmount()).not.toThrow();
|
|
@@ -221,3 +340,44 @@ describe('resolveLanguage', () => {
|
|
|
221
340
|
await expect(resolveLanguage('not-a-language')).resolves.toBeNull();
|
|
222
341
|
});
|
|
223
342
|
});
|
|
343
|
+
|
|
344
|
+
describe('matchBadgeRanges', () => {
|
|
345
|
+
const rule = {
|
|
346
|
+
pattern: /@collection:([\w-]+)/g,
|
|
347
|
+
render: (m: RegExpExecArray) => ({ label: m[1] as string }),
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
it('finds every occurrence, with or without the global flag', () => {
|
|
351
|
+
const text = '@collection:a then @collection:b';
|
|
352
|
+
expect(matchBadgeRanges(rule, text)).toHaveLength(2);
|
|
353
|
+
expect(
|
|
354
|
+
matchBadgeRanges({ ...rule, pattern: /@collection:([\w-]+)/ }, text),
|
|
355
|
+
).toHaveLength(2);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('spans exactly the matched text', () => {
|
|
359
|
+
const [range] = matchBadgeRanges(rule, 'x @collection:a y');
|
|
360
|
+
expect(range?.from).toBe(2);
|
|
361
|
+
expect(range?.to).toBe('x @collection:a'.length);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('skips matches the rule declines', () => {
|
|
365
|
+
expect(
|
|
366
|
+
matchBadgeRanges({ ...rule, render: () => null }, '@collection:a'),
|
|
367
|
+
).toHaveLength(0);
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
describe('detectLineSeparator', () => {
|
|
372
|
+
it('reports the separator a document already uses', () => {
|
|
373
|
+
expect(detectLineSeparator('a\nb')).toBe('\n');
|
|
374
|
+
expect(detectLineSeparator('a\r\nb')).toBe('\r\n');
|
|
375
|
+
expect(detectLineSeparator('no newlines at all')).toBe('\n');
|
|
376
|
+
expect(detectLineSeparator('')).toBe('\n');
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('follows the majority in a mixed document', () => {
|
|
380
|
+
expect(detectLineSeparator('a\r\nb\r\nc\nd')).toBe('\r\n');
|
|
381
|
+
expect(detectLineSeparator('a\nb\nc\r\nd')).toBe('\n');
|
|
382
|
+
});
|
|
383
|
+
});
|
|
@@ -4,6 +4,32 @@ import type { Meta, StoryObj } from '@storybook/vue3-vite';
|
|
|
4
4
|
import CkCodeEditor from './CkCodeEditor.vue';
|
|
5
5
|
import CkMarkdownRenderer from '../CkMarkdownRenderer/CkMarkdownRenderer.vue';
|
|
6
6
|
|
|
7
|
+
const folderIcon =
|
|
8
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" ' +
|
|
9
|
+
'stroke="currentColor" stroke-width="2" stroke-linecap="round" ' +
|
|
10
|
+
'stroke-linejoin="round" width="12" height="12" aria-hidden="true">' +
|
|
11
|
+
'<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>';
|
|
12
|
+
|
|
13
|
+
/** Matches `@collection:<id>` and draws each one as a folder pill. */
|
|
14
|
+
const collectionBadge = {
|
|
15
|
+
pattern: /@collection:([\w-]+)/g,
|
|
16
|
+
render: (match: RegExpExecArray) => ({
|
|
17
|
+
label: (match[1] ?? '').replace(/-/g, ' '),
|
|
18
|
+
icon: folderIcon,
|
|
19
|
+
title: match[0],
|
|
20
|
+
}),
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** Matches `{{ name }}`, showing that the trigger is whatever you pattern for. */
|
|
24
|
+
const handlebarsBadge = {
|
|
25
|
+
pattern: /\{\{\s*([\w-]+)\s*\}\}/g,
|
|
26
|
+
render: (match: RegExpExecArray) => ({
|
|
27
|
+
label: match[1] ?? '',
|
|
28
|
+
icon: folderIcon,
|
|
29
|
+
title: match[0],
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
32
|
+
|
|
7
33
|
const meta: Meta<typeof CkCodeEditor> = {
|
|
8
34
|
title: 'Base/CkCodeEditor',
|
|
9
35
|
component: CkCodeEditor,
|
|
@@ -31,6 +57,21 @@ const meta: Meta<typeof CkCodeEditor> = {
|
|
|
31
57
|
showLineNumbers: { control: 'boolean' },
|
|
32
58
|
lineWrapping: { control: 'boolean' },
|
|
33
59
|
autofocus: { control: 'boolean' },
|
|
60
|
+
scrollFade: { control: 'boolean' },
|
|
61
|
+
// A regex plus a callback cannot be typed into a control, so the rule is
|
|
62
|
+
// offered as named presets instead. Without this, badges could only be seen
|
|
63
|
+
// on the one story that hard-codes a rule.
|
|
64
|
+
badge: {
|
|
65
|
+
options: ['none', 'collections', 'handlebars'],
|
|
66
|
+
mapping: {
|
|
67
|
+
none: undefined,
|
|
68
|
+
collections: collectionBadge,
|
|
69
|
+
handlebars: handlebarsBadge,
|
|
70
|
+
},
|
|
71
|
+
control: { type: 'radio' },
|
|
72
|
+
description:
|
|
73
|
+
'Pattern plus a render callback turning matched text into inline pills. Pick a preset to try it on any story.',
|
|
74
|
+
},
|
|
34
75
|
writeToClipboard: {
|
|
35
76
|
control: false,
|
|
36
77
|
description:
|
|
@@ -69,6 +110,14 @@ const pythonSample = [
|
|
|
69
110
|
' return f"{opener}, {self.name}!"',
|
|
70
111
|
].join('\n');
|
|
71
112
|
|
|
113
|
+
const badgeSample = [
|
|
114
|
+
'You improve and draft work writing: emails, project summaries and status',
|
|
115
|
+
'updates, tightening structure and matching register to the audience.',
|
|
116
|
+
'',
|
|
117
|
+
'For analyses, use @collection:collection-name-3 and @collection:collection-name-4',
|
|
118
|
+
'and distinguish an opinion from a fact.',
|
|
119
|
+
].join('\n');
|
|
120
|
+
|
|
72
121
|
const defaultTemplateSource = `<script setup lang="ts">
|
|
73
122
|
import { ref } from 'vue';
|
|
74
123
|
import { CkCodeEditor } from '@aleph-alpha/chat-kit';
|
|
@@ -149,6 +198,29 @@ const code = ref(longFile);
|
|
|
149
198
|
<CkCodeEditor v-model="code" language="typescript" max-height="16rem" line-wrapping />
|
|
150
199
|
</template>`;
|
|
151
200
|
|
|
201
|
+
const withBadgesTemplateSource = `<script setup lang="ts">
|
|
202
|
+
import { ref } from 'vue';
|
|
203
|
+
import { CkCodeEditor } from '@aleph-alpha/chat-kit';
|
|
204
|
+
|
|
205
|
+
const prompt = ref('For analyses, use @collection:sales and @collection:support.');
|
|
206
|
+
|
|
207
|
+
// The document keeps the raw text; only the drawing is replaced.
|
|
208
|
+
const badge = {
|
|
209
|
+
pattern: /@collection:([\\w-]+)/g,
|
|
210
|
+
render: (m) => ({ label: m[1], icon: folderIcon, title: m[0] }),
|
|
211
|
+
};
|
|
212
|
+
</script>
|
|
213
|
+
|
|
214
|
+
<template>
|
|
215
|
+
<CkCodeEditor
|
|
216
|
+
v-model="prompt"
|
|
217
|
+
language="markdown"
|
|
218
|
+
:badge="badge"
|
|
219
|
+
:line-wrapping="true"
|
|
220
|
+
aria-label="Prompt"
|
|
221
|
+
/>
|
|
222
|
+
</template>`;
|
|
223
|
+
|
|
152
224
|
const matchesRendererTemplateSource = `<script setup lang="ts">
|
|
153
225
|
import { ref } from 'vue';
|
|
154
226
|
import { CkCodeEditor, CkMarkdownRenderer } from '@aleph-alpha/chat-kit';
|
|
@@ -253,7 +325,8 @@ export const WithoutHeader: Story = {
|
|
|
253
325
|
|
|
254
326
|
/**
|
|
255
327
|
* `maxHeight` caps the scrollable area and `lineWrapping` folds long lines
|
|
256
|
-
* instead of scrolling sideways.
|
|
328
|
+
* instead of scrolling sideways. Scroll it: the code fades at whichever edge
|
|
329
|
+
* has more of the document beyond it, and neither edge fades once you reach it.
|
|
257
330
|
*/
|
|
258
331
|
export const ScrollingAndWrapping: Story = {
|
|
259
332
|
render: (args) => ({
|
|
@@ -279,6 +352,29 @@ export const ScrollingAndWrapping: Story = {
|
|
|
279
352
|
parameters: { docs: { source: { code: scrollingTemplateSource } } },
|
|
280
353
|
};
|
|
281
354
|
|
|
355
|
+
/**
|
|
356
|
+
* `badge` draws matched references as inline pills, the way a mention renders in
|
|
357
|
+
* prose. The document still holds the raw `@collection:…` text, so `getCode()`
|
|
358
|
+
* and copy give the real reference; a pill deletes as one unit.
|
|
359
|
+
*/
|
|
360
|
+
export const WithBadges: Story = {
|
|
361
|
+
render: (args) => ({
|
|
362
|
+
components: { CkCodeEditor },
|
|
363
|
+
setup() {
|
|
364
|
+
const code = ref(args.modelValue ?? '');
|
|
365
|
+
return { args, code };
|
|
366
|
+
},
|
|
367
|
+
template: `<CkCodeEditor v-bind="args" v-model="code" aria-label="Prompt" />`,
|
|
368
|
+
}),
|
|
369
|
+
args: {
|
|
370
|
+
modelValue: badgeSample,
|
|
371
|
+
language: 'markdown',
|
|
372
|
+
badge: collectionBadge,
|
|
373
|
+
lineWrapping: true,
|
|
374
|
+
},
|
|
375
|
+
parameters: { docs: { source: { code: withBadgesTemplateSource } } },
|
|
376
|
+
};
|
|
377
|
+
|
|
282
378
|
/**
|
|
283
379
|
* Parity check: the editor on the left, the same snippet rendered through
|
|
284
380
|
* `CkMarkdownRenderer` and `UiCodeBlock` on the right. Border, radius, header,
|