@milkdown/crepe 7.5.9 → 7.6.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 (175) hide show
  1. package/lib/cjs/index-6oBtF_11.js +26 -0
  2. package/lib/cjs/{index-BIXWL4Or.js.map → index-6oBtF_11.js.map} +1 -1
  3. package/lib/cjs/{index-Dt55rF23.js → index-8COAwUnB.js} +5 -5
  4. package/lib/cjs/{index-Dt55rF23.js.map → index-8COAwUnB.js.map} +1 -1
  5. package/lib/cjs/{index-By3ewOiO.js → index-B4NxKjEA.js} +52 -52
  6. package/lib/cjs/index-B4NxKjEA.js.map +1 -0
  7. package/lib/cjs/{index-Dub20F3z.js → index-BOmVuuoY.js} +3 -3
  8. package/lib/cjs/{index-Dub20F3z.js.map → index-BOmVuuoY.js.map} +1 -1
  9. package/lib/cjs/{index-BuSAwj4j.js → index-C4JjdSvv.js} +3 -3
  10. package/lib/cjs/{index-BuSAwj4j.js.map → index-C4JjdSvv.js.map} +1 -1
  11. package/lib/cjs/index-C6EzkFZ_.js +39 -0
  12. package/lib/cjs/{index-DmOOfQPi.js.map → index-C6EzkFZ_.js.map} +1 -1
  13. package/lib/cjs/index-D0OPKY6K.js +65 -0
  14. package/lib/cjs/index-D0OPKY6K.js.map +1 -0
  15. package/lib/cjs/index-DkHBuNoK.js +176 -0
  16. package/lib/cjs/index-DkHBuNoK.js.map +1 -0
  17. package/lib/cjs/index-DnFbvtbD.js +278 -0
  18. package/lib/cjs/index-DnFbvtbD.js.map +1 -0
  19. package/lib/cjs/{index-AlHHvSR_.js → index-YUwSTgPK.js} +3 -3
  20. package/lib/cjs/{index-AlHHvSR_.js.map → index-YUwSTgPK.js.map} +1 -1
  21. package/lib/cjs/{index-A8IrOmzS.js → index-koRsjvWB.js} +116 -18
  22. package/lib/cjs/index-koRsjvWB.js.map +1 -0
  23. package/lib/cjs/index-nDWnWxJ8.js +38 -0
  24. package/lib/cjs/{index-CtNE-wHT.js.map → index-nDWnWxJ8.js.map} +1 -1
  25. package/lib/cjs/index.js +13 -148
  26. package/lib/cjs/index.js.map +1 -1
  27. package/lib/cjs/inline-latex-CwU8Nsfj.js +54 -0
  28. package/lib/cjs/inline-latex-CwU8Nsfj.js.map +1 -0
  29. package/lib/cjs/{todoList-V4JWO5-M.js → todo-list-V4JWO5-M.js} +1 -1
  30. package/lib/cjs/todo-list-V4JWO5-M.js.map +1 -0
  31. package/lib/esm/index-BOzN8fKq.js +36 -0
  32. package/lib/esm/{index-BEWessT5.js.map → index-BOzN8fKq.js.map} +1 -1
  33. package/lib/esm/index-BUFg1FSH.js +172 -0
  34. package/lib/esm/index-BUFg1FSH.js.map +1 -0
  35. package/lib/esm/index-BxpDIDRS.js +63 -0
  36. package/lib/esm/index-BxpDIDRS.js.map +1 -0
  37. package/lib/esm/index-C5_dM0IB.js +276 -0
  38. package/lib/esm/index-C5_dM0IB.js.map +1 -0
  39. package/lib/esm/{index-DlX33Dj0.js → index-C7Xr_l-d.js} +5 -5
  40. package/lib/esm/{index-DlX33Dj0.js.map → index-C7Xr_l-d.js.map} +1 -1
  41. package/lib/esm/{index-CxJ9fxJm.js → index-C7_YCA3P.js} +3 -3
  42. package/lib/esm/{index-CxJ9fxJm.js.map → index-C7_YCA3P.js.map} +1 -1
  43. package/lib/esm/index-C8tiRHTF.js +37 -0
  44. package/lib/esm/{index-BDGHz9p-.js.map → index-C8tiRHTF.js.map} +1 -1
  45. package/lib/esm/{index-D6fLMv29.js → index-CD8fvrRC.js} +3 -3
  46. package/lib/esm/{index-D6fLMv29.js.map → index-CD8fvrRC.js.map} +1 -1
  47. package/lib/esm/index-CEup1V8i.js +24 -0
  48. package/lib/esm/{index-6VoEpEnX.js.map → index-CEup1V8i.js.map} +1 -1
  49. package/lib/esm/{index-kGl7HgN2.js → index-DroARb55.js} +117 -19
  50. package/lib/esm/index-DroARb55.js.map +1 -0
  51. package/lib/esm/{index-zKiGjwHz.js → index-HnGoJxjJ.js} +52 -52
  52. package/lib/esm/index-HnGoJxjJ.js.map +1 -0
  53. package/lib/esm/{index-DOrkOhki.js → index-WCe-Xh61.js} +3 -3
  54. package/lib/esm/{index-DOrkOhki.js.map → index-WCe-Xh61.js.map} +1 -1
  55. package/lib/esm/index.js +11 -150
  56. package/lib/esm/index.js.map +1 -1
  57. package/lib/esm/inline-latex-BLd2QrJC.js +51 -0
  58. package/lib/esm/inline-latex-BLd2QrJC.js.map +1 -0
  59. package/lib/esm/{todoList-CAbx4396.js → todo-list-B78GeGtK.js} +2 -2
  60. package/lib/esm/todo-list-B78GeGtK.js.map +1 -0
  61. package/lib/theme/common/code-mirror.css +93 -34
  62. package/lib/theme/common/latex.css +44 -0
  63. package/lib/theme/common/style.css +1 -0
  64. package/lib/types/core/crepe.d.ts +2 -0
  65. package/lib/types/core/crepe.d.ts.map +1 -1
  66. package/lib/types/feature/code-mirror/index.d.ts +6 -2
  67. package/lib/types/feature/code-mirror/index.d.ts.map +1 -1
  68. package/lib/types/feature/index.d.ts +4 -1
  69. package/lib/types/feature/index.d.ts.map +1 -1
  70. package/lib/types/feature/latex/block-latex.d.ts +2 -0
  71. package/lib/types/feature/latex/block-latex.d.ts.map +1 -0
  72. package/lib/types/feature/latex/index.d.ts +9 -0
  73. package/lib/types/feature/latex/index.d.ts.map +1 -0
  74. package/lib/types/feature/latex/inline-latex.d.ts +3 -0
  75. package/lib/types/feature/latex/inline-latex.d.ts.map +1 -0
  76. package/lib/types/feature/latex/inline-tooltip/component.d.ts +15 -0
  77. package/lib/types/feature/latex/inline-tooltip/component.d.ts.map +1 -0
  78. package/lib/types/feature/latex/inline-tooltip/tooltip.d.ts +2 -0
  79. package/lib/types/feature/latex/inline-tooltip/tooltip.d.ts.map +1 -0
  80. package/lib/types/feature/latex/inline-tooltip/view.d.ts +13 -0
  81. package/lib/types/feature/latex/inline-tooltip/view.d.ts.map +1 -0
  82. package/lib/types/feature/latex/input-rule.d.ts +3 -0
  83. package/lib/types/feature/latex/input-rule.d.ts.map +1 -0
  84. package/lib/types/feature/latex/remark.d.ts +3 -0
  85. package/lib/types/feature/latex/remark.d.ts.map +1 -0
  86. package/lib/types/feature/shared.d.ts +1 -1
  87. package/lib/types/feature/shared.d.ts.map +1 -1
  88. package/lib/types/feature/toolbar/component.d.ts +2 -0
  89. package/lib/types/feature/toolbar/component.d.ts.map +1 -1
  90. package/lib/types/feature/toolbar/index.d.ts +1 -0
  91. package/lib/types/feature/toolbar/index.d.ts.map +1 -1
  92. package/lib/types/icons/{alignCenter.d.ts → align-center.d.ts} +1 -1
  93. package/lib/types/icons/align-center.d.ts.map +1 -0
  94. package/lib/types/icons/{alignLeft.d.ts → align-left.d.ts} +1 -1
  95. package/lib/types/icons/align-left.d.ts.map +1 -0
  96. package/lib/types/icons/{alignRight.d.ts → align-right.d.ts} +1 -1
  97. package/lib/types/icons/align-right.d.ts.map +1 -0
  98. package/lib/types/icons/{bulletList.d.ts → bullet-list.d.ts} +1 -1
  99. package/lib/types/icons/bullet-list.d.ts.map +1 -0
  100. package/lib/types/icons/{checkBoxChecked.d.ts → check-box-checked.d.ts} +1 -1
  101. package/lib/types/icons/check-box-checked.d.ts.map +1 -0
  102. package/lib/types/icons/{checkBoxUnchecked.d.ts → check-box-unchecked.d.ts} +1 -1
  103. package/lib/types/icons/check-box-unchecked.d.ts.map +1 -0
  104. package/lib/types/icons/{chevronDown.d.ts → chevron-down.d.ts} +1 -1
  105. package/lib/types/icons/chevron-down.d.ts.map +1 -0
  106. package/lib/types/icons/{dragHandle.d.ts → drag-handle.d.ts} +1 -1
  107. package/lib/types/icons/drag-handle.d.ts.map +1 -0
  108. package/lib/types/icons/functions.d.ts +2 -0
  109. package/lib/types/icons/functions.d.ts.map +1 -0
  110. package/lib/types/icons/index.d.ts +10 -10
  111. package/lib/types/icons/index.d.ts.map +1 -1
  112. package/lib/types/icons/{orderedList.d.ts → ordered-list.d.ts} +1 -1
  113. package/lib/types/icons/ordered-list.d.ts.map +1 -0
  114. package/lib/types/icons/{todoList.d.ts → todo-list.d.ts} +1 -1
  115. package/lib/types/icons/todo-list.d.ts.map +1 -0
  116. package/lib/types/icons/visibility-off.d.ts +2 -0
  117. package/lib/types/icons/visibility-off.d.ts.map +1 -0
  118. package/package.json +7 -4
  119. package/src/core/crepe.ts +19 -0
  120. package/src/feature/block-edit/handle/index.ts +1 -1
  121. package/src/feature/code-mirror/index.ts +23 -3
  122. package/src/feature/index.ts +8 -0
  123. package/src/feature/latex/block-latex.ts +25 -0
  124. package/src/feature/latex/index.ts +73 -0
  125. package/src/feature/latex/inline-latex.ts +55 -0
  126. package/src/feature/latex/inline-tooltip/component.ts +38 -0
  127. package/src/feature/latex/inline-tooltip/tooltip.ts +3 -0
  128. package/src/feature/latex/inline-tooltip/view.ts +122 -0
  129. package/src/feature/latex/input-rule.ts +25 -0
  130. package/src/feature/latex/remark.ts +35 -0
  131. package/src/feature/shared.ts +1 -1
  132. package/src/feature/toolbar/component.ts +91 -3
  133. package/src/feature/toolbar/index.ts +3 -1
  134. package/src/icons/functions.ts +15 -0
  135. package/src/icons/index.ts +10 -10
  136. package/src/icons/visibility-off.ts +14 -0
  137. package/src/theme/common/code-mirror.css +95 -34
  138. package/src/theme/common/latex.css +44 -0
  139. package/src/theme/common/style.css +1 -0
  140. package/lib/cjs/index-3mj4YaCA.js +0 -43
  141. package/lib/cjs/index-3mj4YaCA.js.map +0 -1
  142. package/lib/cjs/index-A8IrOmzS.js.map +0 -1
  143. package/lib/cjs/index-BIXWL4Or.js +0 -26
  144. package/lib/cjs/index-By3ewOiO.js.map +0 -1
  145. package/lib/cjs/index-CtNE-wHT.js +0 -38
  146. package/lib/cjs/index-DmOOfQPi.js +0 -39
  147. package/lib/cjs/todoList-V4JWO5-M.js.map +0 -1
  148. package/lib/esm/index-6VoEpEnX.js +0 -24
  149. package/lib/esm/index-BB3Rq6BU.js +0 -41
  150. package/lib/esm/index-BB3Rq6BU.js.map +0 -1
  151. package/lib/esm/index-BDGHz9p-.js +0 -37
  152. package/lib/esm/index-BEWessT5.js +0 -36
  153. package/lib/esm/index-kGl7HgN2.js.map +0 -1
  154. package/lib/esm/index-zKiGjwHz.js.map +0 -1
  155. package/lib/esm/todoList-CAbx4396.js.map +0 -1
  156. package/lib/types/icons/alignCenter.d.ts.map +0 -1
  157. package/lib/types/icons/alignLeft.d.ts.map +0 -1
  158. package/lib/types/icons/alignRight.d.ts.map +0 -1
  159. package/lib/types/icons/bulletList.d.ts.map +0 -1
  160. package/lib/types/icons/checkBoxChecked.d.ts.map +0 -1
  161. package/lib/types/icons/checkBoxUnchecked.d.ts.map +0 -1
  162. package/lib/types/icons/chevronDown.d.ts.map +0 -1
  163. package/lib/types/icons/dragHandle.d.ts.map +0 -1
  164. package/lib/types/icons/orderedList.d.ts.map +0 -1
  165. package/lib/types/icons/todoList.d.ts.map +0 -1
  166. /package/src/icons/{alignCenter.ts → align-center.ts} +0 -0
  167. /package/src/icons/{alignLeft.ts → align-left.ts} +0 -0
  168. /package/src/icons/{alignRight.ts → align-right.ts} +0 -0
  169. /package/src/icons/{bulletList.ts → bullet-list.ts} +0 -0
  170. /package/src/icons/{checkBoxChecked.ts → check-box-checked.ts} +0 -0
  171. /package/src/icons/{checkBoxUnchecked.ts → check-box-unchecked.ts} +0 -0
  172. /package/src/icons/{chevronDown.ts → chevron-down.ts} +0 -0
  173. /package/src/icons/{dragHandle.ts → drag-handle.ts} +0 -0
  174. /package/src/icons/{orderedList.ts → ordered-list.ts} +0 -0
  175. /package/src/icons/{todoList.ts → todo-list.ts} +0 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@milkdown/crepe",
