Skip to content

Commit 3e7a477

Browse files
authored
feat: add better readonly support for crepe (#1322)
1 parent 3ac6189 commit 3e7a477

File tree

14 files changed

+51
-11
lines changed

14 files changed

+51
-11
lines changed

Diff for: packages/components/src/__internal__/helper.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export function defIfNotExists(tagName: string, element: CustomElementConstructor) {
2+
if (customElements.get(tagName) == null)
3+
customElements.define(tagName, element)
4+
}

Diff for: packages/components/src/code-block/view/component.ts

+6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface CodeComponentProps {
1515
language: string
1616
getAllLanguages: () => Array<LanguageInfo>
1717
setLanguage: (language: string) => void
18+
isEditorReadonly: () => boolean
1819
config: Omit<CodeBlockConfig, 'languages' | 'extensions'>
1920
}
2021

@@ -25,6 +26,7 @@ export const codeComponent: Component<CodeComponentProps> = ({
2526
setLanguage,
2627
language,
2728
config,
29+
isEditorReadonly,
2830
}) => {
2931
const triggerRef = useRef<HTMLButtonElement>()
3032
const pickerRef = useRef<HTMLDivElement>()
@@ -113,6 +115,9 @@ export const codeComponent: Component<CodeComponentProps> = ({
113115
}
114116

115117
const onTogglePicker = () => {
118+
if (isEditorReadonly?.())
119+
return
120+
116121
const next = !showPicker
117122
const languageList = pickerRef.current
118123
if (next && languageList)
@@ -199,6 +204,7 @@ codeComponent.props = {
199204
language: String,
200205
getAllLanguages: Function,
201206
setLanguage: Function,
207+
isEditorReadonly: Function,
202208
config: Object,
203209
}
204210

Diff for: packages/components/src/code-block/view/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import { codeBlockSchema } from '@milkdown/preset-commonmark'
33
import type { NodeViewConstructor } from '@milkdown/prose/view'
44
import { codeBlockConfig } from '../config'
55
import { withMeta } from '../../__internal__/meta'
6+
import { defIfNotExists } from '../../__internal__/helper'
67
import { CodeMirrorBlock } from './node-view'
78
import { LanguageLoader } from './loader'
89
import { CodeElement } from './component'
910

10-
customElements.define('milkdown-code-block', CodeElement)
11+
defIfNotExists('milkdown-code-block', CodeElement)
1112
export const codeBlockView = $view(codeBlockSchema.node, (ctx): NodeViewConstructor => {
1213
const config = ctx.get(codeBlockConfig.key)
1314
const languageLoader = new LanguageLoader(config.languages)

Diff for: packages/components/src/code-block/view/node-view.ts

+9
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export class CodeMirrorBlock implements NodeView {
2020
private languageName: string = ''
2121

2222
private readonly languageConf: Compartment
23+
private readonly readOnlyConf: Compartment
2324

2425
constructor(
2526
public node: Node,
@@ -29,6 +30,7 @@ export class CodeMirrorBlock implements NodeView {
2930
public config: CodeBlockConfig,
3031
) {
3132
this.languageConf = new Compartment()
33+
this.readOnlyConf = new Compartment()
3234
const changeFilter = EditorState.changeFilter.of((tr) => {
3335
if (!tr.docChanged && !this.updating)
3436
this.forwardSelection()
@@ -39,6 +41,7 @@ export class CodeMirrorBlock implements NodeView {
3941
this.cm = new CodeMirror({
4042
doc: this.node.textContent,
4143
extensions: [
44+
this.readOnlyConf.of(EditorState.readOnly.of(!this.view.editable)),
4245
cmKeymap.of(this.codeMirrorKeymap()),
4346
changeFilter,
4447
this.languageConf.of([]),
@@ -57,6 +60,7 @@ export class CodeMirrorBlock implements NodeView {
5760
dom.codemirror = this.cm
5861
dom.getAllLanguages = this.getAllLanguages
5962
dom.setLanguage = this.setLanguage
63+
dom.isEditorReadonly = () => !this.view.editable
6064
const {
6165
languages,
6266
extensions,
@@ -206,6 +210,11 @@ export class CodeMirrorBlock implements NodeView {
206210

207211
this.node = node
208212
this.updateLanguage()
213+
if (this.view.editable === this.cm.state.readOnly) {
214+
this.cm.dispatch({
215+
effects: this.readOnlyConf.reconfigure(EditorState.readOnly.of(!this.view.editable)),
216+
})
217+
}
209218

210219
const change = computeChange(this.cm.state.doc.toString(), node.textContent)
211220
if (change) {

Diff for: packages/components/src/image-block/view/component.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export interface Attrs {
1616
export type ImageComponentProps = Attrs & {
1717
config: ImageBlockConfig
1818
selected: boolean
19+
readonly: boolean
1920
setAttr: <T extends keyof Attrs>(attr: T, value: Attrs[T]) => void
2021
}
2122

@@ -24,6 +25,7 @@ export const imageComponent: Component<ImageComponentProps> = ({
2425
caption = '',
2526
ratio = 1,
2627
selected = false,
28+
readonly = false,
2729
setAttr,
2830
config,
2931
}) => {
@@ -79,6 +81,8 @@ export const imageComponent: Component<ImageComponentProps> = ({
7981
}
8082

8183
const onToggleCaption = () => {
84+
if (readonly)
85+
return
8286
setShowCaption(x => !x)
8387
}
8488

@@ -99,6 +103,7 @@ export const imageComponent: Component<ImageComponentProps> = ({
99103
<div class=${clsx('link-importer', focusLinkInput && 'focus')}>
100104
<input
101105
ref=${linkInput}
106+
disabled=${readonly}
102107
class="link-input-area"
103108
value=${currentLink}
104109
oninput=${onEditLink}
@@ -107,7 +112,7 @@ export const imageComponent: Component<ImageComponentProps> = ({
107112
onblur=${() => setFocusLinkInput(false)}
108113
/>
109114
<div class=${clsx('placeholder', hidePlaceholder && 'hidden')}>
110-
<input class="hidden" id=${uuid} type="file" accept="image/*" onchange=${onUpload} />
115+
<input disabled=${readonly} class="hidden" id=${uuid} type="file" accept="image/*" onchange=${onUpload} />
111116
<label class="uploader" for=${uuid}>
112117
${config?.uploadButton()}
113118
</label>
@@ -144,6 +149,7 @@ imageComponent.props = {
144149
caption: String,
145150
ratio: Number,
146151
selected: Boolean,
152+
readonly: Boolean,
147153
setAttr: Function,
148154
config: Object,
149155
}

Diff for: packages/components/src/image-block/view/index.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import type { Node } from '@milkdown/prose/model'
44
import { imageBlockSchema } from '../schema'
55
import { imageBlockConfig } from '../config'
66
import { withMeta } from '../../__internal__/meta'
7+
import { defIfNotExists } from '../../__internal__/helper'
78
import type { ImageComponentProps } from './component'
89
import { ImageElement } from './component'
910

10-
customElements.define('milkdown-image-block', ImageElement)
11+
defIfNotExists('milkdown-image-block', ImageElement)
1112
export const imageBlockView = $view(imageBlockSchema.node, (ctx): NodeViewConstructor => {
1213
return (initialNode, view, getPos) => {
1314
const dom = document.createElement('milkdown-image-block') as HTMLElement & ImageComponentProps
@@ -16,6 +17,8 @@ export const imageBlockView = $view(imageBlockSchema.node, (ctx): NodeViewConstr
1617
dom.src = node.attrs.src
1718
dom.ratio = node.attrs.ratio
1819
dom.caption = node.attrs.caption
20+
21+
dom.readonly = !view.editable
1922
}
2023

2124
bindAttrs(initialNode)

Diff for: packages/components/src/image-inline/view.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import type { NodeViewConstructor } from '@milkdown/prose/view'
33
import { imageSchema } from '@milkdown/preset-commonmark'
44
import type { Node } from '@milkdown/prose/model'
55
import { withMeta } from '../__internal__/meta'
6+
import { defIfNotExists } from '../__internal__/helper'
67
import type { InlineImageComponentProps } from './component'
78
import { InlineImageElement } from './component'
89
import { inlineImageConfig } from './config'
910

10-
customElements.define('milkdown-image-inline', InlineImageElement)
11+
defIfNotExists('milkdown-image-inline', InlineImageElement)
1112
export const inlineImageView = $view(imageSchema.node, (ctx): NodeViewConstructor => {
1213
return (initialNode, view, getPos) => {
1314
const dom = document.createElement('milkdown-image-inline') as HTMLElement & InlineImageComponentProps

Diff for: packages/components/src/link-tooltip/edit/edit-configure.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import type { Ctx } from '@milkdown/ctx'
22
import { linkTooltipAPI } from '../slices'
33
import { linkEditTooltip } from '../tooltips'
4+
import { defIfNotExists } from '../../__internal__/helper'
45
import { LinkEditElement } from './edit-component'
56
import { LinkEditTooltip } from './edit-view'
67

7-
customElements.define('milkdown-link-edit', LinkEditElement)
8+
defIfNotExists('milkdown-link-edit', LinkEditElement)
89
export function configureLinkEditTooltip(ctx: Ctx) {
910
let linkEditTooltipView: LinkEditTooltip | null
1011

Diff for: packages/components/src/link-tooltip/preview/preview-configure.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import { posToDOMRect } from '@milkdown/prose'
55
import { linkTooltipState } from '../slices'
66
import { findMarkPosition, shouldShowPreviewWhenHover } from '../utils'
77
import { linkPreviewTooltip } from '../tooltips'
8+
import { defIfNotExists } from '../../__internal__/helper'
89
import { LinkPreviewTooltip } from './preview-view'
910
import { LinkPreviewElement } from './preview-component'
1011

11-
customElements.define('milkdown-link-preview', LinkPreviewElement)
12+
defIfNotExists('milkdown-link-preview', LinkPreviewElement)
1213
export function configureLinkPreviewTooltip(ctx: Ctx) {
1314
let linkPreviewTooltipView: LinkPreviewTooltip | null
1415

Diff for: packages/components/src/list-item-block/view.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import { TextSelection } from '@milkdown/prose/state'
44
import type { Node } from '@milkdown/prose/model'
55
import { listItemSchema } from '@milkdown/preset-commonmark'
66
import { withMeta } from '../__internal__/meta'
7+
import { defIfNotExists } from '../__internal__/helper'
78
import type { ListItemComponentProps } from './component'
89
import { ListItemElement } from './component'
910
import { listItemBlockConfig } from './config'
1011

11-
customElements.define('milkdown-list-item-block', ListItemElement)
12+
defIfNotExists('milkdown-list-item-block', ListItemElement)
1213
export const listItemBlockView = $view(listItemSchema.node, (ctx): NodeViewConstructor => {
1314
return (initialNode, view, getPos) => {
1415
const dom = document.createElement('milkdown-list-item-block') as HTMLElement & ListItemComponentProps

Diff for: packages/crepe/src/feature/block-edit/handle/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { AtomicoThis } from 'atomico/types/dom'
77
import { editorViewCtx, rootDOMCtx } from '@milkdown/core'
88
import { paragraphSchema } from '@milkdown/preset-commonmark'
99
import { menuAPI } from '../menu'
10+
import { defIfNotExists } from '../../../utils'
1011
import type { BlockHandleProps } from './component'
1112
import { BlockHandleElement } from './component'
1213

@@ -70,7 +71,7 @@ export class BlockHandleView implements PluginView {
7071
}
7172
}
7273

73-
customElements.define('milkdown-block-handle', BlockHandleElement)
74+
defIfNotExists('milkdown-block-handle', BlockHandleElement)
7475
export function configureBlockHandle(ctx: Ctx) {
7576
ctx.set(block.key, {
7677
view: view => new BlockHandleView(ctx, view),

Diff for: packages/crepe/src/feature/block-edit/menu/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { Ctx } from '@milkdown/ctx'
55
import type { AtomicoThis } from 'atomico/types/dom'
66
import { $ctx } from '@milkdown/utils'
77
import { rootDOMCtx } from '@milkdown/core'
8-
import { isInCodeBlock, isInList } from '../../../utils'
8+
import { defIfNotExists, isInCodeBlock, isInList } from '../../../utils'
99
import type { MenuProps } from './component'
1010
import { MenuElement } from './component'
1111

@@ -21,7 +21,7 @@ export const menuAPI = $ctx({
2121
hide: () => {},
2222
} as MenuAPI, 'menuAPICtx')
2323

24-
customElements.define('milkdown-slash-menu', MenuElement)
24+
defIfNotExists('milkdown-slash-menu', MenuElement)
2525
export function configureMenu(ctx: Ctx) {
2626
ctx.set(menu.key, {
2727
view: view => new MenuView(ctx, view),

Diff for: packages/crepe/src/feature/toolbar/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { AtomicoThis } from 'atomico/types/dom'
77
import { rootDOMCtx } from '@milkdown/core'
88
import type { DefineFeature } from '../shared'
99
import { injectStyle } from '../../core/slice'
10+
import { defIfNotExists } from '../../utils'
1011
import type { ToolbarProps } from './component'
1112
import { ToolbarElement } from './component'
1213
import style from './style.css?inline'
@@ -77,7 +78,7 @@ class ToolbarView implements PluginView {
7778
}
7879
}
7980

80-
customElements.define('milkdown-toolbar', ToolbarElement)
81+
defIfNotExists('milkdown-toolbar', ToolbarElement)
8182
export const defineFeature: DefineFeature = (editor) => {
8283
editor
8384
.config(injectStyle(style))

Diff for: packages/crepe/src/utils/index.ts

+5
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,8 @@ export function isInList(selection: Selection) {
99
const type = selection.$from.node(selection.$from.depth - 1)?.type
1010
return type?.name === 'list_item'
1111
}
12+
13+
export function defIfNotExists(tagName: string, element: CustomElementConstructor) {
14+
if (customElements.get(tagName) == null)
15+
customElements.define(tagName, element)
16+
}

0 commit comments

Comments
 (0)