@milkdown/crepe 7.19.2 → 7.21.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.
Files changed (209) hide show
  1. package/lib/cjs/builder.js +44 -1
  2. package/lib/cjs/builder.js.map +1 -1
  3. package/lib/cjs/feature/ai/index.js +1492 -0
  4. package/lib/cjs/feature/ai/index.js.map +1 -0
  5. package/lib/cjs/feature/block-edit/index.js +9 -2
  6. package/lib/cjs/feature/block-edit/index.js.map +1 -1
  7. package/lib/cjs/feature/code-mirror/index.js +2 -0
  8. package/lib/cjs/feature/code-mirror/index.js.map +1 -1
  9. package/lib/cjs/feature/cursor/index.js +2 -0
  10. package/lib/cjs/feature/cursor/index.js.map +1 -1
  11. package/lib/cjs/feature/image-block/index.js +5 -1
  12. package/lib/cjs/feature/image-block/index.js.map +1 -1
  13. package/lib/cjs/feature/latex/index.js +7 -0
  14. package/lib/cjs/feature/latex/index.js.map +1 -1
  15. package/lib/cjs/feature/link-tooltip/index.js +2 -0
  16. package/lib/cjs/feature/link-tooltip/index.js.map +1 -1
  17. package/lib/cjs/feature/list-item/index.js +2 -0
  18. package/lib/cjs/feature/list-item/index.js.map +1 -1
  19. package/lib/cjs/feature/placeholder/index.js +2 -0
  20. package/lib/cjs/feature/placeholder/index.js.map +1 -1
  21. package/lib/cjs/feature/table/index.js +2 -0
  22. package/lib/cjs/feature/table/index.js.map +1 -1
  23. package/lib/cjs/feature/toolbar/index.js +497 -5
  24. package/lib/cjs/feature/toolbar/index.js.map +1 -1
  25. package/lib/cjs/feature/top-bar/index.js +791 -0
  26. package/lib/cjs/feature/top-bar/index.js.map +1 -0
  27. package/lib/cjs/index.js +2047 -160
  28. package/lib/cjs/index.js.map +1 -1
  29. package/lib/cjs/llm-providers/anthropic/index.js +147 -0
  30. package/lib/cjs/llm-providers/anthropic/index.js.map +1 -0
  31. package/lib/cjs/llm-providers/openai/index.js +138 -0
  32. package/lib/cjs/llm-providers/openai/index.js.map +1 -0
  33. package/lib/esm/builder.js +44 -1
  34. package/lib/esm/builder.js.map +1 -1
  35. package/lib/esm/feature/ai/index.js +1487 -0
  36. package/lib/esm/feature/ai/index.js.map +1 -0
  37. package/lib/esm/feature/block-edit/index.js +9 -2
  38. package/lib/esm/feature/block-edit/index.js.map +1 -1
  39. package/lib/esm/feature/code-mirror/index.js +2 -0
  40. package/lib/esm/feature/code-mirror/index.js.map +1 -1
  41. package/lib/esm/feature/cursor/index.js +2 -0
  42. package/lib/esm/feature/cursor/index.js.map +1 -1
  43. package/lib/esm/feature/image-block/index.js +5 -1
  44. package/lib/esm/feature/image-block/index.js.map +1 -1
  45. package/lib/esm/feature/latex/index.js +7 -0
  46. package/lib/esm/feature/latex/index.js.map +1 -1
  47. package/lib/esm/feature/link-tooltip/index.js +2 -0
  48. package/lib/esm/feature/link-tooltip/index.js.map +1 -1
  49. package/lib/esm/feature/list-item/index.js +2 -0
  50. package/lib/esm/feature/list-item/index.js.map +1 -1
  51. package/lib/esm/feature/placeholder/index.js +2 -0
  52. package/lib/esm/feature/placeholder/index.js.map +1 -1
  53. package/lib/esm/feature/table/index.js +2 -0
  54. package/lib/esm/feature/table/index.js.map +1 -1
  55. package/lib/esm/feature/toolbar/index.js +499 -7
  56. package/lib/esm/feature/toolbar/index.js.map +1 -1
  57. package/lib/esm/feature/top-bar/index.js +789 -0
  58. package/lib/esm/feature/top-bar/index.js.map +1 -0
  59. package/lib/esm/index.js +2040 -153
  60. package/lib/esm/index.js.map +1 -1
  61. package/lib/esm/llm-providers/anthropic/index.js +145 -0
  62. package/lib/esm/llm-providers/anthropic/index.js.map +1 -0
  63. package/lib/esm/llm-providers/openai/index.js +136 -0
  64. package/lib/esm/llm-providers/openai/index.js.map +1 -0
  65. package/lib/theme/common/ai.css +446 -0
  66. package/lib/theme/common/code-mirror.css +14 -0
  67. package/lib/theme/common/diff.css +177 -0
  68. package/lib/theme/common/style.css +3 -0
  69. package/lib/theme/common/top-bar.css +152 -0
  70. package/lib/tsconfig.tsbuildinfo +1 -1
  71. package/lib/types/core/builder.d.ts +2 -1
  72. package/lib/types/core/builder.d.ts.map +1 -1
  73. package/lib/types/feature/ai/ai.spec.d.ts +2 -0
  74. package/lib/types/feature/ai/ai.spec.d.ts.map +1 -0
  75. package/lib/types/feature/ai/commands.d.ts +24 -0
  76. package/lib/types/feature/ai/commands.d.ts.map +1 -0
  77. package/lib/types/feature/ai/context.d.ts +4 -0
  78. package/lib/types/feature/ai/context.d.ts.map +1 -0
  79. package/lib/types/feature/ai/diff-actions/index.d.ts +12 -0
  80. package/lib/types/feature/ai/diff-actions/index.d.ts.map +1 -0
  81. package/lib/types/feature/ai/diff-actions/view.d.ts +21 -0
  82. package/lib/types/feature/ai/diff-actions/view.d.ts.map +1 -0
  83. package/lib/types/feature/ai/index.d.ts +7 -0
  84. package/lib/types/feature/ai/index.d.ts.map +1 -0
  85. package/lib/types/feature/ai/instruction-tooltip/component.d.ts +26 -0
  86. package/lib/types/feature/ai/instruction-tooltip/component.d.ts.map +1 -0
  87. package/lib/types/feature/ai/instruction-tooltip/index.d.ts +17 -0
  88. package/lib/types/feature/ai/instruction-tooltip/index.d.ts.map +1 -0
  89. package/lib/types/feature/ai/instruction-tooltip/suggestions.d.ts +50 -0
  90. package/lib/types/feature/ai/instruction-tooltip/suggestions.d.ts.map +1 -0
  91. package/lib/types/feature/ai/instruction-tooltip/view.d.ts +19 -0
  92. package/lib/types/feature/ai/instruction-tooltip/view.d.ts.map +1 -0
  93. package/lib/types/feature/ai/streaming-indicator.d.ts +9 -0
  94. package/lib/types/feature/ai/streaming-indicator.d.ts.map +1 -0
  95. package/lib/types/feature/ai/types.d.ts +58 -0
  96. package/lib/types/feature/ai/types.d.ts.map +1 -0
  97. package/lib/types/feature/block-edit/handle/component.d.ts.map +1 -1
  98. package/lib/types/feature/block-edit/menu/component.d.ts.map +1 -1
  99. package/lib/types/feature/image-block/index.d.ts +2 -0
  100. package/lib/types/feature/image-block/index.d.ts.map +1 -1
  101. package/lib/types/feature/index.d.ts +7 -1
  102. package/lib/types/feature/index.d.ts.map +1 -1
  103. package/lib/types/feature/latex/inline-tooltip/component.d.ts.map +1 -1
  104. package/lib/types/feature/latex/inline-tooltip/inline-tooltip.spec.d.ts +2 -0
  105. package/lib/types/feature/latex/inline-tooltip/inline-tooltip.spec.d.ts.map +1 -0
  106. package/lib/types/feature/latex/inline-tooltip/view.d.ts.map +1 -1
  107. package/lib/types/feature/loader.d.ts.map +1 -1
  108. package/lib/types/feature/toolbar/component.d.ts.map +1 -1
  109. package/lib/types/feature/toolbar/config.d.ts +1 -1
  110. package/lib/types/feature/toolbar/config.d.ts.map +1 -1
  111. package/lib/types/feature/toolbar/index.d.ts +1 -0
  112. package/lib/types/feature/toolbar/index.d.ts.map +1 -1
  113. package/lib/types/feature/top-bar/component.d.ts +11 -0
  114. package/lib/types/feature/top-bar/component.d.ts.map +1 -0
  115. package/lib/types/feature/top-bar/config.d.ts +34 -0
  116. package/lib/types/feature/top-bar/config.d.ts.map +1 -0
  117. package/lib/types/feature/top-bar/index.d.ts +26 -0
  118. package/lib/types/feature/top-bar/index.d.ts.map +1 -0
  119. package/lib/types/icons/ai.d.ts +2 -0
  120. package/lib/types/icons/ai.d.ts.map +1 -0
  121. package/lib/types/icons/chevron-left.d.ts +2 -0
  122. package/lib/types/icons/chevron-left.d.ts.map +1 -0
  123. package/lib/types/icons/chevron-right.d.ts +2 -0
  124. package/lib/types/icons/chevron-right.d.ts.map +1 -0
  125. package/lib/types/icons/code-block.d.ts +2 -0
  126. package/lib/types/icons/code-block.d.ts.map +1 -0
  127. package/lib/types/icons/enter-key.d.ts +2 -0
  128. package/lib/types/icons/enter-key.d.ts.map +1 -0
  129. package/lib/types/icons/grammar-check.d.ts +2 -0
  130. package/lib/types/icons/grammar-check.d.ts.map +1 -0
  131. package/lib/types/icons/index.d.ts +12 -0
  132. package/lib/types/icons/index.d.ts.map +1 -1
  133. package/lib/types/icons/longer.d.ts +2 -0
  134. package/lib/types/icons/longer.d.ts.map +1 -0
  135. package/lib/types/icons/retry.d.ts +2 -0
  136. package/lib/types/icons/retry.d.ts.map +1 -0
  137. package/lib/types/icons/send-prompt.d.ts +2 -0
  138. package/lib/types/icons/send-prompt.d.ts.map +1 -0
  139. package/lib/types/icons/send.d.ts +2 -0
  140. package/lib/types/icons/send.d.ts.map +1 -0
  141. package/lib/types/icons/shorter.d.ts +2 -0
  142. package/lib/types/icons/shorter.d.ts.map +1 -0
  143. package/lib/types/icons/translate.d.ts +2 -0
  144. package/lib/types/icons/translate.d.ts.map +1 -0
  145. package/lib/types/llm-providers/anthropic/index.d.ts +21 -0
  146. package/lib/types/llm-providers/anthropic/index.d.ts.map +1 -0
  147. package/lib/types/llm-providers/openai/index.d.ts +15 -0
  148. package/lib/types/llm-providers/openai/index.d.ts.map +1 -0
  149. package/lib/types/llm-providers/providers.spec.d.ts +2 -0
  150. package/lib/types/llm-providers/providers.spec.d.ts.map +1 -0
  151. package/lib/types/llm-providers/shared.d.ts +16 -0
  152. package/lib/types/llm-providers/shared.d.ts.map +1 -0
  153. package/lib/types/utils/group-builder.d.ts +1 -1
  154. package/lib/types/utils/group-builder.d.ts.map +1 -1
  155. package/lib/types/utils/keep-alive.d.ts +2 -0
  156. package/lib/types/utils/keep-alive.d.ts.map +1 -0
  157. package/package.json +34 -13
  158. package/src/core/builder.ts +39 -2
  159. package/src/feature/ai/ai.spec.ts +742 -0
  160. package/src/feature/ai/commands.ts +257 -0
  161. package/src/feature/ai/context.ts +45 -0
  162. package/src/feature/ai/diff-actions/index.ts +95 -0
  163. package/src/feature/ai/diff-actions/view.ts +237 -0
  164. package/src/feature/ai/index.ts +118 -0
  165. package/src/feature/ai/instruction-tooltip/component.tsx +414 -0
  166. package/src/feature/ai/instruction-tooltip/index.ts +101 -0
  167. package/src/feature/ai/instruction-tooltip/suggestions.ts +249 -0
  168. package/src/feature/ai/instruction-tooltip/view.ts +159 -0
  169. package/src/feature/ai/streaming-indicator.ts +183 -0
  170. package/src/feature/ai/types.ts +178 -0
  171. package/src/feature/block-edit/handle/component.tsx +3 -2
  172. package/src/feature/block-edit/menu/component.tsx +3 -2
  173. package/src/feature/block-edit/menu/config.ts +1 -1
  174. package/src/feature/image-block/index.ts +4 -0
  175. package/src/feature/index.ts +14 -2
  176. package/src/feature/latex/inline-tooltip/component.tsx +4 -2
  177. package/src/feature/latex/inline-tooltip/inline-tooltip.spec.ts +81 -0
  178. package/src/feature/latex/inline-tooltip/view.ts +2 -0
  179. package/src/feature/loader.ts +8 -0
  180. package/src/feature/toolbar/component.tsx +7 -5
  181. package/src/feature/toolbar/config.ts +27 -1
  182. package/src/feature/toolbar/index.ts +1 -0
  183. package/src/feature/top-bar/component.tsx +198 -0
  184. package/src/feature/top-bar/config.ts +367 -0
  185. package/src/feature/top-bar/index.ts +113 -0
  186. package/src/icons/ai.ts +14 -0
  187. package/src/icons/chevron-left.ts +15 -0
  188. package/src/icons/chevron-right.ts +15 -0
  189. package/src/icons/code-block.ts +12 -0
  190. package/src/icons/enter-key.ts +13 -0
  191. package/src/icons/grammar-check.ts +13 -0
  192. package/src/icons/index.ts +12 -0
  193. package/src/icons/longer.ts +13 -0
  194. package/src/icons/retry.ts +13 -0
  195. package/src/icons/send-prompt.ts +13 -0
  196. package/src/icons/send.ts +13 -0
  197. package/src/icons/shorter.ts +13 -0
  198. package/src/icons/translate.ts +13 -0
  199. package/src/llm-providers/anthropic/index.ts +132 -0
  200. package/src/llm-providers/openai/index.ts +109 -0
  201. package/src/llm-providers/providers.spec.ts +472 -0
  202. package/src/llm-providers/shared.ts +160 -0
  203. package/src/theme/common/ai.css +430 -0
  204. package/src/theme/common/code-mirror.css +14 -0
  205. package/src/theme/common/diff.css +196 -0
  206. package/src/theme/common/style.css +3 -0
  207. package/src/theme/common/top-bar.css +156 -0
  208. package/src/utils/group-builder.ts +1 -1
  209. package/src/utils/keep-alive.ts +3 -0
