Skip to content

Commit

Permalink
feat: add better readonly support for crepe (#1322)
Browse files Browse the repository at this point in the history
  • Loading branch information
Saul-Mirone committed May 11, 2024
1 parent 3ac6189 commit 3e7a477
Show file tree
Hide file tree
Showing 14 changed files with 51 additions and 11 deletions.
4 changes: 4 additions & 0 deletions packages/components/src/__internal__/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export function defIfNotExists(tagName: string, element: CustomElementConstructor) {
if (customElements.get(tagName) == null)
customElements.define(tagName, element)
}
6 changes: 6 additions & 0 deletions packages/components/src/code-block/view/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface CodeComponentProps {
language: string
getAllLanguages: () => Array<LanguageInfo>
setLanguage: (language: string) => void
isEditorReadonly: () => boolean
config: Omit<CodeBlockConfig, 'languages' | 'extensions'>
}

Expand All @@ -25,6 +26,7 @@ export const codeComponent: Component<CodeComponentProps> = ({
setLanguage,
language,
config,
isEditorReadonly,
}) => {
const triggerRef = useRef<HTMLButtonElement>()
const pickerRef = useRef<HTMLDivElement>()
Expand Down Expand Up @@ -113,6 +115,9 @@ export const codeComponent: Component<CodeComponentProps> = ({
}

const onTogglePicker = () => {
if (isEditorReadonly?.())
return

const next = !showPicker
const languageList = pickerRef.current
if (next && languageList)
Expand Down Expand Up @@ -199,6 +204,7 @@ codeComponent.props = {
language: String,
getAllLanguages: Function,
setLanguage: Function,
isEditorReadonly: Function,
config: Object,
}

Expand Down
3 changes: 2 additions & 1 deletion packages/components/src/code-block/view/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import { codeBlockSchema } from '@milkdown/preset-commonmark'
import type { NodeViewConstructor } from '@milkdown/prose/view'
import { codeBlockConfig } from '../config'
import { withMeta } from '../../__internal__/meta'
import { defIfNotExists } from '../../__internal__/helper'
import { CodeMirrorBlock } from './node-view'
import { LanguageLoader } from './loader'
import { CodeElement } from './component'

customElements.define('milkdown-code-block', CodeElement)
defIfNotExists('milkdown-code-block', CodeElement)
export const codeBlockView = $view(codeBlockSchema.node, (ctx): NodeViewConstructor => {
const config = ctx.get(codeBlockConfig.key)
const languageLoader = new LanguageLoader(config.languages)
Expand Down
9 changes: 9 additions & 0 deletions packages/components/src/code-block/view/node-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class CodeMirrorBlock implements NodeView {
private languageName: string = ''

private readonly languageConf: Compartment
private readonly readOnlyConf: Compartment

constructor(
public node: Node,
Expand All @@ -29,6 +30,7 @@ export class CodeMirrorBlock implements NodeView {
public config: CodeBlockConfig,
) {
this.languageConf = new Compartment()
this.readOnlyConf = new Compartment()
const changeFilter = EditorState.changeFilter.of((tr) => {
if (!tr.docChanged && !this.updating)
this.forwardSelection()
Expand All @@ -39,6 +41,7 @@ export class CodeMirrorBlock implements NodeView {
this.cm = new CodeMirror({
doc: this.node.textContent,
extensions: [
this.readOnlyConf.of(EditorState.readOnly.of(!this.view.editable)),
cmKeymap.of(this.codeMirrorKeymap()),
changeFilter,
this.languageConf.of([]),
Expand All @@ -57,6 +60,7 @@ export class CodeMirrorBlock implements NodeView {
dom.codemirror = this.cm
dom.getAllLanguages = this.getAllLanguages
dom.setLanguage = this.setLanguage
dom.isEditorReadonly = () => !this.view.editable
const {
languages,
extensions,
Expand Down Expand Up @@ -206,6 +210,11 @@ export class CodeMirrorBlock implements NodeView {

this.node = node
this.updateLanguage()
if (this.view.editable === this.cm.state.readOnly) {
this.cm.dispatch({
effects: this.readOnlyConf.reconfigure(EditorState.readOnly.of(!this.view.editable)),
})
}

const change = computeChange(this.cm.state.doc.toString(), node.textContent)
if (change) {
Expand Down
8 changes: 7 additions & 1 deletion packages/components/src/image-block/view/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface Attrs {
export type ImageComponentProps = Attrs & {
config: ImageBlockConfig
selected: boolean
readonly: boolean
setAttr: <T extends keyof Attrs>(attr: T, value: Attrs[T]) => void
}

Expand All @@ -24,6 +25,7 @@ export const imageComponent: Component<ImageComponentProps> = ({
caption = '',
ratio = 1,
selected = false,
readonly = false,
setAttr,
config,
}) => {
Expand Down Expand Up @@ -79,6 +81,8 @@ export const imageComponent: Component<ImageComponentProps> = ({
}

const onToggleCaption = () => {
if (readonly)
return
setShowCaption(x => !x)
}

Expand All @@ -99,6 +103,7 @@ export const imageComponent: Component<ImageComponentProps> = ({
<div class=${clsx('link-importer', focusLinkInput && 'focus')}>
<input
ref=${linkInput}
disabled=${readonly}
class="link-input-area"
value=${currentLink}
oninput=${onEditLink}
Expand All @@ -107,7 +112,7 @@ export const imageComponent: Component<ImageComponentProps> = ({
onblur=${() => setFocusLinkInput(false)}
/>
<div class=${clsx('placeholder', hidePlaceholder && 'hidden')}>
<input class="hidden" id=${uuid} type="file" accept="image/*" onchange=${onUpload} />
<input disabled=${readonly} class="hidden" id=${uuid} type="file" accept="image/*" onchange=${onUpload} />
<label class="uploader" for=${uuid}>
${config?.uploadButton()}
</label>
Expand Down Expand Up @@ -144,6 +149,7 @@ imageComponent.props = {
caption: String,
ratio: Number,
selected: Boolean,
readonly: Boolean,
setAttr: Function,
config: Object,
}
Expand Down
5 changes: 4 additions & 1 deletion packages/components/src/image-block/view/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import type { Node } from '@milkdown/prose/model'
import { imageBlockSchema } from '../schema'
import { imageBlockConfig } from '../config'
import { withMeta } from '../../__internal__/meta'
import { defIfNotExists } from '../../__internal__/helper'
import type { ImageComponentProps } from './component'
import { ImageElement } from './component'

customElements.define('milkdown-image-block', ImageElement)
defIfNotExists('milkdown-image-block', ImageElement)
export const imageBlockView = $view(imageBlockSchema.node, (ctx): NodeViewConstructor => {
return (initialNode, view, getPos) => {
const dom = document.createElement('milkdown-image-block') as HTMLElement & ImageComponentProps
Expand All @@ -16,6 +17,8 @@ export const imageBlockView = $view(imageBlockSchema.node, (ctx): NodeViewConstr
dom.src = node.attrs.src
dom.ratio = node.attrs.ratio
dom.caption = node.attrs.caption

dom.readonly = !view.editable
}

bindAttrs(initialNode)
Expand Down
3 changes: 2 additions & 1 deletion packages/components/src/image-inline/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import type { NodeViewConstructor } from '@milkdown/prose/view'
import { imageSchema } from '@milkdown/preset-commonmark'
import type { Node } from '@milkdown/prose/model'
import { withMeta } from '../__internal__/meta'
import { defIfNotExists } from '../__internal__/helper'
import type { InlineImageComponentProps } from './component'
import { InlineImageElement } from './component'
import { inlineImageConfig } from './config'

customElements.define('milkdown-image-inline', InlineImageElement)
defIfNotExists('milkdown-image-inline', InlineImageElement)
export const inlineImageView = $view(imageSchema.node, (ctx): NodeViewConstructor => {
return (initialNode, view, getPos) => {
const dom = document.createElement('milkdown-image-inline') as HTMLElement & InlineImageComponentProps
Expand Down
3 changes: 2 additions & 1 deletion packages/components/src/link-tooltip/edit/edit-configure.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { Ctx } from '@milkdown/ctx'
import { linkTooltipAPI } from '../slices'
import { linkEditTooltip } from '../tooltips'
import { defIfNotExists } from '../../__internal__/helper'
import { LinkEditElement } from './edit-component'
import { LinkEditTooltip } from './edit-view'

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import { posToDOMRect } from '@milkdown/prose'
import { linkTooltipState } from '../slices'
import { findMarkPosition, shouldShowPreviewWhenHover } from '../utils'
import { linkPreviewTooltip } from '../tooltips'
import { defIfNotExists } from '../../__internal__/helper'
import { LinkPreviewTooltip } from './preview-view'
import { LinkPreviewElement } from './preview-component'

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

Expand Down
3 changes: 2 additions & 1 deletion packages/components/src/list-item-block/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { TextSelection } from '@milkdown/prose/state'
import type { Node } from '@milkdown/prose/model'
import { listItemSchema } from '@milkdown/preset-commonmark'
import { withMeta } from '../__internal__/meta'
import { defIfNotExists } from '../__internal__/helper'
import type { ListItemComponentProps } from './component'
import { ListItemElement } from './component'
import { listItemBlockConfig } from './config'

customElements.define('milkdown-list-item-block', ListItemElement)
defIfNotExists('milkdown-list-item-block', ListItemElement)
export const listItemBlockView = $view(listItemSchema.node, (ctx): NodeViewConstructor => {
return (initialNode, view, getPos) => {
const dom = document.createElement('milkdown-list-item-block') as HTMLElement & ListItemComponentProps
Expand Down
3 changes: 2 additions & 1 deletion packages/crepe/src/feature/block-edit/handle/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { AtomicoThis } from 'atomico/types/dom'
import { editorViewCtx, rootDOMCtx } from '@milkdown/core'
import { paragraphSchema } from '@milkdown/preset-commonmark'
import { menuAPI } from '../menu'
import { defIfNotExists } from '../../../utils'
import type { BlockHandleProps } from './component'
import { BlockHandleElement } from './component'

Expand Down Expand Up @@ -70,7 +71,7 @@ export class BlockHandleView implements PluginView {
}
}

customElements.define('milkdown-block-handle', BlockHandleElement)
defIfNotExists('milkdown-block-handle', BlockHandleElement)
export function configureBlockHandle(ctx: Ctx) {
ctx.set(block.key, {
view: view => new BlockHandleView(ctx, view),
Expand Down
4 changes: 2 additions & 2 deletions packages/crepe/src/feature/block-edit/menu/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { Ctx } from '@milkdown/ctx'
import type { AtomicoThis } from 'atomico/types/dom'
import { $ctx } from '@milkdown/utils'
import { rootDOMCtx } from '@milkdown/core'
import { isInCodeBlock, isInList } from '../../../utils'
import { defIfNotExists, isInCodeBlock, isInList } from '../../../utils'
import type { MenuProps } from './component'
import { MenuElement } from './component'

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

customElements.define('milkdown-slash-menu', MenuElement)
defIfNotExists('milkdown-slash-menu', MenuElement)
export function configureMenu(ctx: Ctx) {
ctx.set(menu.key, {
view: view => new MenuView(ctx, view),
Expand Down
3 changes: 2 additions & 1 deletion packages/crepe/src/feature/toolbar/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { AtomicoThis } from 'atomico/types/dom'
import { rootDOMCtx } from '@milkdown/core'
import type { DefineFeature } from '../shared'
import { injectStyle } from '../../core/slice'
import { defIfNotExists } from '../../utils'
import type { ToolbarProps } from './component'
import { ToolbarElement } from './component'
import style from './style.css?inline'
Expand Down Expand Up @@ -77,7 +78,7 @@ class ToolbarView implements PluginView {
}
}

customElements.define('milkdown-toolbar', ToolbarElement)
defIfNotExists('milkdown-toolbar', ToolbarElement)
export const defineFeature: DefineFeature = (editor) => {
editor
.config(injectStyle(style))
Expand Down
5 changes: 5 additions & 0 deletions packages/crepe/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ export function isInList(selection: Selection) {
const type = selection.$from.node(selection.$from.depth - 1)?.type
return type?.name === 'list_item'
}

export function defIfNotExists(tagName: string, element: CustomElementConstructor) {
if (customElements.get(tagName) == null)
customElements.define(tagName, element)
}

0 comments on commit 3e7a477

Please sign in to comment.