Skip to content

Commit 9efdf29

Browse files
committed
fix: render alert rule but can not edit now
Signed-off-by: Innei <[email protected]>
1 parent f9c9781 commit 9efdf29

File tree

9 files changed

+258
-51
lines changed

9 files changed

+258
-51
lines changed

.npmrc

+2
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22
strict-peer-dependencies=false
33

44
registry=https://registry.npmjs.org
5+
6+
public-hoist-pattern[]=mdast-util-to-markdown

package.json

-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@
108108
"react-tweet": "3.2.0",
109109
"react-wrap-balancer": "1.1.0",
110110
"remark-directive": "3.0.0",
111-
"remark-github-alerts": "^0.0.4",
112111
"remove-markdown": "0.5.0",
113112
"server-only": "^0.0.1",
114113
"socket.io-client": "4.7.4",

pnpm-lock.yaml

-14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/ui/editor/Milkdown/MilkdownEditor.tsx

+11
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
EditorStatus,
2020
editorViewCtx,
2121
editorViewOptionsCtx,
22+
remarkStringifyOptionsCtx,
2223
rootCtx,
2324
serializerCtx,
2425
} from '@milkdown/core'
@@ -34,6 +35,7 @@ import { useIsUnMounted } from '~/hooks/common/use-is-unmounted'
3435
import { isDev } from '~/lib/env'
3536

3637
import { setEditorCtx } from './ctx'
38+
import { extensionOfRemarkStringify } from './extensions/remark-stringify'
3739
import styles from './index.module.css'
3840
import { createPlugins } from './plugins'
3941

@@ -102,6 +104,15 @@ const MilkdownEditorImpl = forwardRef<MilkdownRef, MilkdownProps>(
102104
editable: () => !props.readonly,
103105
}))
104106