@@ -0,0 +1,178 @@
1
+ import type { Ctx } from '@milkdown/kit/ctx'
2
+ import type { MilkdownError } from '@milkdown/kit/exception'
3
+ import type { StreamingConfig } from '@milkdown/kit/plugin/streaming'
4
+
5
+ import type { AISuggestionsBuilder } from './instruction-tooltip/suggestions'
6
+
7
+ export type {
8
+ AISubmenuBuilder,
9
+ AISubmenuDef,
10
+ AISuggestionItem,
11
+ AISuggestionsBuilder,
12
+ } from './instruction-tooltip/suggestions'
13
+
14
+ export interface AIPromptContext {
15
+ document: string
16
+ selection: string
17
+ instruction: string
18
+ }
19
+
20
+ /// An async generator that yields markdown tokens from an LLM or
21
+ /// similar source. Called by `runAICmd` with the assembled prompt
22
+ /// context and an abort signal.
23
+ export type AIProvider = (
24
+ context: AIPromptContext,
25
+ signal: AbortSignal
26
+ ) => AsyncIterable<string>
27
+
28
+ /// Diff-related config options passed through to the diff plugin and component.
29
+ export interface AIDiffConfig {
30
+ acceptLabel?: string
31
+ rejectLabel?: string
32
+ customBlockTypes?: string[]
33
+ ignoreAttrs?: Record<string, string[]>
34
+ }
35
+
36
+ /// Customize the inline streaming indicator pill shown while AI runs.
37
+ export interface AIStreamingIndicatorConfig {
38
+ /// Fallback active-form label when the current session has none set
39
+ /// (i.e., `runAICmd` was called without a `label`).
40
+ /// @default DEFAULT_STREAMING_FALLBACK_LABEL
41
+ fallbackLabel?: string
42
+
43
+ /// Hint text shown after the label, suggesting how to cancel.
44
+ /// @default DEFAULT_STREAMING_CANCEL_HINT
45
+ cancelHint?: string
46
+ }
47
+
48
+ /// Customize the floating diff actions panel (Retry / Reject all / Accept all).
49
+ ///
50
+ /// Note: the enter-key icon used in the Accept-all shortcut chip is
51
+ /// shared with the instruction tooltip and overridden via the top-level
52
+ /// `AIFeatureConfig.enterKeyIcon`.
53
+ export interface AIDiffActionsConfig {
54
+ /// @default DEFAULT_DIFF_ACTIONS_RETRY_LABEL
55
+ retryLabel?: string
56
+
57
+ /// @default DEFAULT_DIFF_ACTIONS_REJECT_ALL_LABEL
58
+ rejectAllLabel?: string
59
+
60
+ /// @default DEFAULT_DIFF_ACTIONS_ACCEPT_ALL_LABEL
61
+ acceptAllLabel?: string
62
+
63
+ /// Icon for the Retry button. Default: refresh icon.
64
+ retryIcon?: string
65
+
66
+ /// Icon for the Reject all button. Default: 'X' icon.
67
+ rejectIcon?: string
68
+
69
+ /// Icon for the Accept all button. Default: checkmark icon.
70
+ acceptIcon?: string
71
+
72
+ /// Modifier key glyph shown alongside the enter-key icon. Set to
73
+ /// 'Ctrl' on non-Mac platforms if you need to disambiguate.
74
+ /// @default DEFAULT_DIFF_ACTIONS_MOD_SYMBOL
75
+ modSymbol?: string
76
+ }
77
+
78
+ /// Configuration for `CrepeFeature.AI`.
79
+ export interface AIFeatureConfig {
80
+ /// Async generator that yields markdown tokens. Required for
81
+ /// `runAICmd`; without it the command returns false. The diff and
82
+ /// streaming plugins load either way, so the feature can be enabled
83
+ /// without a provider for diff-only or manual-streaming use cases.
84
+ provider?: AIProvider
85
+
86
+ /// Optional. Assemble the context passed to `provider`.
87
+ /// Defaults to serializing the document (+ selection if any) as markdown.
88
+ buildContext?: (ctx: Ctx, instruction: string) => AIPromptContext
89
+
90
+ /// Whether to enter diff review after streaming completes. Default true.
91
+ diffReviewOnEnd?: boolean
92
+
93
+ /// Pass-through config for the diff plugin.
94
+ diff?: AIDiffConfig
95
+
96
+ /// Pass-through config for the streaming plugin.
97
+ /// `diffReviewOnEnd` is excluded here because it's controlled by
98
+ /// `AIFeatureConfig.diffReviewOnEnd` at the AI layer — setting it on
99
+ /// both would be confusing.
100
+ streaming?: Partial<Omit<StreamingConfig, 'diffReviewOnEnd'>>
101
+
102
+ /// Called when an error occurs during AI processing.
103
+ /// Receives a `MilkdownError` with code `aiProviderError` or
104
+ /// `aiBuildContextError`.
105
+ onError?: (error: MilkdownError) => void
106
+
107
+ /// Custom icon for both the toolbar AI entry point and the prefix slot
108
+ /// inside the instruction input. The toolbar feature can override this
109
+ /// for just the toolbar button via `ToolbarFeatureConfig.aiIcon`.
110
+ aiIcon?: string
111
+
112
+ /// Placeholder text for the AI instruction input on the main view.
113
+ /// @default DEFAULT_INSTRUCTION_PLACEHOLDER
114
+ instructionPlaceholder?: string
115
+
116
+ /// Label for the suggestions section header.
117
+ /// @default DEFAULT_SUGGESTIONS_HEADER_LABEL
118
+ suggestionsHeaderLabel?: string
119
+
120
+ /// Label for the free-text prompt section header.
121
+ /// @default DEFAULT_SEND_AS_PROMPT_HEADER_LABEL
122
+ sendAsPromptHeaderLabel?: string
123
+
124
+ /// Prefix text for the free-text prompt entry, before the quoted input.
125
+ /// @default DEFAULT_SEND_AS_PROMPT_LABEL
126
+ sendAsPromptLabel?: string
127
+
128
+ /// Accessible name for the round submit button in the input pill.
129
+ /// Surfaced as `aria-label` so screen readers don't announce the
130
+ /// icon-only button as "unlabeled".
131
+ /// @default DEFAULT_SUBMIT_BUTTON_LABEL
132
+ submitButtonLabel?: string
133
+
134
+ /// Accessible name announced for the suggestion list (`role="listbox"`)
135
+ /// inside the palette. Localize alongside the other strings.
136
+ /// @default DEFAULT_LISTBOX_LABEL
137
+ listboxLabel?: string
138
+
139
+ /// Icon for the round submit button in the input pill.
140
+ /// Default: an upward arrow.
141
+ sendIcon?: string
142
+
143
+ /// Icon shown next to the "Ask AI: …" entry. Default: paper-plane.
144
+ sendPromptIcon?: string
145
+
146
+ /// Icon used in the keyboard shortcut chip on the prompt entry.
147
+ /// Default: enter-key arrow.
148
+ enterKeyIcon?: string
149
+
150
+ /// Icon for the back arrow at the top of a submenu. Default: chevron-left.
151
+ chevronLeftIcon?: string
152
+
153
+ /// Icon shown on the right of submenu entries. Default: chevron-right.
154
+ chevronRightIcon?: string
155
+
156
+ /// Customize the suggestion list. The builder is pre-populated with the
157
+ /// built-in suggestions; the callback can add, remove, or replace any
158
+ /// item or submenu. To start from scratch, call `builder.clear()` first.
159
+ buildAISuggestions?: (builder: AISuggestionsBuilder) => void
160
+
161
+ /// Customize the inline streaming indicator pill shown while AI runs.
162
+ streamingIndicator?: AIStreamingIndicatorConfig
163
+
164
+ /// Customize the floating diff actions panel (Retry / Reject all /
165
+ /// Accept all) that appears once streaming hands off to diff review.
166
+ diffActions?: AIDiffActionsConfig
167
+ }
168
+
169
+ /// Options passed to `runAICmd`.
170
+ export interface RunAIOptions {
171
+ /// The user instruction to send to the AI provider.
172
+ instruction: string
173
+
174
+ /// Optional active-form label shown in the streaming indicator
175
+ /// (e.g., "Improving writing", "Translating"). When omitted, falls
176
+ /// back to `streamingIndicator.fallbackLabel` (default `'Generating'`).
177
+ label?: string
178
+ }
@@ -1,8 +1,9 @@
1
1
  import { Icon } from '@milkdown/kit/component'