3
3
  "type": "module",
4
- "version": "7.5.9",
4
+ "version": "7.6.1",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -39,14 +39,17 @@
39
39
  "@codemirror/language": "^6.10.1",
40
40
  "@codemirror/language-data": "^6.3.1",
41
41
  "@codemirror/state": "^6.4.1",
42
- "@codemirror/view": "^6.16.0",
43
42
  "@codemirror/theme-one-dark": "^6.1.2",
43
+ "@codemirror/view": "^6.16.0",
44
44
  "atomico": "^1.75.1",
45
45
  "clsx": "^2.0.0",
46
46
  "codemirror": "^6.0.1",
47
+ "katex": "^0.16.0",
47
48
  "nanoid": "^5.0.0",
48
- "tslib": "^2.5.0",
49
- "@milkdown/kit": "7.5.9"
49
+ "remark-math": "^6.0.0",
50
+ "tslib": "^2.8.1",
51
+ "unist-util-visit": "^5.0.0",
52
+ "@milkdown/kit": "7.6.1"
50
53
  },
51
54
  "nx": {
52
55
  "targets": {
package/src/core/crepe.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { DefaultValue } from '@milkdown/kit/core'
2
2
  import {
3
3
  Editor,
4
+ EditorStatus,
4
5
  defaultValueCtx,
5
6
  editorViewOptionsCtx,
6
7
  rootCtx,
@@ -17,6 +18,8 @@ import { trailing } from '@milkdown/kit/plugin/trailing'
17
18
  import type { CrepeFeatureConfig } from '../feature'
18
19
  import { CrepeFeature, defaultFeatures, loadFeature } from '../feature'
19
20
  import { configureFeatures } from './slice'
21
+ import type { ListenerManager } from '@milkdown/kit/plugin/listener'
22
+ import { listener, listenerCtx } from '@milkdown/kit/plugin/listener'
20
23
 
21
24
  export interface CrepeConfig {
22
25
  features?: Partial<Record<CrepeFeature, boolean>>
@@ -62,6 +65,7 @@ export class Crepe {
62
65
  }))
63
66
  })
