@aleph-alpha/chat-kit 6.0.0 → 6.1.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.
Files changed (164) hide show
  1. package/dist/{CkHistory.vue_vue_type_script_setup_true_lang-D9Ph9eYp.js → CkHistory.vue_vue_type_script_setup_true_lang-Cr8vF94e.js} +214 -107
  2. package/dist/apl-fqmucPXA.js +140 -0
  3. package/dist/asciiarmor-DucZyvP0.js +56 -0
  4. package/dist/asn1-BnOEsgAm.js +144 -0
  5. package/dist/asterisk-QAlztEwS.js +345 -0
  6. package/dist/brainfuck-DZVCuF_t.js +53 -0
  7. package/dist/chat-kit.css +1184 -1
  8. package/dist/clike-CTYGlS6x.js +799 -0
  9. package/dist/clojure-BhXMqnxz.js +849 -0
  10. package/dist/cmake-BGaNd9E7.js +71 -0
  11. package/dist/cobol-4yqQntpt.js +120 -0
  12. package/dist/coffeescript-D2dXvhEc.js +308 -0
  13. package/dist/commonlisp-CF_VNHQR.js +130 -0
  14. package/dist/components/base/CkMilkdownEditor/CkMilkdownEditor.stories.d.ts +41 -0
  15. package/dist/components/base/CkMilkdownEditor/CkMilkdownEditor.stories.d.ts.map +1 -0
  16. package/dist/components/base/CkMilkdownEditor/CkMilkdownEditor.vue.d.ts +26 -0
  17. package/dist/components/base/CkMilkdownEditor/CkMilkdownEditor.vue.d.ts.map +1 -0
  18. package/dist/components/base/CkMilkdownEditor/codeMirrorTheme.d.ts +20 -0
  19. package/dist/components/base/CkMilkdownEditor/codeMirrorTheme.d.ts.map +1 -0
  20. package/dist/components/base/CkMilkdownEditor/createCrepeEditor.d.ts +42 -0
  21. package/dist/components/base/CkMilkdownEditor/createCrepeEditor.d.ts.map +1 -0
  22. package/dist/components/base/CkMilkdownEditor/crepeIcons.d.ts +33 -0
  23. package/dist/components/base/CkMilkdownEditor/crepeIcons.d.ts.map +1 -0
  24. package/dist/components/base/CkMilkdownEditor/index.d.ts +3 -0
  25. package/dist/components/base/CkMilkdownEditor/index.d.ts.map +1 -0
  26. package/dist/components/base/CkMilkdownEditor/markdownClassAttrs.d.ts +16 -0
  27. package/dist/components/base/CkMilkdownEditor/markdownClassAttrs.d.ts.map +1 -0
  28. package/dist/components/base/CkMilkdownEditor/types.d.ts +98 -0
  29. package/dist/components/base/CkMilkdownEditor/types.d.ts.map +1 -0
  30. package/dist/components/base/index.d.ts +2 -0
  31. package/dist/components/base/index.d.ts.map +1 -1
  32. package/dist/components/index.d.ts +2 -2
  33. package/dist/components/index.d.ts.map +1 -1
  34. package/dist/components/index.js +12 -11
  35. package/dist/createCrepeEditor-CFbvSH3s.js +74141 -0
  36. package/dist/crystal-DyuLTqLs.js +398 -0
  37. package/dist/css-DkSyTW67.js +1772 -0
  38. package/dist/cypher-Dlu_3r4V.js +121 -0
  39. package/dist/d-UURgV0Ux.js +179 -0
  40. package/dist/diff-B_Bi2Crb.js +25 -0
  41. package/dist/dockerfile-Bvk733Ga.js +201 -0
  42. package/dist/dtd-Dy74G54E.js +114 -0
  43. package/dist/dylan-TSb-Nfix.js +314 -0
  44. package/dist/ebnf-4fKAGW3a.js +140 -0
  45. package/dist/ecl-B59qGGVg.js +178 -0
  46. package/dist/eiffel-Dze7nlu3.js +134 -0
  47. package/dist/elm-DG7jkhNZ.js +176 -0
  48. package/dist/erlang-BO6gOnGA.js +674 -0
  49. package/dist/factor-CMxFHDqz.js +65 -0
  50. package/dist/fcl-CDDUNjTj.js +141 -0
  51. package/dist/forth-B9D2JCeE.js +116 -0
  52. package/dist/fortran-CAG2BFbe.js +467 -0
  53. package/dist/gas-0Aw8zDr5.js +293 -0
  54. package/dist/gherkin-DhZlEZiy.js +115 -0
  55. package/dist/groovy-CpwJiBl7.js +223 -0
  56. package/dist/handlers/index.js +1 -1
  57. package/dist/handlers/useChatKitLabels.d.ts +27 -0
  58. package/dist/handlers/useChatKitLabels.d.ts.map +1 -1
  59. package/dist/haskell-ySd-OUo8.js +459 -0
  60. package/dist/haxe-7MlzfeYV.js +514 -0
  61. package/dist/http-BqypyemW.js +79 -0
  62. package/dist/idl-4HIGJlDI.js +985 -0
  63. package/dist/index-A5rWjRtZ.js +689 -0
  64. package/dist/index-B0ldhzaj.js +385 -0
  65. package/dist/index-BVOjyn3y.js +2490 -0
  66. package/dist/index-BWvkps_G.js +309 -0
  67. package/dist/index-BaS8OW8V.js +151 -0
  68. package/dist/index-BlHHEbV-.js +406 -0
  69. package/dist/index-BoqzHVNB.js +82 -0
  70. package/dist/index-C6zYjwIQ.js +704 -0
  71. package/dist/index-CMJGuXap.js +330 -0
  72. package/dist/index-CRfGWVdQ.js +116 -0
  73. package/dist/index-C_gTb50u.js +81 -0
  74. package/dist/index-DSU7cBGz.js +178 -0
  75. package/dist/index-DSq3gvqU.js +1767 -0
  76. package/dist/index-Da3777l2.js +289 -0
  77. package/dist/index-DdlzabmR.js +1021 -0
  78. package/dist/index-DhY_9bdS.js +61 -0
  79. package/dist/index-Dm6C1blN.js +1052 -0
  80. package/dist/index-Tj3pzx-E.js +327 -0
  81. package/dist/index-ZeRS5rER.js +97 -0
  82. package/dist/index-gzleljO6.js +157 -0
  83. package/dist/index-naWlQpaL.js +642 -0
  84. package/dist/index-sKO1lHHC.js +312 -0
  85. package/dist/index.d.ts +2 -2
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +14 -13
  88. package/dist/javascript-Bt8B7yTi.js +992 -0
  89. package/dist/julia-Bs6JJhYG.js +407 -0
  90. package/dist/livescript-DmzgM3Yt.js +296 -0
  91. package/dist/lua-Cvg8VrAA.js +256 -0
  92. package/dist/mathematica-DNLOL9PQ.js +110 -0
  93. package/dist/mbox-Ga7d4MMN.js +117 -0
  94. package/dist/mirc-Dma3B8rS.js +107 -0
  95. package/dist/mllike-DHn7xckP.js +334 -0
  96. package/dist/modelica-0d55jYY0.js +147 -0
  97. package/dist/mscgen-DdqZYINH.js +135 -0
  98. package/dist/mumps-Btr8VblO.js +93 -0
  99. package/dist/nginx-DTDtBDVN.js +141 -0
  100. package/dist/nsis-CyKBdPWp.js +62 -0
  101. package/dist/ntriples-CvgOYMpL.js +153 -0
  102. package/dist/octave-DYBj3-tl.js +200 -0
  103. package/dist/oz-R_e8WMIi.js +231 -0
  104. package/dist/pascal-GD8iposT.js +105 -0
  105. package/dist/perl-DL9mHpoi.js +1105 -0
  106. package/dist/pig-C_4T4YIV.js +101 -0
  107. package/dist/powershell-B0suO7Vd.js +328 -0
  108. package/dist/properties-BR-vP1aU.js +58 -0
  109. package/dist/protobuf-BxgpyhoW.js +77 -0
  110. package/dist/pug-vHnWR0UE.js +405 -0
  111. package/dist/puppet-Bdao66PW.js +137 -0
  112. package/dist/python-BFGRmuZ9.js +426 -0
  113. package/dist/q-CrbCVq4a.js +131 -0
  114. package/dist/r-V7nswm59.js +170 -0
  115. package/dist/rpm-C-DLY-If.js +109 -0
  116. package/dist/ruby-CQTpZw6f.js +331 -0
  117. package/dist/sas-D2UG-yhZ.js +207 -0
  118. package/dist/scheme-BKzrkGJD.js +222 -0
  119. package/dist/shell-BlsXDxCn.js +222 -0
  120. package/dist/sieve-CjwBwOY5.js +135 -0
  121. package/dist/simple-mode-DMneyfDu.js +130 -0
  122. package/dist/smalltalk-BOIGQuhN.js +121 -0
  123. package/dist/solr-CwD7U71z.js +69 -0
  124. package/dist/sparql-DYskk2vE.js +249 -0
  125. package/dist/spreadsheet-Bgtt3oLP.js +87 -0
  126. package/dist/sql-Cei9CMfk.js +342 -0
  127. package/dist/stex-C1nZSzAw.js +230 -0
  128. package/dist/stylus-BkS-boTH.js +565 -0
  129. package/dist/swift-FRZi1uvB.js +291 -0
  130. package/dist/tcl-CUcaCdmq.js +114 -0
  131. package/dist/textile-BnFpjsrl.js +414 -0
  132. package/dist/tiddlywiki-CjprD-Qp.js +218 -0
  133. package/dist/tiki-DK9DOeWn.js +268 -0
  134. package/dist/toml-BOuWGMcf.js +76 -0
  135. package/dist/troff-E1bJ0PPL.js +61 -0
  136. package/dist/ttcn-cfg-Dc39-fIP.js +133 -0
  137. package/dist/ttcn-tKd4HLu4.js +192 -0
  138. package/dist/turtle-Dq7-1WAf.js +124 -0
  139. package/dist/{useChatKitLabels-368lodS5.js → useChatKitLabels-Ww1Q7xUr.js} +25 -2
  140. package/dist/vb-Dp90gtsv.js +196 -0
  141. package/dist/vbscript-Bfn8O8I7.js +478 -0
  142. package/dist/velocity-BwIZK1TH.js +149 -0
  143. package/dist/verilog-CnT9bMk0.js +429 -0
  144. package/dist/vhdl-DCkMIyT9.js +158 -0
  145. package/dist/webidl-BTLTThCm.js +204 -0
  146. package/dist/xquery-BrBUuxMR.js +525 -0
  147. package/dist/yacas-b5lAVEIl.js +130 -0
  148. package/dist/z80-ClgwfNdB.js +92 -0
  149. package/package.json +12 -6
  150. package/src/components/base/CkMilkdownEditor/CkMilkdownEditor.spec.ts +221 -0
  151. package/src/components/base/CkMilkdownEditor/CkMilkdownEditor.stories.ts +274 -0
  152. package/src/components/base/CkMilkdownEditor/CkMilkdownEditor.vue +151 -0
  153. package/src/components/base/CkMilkdownEditor/codeMirrorTheme.ts +132 -0
  154. package/src/components/base/CkMilkdownEditor/createCrepeEditor.ts +192 -0
  155. package/src/components/base/CkMilkdownEditor/crepeIcons.ts +77 -0
  156. package/src/components/base/CkMilkdownEditor/index.ts +2 -0
  157. package/src/components/base/CkMilkdownEditor/markdownClassAttrs.ts +64 -0
  158. package/src/components/base/CkMilkdownEditor/milkdown-theme.css +459 -0
  159. package/src/components/base/CkMilkdownEditor/types.ts +100 -0
  160. package/src/components/base/CkPromptInputDisclaimer/CkPromptInputDisclaimer.stories.ts +2 -2
  161. package/src/components/base/index.ts +6 -0
  162. package/src/components/index.ts +4 -0
  163. package/src/handlers/useChatKitLabels.ts +27 -0
  164. package/src/index.ts +4 -0
