@nuasite/cms-mdx-editor 0.43.3 → 0.44.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/types/format-toolbar.d.ts +3 -1
- package/dist/types/format-toolbar.d.ts.map +1 -1
- package/dist/types/image-popover.d.ts +7 -0
- package/dist/types/image-popover.d.ts.map +1 -0
- package/dist/types/link-popover.d.ts +4 -0
- package/dist/types/link-popover.d.ts.map +1 -1
- package/dist/types/mdx-body-editor.d.ts +4 -2
- package/dist/types/mdx-body-editor.d.ts.map +1 -1
- package/dist/types/milkdown-utils.d.ts +1 -0
- package/dist/types/milkdown-utils.d.ts.map +1 -1
- package/dist/types/styled-list-plugin.d.ts +19 -0
- package/dist/types/styled-list-plugin.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/dist/types/youtube-popover.d.ts +6 -0
- package/dist/types/youtube-popover.d.ts.map +1 -0
- package/dist/types/youtube.d.ts +7 -0
- package/dist/types/youtube.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/format-toolbar.tsx +99 -5
- package/src/image-popover.tsx +62 -0
- package/src/link-popover.tsx +7 -3
- package/src/mdx-body-editor.tsx +7 -2
- package/src/milkdown-utils.ts +10 -3
- package/src/styled-list-plugin.ts +361 -0
- package/src/youtube-popover.tsx +62 -0
- package/src/youtube.ts +27 -0
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"directory": "packages/cms-mdx-editor"
|
|
15
15
|
},
|
|
16
16
|
"license": "Apache-2.0",
|
|
17
|
-
"version": "0.
|
|
17
|
+
"version": "0.44.1",
|
|
18
18
|
"module": "src/index.ts",
|
|
19
19
|
"types": "src/index.ts",
|
|
20
20
|
"type": "module",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@milkdown/preset-commonmark": "^7.20.0",
|
|
32
32
|
"@milkdown/preset-gfm": "^7.20.0",
|
|
33
33
|
"@milkdown/utils": "^7.20.0",
|
|
34
|
-
"@nuasite/cms-types": "0.
|
|
34
|
+
"@nuasite/cms-types": "0.44.1",
|
|
35
35
|
"remark-mdx": "^3.1.0",
|
|
36
36
|
"remark-parse": "^11.0.0",
|
|
37
37
|
"unified": "^11.0.5"
|
package/src/format-toolbar.tsx
CHANGED
|
@@ -16,13 +16,17 @@ import {
|
|
|
16
16
|
wrapInBulletListCommand,
|
|
17
17
|
wrapInOrderedListCommand,
|
|
18
18
|
} from '@milkdown/preset-commonmark'
|
|
19
|
-
import { toggleStrikethroughCommand } from '@milkdown/preset-gfm'
|
|
19
|
+
import { insertTableCommand, toggleStrikethroughCommand } from '@milkdown/preset-gfm'
|
|
20
20
|
import { callCommand } from '@milkdown/utils'
|
|
21
|
+
import type { CmsListStyle } from '@nuasite/cms-types'
|
|
21
22
|
import { useEffect, useState } from 'react'
|
|
23
|
+
import { ImagePopover } from './image-popover'
|
|
22
24
|
import { LinkPopover } from './link-popover'
|
|
23
25
|
import { MediaLibrary } from './media-library'
|
|
24
26
|
import type { MediaContext, MediaSource } from './media-source'
|
|
25
27
|
import { type ActiveFormats, defaultActiveFormats, isInListType, removeLinkMark, setupFormatTracking, toggleHeading } from './milkdown-utils'
|
|
28
|
+
import { setListStyleCommand } from './styled-list-plugin'
|
|
29
|
+
import { YoutubePopover } from './youtube-popover'
|
|
26
30
|
|
|
27
31
|
/** Track active formats on the editor, re-attaching when the instance changes. */
|
|
28
32
|
export function useFormatTracking(editor: Editor | null): ActiveFormats {
|
|
@@ -51,18 +55,38 @@ function toggleList(editor: Editor, type: 'bullet' | 'ordered') {
|
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
57
|
|
|
54
|
-
function
|
|
58
|
+
function applyListStyle(editor: Editor, listStyle: string | null) {
|
|
59
|
+
const view = editor.ctx.get(editorViewCtx)
|
|
60
|
+
view.focus()
|
|
61
|
+
editor.action(callCommand(setListStyleCommand.key, listStyle))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function insertImage(editor: Editor, src: string, alt: string, title: string) {
|
|
55
65
|
editor.action((ctx) => {
|
|
56
66
|
const view = ctx.get(editorViewCtx)
|
|
57
67
|
const imageType = view.state.schema.nodes.image
|
|
58
68
|
if (!imageType) return
|
|
59
69
|
view.focus()
|
|
60
|
-
view.dispatch(view.state.tr.replaceSelectionWith(imageType.create({ src, alt })).scrollIntoView())
|
|
70
|
+
view.dispatch(view.state.tr.replaceSelectionWith(imageType.create({ src, alt, title })).scrollIntoView())
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function insertYoutubeDirective(editor: Editor, id: string) {
|
|
75
|
+
editor.action((ctx) => {
|
|
76
|
+
const view = ctx.get(editorViewCtx)
|
|
77
|
+
const paragraphType = view.state.schema.nodes.paragraph
|
|
78
|
+
if (!paragraphType) return
|
|
79
|
+
|
|
80
|
+
// Directive format: `:::youtube{<id>}` where id is the bare 11-char video id, with no surrounding spaces.
|
|
81
|
+
const paragraph = paragraphType.create(null, view.state.schema.text(`:::youtube{${id}}`))
|
|
82
|
+
view.focus()
|
|
83
|
+
view.dispatch(view.state.tr.replaceSelectionWith(paragraph).scrollIntoView())
|
|
61
84
|
})
|
|
62
85
|
}
|
|
63
86
|
|
|
64
87
|
export interface FormatToolbarProps {
|
|
65
88
|
editor: Editor | null
|
|
89
|
+
listStyles?: CmsListStyle[]
|
|
66
90
|
media?: MediaSource
|
|
67
91
|
mediaContext?: MediaContext
|
|
68
92
|
/** Upload field the image is filed under (e.g. 'body'). */
|
|
@@ -92,6 +116,17 @@ const baseBtn: React.CSSProperties = {
|
|
|
92
116
|
color: '#52525b',
|
|
93
117
|
}
|
|
94
118
|
const activeBtn: React.CSSProperties = { ...baseBtn, background: '#2563eb', borderColor: '#2563eb', color: '#fff' }
|
|
119
|
+
const selectStyle: React.CSSProperties = {
|
|
120
|
+
border: '1px solid #d4d4d8',
|
|
121
|
+
borderRadius: 4,
|
|
122
|
+
background: '#fff',
|
|
123
|
+
color: '#3f3f46',
|
|
124
|
+
font: 'inherit',
|
|
125
|
+
fontSize: 12,
|
|
126
|
+
lineHeight: 1.4,
|
|
127
|
+
padding: '2px 6px',
|
|
128
|
+
maxWidth: 150,
|
|
129
|
+
}
|
|
95
130
|
|
|
96
131
|
function Btn({ active, title, onClick, style, children }: {
|
|
97
132
|
active?: boolean
|
|
@@ -114,11 +149,16 @@ function Btn({ active, title, onClick, style, children }: {
|
|
|
114
149
|
)
|
|
115
150
|
}
|
|
116
151
|
|
|
117
|
-
export function FormatToolbar({ editor, media, mediaContext, field, onInsertComponent }: FormatToolbarProps) {
|
|
152
|
+
export function FormatToolbar({ editor, listStyles, media, mediaContext, field, onInsertComponent }: FormatToolbarProps) {
|
|
118
153
|
const formats = useFormatTracking(editor)
|
|
119
154
|
const [linkOpen, setLinkOpen] = useState(false)
|
|
120
155
|
const [mediaOpen, setMediaOpen] = useState(false)
|
|
156
|
+
const [youtubeOpen, setYoutubeOpen] = useState(false)
|
|
157
|
+
const [pendingImage, setPendingImage] = useState<{ url: string; alt: string } | null>(null)
|
|
121
158
|
const disabled = editor === null
|
|
159
|
+
const hasListStyles = (listStyles?.length ?? 0) > 0
|
|
160
|
+
const inList = formats.bulletList || formats.orderedList
|
|
161
|
+
const currentListStyle = formats.listStyle && listStyles?.some(style => style.class === formats.listStyle) ? formats.listStyle : ''
|
|
122
162
|
|
|
123
163
|
const applyLink = (url: string) => {
|
|
124
164
|
setLinkOpen(false)
|
|
@@ -163,7 +203,31 @@ export function FormatToolbar({ editor, media, mediaContext, field, onInsertComp
|
|
|
163
203
|
<span style={sep} />
|
|
164
204
|
<Btn active={formats.bulletList} title="Bullet list" onClick={() => editor && toggleList(editor, 'bullet')}>• List</Btn>
|
|
165
205
|
<Btn active={formats.orderedList} title="Numbered list" onClick={() => editor && toggleList(editor, 'ordered')}>1. List</Btn>
|
|
206
|
+
{hasListStyles
|
|
207
|
+
? (
|
|
208
|
+
<select
|
|
209
|
+
title="List style"
|
|
210
|
+
aria-label="List style"
|
|
211
|
+
disabled={disabled || !inList}
|
|
212
|
+
value={currentListStyle}
|
|
213
|
+
onChange={(event) => {
|
|
214
|
+
if (!editor) return
|
|
215
|
+
applyListStyle(editor, event.currentTarget.value || null)
|
|
216
|
+
}}
|
|
217
|
+
style={{
|
|
218
|
+
...selectStyle,
|
|
219
|
+
opacity: disabled || !inList ? 0.55 : 1,
|
|
220
|
+
cursor: disabled || !inList ? 'not-allowed' : 'default',
|
|
221
|
+
}}
|
|
222
|
+
>
|
|
223
|
+
<option value="">Default</option>
|
|
224
|
+
{listStyles?.map(style => <option key={style.class} value={style.class}>{style.label}</option>)}
|
|
225
|
+
</select>
|
|
226
|
+
)
|
|
227
|
+
: null}
|
|
166
228
|
<Btn active={formats.blockquote} title="Quote" onClick={() => editor?.action(callCommand(wrapInBlockquoteCommand.key))}>❝</Btn>
|
|
229
|
+
<Btn title="Insert table" onClick={() => editor?.action(callCommand(insertTableCommand.key, { row: 3, col: 3 }))}>▦ Table</Btn>
|
|
230
|
+
<Btn active={youtubeOpen} title="Insert YouTube" onClick={() => !disabled && setYoutubeOpen(v => !v)}>YouTube</Btn>
|
|
167
231
|
<span style={sep} />
|
|
168
232
|
<Btn active={formats.link || linkOpen} title="Link" onClick={() => !disabled && setLinkOpen(v => !v)}>🔗 Link</Btn>
|
|
169
233
|
{media ? <Btn title="Insert image" onClick={() => !disabled && setMediaOpen(true)}>🖼 Image</Btn> : null}
|
|
@@ -193,6 +257,36 @@ export function FormatToolbar({ editor, media, mediaContext, field, onInsertComp
|
|
|
193
257
|
)
|
|
194
258
|
: null}
|
|
195
259
|
|
|
260
|
+
{youtubeOpen
|
|
261
|
+
? (
|
|
262
|
+
<div style={{ padding: '0 6px' }}>
|
|
263
|
+
<YoutubePopover
|
|
264
|
+
onApply={(id) => {
|
|
265
|
+
setYoutubeOpen(false)
|
|
266
|
+
if (editor) insertYoutubeDirective(editor, id)
|
|
267
|
+
}}
|
|
268
|
+
onClose={() => setYoutubeOpen(false)}
|
|
269
|
+
/>
|
|
270
|
+
</div>
|
|
271
|
+
)
|
|
272
|
+
: null}
|
|
273
|
+
|
|
274
|
+
{pendingImage
|
|
275
|
+
? (
|
|
276
|
+
<div style={{ padding: '0 6px' }}>
|
|
277
|
+
<ImagePopover
|
|
278
|
+
initialAlt={pendingImage.alt}
|
|
279
|
+
onApply={(alt, caption) => {
|
|
280
|
+
const { url } = pendingImage
|
|
281
|
+
setPendingImage(null)
|
|
282
|
+
if (editor) insertImage(editor, url, alt, caption)
|
|
283
|
+
}}
|
|
284
|
+
onClose={() => setPendingImage(null)}
|
|
285
|
+
/>
|
|
286
|
+
</div>
|
|
287
|
+
)
|
|
288
|
+
: null}
|
|
289
|
+
|
|
196
290
|
{mediaOpen && media
|
|
197
291
|
? (
|
|
198
292
|
<MediaLibrary
|
|
@@ -202,7 +296,7 @@ export function FormatToolbar({ editor, media, mediaContext, field, onInsertComp
|
|
|
202
296
|
accept="image/*"
|
|
203
297
|
onSelect={(url, alt) => {
|
|
204
298
|
setMediaOpen(false)
|
|
205
|
-
if (editor)
|
|
299
|
+
if (editor) setPendingImage({ url, alt: alt ?? '' })
|
|
206
300
|
}}
|
|
207
301
|
onClose={() => setMediaOpen(false)}
|
|
208
302
|
/>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline popover shown after an image is picked from the media library: confirm the
|
|
3
|
+
* alt text (prefilled from the library) and optionally add a caption/source. Apply
|
|
4
|
+
* inserts the image; Cancel aborts insertion entirely. Mirrors the LinkPopover style.
|
|
5
|
+
*/
|
|
6
|
+
import { useState } from 'react'
|
|
7
|
+
import { popoverBtn, popoverInput, popoverWrap } from './link-popover'
|
|
8
|
+
|
|
9
|
+
export interface ImagePopoverProps {
|
|
10
|
+
initialAlt: string
|
|
11
|
+
onApply: (alt: string, caption: string) => void
|
|
12
|
+
onClose: () => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const wrap: React.CSSProperties = { ...popoverWrap, flexWrap: 'wrap' }
|
|
16
|
+
const label: React.CSSProperties = { fontSize: 11, color: '#71717a', whiteSpace: 'nowrap' }
|
|
17
|
+
|
|
18
|
+
export function ImagePopover({ initialAlt, onApply, onClose }: ImagePopoverProps) {
|
|
19
|
+
const [alt, setAlt] = useState(initialAlt)
|
|
20
|
+
const [caption, setCaption] = useState('')
|
|
21
|
+
|
|
22
|
+
const apply = () => onApply(alt.trim(), caption.trim())
|
|
23
|
+
|
|
24
|
+
const onKeyDown = (e: React.KeyboardEvent) => {
|
|
25
|
+
if (e.key === 'Enter') {
|
|
26
|
+
e.preventDefault()
|
|
27
|
+
apply()
|
|
28
|
+
}
|
|
29
|
+
if (e.key === 'Escape') onClose()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div style={wrap} data-mdx-action="image" onMouseDown={e => e.stopPropagation()}>
|
|
34
|
+
<span style={label}>Alt</span>
|
|
35
|
+
<input
|
|
36
|
+
style={popoverInput}
|
|
37
|
+
autoFocus
|
|
38
|
+
placeholder="Alt text for screen readers"
|
|
39
|
+
value={alt}
|
|
40
|
+
onChange={e => setAlt(e.currentTarget.value)}
|
|
41
|
+
onKeyDown={onKeyDown}
|
|
42
|
+
/>
|
|
43
|
+
<span style={label}>Caption</span>
|
|
44
|
+
<input
|
|
45
|
+
style={popoverInput}
|
|
46
|
+
placeholder="Caption / source (optional)"
|
|
47
|
+
value={caption}
|
|
48
|
+
onChange={e => setCaption(e.currentTarget.value)}
|
|
49
|
+
onKeyDown={onKeyDown}
|
|
50
|
+
/>
|
|
51
|
+
<button
|
|
52
|
+
type="button"
|
|
53
|
+
style={{ ...popoverBtn, background: '#2563eb', borderColor: '#2563eb', color: '#fff' }}
|
|
54
|
+
onMouseDown={e => e.preventDefault()}
|
|
55
|
+
onClick={apply}
|
|
56
|
+
>
|
|
57
|
+
Insert
|
|
58
|
+
</button>
|
|
59
|
+
<button type="button" style={popoverBtn} onMouseDown={e => e.preventDefault()} onClick={onClose}>Cancel</button>
|
|
60
|
+
</div>
|
|
61
|
+
)
|
|
62
|
+
}
|
package/src/link-popover.tsx
CHANGED
|
@@ -14,7 +14,8 @@ export interface LinkPopoverProps {
|
|
|
14
14
|
onClose: () => void
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
/** Shared popover styles, reused by the image and YouTube insert popovers. */
|
|
18
|
+
export const popoverWrap: React.CSSProperties = {
|
|
18
19
|
display: 'flex',
|
|
19
20
|
gap: 6,
|
|
20
21
|
alignItems: 'center',
|
|
@@ -25,7 +26,7 @@ const wrap: React.CSSProperties = {
|
|
|
25
26
|
boxShadow: '0 4px 16px rgba(0,0,0,0.12)',
|
|
26
27
|
marginTop: 6,
|
|
27
28
|
}
|
|
28
|
-
const
|
|
29
|
+
export const popoverInput: React.CSSProperties = {
|
|
29
30
|
flex: 1,
|
|
30
31
|
border: '1px solid #d4d4d8',
|
|
31
32
|
borderRadius: 4,
|
|
@@ -34,7 +35,7 @@ const input: React.CSSProperties = {
|
|
|
34
35
|
outline: 'none',
|
|
35
36
|
minWidth: 0,
|
|
36
37
|
}
|
|
37
|
-
const
|
|
38
|
+
export const popoverBtn: React.CSSProperties = {
|
|
38
39
|
border: '1px solid #d4d4d8',
|
|
39
40
|
background: '#fff',
|
|
40
41
|
borderRadius: 4,
|
|
@@ -45,6 +46,9 @@ const btn: React.CSSProperties = {
|
|
|
45
46
|
color: '#3f3f46',
|
|
46
47
|
whiteSpace: 'nowrap',
|
|
47
48
|
}
|
|
49
|
+
const wrap = popoverWrap
|
|
50
|
+
const input = popoverInput
|
|
51
|
+
const btn = popoverBtn
|
|
48
52
|
|
|
49
53
|
export function LinkPopover({ initialUrl, isEdit, onApply, onRemove, onClose }: LinkPopoverProps) {
|
|
50
54
|
const [url, setUrl] = useState(initialUrl)
|
package/src/mdx-body-editor.tsx
CHANGED
|
@@ -13,19 +13,22 @@ import { listener, listenerCtx } from '@milkdown/plugin-listener'
|
|
|
13
13
|
import { commonmark } from '@milkdown/preset-commonmark'
|
|
14
14
|
import { gfm } from '@milkdown/preset-gfm'
|
|
15
15
|
import { callCommand, replaceAll } from '@milkdown/utils'
|
|
16
|
-
import type { ComponentDefinition } from '@nuasite/cms-types'
|
|
16
|
+
import type { CmsListStyle, ComponentDefinition } from '@nuasite/cms-types'
|
|
17
17
|
import { useEffect, useRef, useState } from 'react'
|
|
18
18
|
import { ComponentPicker } from './component-picker'
|
|
19
19
|
import { FormatToolbar } from './format-toolbar'
|
|
20
20
|
import { insertMdxComponentCommand, mdxComponentNode, mdxEsmNode, remarkMdxPlugin } from './mdx-plugin'
|
|
21
21
|
import { type ComponentResolver, createMdxComponentView } from './mdx-view'
|
|
22
22
|
import type { MediaContext, MediaSource } from './media-source'
|
|
23
|
+
import { styledListPlugin } from './styled-list-plugin'
|
|
23
24
|
|
|
24
25
|
export interface MdxBodyEditorProps {
|
|
25
26
|
value: string
|
|
26
27
|
onChange: (markdown: string) => void
|
|
27
28
|
/** Project component definitions — drives the insert picker and prop labels. */
|
|
28
29
|
components?: ComponentDefinition[]
|
|
30
|
+
/** Project-defined list styles shown in the toolbar. */
|
|
31
|
+
listStyles?: CmsListStyle[]
|
|
29
32
|
/**
|
|
30
33
|
* Media source (the host's `CmsClient` satisfies it) — enables the toolbar's
|
|
31
34
|
* image insert and image-prop browse/upload. Absent → those affordances hide.
|
|
@@ -83,7 +86,7 @@ function useEditorStyles() {
|
|
|
83
86
|
}, [])
|
|
84
87
|
}
|
|
85
88
|
|
|
86
|
-
export function MdxBodyEditor({ value, onChange, components, media, mediaContext, allowComponents = true }: MdxBodyEditorProps) {
|
|
89
|
+
export function MdxBodyEditor({ value, onChange, components, listStyles, media, mediaContext, allowComponents = true }: MdxBodyEditorProps) {
|
|
87
90
|
useEditorStyles()
|
|
88
91
|
const hostRef = useRef<HTMLDivElement>(null)
|
|
89
92
|
const editorRef = useRef<Editor | null>(null)
|
|
@@ -130,6 +133,7 @@ export function MdxBodyEditor({ value, onChange, components, media, mediaContext
|
|
|
130
133
|
})
|
|
131
134
|
})
|
|
132
135
|
.use(commonmark)
|
|
136
|
+
.use(styledListPlugin)
|
|
133
137
|
.use(gfm)
|
|
134
138
|
.use(listener)
|
|
135
139
|
.use(remarkMdxPlugin)
|
|
@@ -182,6 +186,7 @@ export function MdxBodyEditor({ value, onChange, components, media, mediaContext
|
|
|
182
186
|
<div className="nua-mdx-editor" style={wrapper}>
|
|
183
187
|
<FormatToolbar
|
|
184
188
|
editor={editorInstance}
|
|
189
|
+
listStyles={listStyles}
|
|
185
190
|
media={media}
|
|
186
191
|
mediaContext={mediaContext}
|
|
187
192
|
field="body"
|
package/src/milkdown-utils.ts
CHANGED
|
@@ -15,6 +15,7 @@ export interface ActiveFormats {
|
|
|
15
15
|
linkHref: string | null
|
|
16
16
|
bulletList: boolean
|
|
17
17
|
orderedList: boolean
|
|
18
|
+
listStyle: string | null
|
|
18
19
|
blockquote: boolean
|
|
19
20
|
heading: number | null
|
|
20
21
|
}
|
|
@@ -27,6 +28,7 @@ export const defaultActiveFormats: ActiveFormats = {
|
|
|
27
28
|
linkHref: null,
|
|
28
29
|
bulletList: false,
|
|
29
30
|
orderedList: false,
|
|
31
|
+
listStyle: null,
|
|
30
32
|
blockquote: false,
|
|
31
33
|
heading: null,
|
|
32
34
|
}
|
|
@@ -69,13 +71,17 @@ export function getActiveFormats(view: EditorView): ActiveFormats {
|
|
|
69
71
|
|
|
70
72
|
let bulletList = false
|
|
71
73
|
let orderedList = false
|
|
74
|
+
let listStyle: string | null = null
|
|
72
75
|
let blockquote = false
|
|
73
76
|
let heading: number | null = null
|
|
74
77
|
|
|
75
78
|
for (let depth = $from.depth; depth > 0; depth--) {
|
|
76
79
|
const node = $from.node(depth)
|
|
77
|
-
if (node.type.name === 'bullet_list'
|
|
78
|
-
|
|
80
|
+
if (node.type.name === 'bullet_list' || node.type.name === 'ordered_list') {
|
|
81
|
+
if (node.type.name === 'bullet_list') bulletList = true
|
|
82
|
+
if (node.type.name === 'ordered_list') orderedList = true
|
|
83
|
+
if (listStyle === null) listStyle = typeof node.attrs.listStyle === 'string' ? node.attrs.listStyle : null
|
|
84
|
+
}
|
|
79
85
|
if (node.type.name === 'blockquote') blockquote = true
|
|
80
86
|
}
|
|
81
87
|
|
|
@@ -84,7 +90,7 @@ export function getActiveFormats(view: EditorView): ActiveFormats {
|
|
|
84
90
|
heading = typeof level === 'number' ? level : null
|
|
85
91
|
}
|
|
86
92
|
|
|
87
|
-
return { bold, italic, strikethrough, link, linkHref, bulletList, orderedList, blockquote, heading }
|
|
93
|
+
return { bold, italic, strikethrough, link, linkHref, bulletList, orderedList, listStyle, blockquote, heading }
|
|
88
94
|
}
|
|
89
95
|
|
|
90
96
|
/** Whether the current selection is inside a list of the given node-type name. */
|
|
@@ -143,6 +149,7 @@ function formatsEqual(a: ActiveFormats, b: ActiveFormats): boolean {
|
|
|
143
149
|
&& a.linkHref === b.linkHref
|
|
144
150
|
&& a.bulletList === b.bulletList
|
|
145
151
|
&& a.orderedList === b.orderedList
|
|
152
|
+
&& a.listStyle === b.listStyle
|
|
146
153
|
&& a.blockquote === b.blockquote
|
|
147
154
|
&& a.heading === b.heading
|
|
148
155
|
}
|