107+
const originalStringifyOptions = ctx.get(remarkStringifyOptionsCtx)
108+
109+
ctx.set(remarkStringifyOptionsCtx, {
110+
handlers: {
111+
...originalStringifyOptions.handlers,
112+
...(extensionOfRemarkStringify as any),
113+
},
114+
})
115+
105116
ctx
106117
.get(listenerCtx)
107118
.markdownUpdated((ctx, markdown) => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { Handle } from 'mdast-util-to-markdown'
2+
3+
export const extensionOfRemarkStringify: Record<string, Handle> = {
4+
alert: (node, _, state, info) => {
5+
const exit = state.enter('alert' as any)
6+
const tracker = state.createTracker(info)
7+
let value = tracker.move('')
8+
const { type, text } = node.value as {
9+
type: string
10+
text: string
11+
}
12+
13+
value += `> [!${type.toUpperCase()}]\n${text
14+
.split('\n')
15+
.map((line) => `> ${line}`)
16+
.join('\n')}\n`
17+
exit()
18+
19+
return value
20+
},
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
/* Copyright 2021, Milkdown by Mirone. */
2+
import { useNodeViewContext } from '@prosemirror-adapter/react'
3+
import { visit } from 'unist-util-visit'
4+
import type { Ctx, MilkdownPlugin } from '@milkdown/ctx'
5+
import type { Node } from '@milkdown/transformer'
6+
import type { $NodeSchema } from '@milkdown/utils'
7+
import type { PluginCtx } from './types'
8+
9+
import { remarkCtx } from '@milkdown/core'
10+
import { wrappingInputRule } from '@milkdown/prose/inputrules'
11+
import {
12+
$inputRule,
13+
$nodeAttr,
14+
$nodeSchema,
15+
$remark,
16+
$view,
17+
} from '@milkdown/utils'
18+
19+
import { AlertIcon } from '~/components/ui/markdown/parsers/alert'
20+
import { cloneDeep } from '~/lib/lodash'
21+
22+
/// HTML attributes for alert node.
23+
export const alertAttr = $nodeAttr('alert')
24+
25+
/// Schema for alert node.
26+
export const alertSchema: $NodeSchema<'alert'> = $nodeSchema(
27+
'alert',
28+
(ctx) => ({
29+
content: 'block+',
30+
group: 'block',
31+
attrs: {
32+
type: { default: '' },
33+
text: { default: '' },
34+
},
35+
defining: true,
36+
parseDOM: [{ tag: 'blockquote' }],
37+
toDOM: (node) => ['blockquote', ctx.get(alertAttr.key)(node), 0],
38+
parseMarkdown: {
39+
match: ({ type }) => type === 'alert',
40+
runner: (state, node, type) => {
41+
state
42+
.openNode(type, (node as any as { value: AlertValue }).value)
43+
.closeNode()
44+
},
45+
},
46+
toMarkdown: {
47+
match: (node) => node.type.name === 'alert',
48+
runner: (state, node) => {
49+
state
50+
.openNode('alert', node.attrs as any)
51+
52+
.closeNode()
53+
},
54+
},
55+
}),
56+
)
57+
58+
export type AlertValue = {
59+
type: string
60+
text: string
61+
}
62+
function createAlertDiv(contents: AlertValue) {
63+
return {
64+
type: 'alert',
65+
value: contents,
66+
}
67+
}
68+
69+
function visitCodeBlock(ast: Node, stringify: (ast: any) => string) {
70+
return visit(
71+
ast,
72+
'blockquote',
73+
(node: any, index, parent: Node & { children: Node[] }) => {
74+
if (node.children.length < 1) {
75+
return node
76+
}
77+
78+
const firstChildren = node.children[0]
79+
if (firstChildren.type !== 'paragraph') {
80+
return node
81+
}
82+
const innerChild = firstChildren.children[0]
83+
if (!innerChild || innerChild.type !== 'text') {
84+
return node
85+
}
86+
87+
const firstLineOfInnerChildText = innerChild.value.split(
88+
'\n',
89+
)[0] as string
90+
91+
const matched = firstLineOfInnerChildText.match(/^\[!(.*?)\]$/)
92+
93+
if (!matched) {
94+
return node
95+
}
96+
97+
const transformedTree = cloneDeep(node)
98+
transformedTree.children[0].children =
99+
transformedTree.children[0].children.slice(1)
100+
101+
transformedTree.type = 'root'
102+
const newNode = createAlertDiv({
103+
// FIXME: replace all backslashes with empty string,
104+
text: stringify(transformedTree).replace(/\\/g, '') || '',
105+
type: (matched[1] as string) || 'NOTE',
106+
})
107+
108+
if (parent && index != null) parent.children.splice(index, 1, newNode)
109+
110+
return node
111+
},
112+
)
113+
}
114+
115+
function remarkAlert(ctx: Ctx) {
116+
function transformer(tree: Node) {
117+
visitCodeBlock(tree, (val) => ctx.get(remarkCtx).stringify(val))
118+
}
119+
120+
return transformer
121+
}
122+
123+
export const remarkAlertPlugin = $remark('remarkAlert', (c) =>
124+
remarkAlert.bind(null, c),
125+
)
126+
127+
export const wrapInAlertInputRule = [
128+
$inputRule((ctx) => wrappingInputRule(/^\s*>\[\s$/, alertSchema.type(ctx))),
129+
130+
$inputRule((ctx) => wrappingInputRule(/^\s*> \[!\s$/, alertSchema.type(ctx))),
131+
$inputRule((ctx) =>
132+
wrappingInputRule(
133+
/^\s*> \[!(?<type>NOTE|IMPORTANT|WARNING)\]\s$/,
134+
alertSchema.type(ctx),
135+
(match) => {
136+
return {
137+
type: match.groups?.type,
138+
}
139+
},
140+
),
141+
),
142+
]
143+
144+
// TODO text editor for alert
145+
146+
const AlertRender = () => {
147+
const { contentRef, setAttrs, node, selected } = useNodeViewContext()
148+
149+
const attrs = node.attrs as AlertValue
150+
const type = attrs.type
151+
152+
return (
153+
<div>
154+
<blockquote
155+
className="my-4 flex flex-col rounded !bg-accent/10 p-0.5"
156+
contentEditable={false}
157+
>
158+
<AlertIcon type={type as any} />
159+
<div>{attrs.text}</div>
160+
</blockquote>
161+
</div>
162+
)
163+
}
164+
165+
export const AlertPlugin: (pluginCtx: PluginCtx) => MilkdownPlugin[] = ({
166+
nodeViewFactory,
167+
}) => [
168+
$view(alertSchema.node, () =>
169+
nodeViewFactory({
170+
component: AlertRender,
171+
}),
172+
),
173+
alertSchema as any as MilkdownPlugin,
174+
alertAttr,
175+
remarkAlertPlugin as any as MilkdownPlugin,
176+
...(wrapInAlertInputRule.flat() as any as MilkdownPlugin[]),
177+
]

src/components/ui/editor/Milkdown/plugins/index.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import remarkGithubAlerts from 'remark-github-alerts'
21
import type { MilkdownPlugin } from '@milkdown/ctx'
32
import type { PluginCtx } from './types'
43

54
import { diagram } from '@milkdown/plugin-diagram'
6-
import { $remark } from '@milkdown/utils'
75

6+
import { AlertPlugin } from './Alert'
87
import { BlockquotePlugin } from './Blockquote'
98
import { CodeBlockPlugin } from './CodeBlock'
109
import { ExcalidrawPlugins } from './Excalidraw'
@@ -17,7 +16,9 @@ export const createPlugins = (pluginCtx: PluginCtx): MilkdownPlugin[] =>
1716
CodeBlockPlugin(pluginCtx),
1817
MermaidPlugin(pluginCtx),
1918
ImagePlugin(pluginCtx),
20-
$remark('alerts', () => remarkGithubAlerts),
19+
2120
diagram,
2221
ExcalidrawPlugins(pluginCtx),
22+
23+
AlertPlugin(pluginCtx),
2324
].flat()

src/components/ui/markdown/parsers/alert.tsx

+29-19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import clsx from 'clsx'
22
import { blockRegex, Priority } from 'markdown-to-jsx'
33
import type { MarkdownToJSX } from 'markdown-to-jsx'
4+
import type { FC } from 'react'
45

56
import {
67
FluentShieldError20Regular,
@@ -26,7 +27,33 @@ const typedIconMap = {
2627
NOTE: IonInformation,
2728
IMPORTANT: FluentWarning28Regular,
2829
WARNING: FluentShieldError20Regular,
29-
} as any
30+
}
31+
32+
export const AlertIcon: FC<{
33+
type: keyof typeof typedIconMap
34+
}> = ({ type }) => {
35+
const finalType = type || 'NOTE'
36+
const Icon = typedIconMap[finalType] || typedIconMap.NOTE
37+
const typePrefix = finalType[0] + finalType.toLowerCase().slice(1)
38+
39+
return (
40+
<span
41+
className={clsx(
42+
'text-semibold mb-1 inline-flex items-center',
43+
textColorMap[finalType],
44+
)}
45+
>
46+
<Icon
47+
className={clsx(
48+
`flex-shrink-0 text-3xl md:mr-2 md:self-start md:text-left`,
49+
typedIconMap[finalType] || typedIconMap.NOTE,
50+
)}
51+
/>
52+
53+
{typePrefix}
54+
</span>
55+
)
56+
}
3057

3158
/**
3259
*
@@ -51,29 +78,12 @@ export const AlertsRule: MarkdownToJSX.Rule = {
5178
const { type, body } = node.parsed
5279
const bodyClean = body.replace(/^> */gm, '')
5380

54-
const typePrefix = type[0] + type.toLowerCase().slice(1)
55-
56-
const Icon = typedIconMap[type] || typedIconMap.info
5781
return (
5882
<blockquote
5983
className={clsx(borderColorMap[type], 'not-italic')}
6084
key={state.key}
6185
>
62-
<span
63-
className={clsx(
64-
'text-semibold mb-1 inline-flex items-center',
65-
textColorMap[type],
66-
)}
67-
>
68-
<Icon
69-
className={clsx(
70-
`flex-shrink-0 text-3xl md:mr-2 md:self-start md:text-left`,
71-
typedIconMap[type] || typedIconMap.info,
72-
)}
73-
/>
74-
75-
{typePrefix}
76-
</span>
86+
<AlertIcon type={type as any} />
7787
<br />
7888

7989
<Markdown

0 commit comments

Comments
 (0)