64
67
  .use(commonmark)
68
+ .use(listener)
65
69
  .use(history)
66
70
  .use(indent)
67
71
  .use(trailing)
@@ -102,4 +106,19 @@ export class Crepe {
102
106
  getMarkdown() {
103
107
  return this.#editor.action(getMarkdown())
104
108
  }
109
+
110
+ on(fn: (api: ListenerManager) => void) {
111
+ if (this.#editor.status !== EditorStatus.Created) {
112
+ this.#editor.config((ctx) => {
113
+ const listener = ctx.get(listenerCtx)
114
+ fn(listener)
115
+ })
116
+ return this
117
+ }
118
+ this.#editor.action((ctx) => {
119
+ const listener = ctx.get(listenerCtx)
120
+ fn(listener)
121
+ })
122
+ return this
123
+ }
105
124
  }
@@ -88,7 +88,7 @@ export function configureBlockHandle(
88
88
  ctx.set(blockConfig.key, {
89
89
  filterNodes: (pos) => {
90
90
  const filter = findParent((node) =>
91
- ['table', 'blockquote'].includes(node.type.name)
91
+ ['table', 'blockquote', 'math_inline'].includes(node.type.name)
92
92
  )(pos)
93
93
  if (filter) return false
94
94
 
@@ -7,9 +7,10 @@ import type { Extension } from '@codemirror/state'
7
7
  import { basicSetup } from 'codemirror'
8
8
  import { keymap } from '@codemirror/view'
9
9
  import { defaultKeymap, indentWithTab } from '@codemirror/commands'
10
- import type { html } from 'atomico'
10
+ import { html } from 'atomico'
11
11
  import type { DefineFeature, Icon } from '../shared'
12
- import { chevronDownIcon, clearIcon, searchIcon } from '../../icons'
12
+ import { chevronDownIcon, clearIcon, editIcon, searchIcon } from '../../icons'
13
+ import { visibilityOffIcon } from '../../icons/visibility-off'
13
14
 
14
15
  interface CodeMirrorConfig {
15
16
  extensions: Extension[]
@@ -26,7 +27,16 @@ interface CodeMirrorConfig {
26
27
  renderLanguage: (
27
28
  language: string,
28
29
  selected: boolean
29
- ) => ReturnType<typeof html> | string | HTMLElement
30
+ ) => ReturnType<typeof html> | string
31
+
32
+ renderPreview: (
33
+ language: string,
34
+ content: string
35
+ ) => string | HTMLElement | null
36
+
37
+ previewToggleIcon: (previewOnlyMode: boolean) => ReturnType<Icon>
38
+ previewToggleText: (previewOnlyMode: boolean) => ReturnType<typeof html>
39
+ previewLabel: () => ReturnType<typeof html>
30
40
  }
31
41
  export type CodeMirrorFeatureConfig = Partial<CodeMirrorConfig>
32
42
 
@@ -62,6 +72,16 @@ export const defineFeature: DefineFeature<CodeMirrorFeatureConfig> = (
62
72
  searchPlaceholder: config.searchPlaceholder || 'Search language',
63
73
  noResultText: config.noResultText || 'No result',
64
74
  renderLanguage: config.renderLanguage || defaultConfig.renderLanguage,
75
+ renderPreview: config.renderPreview || defaultConfig.renderPreview,
76
+ previewToggleButton: (previewOnlyMode) => {
77
+ return html`
78
+ ${config.previewToggleIcon?.(previewOnlyMode) ||
79
+ (previewOnlyMode ? editIcon : visibilityOffIcon)}
80
+ ${config.previewToggleText?.(previewOnlyMode) ||
81
+ (previewOnlyMode ? 'Edit' : 'Hide')}
82
+ `
83
+ },
84
+ previewLabel: config.previewLabel || defaultConfig.previewLabel,
65
85
  }))
66
86
  })