2
2
  import { defineComponent, ref, h, Fragment } from 'vue'
3
3
 
4
- h
5
- Fragment
4
+ import { keepAlive } from '../../../utils/keep-alive'
5
+
6
+ keepAlive(h, Fragment)
6
7
 
7
8
  interface BlockHandleProps {
8
9
  onAdd: () => void
@@ -14,9 +14,10 @@ import {
14
14
 
15
15
  import type { BlockEditFeatureConfig } from '..'
16
16
 
17
+ import { keepAlive } from '../../../utils/keep-alive'
17
18
  import { getGroups } from './config'
18
19
 
19
- h
20
+ keepAlive(h)
20
21
 
21
22
  type MenuProps = {
22
23
  ctx: Ctx
@@ -91,7 +92,7 @@ export const Menu = defineComponent<MenuProps>({
91
92
  const item = groupInfo.value.groups
92
93
  .flatMap((group) => group.items)
93
94
  .at(index)
94
- if (item && ctx) item.onRun(ctx)
95
+ if (item?.onRun && ctx) item.onRun(ctx)
95
96
 
96
97
  hide()
97
98
  }
@@ -350,7 +350,7 @@ export function getGroups(
350
350
  commands.call(clearTextInCurrentBlockCommand.key)
351
351
  commands.call(addBlockTypeCommand.key, {
352
352
  nodeType: codeBlock,
353
- attrs: { language: 'LaTex' },
353
+ attrs: { language: 'LaTeX' },
354
354
  })
355
355
  },
356
356
  })
@@ -31,6 +31,8 @@ interface ImageBlockConfig {
31
31
  blockUploadPlaceholderText: string
32
32
  blockOnUpload: (file: File) => Promise<string>
33
33
  onImageLoadError: (event: Event) => void | Promise<void>
34
+ maxWidth: number
35
+ maxHeight: number
34
36
  }
35
37
 
36
38
  export type ImageBlockFeatureConfig = Partial<ImageBlockConfig>
@@ -63,6 +65,8 @@ export const imageBlock: DefineFeature<ImageBlockFeatureConfig> = (
63
65
  onUpload: config?.blockOnUpload ?? config?.onUpload ?? value.onUpload,
64
66
  proxyDomURL: config?.proxyDomURL,
65
67
  onImageLoadError: config?.onImageLoadError ?? value.onImageLoadError,
68
+ maxWidth: config?.maxWidth,
69
+ maxHeight: config?.maxHeight,
66
70
  }))