@@ -0,0 +1,151 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ computed,
4
+ onBeforeUnmount,
5
+ onMounted,
6
+ ref,
7
+ useTemplateRef,
8
+ watch,
9
+ } from 'vue';
10
+ import { useChatKitLabels } from '../../../handlers/useChatKitLabels';
11
+ import { defaultMarkdownClasses } from '../CkMarkdownRenderer/defaultMarkdownClasses';
12
+ import type { CrepeEditorHandle } from './createCrepeEditor';
13
+ import type { CkMilkdownEditorEmits, CkMilkdownEditorProps } from './types';
14
+
15
+ defineOptions({ name: 'CkMilkdownEditor' });
16
+
17
+ const props = withDefaults(defineProps<CkMilkdownEditorProps>(), {
18
+ modelValue: '',
19
+ placeholder: undefined,
20
+ readonly: false,
21
+ disabled: false,
22
+ ariaLabel: undefined,
23
+ classes: () => ({}),
24
+ autofocus: false,
25
+ });
26
+
27
+ const emit = defineEmits<CkMilkdownEditorEmits>();
28
+
29
+ const labels = useChatKitLabels();
30
+ const rootRef = useTemplateRef<HTMLDivElement>('rootRef');
31
+ const isLocked = computed(() => props.readonly || props.disabled);
32
+ // Flips once Crepe has finished creating the view; exposed as a root class so
33
+ // consumers (and tests) can tell the lazy-loaded editor apart from its shell.
34
+ const isReady = ref(false);
35
+
36
+ let editor: CrepeEditorHandle | undefined;
37
+ let destroyed = false;
38
+ // The last markdown we either emitted or applied from the prop. Comparing
39
+ // against it (rather than a boolean "applying" flag) is what makes the echo
40
+ // guard immune to Milkdown's ~200 ms debounce on `markdownUpdated`.
41
+ let lastKnownMarkdown = props.modelValue;
42
+
43
+ function handleMarkdownUpdated(markdown: string): void {
44
+ if (markdown === lastKnownMarkdown) return;
45
+ lastKnownMarkdown = markdown;
46
+ emit('update:modelValue', markdown);
47
+ }
48
+
49
+ onMounted(async () => {
50
+ const root = rootRef.value;
51
+ if (!root) return;
52
+
53
+ // Lazy: Milkdown, ProseMirror and CodeMirror only load once an editor is
54
+ // actually mounted, keeping them out of consumers' initial bundle.
55
+ const { createCrepeEditor } = await import('./createCrepeEditor');
56
+ if (destroyed) return;
57
+
58
+ const initialMarkdown = props.modelValue;
59
+ lastKnownMarkdown = initialMarkdown;
60
+ const handle = await createCrepeEditor({
61
+ root,
62
+ defaultValue: initialMarkdown,
63
+ placeholder: props.placeholder,
64
+ ariaLabel: props.ariaLabel,
65
+ readonly: isLocked.value,
66
+ classes: { ...defaultMarkdownClasses, ...props.classes },
67
+ labels: labels.value,
68
+ onMarkdownUpdated: handleMarkdownUpdated,
69
+ });
70
+
71
+ // Unmounted while `create()` was pending.
72
+ if (destroyed) {
73
+ handle.destroy();
74
+ return;
75
+ }
76
+
77
+ editor = handle;
78
+ // Props may have moved on while the editor was being created; the watchers
79
+ // below could not apply them yet, so catch up here.
80
+ if (props.modelValue !== initialMarkdown)
81
+ handle.setMarkdown(props.modelValue);
82
+ handle.setReadonly(isLocked.value);
83
+ isReady.value = true;
84
+ if (props.autofocus) handle.focus();
85
+ });
86
+
87
+ watch(
88
+ () => props.modelValue,
89
+ (next) => {
90
+ if (next === lastKnownMarkdown) return;
91
+ lastKnownMarkdown = next;
92
+ editor?.setMarkdown(next);
93
+ },
94
+ );
95
+
96
+ watch(isLocked, (locked) => {
97
+ editor?.setReadonly(locked);
98
+ });
99
+
100
+ onBeforeUnmount(() => {
101
+ destroyed = true;
102
+ editor?.destroy();
103
+ editor = undefined;
104
+ });
105
+
106
+ function isInsideRoot(target: EventTarget | null): boolean {
107
+ return target instanceof Node && (rootRef.value?.contains(target) ?? false);
108
+ }
109
+
110
+ // The floating toolbar and the link tooltip live inside the root, so moving
111
+ // focus between them and the text must not count as leaving the editor.
112
+ function onFocusIn(event: FocusEvent): void {
113
+ if (!isInsideRoot(event.relatedTarget)) emit('focus');
114
+ }
115
+
116
+ function onFocusOut(event: FocusEvent): void {
117
+ if (!isInsideRoot(event.relatedTarget)) emit('blur');
118
+ }
119
+
120
+ function focus(): void {
121
+ editor?.focus();
122
+ }
123
+
124
+ function getMarkdown(): string {
125
+ return editor?.getMarkdown() ?? props.modelValue;
126
+ }
127
+
128
+ defineExpose({ focus, getMarkdown });
129
+ </script>
130
+
131
+ <template>
132
+ <div
133
+ ref="rootRef"
134
+ :class="[
135
+ 'ck-milkdown min-w-0 break-words',
136
+ isReady && 'ck-milkdown--ready',
137
+ disabled && 'ck-milkdown--disabled',
138
+ ]"
139
+ :aria-disabled="disabled || undefined"
140
+ @focusin="onFocusIn"
141
+ @focusout="onFocusOut"
142
+ />
143
+ </template>
144
+
145
+ <style scoped>
146
+ .ck-milkdown--disabled {
147
+ opacity: 0.6;
148
+ cursor: not-allowed;
149
+ pointer-events: none;
150
+ }
151
+ </style>
@@ -0,0 +1,132 @@
1
+ import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
2
+ import type { Extension } from '@codemirror/state';
3
+ import { EditorView } from '@codemirror/view';
4
+ import { tags as t } from '@lezer/highlight';
5
+
6
+ /**
7
+ * Same monospace stack as `UiCodeBlock` in ui-library, so code in the editor
8
+ * and in the rendered message uses identical glyph metrics.
9
+ */
10
+ export const CODE_FONT_FAMILY =
11
+ "ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace";
12
+
13
+ // Every colour is a CSS custom property so the light and dark palettes live in
14
+ // `milkdown-theme.css` (`.ck-milkdown` / `html.dark .ck-milkdown`) and switch
15
+ // with the rest of the design tokens — no JavaScript theme swap needed.
16
+ const cssVar = (name: string): string => `var(--ck-cm-${name})`;
17
+
18
+ /**
19
+ * Token → colour mapping approximating Shiki's `github-light` / `github-dark`
20
+ * themes, which `UiCodeBlock` uses, so highlighted code looks the same in the
21
+ * editor and in `CkMarkdownRenderer`.
22
+ */
23
+ export const ckHighlightStyle = HighlightStyle.define([
24
+ {
25
+ tag: [
26
+ t.keyword,
27
+ t.modifier,
28
+ t.controlKeyword,
29
+ t.definitionKeyword,
30
+ t.moduleKeyword,
31
+ t.operatorKeyword,
32
+ ],
33
+ color: cssVar('keyword'),
34
+ },
35
+ {
36
+ tag: [t.string, t.special(t.string), t.regexp, t.escape, t.character],
37
+ color: cssVar('string'),
38
+ },
39
+ {
40
+ tag: [t.comment, t.lineComment, t.blockComment, t.docComment],
41
+ color: cssVar('comment'),
42
+ },
43
+ {
44
+ tag: [
45
+ t.function(t.variableName),
46
+ t.function(t.propertyName),
47
+ t.definition(t.function(t.variableName)),
48
+ ],
49
+ color: cssVar('function'),
50
+ },
51
+ {
52
+ tag: [
53
+ t.number,
54
+ t.bool,
55
+ t.null,
56
+ t.atom,
57
+ t.constant(t.name),
58
+ t.constant(t.variableName),
59
+ t.propertyName,
60
+ t.attributeName,
61
+ t.attributeValue,
62
+ ],
63
+ color: cssVar('constant'),
64
+ },
65
+ {
66
+ tag: [
67
+ t.typeName,
68
+ t.className,
69
+ t.namespace,
70
+ t.definition(t.variableName),
71
+ t.self,
72
+ ],
73
+ color: cssVar('entity'),
74
+ },
75
+ { tag: [t.tagName, t.angleBracket], color: cssVar('tag') },
76
+ {
77
+ tag: [t.variableName, t.operator, t.punctuation, t.bracket, t.separator],
78
+ color: cssVar('foreground'),
79
+ },
80
+ { tag: t.heading, fontWeight: 'bold' },
81
+ { tag: t.strong, fontWeight: 'bold' },
82
+ { tag: t.emphasis, fontStyle: 'italic' },
83
+ { tag: t.link, textDecoration: 'underline' },
84
+ { tag: t.invalid, color: cssVar('error') },
85
+ ]);
86
+
87
+ /**
88
+ * CodeMirror extension bundling the editor chrome (`UiCodeBlock`-like metrics:
89
+ * 14px mono text, 1.625 line height, 1rem padding, no gutter) with
90
+ * {@link ckHighlightStyle}. Pass it as the Crepe `CodeMirror` feature `theme`.
91
+ */
92
+ export const ckCodeMirrorTheme: Extension = [
93
+ EditorView.theme({
94
+ '&': {
95
+ backgroundColor: cssVar('bg'),
96
+ color: cssVar('foreground'),
97
+ },
98
+ '.cm-scroller': {
99
+ fontFamily: CODE_FONT_FAMILY,
100
+ fontSize: '0.875rem',
101
+ lineHeight: '1.625',
102
+ },
103
+ '.cm-content': {
104
+ padding: '1rem',
105
+ caretColor: cssVar('foreground'),
106
+ },
107
+ '.cm-line': {
108
+ padding: '0',
109
+ },
110
+ '.cm-gutters': {
111
+ display: 'none',
112
+ },
113
+ '.cm-cursor, .cm-dropCursor': {
114
+ borderLeftColor: cssVar('foreground'),
115
+ },
116
+ '&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection':
117
+ {
118
+ backgroundColor: cssVar('selection'),
119
+ },
120
+ '.cm-activeLine': {
121
+ backgroundColor: 'transparent',
122
+ },
123
+ '&.cm-focused': {
124
+ outline: 'none',
125
+ },
126
+ '.cm-matchingBracket, .cm-nonmatchingBracket': {
127
+ backgroundColor: 'transparent',
128
+ outline: `1px solid ${cssVar('comment')}`,
129
+ },
130
+ }),
131
+ syntaxHighlighting(ckHighlightStyle),
132
+ ];
@@ -0,0 +1,192 @@
1
+ import { Crepe, CrepeFeature, type CrepeConfig } from '@milkdown/crepe';
2
+ import { editorViewCtx, editorViewOptionsCtx } from '@milkdown/kit/core';
3
+ import { replaceAll } from '@milkdown/kit/utils';
4
+ import {
5
+ defaultChatKitLabels,
6
+ type ChatKitLabels,
7
+ } from '../../../handlers/useChatKitLabels';
8
+ import type { CkMarkdownElementClasses } from '../CkMarkdownRenderer';
9
+ import { ckCodeMirrorTheme } from './codeMirrorTheme';
10
+ import { crepeIcons } from './crepeIcons';
11
+ import { applyMarkdownClasses } from './markdownClassAttrs';
12
+
13
+ // Crepe's per-feature stylesheets, imported individually. `reset.css` (part of
14
+ // the bundled `theme/common/style.css`) is deliberately skipped: it styles
15
+ // headings, paragraphs, code, links and quotes with two-class selectors that
16
+ // would beat the utility classes `applyMarkdownClasses` puts on the nodes, and
17
+ // it forces a 60px/120px document padding. `milkdown-theme.css` replaces it
18
+ // and maps Crepe's `--crepe-*` variables to design tokens. It is imported last
19
+ // so its rules win ties.
20
+ import '@milkdown/crepe/theme/common/prosemirror.css';
21
+ import '@milkdown/crepe/theme/common/cursor.css';
22
+ import '@milkdown/crepe/theme/common/placeholder.css';
23
+ import '@milkdown/crepe/theme/common/list-item.css';
24
+ import '@milkdown/crepe/theme/common/link-tooltip.css';
25
+ import '@milkdown/crepe/theme/common/toolbar.css';
26
+ import '@milkdown/crepe/theme/common/code-mirror.css';
27
+ import './milkdown-theme.css';
28
+
29
+ export interface CreateCrepeEditorOptions {
30
+ /** Element the editor is mounted into. */
31
+ root: HTMLElement;
32
+ /** Initial markdown. */
33
+ defaultValue: string;
34
+ placeholder?: string;
35
+ ariaLabel?: string;
36
+ /** Start in read-only mode (applied before the view is created). */
37
+ readonly?: boolean;
38
+ /** Fully merged per-tag class map (defaults + consumer overrides). */
39
+ classes: CkMarkdownElementClasses;
40
+ labels: ChatKitLabels;
41
+ /** Called (debounced by Milkdown) with the serialised document on change. */
42
+ onMarkdownUpdated: (markdown: string) => void;
43
+ }
44
+
45
+ /** Imperative surface `CkMilkdownEditor` drives; keeps Milkdown types out of the SFC. */
46
+ export interface CrepeEditorHandle {
47
+ crepe: Crepe;
48
+ /** Replace the whole document with new markdown. */
49
+ setMarkdown: (markdown: string) => void;
50
+ getMarkdown: () => string;
51
+ setReadonly: (readonly: boolean) => void;
52
+ focus: () => void;
53
+ destroy: () => void;
54
+ }
55
+
56
+ type LabelKey = keyof typeof defaultChatKitLabels;
57
+
58
+ function pickLabel(labels: ChatKitLabels, key: LabelKey): string {
59
+ return labels[key] ?? defaultChatKitLabels[key];
60
+ }
61
+
62
+ const FEATURES: Partial<Record<CrepeFeature, boolean>> = {
63
+ [Crepe.Feature.Cursor]: true,
64
+ [Crepe.Feature.ListItem]: true,
65
+ [Crepe.Feature.LinkTooltip]: true,
66
+ [Crepe.Feature.Placeholder]: true,
67
+ [Crepe.Feature.Toolbar]: true,
68
+ [Crepe.Feature.CodeMirror]: true,
69
+ [Crepe.Feature.BlockEdit]: false,
70
+ [Crepe.Feature.ImageBlock]: false,
71
+ [Crepe.Feature.Table]: false,
72
+ [Crepe.Feature.Latex]: false,
73
+ [Crepe.Feature.TopBar]: false,
74
+ [Crepe.Feature.AI]: false,
75
+ };
76
+
77
+ function buildFeatureConfigs(
78
+ options: CreateCrepeEditorOptions,
79
+ ): NonNullable<CrepeConfig['featureConfigs']> {
80
+ const { labels } = options;
81
+ return {
82
+ [Crepe.Feature.Toolbar]: {
83
+ boldIcon: crepeIcons.bold,
84
+ italicIcon: crepeIcons.italic,
85
+ strikethroughIcon: crepeIcons.strikethrough,
86
+ codeIcon: crepeIcons.code,
87
+ linkIcon: crepeIcons.link,
88
+ boldLabel: pickLabel(labels, 'markdownEditorBold'),
89
+ italicLabel: pickLabel(labels, 'markdownEditorItalic'),
90
+ strikethroughLabel: pickLabel(labels, 'markdownEditorStrikethrough'),
91
+ codeLabel: pickLabel(labels, 'markdownEditorInlineCode'),
92
+ linkLabel: pickLabel(labels, 'markdownEditorLink'),
93
+ },
94
+ [Crepe.Feature.LinkTooltip]: {
95
+ linkIcon: crepeIcons.copy,
96
+ editButton: crepeIcons.pencil,
97
+ removeButton: crepeIcons.trash,
98
+ confirmButton: crepeIcons.check,
99
+ inputPlaceholder: pickLabel(labels, 'markdownEditorLinkPlaceholder'),
100
+ },
101
+ [Crepe.Feature.ListItem]: {
102
+ bulletIcon: crepeIcons.bullet,
103
+ checkBoxCheckedIcon: crepeIcons.squareCheck,
104
+ checkBoxUncheckedIcon: crepeIcons.square,
105
+ },
106
+ [Crepe.Feature.Placeholder]: {
107
+ text: options.placeholder ?? '',
108
+ mode: 'doc',
109
+ },
110
+ [Crepe.Feature.CodeMirror]: {
111
+ // Crepe's defaults provide the CodeMirror language list; only the theme
112
+ // and the chrome are replaced.
113
+ theme: ckCodeMirrorTheme,
114
+ copyIcon: crepeIcons.copy,
115
+ copyText: pickLabel(labels, 'markdownEditorCopyCode'),
116
+ expandIcon: crepeIcons.chevronDown,
117
+ searchIcon: crepeIcons.search,
118
+ clearSearchIcon: crepeIcons.close,
119
+ searchPlaceholder: pickLabel(labels, 'markdownEditorSearchLanguage'),
120
+ noResultText: pickLabel(labels, 'markdownEditorNoLanguageFound'),
121
+ },
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Build, configure and create a Crepe editor styled like `CkMarkdownRenderer`.
127
+ * Loaded lazily by `CkMilkdownEditor` so Milkdown stays out of the main chunk.
128
+ */
129
+ export async function createCrepeEditor(
130
+ options: CreateCrepeEditorOptions,
131
+ ): Promise<CrepeEditorHandle> {
132
+ const crepe = new Crepe({
133
+ root: options.root,
134
+ defaultValue: options.defaultValue,
135
+ features: FEATURES,
136
+ featureConfigs: buildFeatureConfigs(options),
137
+ });
138
+
139
+ crepe.editor.config((ctx) => {
140
+ applyMarkdownClasses(ctx, options.classes);
141
+
142
+ // Give the contenteditable an accessible role and name. Milkdown may set
143
+ // its own attributes (e.g. `class`), so merge rather than replace.
144
+ ctx.update(editorViewOptionsCtx, (prev) => ({
145
+ ...prev,
146
+ attributes: (state) => {
147
+ const base =
148
+ typeof prev.attributes === 'function'
149
+ ? prev.attributes(state)
150
+ : prev.attributes;
151
+ return {
152
+ ...(base ?? {}),
153
+ role: 'textbox',
154
+ 'aria-multiline': 'true',
155
+ ...(options.ariaLabel ? { 'aria-label': options.ariaLabel } : {}),
156
+ };
157
+ },
158
+ }));
159
+ });
160
+
161
+ crepe.on((api) => {
162
+ api.markdownUpdated((_ctx, markdown) => {
163
+ options.onMarkdownUpdated(markdown);
164
+ });
165
+ });
166
+
167
+ // Before `create()` this only records the flag; Crepe reads it when it
168
+ // builds the view, so the first render is already non-editable.
169
+ crepe.setReadonly(options.readonly ?? false);
170
+
171
+ await crepe.create();
172
+
173
+ return {
174
+ crepe,
175
+ setMarkdown: (markdown) => {
176
+ crepe.editor.action(replaceAll(markdown));
177
+ },
178
+ getMarkdown: () => crepe.getMarkdown(),
179
+ setReadonly: (readonly) => {
180
+ crepe.setReadonly(readonly);
181
+ },
182
+ focus: () => {
183
+ crepe.editor.action((ctx) => {
184
+ ctx.get(editorViewCtx).focus();
185
+ });
186
+ },
187
+ destroy: () => {
188
+ // Teardown is best-effort; a rejection here has nothing to report to.
189
+ crepe.destroy().catch(() => undefined);
190
+ },
191
+ };
192
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Inline SVG strings for the Crepe feature configs (toolbar, link tooltip,
3
+ * list markers, code block tools). Crepe expects raw markup, not components,
4
+ * so these mirror the Lucide icons ui-library's `UiIcon` renders — same
5
+ * 24×24 grid, 2px round strokes — to keep the editor visually consistent.
6
+ *
7
+ * Each shape carries its own `fill`/`stroke` presentation attributes because
8
+ * Crepe's stylesheet sets `fill` on the `<svg>` element, which would otherwise
9
+ * inherit into the paths and blot out stroke-only icons.
10
+ */
11
+
12
+ const STROKE_ATTRS =
13
+ 'fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"';
14
+
15
+ function strokeIcon(shapes: string[]): string {
16
+ return `<svg class="ck-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-hidden="true" focusable="false">${shapes
17
+ .map((shape) => shape.replace('/>', ` ${STROKE_ATTRS}/>`))
18
+ .join('')}</svg>`;
19
+ }
20
+
21
+ export const crepeIcons = {
22
+ bold: strokeIcon([
23
+ '<path d="M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8"/>',
24
+ ]),
25
+ italic: strokeIcon([
26
+ '<line x1="19" x2="10" y1="4" y2="4"/>',
27
+ '<line x1="14" x2="5" y1="20" y2="20"/>',
28
+ '<line x1="15" x2="9" y1="4" y2="20"/>',
29
+ ]),
30
+ strikethrough: strokeIcon([
31
+ '<path d="M16 4H9a3 3 0 0 0-2.83 4"/>',
32
+ '<path d="M14 12a4 4 0 0 1 0 8H6"/>',
33
+ '<line x1="4" x2="20" y1="12" y2="12"/>',
34
+ ]),
35
+ code: strokeIcon([
36
+ '<polyline points="16 18 22 12 16 6"/>',
37
+ '<polyline points="8 6 2 12 8 18"/>',
38
+ ]),
39
+ link: strokeIcon([
40
+ '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>',
41
+ '<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>',
42
+ ]),
43
+ pencil: strokeIcon([
44
+ '<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/>',
45
+ '<path d="m15 5 4 4"/>',
46
+ ]),
47
+ trash: strokeIcon([
48
+ '<path d="M3 6h18"/>',
49
+ '<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/>',
50
+ '<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/>',
51
+ '<line x1="10" x2="10" y1="11" y2="17"/>',
52
+ '<line x1="14" x2="14" y1="11" y2="17"/>',
53
+ ]),
54
+ check: strokeIcon(['<path d="M20 6 9 17l-5-5"/>']),
55
+ copy: strokeIcon([
56
+ '<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>',
57
+ '<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>',
58
+ ]),
59
+ close: strokeIcon(['<path d="M18 6 6 18"/>', '<path d="m6 6 12 12"/>']),
60
+ square: strokeIcon(['<rect width="18" height="18" x="3" y="3" rx="2"/>']),
61
+ squareCheck: strokeIcon([
62
+ '<rect width="18" height="18" x="3" y="3" rx="2"/>',
63
+ '<path d="m9 12 2 2 4-4"/>',
64
+ ]),
65
+ chevronDown: strokeIcon(['<path d="m6 9 6 6 6-6"/>']),
66
+ search: strokeIcon([
67
+ '<circle cx="11" cy="11" r="8"/>',
68
+ '<path d="m21 21-4.3-4.3"/>',
69
+ ]),
70
+ /**
71
+ * Bullet marker approximating the browser's `list-style: disc` that
72
+ * `CkMarkdownRenderer` gets from the `list-disc` utility: a filled dot in the
73
+ * text colour.
74
+ */
75
+ bullet:
76
+ '<svg class="ck-icon ck-icon--bullet" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="3" fill="currentColor"/></svg>',
77
+ } as const;
@@ -0,0 +1,2 @@
1
+ export { default as CkMilkdownEditor } from './CkMilkdownEditor.vue';
2
+ export type * from './types';
@@ -0,0 +1,64 @@
1
+ import type { Ctx } from '@milkdown/kit/ctx';
2
+ import {
3
+ blockquoteAttr,
4
+ bulletListAttr,
5
+ codeBlockAttr,
6
+ emphasisAttr,
7
+ headingAttr,
8
+ hrAttr,
9
+ imageAttr,
10
+ inlineCodeAttr,
11
+ linkAttr,
12
+ listItemAttr,
13
+ orderedListAttr,
14
+ paragraphAttr,
15
+ strongAttr,
16
+ } from '@milkdown/kit/preset/commonmark';
17
+ import { strikethroughAttr } from '@milkdown/kit/preset/gfm';
18
+ import type { CkMarkdownElementClasses } from '../CkMarkdownRenderer';
19
+
20
+ type HeadingTag = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
21
+
22
+ /** Minimal shape of the Milkdown `ctx` this module needs; eases unit testing. */
23
+ export type AttrCtx = Pick<Ctx, 'set'>;
24
+
25
+ function toAttrs(className: string | undefined): Record<string, string> {
26
+ return className ? { class: className } : {};
27
+ }
28
+
29
+ /**
30
+ * Feed the per-tag class map that `CkMarkdownRenderer` uses into Milkdown's
31
+ * node/mark attribute hooks, so the editor emits the same class names on the
32
+ * same elements. Must run inside `editor.config()` before the editor is
33
+ * created.
34
+ *
35
+ * `li` and `pre` are Crepe node views (list item block, CodeMirror block); the
36
+ * hooks below still register for them so a non-Crepe render path keeps parity,
37
+ * but inside `CkMilkdownEditor` those two are styled by `milkdown-theme.css`.
38
+ */
39
+ export function applyMarkdownClasses(
40
+ ctx: AttrCtx,
41
+ classes: CkMarkdownElementClasses,
42
+ ): void {
43
+ ctx.set(paragraphAttr.key, () => toAttrs(classes.p));
44
+ ctx.set(headingAttr.key, (node) => {
45
+ const tag = `h${node.attrs.level as number}` as HeadingTag;
46
+ return toAttrs(classes[tag]);
47
+ });
48
+ ctx.set(blockquoteAttr.key, () => toAttrs(classes.blockquote));
49
+ ctx.set(hrAttr.key, () => toAttrs(classes.hr));
50
+ ctx.set(imageAttr.key, () => toAttrs(classes.img));
51
+ ctx.set(bulletListAttr.key, () => toAttrs(classes.ul));
52
+ ctx.set(orderedListAttr.key, () => toAttrs(classes.ol));
53
+ ctx.set(listItemAttr.key, () => toAttrs(classes.li));
54
+ // The code block hook returns attributes for both generated elements.
55
+ ctx.set(codeBlockAttr.key, () => ({
56
+ pre: toAttrs(classes.pre),
57
+ code: toAttrs(classes.code),
58
+ }));
59
+ ctx.set(inlineCodeAttr.key, () => toAttrs(classes.code));
60
+ ctx.set(linkAttr.key, () => toAttrs(classes.a));
61
+ ctx.set(strongAttr.key, () => toAttrs(classes.strong));
62
+ ctx.set(emphasisAttr.key, () => toAttrs(classes.em));
63
+ ctx.set(strikethroughAttr.key, () => toAttrs(classes.del));
64
+ }