67
87
  .use(codeBlockComponent)
@@ -8,6 +8,7 @@ import type { LinkTooltipFeatureConfig } from './link-tooltip'
8
8
  import type { ListItemFeatureConfig } from './list-item'
9
9
  import type { ToolbarFeatureConfig } from './toolbar'
10
10
  import type { TableFeatureConfig } from './table'
11
+ import type { LatexFeatureConfig } from './latex'
11
12
 
12
13
  export enum CrepeFeature {
13
14
  CodeMirror = 'code-mirror',
@@ -19,6 +20,7 @@ export enum CrepeFeature {
19
20
  Toolbar = 'toolbar',
20
21
  Placeholder = 'placeholder',
21
22
  Table = 'table',
23
+ Latex = 'latex',
22
24
  }
23
25
 
24
26
  export interface CrepeFeatureConfig {
@@ -31,6 +33,7 @@ export interface CrepeFeatureConfig {
31
33
  [CrepeFeature.Toolbar]?: ToolbarFeatureConfig
32
34
  [CrepeFeature.CodeMirror]?: CodeMirrorFeatureConfig
33
35
  [CrepeFeature.Table]?: TableFeatureConfig
36
+ [CrepeFeature.Latex]?: LatexFeatureConfig
34
37
  }
35
38
 
36
39
  export const defaultFeatures: Record<CrepeFeature, boolean> = {
@@ -43,6 +46,7 @@ export const defaultFeatures: Record<CrepeFeature, boolean> = {
43
46
  [CrepeFeature.Toolbar]: true,
44
47
  [CrepeFeature.CodeMirror]: true,
45
48
  [CrepeFeature.Table]: true,
49
+ [CrepeFeature.Latex]: true,
46
50
  }
47
51
 
48
52
  export async function loadFeature(
@@ -87,5 +91,9 @@ export async function loadFeature(
87
91
  const { defineFeature } = await import('./table')
88
92
  return defineFeature(editor, config)
89
93
  }
94
+ case CrepeFeature.Latex: {
95
+ const { defineFeature } = await import('./latex')
96
+ return defineFeature(editor, config)
97
+ }
90
98
  }
91
99
  }
@@ -0,0 +1,25 @@
1
+ import { codeBlockSchema } from '@milkdown/kit/preset/commonmark'
2
+
3
+ export const blockLatexSchema = codeBlockSchema.extendSchema((prev) => {
4
+ return (ctx) => {
5
+ const baseSchema = prev(ctx)
6
+ return {
7
+ ...baseSchema,
8
+ toMarkdown: {
9
+ match: baseSchema.toMarkdown.match,
10
+ runner: (state, node) => {
11
+ const language = node.attrs.language
12
+ if (language.toLowerCase() === 'latex') {
13
+ state.addNode(
14
+ 'math',
15
+ undefined,
16
+ node.content.firstChild?.text || ''
17
+ )
18
+ } else {
19
+ return baseSchema.toMarkdown.runner(state, node)
20
+ }
21
+ },
22
+ },
23
+ }
24
+ }
25
+ })
@@ -0,0 +1,73 @@
1
+ import type { KatexOptions } from 'katex'
2
+ import katex from 'katex'
3
+ import { codeBlockConfig } from '@milkdown/kit/component/code-block'
4
+ import { CrepeFeature } from '../..'
5
+ import { FeaturesCtx } from '../../core/slice'
6
+ import type { DefineFeature, Icon } from '../shared'
7
+ import { remarkMathBlockPlugin, remarkMathPlugin } from './remark'
8
+ import { mathInlineSchema } from './inline-latex'
9
+ import { defIfNotExists } from '../../utils'
10
+ import { LatexInlineEditElement } from './inline-tooltip/component'
11
+ import { inlineLatexTooltip } from './inline-tooltip/tooltip'
12
+ import { LatexInlineTooltip } from './inline-tooltip/view'
13
+ import { confirmIcon } from '../../icons'
14
+ import { mathBlockInputRule, mathInlineInputRule } from './input-rule'
15
+ import { blockLatexSchema } from './block-latex'
16
+
17
+ export interface LatexConfig {
18
+ katexOptions: KatexOptions
19
+ inlineEditConfirm: Icon
20
+ }
21
+
22
+ export type LatexFeatureConfig = Partial<LatexConfig>
23
+
24
+ defIfNotExists('milkdown-latex-inline-edit', LatexInlineEditElement)
25
+ export const defineFeature: DefineFeature<LatexFeatureConfig> = (
26
+ editor,
27
+ config
28
+ ) => {
29
+ editor
30
+ .config((ctx) => {
31
+ const flags = ctx.get(FeaturesCtx)
32
+ const isCodeMirrorEnabled = flags.includes(CrepeFeature.CodeMirror)
33
+ if (!isCodeMirrorEnabled) {
34
+ throw new Error('You need to enable CodeMirror to use LaTeX feature')
35
+ }
36
+
37
+ ctx.update(codeBlockConfig.key, (prev) => ({
38
+ ...prev,
39
+ renderPreview: (language, content) => {
40
+ if (language.toLowerCase() === 'latex' && content.length > 0) {
41
+ return renderLatex(content, config?.katexOptions)
42
+ }
43
+ const renderPreview = prev.renderPreview
44
+ return renderPreview(language, content)
45
+ },
46
+ }))
47
+
48
+ ctx.set(inlineLatexTooltip.key, {
49
+ view: (view) => {
50
+ return new LatexInlineTooltip(ctx, view, {
51
+ inlineEditConfirm: config?.inlineEditConfirm ?? (() => confirmIcon),
52
+ ...config,
53
+ })
54
+ },
55
+ })
56
+ })
57
+ .use(remarkMathPlugin)
58
+ .use(remarkMathBlockPlugin)
59
+ .use(mathInlineSchema)
60
+ .use(inlineLatexTooltip)
61
+ .use(mathInlineInputRule)
62
+ .use(mathBlockInputRule)
63
+ .use(blockLatexSchema)
64
+ }
65
+
66
+ function renderLatex(content: string, options?: KatexOptions) {
67
+ const html = katex.renderToString(content, {
68
+ ...options,
69
+ throwOnError: false,
70
+ displayMode: true,
71
+ })
72
+ return html
73
+ }
@@ -0,0 +1,55 @@
1
+ import { $nodeSchema } from '@milkdown/kit/utils'
2
+ import katex from 'katex'
3
+
4
+ export const mathInlineId = 'math_inline'
5
+
6
+ /// Schema for inline math node.
7
+ /// Add support for:
8
+ ///
9
+ /// ```markdown
10
+ /// $a^2 + b^2 = c^2$
11
+ /// ```
12
+ export const mathInlineSchema = $nodeSchema(mathInlineId, () => ({
13
+ group: 'inline',
14
+ inline: true,
15
+ draggable: true,
16
+ atom: true,
17
+ attrs: {
18
+ value: {
19
+ default: '',
20
+ },
21
+ },
22
+ parseDOM: [
23
+ {
24
+ tag: `span[data-type="${mathInlineId}"]`,
25
+ getAttrs: (dom) => {
26
+ return {
27
+ value: (dom as HTMLElement).dataset.value ?? '',
28
+ }
29
+ },
30
+ },
31
+ ],
32
+ toDOM: (node) => {
33
+ const code: string = node.attrs.value
34
+ const dom = document.createElement('span')
35
+ dom.dataset.type = mathInlineId
36
+ dom.dataset.value = code
37
+ katex.render(code, dom, {
38
+ throwOnError: false,
39
+ })
40
+
41
+ return dom
42
+ },
43
+ parseMarkdown: {
44
+ match: (node) => node.type === 'inlineMath',
45
+ runner: (state, node, type) => {
46
+ state.addNode(type, { value: node.value as string })
47
+ },
48
+ },
49
+ toMarkdown: {
50
+ match: (node) => node.type.name === mathInlineId,
51
+ runner: (state, node) => {
52
+ state.addNode('inlineMath', undefined, node.attrs.value)
53
+ },
54
+ },
55
+ }))
@@ -0,0 +1,38 @@
1
+ import { c, h, html, type Component } from 'atomico'
2
+ import type { LatexConfig } from '..'
3
+ import type { EditorView } from '@milkdown/kit/prose/view'
4
+
5
+ type LatexEditComponentProps = {
6
+ config: Partial<LatexConfig>
7
+ innerView: EditorView
8
+ updateValue: () => void
9
+ }
10
+
11
+ export const latexEditComponent: Component<LatexEditComponentProps> = ({
12
+ config,
13
+ innerView,
14
+ updateValue,
15
+ }) => {
16
+ const onMouseDown = (e: MouseEvent) => {
17
+ e.preventDefault()
18
+ updateValue?.()
19
+ }
20
+ return html`
21
+ <host>
22
+ <div class="container">
23
+ ${innerView && h(innerView.dom, {})}
24
+ <button onmousedown=${onMouseDown}>
25
+ ${config?.inlineEditConfirm?.()}
26
+ </button>
27
+ </div>
28
+ </host>
29
+ `
30
+ }
31
+
32
+ latexEditComponent.props = {
33
+ config: Object,
34
+ innerView: Object,
35
+ updateValue: Function,
36
+ }
37
+
38
+ export const LatexInlineEditElement = c(latexEditComponent)
@@ -0,0 +1,3 @@
1
+ import { tooltipFactory } from '@milkdown/kit/plugin/tooltip'
2
+
3
+ export const inlineLatexTooltip = tooltipFactory('INLINE_LATEX')
@@ -0,0 +1,122 @@
1
+ import type { Ctx } from '@milkdown/kit/ctx'
2
+ import { TooltipProvider } from '@milkdown/kit/plugin/tooltip'
3
+ import type { PluginView } from '@milkdown/kit/prose/state'
4
+ import { EditorState, NodeSelection } from '@milkdown/kit/prose/state'
5
+ import { EditorView } from '@milkdown/kit/prose/view'
6
+ import { mathInlineId } from '../inline-latex'
7
+ import { LatexInlineEditElement } from './component'
8
+ import type { LatexConfig } from '..'
9
+ import { keymap } from '@milkdown/kit/prose/keymap'
10
+ import { redo, undo } from '@milkdown/kit/prose/history'
11
+ import { Schema } from '@milkdown/kit/prose/model'
12
+
13
+ export class LatexInlineTooltip implements PluginView {
14
+ #content = new LatexInlineEditElement()
15
+ #provider: TooltipProvider
16
+ #dom: HTMLElement
17
+ #innerView: EditorView | null
18
+
19
+ constructor(
20
+ readonly ctx: Ctx,
21
+ view: EditorView,
22
+ config: Partial<LatexConfig>
23
+ ) {
24
+ this.#provider = new TooltipProvider({
25
+ debounce: 0,
26
+ content: this.#content,
27
+ shouldShow: this.#shouldShow,
28
+ offset: 10,
29
+ floatingUIOptions: {
30
+ placement: 'bottom',
31
+ },
32
+ })
33
+ this.#content.config = config
34
+ this.#provider.update(view)
35
+ this.#dom = document.createElement('div')
36
+ this.#innerView = null
37
+ }
38
+
39
+ #onHide = () => {
40
+ if (this.#innerView) {
41
+ this.#innerView.destroy()
42
+ this.#innerView = null
43
+ }
44
+ }
45
+
46
+ #shouldShow = (view: EditorView) => {
47
+ const shouldShow = () => {
48
+ const { selection, schema } = view.state
49
+ if (selection.empty) return false
50
+ if (!(selection instanceof NodeSelection)) return false
51
+ const node = selection.node
52
+ if (node.type.name !== mathInlineId) return false
53
+
54
+ const textFrom = selection.from
55
+
56
+ const paragraph = schema.nodes.paragraph!.create(
57
+ null,
58
+ schema.text(node.attrs.value)
59
+ )
60
+
61
+ const innerView = new EditorView(this.#dom, {
62
+ state: EditorState.create({
63
+ doc: paragraph,
64
+ schema: new Schema({
65
+ nodes: {
66
+ doc: {
67
+ content: 'block+',
68
+ },
69
+ paragraph: {
70
+ content: 'inline*',
71
+ group: 'block',
72
+ parseDOM: [{ tag: 'p' }],
73
+ toDOM() {
74
+ return ['p', 0]
75
+ },
76
+ },
77
+ text: {
78
+ group: 'inline',
79
+ },
80
+ },
81
+ }),
82
+ plugins: [
83
+ keymap({
84
+ 'Mod-z': undo,
85
+ 'Mod-Z': redo,
86
+ 'Mod-y': redo,
87
+ Enter: () => {
88
+ this.#content.updateValue?.()
89
+ return true
90
+ },
91
+ }),
92
+ ],
93
+ }),
94
+ })
95
+
96
+ this.#innerView = innerView
97
+ this.#content.innerView = this.#innerView
98
+ this.#content.updateValue = () => {
99
+ const { tr } = view.state
100
+ tr.setNodeAttribute(textFrom, 'value', innerView.state.doc.textContent)
101
+ view.dispatch(tr)
102
+ requestAnimationFrame(() => {
103
+ view.focus()
104
+ })
105
+ }
106
+ return true
107
+ }
108
+
109
+ const show = shouldShow()
110
+ if (!show) this.#onHide()
111
+ return show
112
+ }
113
+
114
+ update = (view: EditorView, prevState?: EditorState) => {
115
+ this.#provider.update(view, prevState)
116
+ }
117
+
118
+ destroy = () => {
119
+ this.#provider.destroy()
120
+ this.#content.remove()
121
+ }
122
+ }
@@ -0,0 +1,25 @@
1
+ import { $inputRule } from '@milkdown/kit/utils'
2
+ import { nodeRule } from '@milkdown/kit/prose'
3
+ import { mathInlineSchema } from './inline-latex'
4
+ import { codeBlockSchema } from '@milkdown/kit/preset/commonmark'
5
+ import { textblockTypeInputRule } from '@milkdown/kit/prose/inputrules'
6
+
7
+ /// Input rule for inline math.
8
+ /// When you type $E=MC^2$, it will create an inline math node.
9
+ export const mathInlineInputRule = $inputRule((ctx) =>
10
+ nodeRule(/(?:\$)([^$]+)(?:\$)$/, mathInlineSchema.type(ctx), {
11
+ getAttr: (match) => {
12
+ return {
13
+ value: match[1] ?? '',
14
+ }
15
+ },
16
+ })
17
+ )
18
+
19
+ /// A input rule for creating block math.
20
+ /// For example, `$$ ` will create a code block with language javascript.
21
+ export const mathBlockInputRule = $inputRule((ctx) =>
22
+ textblockTypeInputRule(/^\$\$[\s\n]$/, codeBlockSchema.type(ctx), () => ({
23
+ language: 'LaTeX',
24
+ }))
25
+ )
@@ -0,0 +1,35 @@
1
+ import remarkMath from 'remark-math'
2
+ import { $remark } from '@milkdown/kit/utils'
3
+ import type { Node } from '@milkdown/kit/transformer'
4
+ import { visit } from 'unist-util-visit'
5
+
6
+ export const remarkMathPlugin = $remark<'remarkMath', undefined>(
7
+ 'remarkMath',
8
+ () => remarkMath
9
+ )
10
+
11
+ function visitMathBlock(ast: Node) {
12
+ return visit(
13
+ ast,
14
+ 'math',
15
+ (
16
+ node: Node & { value: string },
17
+ index: number,
18
+ parent: Node & { children: Node[] }
19
+ ) => {
20
+ const { value } = node as Node & { value: string }
21
+ const newNode = {
22
+ type: 'code',
23
+ lang: 'LaTeX',
24
+ value,
25
+ }
26
+ parent.children.splice(index, 1, newNode)
27
+ }
28
+ )
29
+ }
30
+
31
+ /// Turn math block into code block with language LaTeX.
32
+ export const remarkMathBlockPlugin = $remark(
33
+ 'remarkMathBlock',
34
+ () => () => visitMathBlock
35
+ )
@@ -6,4 +6,4 @@ export type DefineFeature<Config = unknown> = (
6
6
  config?: Config
7
7
  ) => void | Promise<void>
8
8
 
9
- export type Icon = () => HTMLElement | ReturnType<typeof html> | string | null
9
+ export type Icon = () => ReturnType<typeof html> | string | null