67
71
  })
68
72
  .use(imageBlockComponent)
@@ -1,3 +1,4 @@
1
+ import type { AIFeatureConfig } from './ai'
1
2
  import type { BlockEditFeatureConfig } from './block-edit'
2
3
  import type { CodeMirrorFeatureConfig } from './code-mirror'
3
4
  import type { CursorFeatureConfig } from './cursor'
@@ -8,10 +9,11 @@ import type { ListItemFeatureConfig } from './list-item'
8
9
  import type { PlaceholderFeatureConfig } from './placeholder'
9
10
  import type { TableFeatureConfig } from './table'
10
11
  import type { ToolbarFeatureConfig } from './toolbar'
12
+ import type { TopBarFeatureConfig } from './top-bar'
11
13
 
12
14
  /// The crepe editor feature flags.
13
- /// Every feature is enabled by default.
14
- /// Every feature is a string literal type.
15
+ /// Most features are enabled by default; `TopBar` and `AI` are opt-in.
16
+ /// See `defaultFeatures` for the per-flag default.
15
17
  export enum CrepeFeature {
16
18
  /// Syntax highlighting and editing for code blocks with language support, theme customization, and preview capabilities.
17
19
  CodeMirror = 'code-mirror',
@@ -42,6 +44,12 @@ export enum CrepeFeature {
42
44
 
43
45
  /// Mathematical formula support with both inline and block math rendering using KaTeX.
44
46
  Latex = 'latex',
47
+
48
+ /// Fixed top toolbar with heading selector, formatting buttons, insert actions, and block commands.
49
+ TopBar = 'top-bar',
50
+
51
+ /// AI-assisted editing: streaming input, diff review, and provider integration.
52
+ AI = 'ai',
45
53
  }
46
54
 
47
55
  export interface CrepeFeatureConfig {
@@ -55,6 +63,8 @@ export interface CrepeFeatureConfig {
55
63
  [CrepeFeature.CodeMirror]?: CodeMirrorFeatureConfig
56
64
  [CrepeFeature.Table]?: TableFeatureConfig
57
65
  [CrepeFeature.Latex]?: LatexFeatureConfig
66
+ [CrepeFeature.TopBar]?: TopBarFeatureConfig
67
+ [CrepeFeature.AI]?: AIFeatureConfig
58
68
  }
59
69
 
60
70
  export const defaultFeatures: Record<CrepeFeature, boolean> = {
@@ -68,4 +78,6 @@ export const defaultFeatures: Record<CrepeFeature, boolean> = {
68
78
  [CrepeFeature.CodeMirror]: true,
69
79
  [CrepeFeature.Table]: true,
70
80
  [CrepeFeature.Latex]: true,
81
+ [CrepeFeature.TopBar]: false,
82
+ [CrepeFeature.AI]: false,
71
83
  }
@@ -5,14 +5,16 @@ import { defineComponent, type ShallowRef, type VNodeRef, h } from 'vue'
5
5
 
6
6
  import type { LatexConfig } from '..'
7
7
 
8
+ import { keepAlive } from '../../../utils/keep-alive'
9
+
10
+ keepAlive(h)
11
+
8
12
  type LatexTooltipProps = {
9
13
  config: Partial<LatexConfig>
10
14
  innerView: ShallowRef<EditorView | null>
11
15
  updateValue: ShallowRef<() => void>
12
16
  }
13
17
 
14
- h
15
-
16
18
  export const LatexTooltip = defineComponent<LatexTooltipProps>({
17
19
  props: {
18
20
  config: {
@@ -0,0 +1,81 @@
1
+ import '@testing-library/jest-dom/vitest'
2
+ import type { Node } from '@milkdown/kit/prose/model'
3
+
4
+ import { editorViewCtx } from '@milkdown/kit/core'
5
+ import { NodeSelection } from '@milkdown/kit/prose/state'
6
+ import { afterEach, describe, expect, test } from 'vitest'
7
+
8
+ import { Crepe } from '../../../core'
9
+ import { mathInlineId } from '../inline-latex'
10
+
11
+ function waitForAsync() {
12
+ return new Promise<void>((resolve) => setTimeout(resolve, 0))
13
+ }
14
+
15
+ function findMathInlinePos(doc: Node) {
16
+ let pos: number | undefined
17
+ doc.descendants((node, currentPos) => {
18
+ if (node.type.name === mathInlineId) {
19
+ pos = currentPos
20
+ return false
21
+ }
22
+ return true
23
+ })
24
+ return pos
25
+ }
26
+
27
+ describe('latex inline tooltip', () => {
28
+ afterEach(() => {
29
+ document.body.replaceChildren()
30
+ })
31
+
32
+ test('should show in edit mode', async () => {
33
+ const crepe = new Crepe({
34
+ defaultValue: '$x$',
35
+ })
36
+
37
+ await crepe.create()
38
+
39
+ const view = crepe.editor.ctx.get(editorViewCtx)
40
+ const pos = findMathInlinePos(view.state.doc)
41
+ expect(pos).toBeTypeOf('number')
42
+
43
+ view.dispatch(
44
+ view.state.tr.setSelection(NodeSelection.create(view.state.doc, pos!))
45
+ )
46
+ await waitForAsync()
47
+
48
+ expect(view.state.selection).toBeInstanceOf(NodeSelection)
49
+ expect((view.state.selection as NodeSelection).node.type.name).toBe(
50
+ mathInlineId
51
+ )
52
+ expect(
53
+ document.body.querySelector('.milkdown-latex-inline-edit')
54
+ ).toHaveAttribute('data-show', 'true')
55
+ })
56
+
57
+ test('should not show in readonly editor', async () => {
58
+ const crepe = new Crepe({
59
+ defaultValue: '$x$',
60
+ }).setReadonly(true)
61
+
62
+ await crepe.create()
63
+
64
+ const view = crepe.editor.ctx.get(editorViewCtx)
65
+ const pos = findMathInlinePos(view.state.doc)
66
+ expect(pos).toBeTypeOf('number')
67
+
68
+ view.dispatch(
69
+ view.state.tr.setSelection(NodeSelection.create(view.state.doc, pos!))
70
+ )
71
+ await waitForAsync()
72
+
73
+ expect(view.state.selection).toBeInstanceOf(NodeSelection)
74
+ expect((view.state.selection as NodeSelection).node.type.name).toBe(
75
+ mathInlineId
76
+ )
77
+ expect(
78
+ document.body.querySelector('.milkdown-latex-inline-edit')
79
+ ).toHaveAttribute('data-show', 'false')
80
+ })
81
+ })
@@ -58,6 +58,8 @@ export class LatexInlineTooltip implements PluginView {
58
58
 
59
59
  #shouldShow = (view: EditorView) => {
60
60
  const shouldShow = () => {
61
+ if (!view.editable) return false
62
+
61
63
  const { selection, schema } = view.state
62
64
  if (selection.empty) return false
63
65
  if (!(selection instanceof NodeSelection)) return false
@@ -1,5 +1,6 @@
1
1
  import type { Editor } from '@milkdown/kit/core'
2
2
 
3
+ import { ai } from './ai'
3
4
  import { blockEdit } from './block-edit'
4
5
  import { codeMirror } from './code-mirror'
5
6
  import { cursor } from './cursor'
@@ -11,6 +12,7 @@ import { listItem } from './list-item'
11
12
  import { placeholder } from './placeholder'
12
13
  import { table } from './table'
13
14
  import { toolbar } from './toolbar'
15
+ import { topBar } from './top-bar'
14
16
 
15
17
  export function loadFeature(
16
18
  feature: CrepeFeature,
@@ -48,5 +50,11 @@ export function loadFeature(
48
50
  case CrepeFeature.Latex: {
49
51
  return latex(editor, config)
50
52
  }
53
+ case CrepeFeature.TopBar: {
54
+ return topBar(editor, config)
55
+ }
56
+ case CrepeFeature.AI: {
57
+ return ai(editor, config)
58
+ }
51
59
  }
52
60
  }
@@ -15,10 +15,10 @@ import {
15
15
 
16
16
  import type { ToolbarFeatureConfig } from '.'
17
17
 
18
+ import { keepAlive } from '../../utils/keep-alive'
18
19
  import { getGroups, type ToolbarItem } from './config'
19
20
 
20
- h
21
- Fragment
21
+ keepAlive(h, Fragment)
22
22
 
23
23
  type ToolbarProps = {
24
24
  ctx: Ctx
@@ -54,14 +54,16 @@ export const Toolbar = defineComponent<ToolbarProps>({
54
54
  setup(props) {
55
55
  const { ctx, config } = props
56
56
 
57
- const onClick = (fn: (ctx: Ctx) => void) => (e: MouseEvent) => {
57
+ const onClick = (fn?: (ctx: Ctx) => void) => (e: MouseEvent) => {
58
58
  e.preventDefault()
59
- ctx && fn(ctx)
59
+ if (ctx) {
60
+ fn?.(ctx)
61
+ }
60
62
  }
61
63
 
62
64
  function checkActive(checker: ToolbarItem['active']) {
63
65
  // make sure the function subscribed to vue reactive
64
- props.selection.value
66
+ keepAlive(props.selection.value)
65
67
  // Check if the edtior is ready
66
68
  const status = ctx.get(editorCtx).status
67
69
  if (status !== EditorStatus.Created) return false
@@ -1,7 +1,7 @@
1
1
  import type { Ctx } from '@milkdown/kit/ctx'
2
2
 
3
3
  import { toggleLinkCommand } from '@milkdown/kit/component/link-tooltip'
4
- import { commandsCtx } from '@milkdown/kit/core'
4
+ import { commandsCtx, editorViewCtx } from '@milkdown/kit/core'
5
5
  import {
6
6
  emphasisSchema,
7
7
  inlineCodeSchema,
@@ -23,6 +23,7 @@ import type { ToolbarFeatureConfig } from '.'
23
23
  import { CrepeFeature } from '..'
24
24
  import { useCrepeFeatures } from '../../core/slice'
25
25
  import {
26
+ aiIcon,
26
27
  boldIcon,
27
28
  codeIcon,
28
29
  functionsIcon,
@@ -31,6 +32,8 @@ import {
31
32
  strikethroughIcon,
32
33
  } from '../../icons'
33
34
  import { GroupBuilder } from '../../utils/group-builder'
35
+ import { aiProviderConfig } from '../ai/commands'
36
+ import { aiInstructionTooltipAPI } from '../ai/instruction-tooltip'
34
37
  import { toggleLatexCommand } from '../latex/command'
35
38
  import { mathInlineSchema } from '../latex/inline-latex'
36
39
 
@@ -130,6 +133,29 @@ export function getGroups(config?: ToolbarFeatureConfig, ctx?: Ctx) {
130
133
  },
131
134
  })
132
135
 
136
+ // Skip the AI button entirely when the feature is disabled or when no
137
+ // provider is configured — without a provider the palette would open
138
+ // but `runAICmd` would silently reject every action. Toolbar-level
139
+ // `aiIcon` wins over `AIFeatureConfig.aiIcon` so consumers can override
140
+ // just the toolbar entry without touching the tooltip prefix.
141
+ // The aiProviderConfig slice is only injected when the AI feature is
142
+ // active, so guard the lookup behind the flag check.
143
+ if (ctx && flags?.includes(CrepeFeature.AI)) {
144
+ const aiCfg = ctx.get(aiProviderConfig.key)
145
+ if (aiCfg.provider) {
146
+ functionGroup.addItem('ai', {
147
+ icon: config?.aiIcon ?? aiCfg.aiIcon ?? aiIcon,
148
+ active: () => false,
149
+ onRun: (ctx) => {
150
+ const api = ctx.get(aiInstructionTooltipAPI.key)
151
+ const view = ctx.get(editorViewCtx)
152
+ const { from, to } = view.state.selection
153
+ api.show(from, to)
154
+ },
155
+ })
156
+ }
157
+ }
158
+
133
159
  config?.buildToolbar?.(groupBuilder)
134
160
 
135
161
  return groupBuilder.build()
@@ -25,6 +25,7 @@ interface ToolbarConfig {
25
25
  linkIcon: string
26
26
  strikethroughIcon: string
27
27
  latexIcon: string
28
+ aiIcon: string
28
29
  buildToolbar: (builder: GroupBuilder<ToolbarItem>) => void
29
30
  }
30
31