diff --git a/.oxlintrc.json b/.oxlintrc.json index 1cf09b98f61..1467ddeb906 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -1,7 +1,7 @@ { "plugins": ["import", "typescript", "unicorn", "promise"], "rules": { - "no-console": "error", + "no-console": ["error", { "allow": ["warn", "error"] }], "for-direction": "error", "require-yield": "error", "use-isnan": "error", diff --git a/packages/components/package.json b/packages/components/package.json index 74eebdbd261..c95b324e1eb 100644 --- a/packages/components/package.json +++ b/packages/components/package.json @@ -103,17 +103,17 @@ "@codemirror/view": "^6" }, "dependencies": { + "@atomico/hooks": "^4.1.2", + "@floating-ui/dom": "^1.5.1", "@milkdown/core": "workspace:*", "@milkdown/ctx": "workspace:*", + "@milkdown/exception": "workspace:*", "@milkdown/plugin-tooltip": "workspace:*", "@milkdown/preset-commonmark": "workspace:*", "@milkdown/preset-gfm": "workspace:*", "@milkdown/prose": "workspace:*", "@milkdown/transformer": "workspace:*", "@milkdown/utils": "workspace:*", - "@atomico/hooks": "^4.1.2", - "@floating-ui/dom": "^1.5.1", - "@milkdown/exception": "workspace:*", "@types/lodash.debounce": "^4.0.7", "@types/lodash.throttle": "^4.1.9", "atomico": "^1.75.1", @@ -122,7 +122,8 @@ "lodash.throttle": "^4.1.1", "nanoid": "^5.0.9", "tslib": "^2.8.1", - "unist-util-visit": "^5.0.0" + "unist-util-visit": "^5.0.0", + "vue": "^3.5.13" }, "devDependencies": { "@codemirror/language": "^6.10.1", diff --git a/packages/components/src/__internal__/icon.tsx b/packages/components/src/__internal__/icon.tsx new file mode 100644 index 00000000000..3c15cb11c89 --- /dev/null +++ b/packages/components/src/__internal__/icon.tsx @@ -0,0 +1,33 @@ +import clsx from 'clsx' +import { h } from 'vue' + +h + +type IconProps = { + icon: string | null + class?: string +} + +export function Icon({ icon, class: className }: IconProps) { + return ( + { + if (el && icon) { + ;(el as HTMLElement).innerHTML = icon + } + }} + /> + ) +} + +Icon.props = { + icon: { + type: String, + required: false, + }, + class: { + type: String, + required: false, + }, +} diff --git a/packages/components/src/image-block/config.ts b/packages/components/src/image-block/config.ts index 45c9a40102a..089bcc88f96 100644 --- a/packages/components/src/image-block/config.ts +++ b/packages/components/src/image-block/config.ts @@ -1,12 +1,11 @@ import { $ctx } from '@milkdown/utils' -import { html } from 'atomico' import { withMeta } from '../__internal__/meta' export interface ImageBlockConfig { - imageIcon: () => ReturnType | string | HTMLElement - captionIcon: () => ReturnType | string | HTMLElement - uploadButton: () => ReturnType | string | HTMLElement - confirmButton: () => ReturnType | string | HTMLElement + imageIcon: () => string | null + captionIcon: () => string | null + uploadButton: () => string | null + confirmButton: () => string | null uploadPlaceholderText: string captionPlaceholderText: string onUpload: (file: File) => Promise @@ -16,8 +15,8 @@ export interface ImageBlockConfig { export const defaultImageBlockConfig: ImageBlockConfig = { imageIcon: () => '🌌', captionIcon: () => 'πŸ’¬', - uploadButton: () => html`Upload file`, - confirmButton: () => html`Confirm ⏎`, + uploadButton: () => 'Upload file', + confirmButton: () => 'Confirm ⏎', uploadPlaceholderText: 'or paste the image link ...', captionPlaceholderText: 'Image caption', onUpload: (file) => Promise.resolve(URL.createObjectURL(file)), diff --git a/packages/components/src/image-block/view/component.ts b/packages/components/src/image-block/view/component.ts deleted file mode 100644 index e65b8fddf17..00000000000 --- a/packages/components/src/image-block/view/component.ts +++ /dev/null @@ -1,195 +0,0 @@ -import type { Component } from 'atomico' -import { c, html, useEffect, useRef, useState } from 'atomico' -import { customAlphabet } from 'nanoid' -import clsx from 'clsx' -import type { ImageBlockConfig } from '../config' -import { IMAGE_DATA_TYPE } from '../schema' -import { useBlockEffect } from './event' - -export interface Attrs { - src: string - caption: string - ratio: number -} - -export type ImageComponentProps = Attrs & { - config: ImageBlockConfig - selected: boolean - readonly: boolean - setAttr: (attr: T, value: Attrs[T]) => void -} - -let timer: number = 0 - -const nanoid = customAlphabet('abcdefg', 8) - -export const imageComponent: Component = ({ - src = '', - caption = '', - ratio = 1, - selected = false, - readonly = false, - setAttr, - config, -}) => { - const image = useRef() - const resizeHandle = useRef() - const linkInput = useRef() - const [showCaption, setShowCaption] = useState(caption.length > 0) - const [hidePlaceholder, setHidePlaceholder] = useState(src.length !== 0) - const [uuid] = useState(nanoid()) - const [focusLinkInput, setFocusLinkInput] = useState(false) - const [currentLink, setCurrentLink] = useState(src) - - useBlockEffect({ - image, - resizeHandle, - ratio, - setRatio: (r) => setAttr?.('ratio', r), - src, - readonly, - }) - - useEffect(() => { - if (selected) return - - setShowCaption(caption.length > 0) - }, [selected]) - - const onInput = (e: InputEvent) => { - const target = e.target as HTMLInputElement - const value = target.value - if (timer) window.clearTimeout(timer) - - timer = window.setTimeout(() => { - setAttr?.('caption', value) - }, 1000) - } - - const onBlurCaption = (e: InputEvent) => { - const target = e.target as HTMLInputElement - const value = target.value - if (timer) { - window.clearTimeout(timer) - timer = 0 - } - - setAttr?.('caption', value) - } - - const onEditLink = (e: InputEvent) => { - const target = e.target as HTMLInputElement - const value = target.value - setHidePlaceholder(value.length !== 0) - setCurrentLink(value) - } - - const onUpload = async (e: InputEvent) => { - const file = (e.target as HTMLInputElement).files?.[0] - if (!file) return - - const url = await config?.onUpload(file) - if (!url) return - - setAttr?.('src', url) - setHidePlaceholder(true) - } - - const onToggleCaption = (e: Event) => { - e.preventDefault() - e.stopPropagation() - if (readonly) return - setShowCaption((x) => !x) - } - - const onConfirmLinkInput = () => { - setAttr?.('src', linkInput.current?.value ?? '') - } - - const onKeydown = (e: KeyboardEvent) => { - if (e.key === 'Enter') onConfirmLinkInput() - } - - const preventDrag = (e: Event) => { - e.preventDefault() - e.stopPropagation() - } - - return html` -
0 && 'hidden')}> -
${config?.imageIcon()}
- -
onConfirmLinkInput()} - > - ${config?.confirmButton()} -
-
-
-
-
- ${config?.captionIcon()} -
-
- ${caption} -
-
- -
` -} - -imageComponent.props = { - src: String, - caption: String, - ratio: Number, - selected: Boolean, - readonly: Boolean, - setAttr: Function, - config: Object, -} - -export const ImageElement = c(imageComponent) diff --git a/packages/components/src/image-block/view/components/image-block.tsx b/packages/components/src/image-block/view/components/image-block.tsx new file mode 100644 index 00000000000..e4807681b2f --- /dev/null +++ b/packages/components/src/image-block/view/components/image-block.tsx @@ -0,0 +1,65 @@ +import { h, Fragment, type Ref, defineComponent } from 'vue' +import type { ImageBlockConfig } from '../../config' +import { ImageViewer } from './image-viewer' +import { ImageInput } from './image-input' + +h +Fragment + +type Attrs = { + src: string + caption: string + ratio: number +} + +export type MilkdownImageBlockProps = { + selected: Ref + readonly: Ref + setAttr: (attr: T, value: Attrs[T]) => void + config: ImageBlockConfig +} & { + [P in keyof Attrs]: Ref +} + +export const MilkdownImageBlock = defineComponent({ + props: { + src: { + type: Object, + required: true, + }, + caption: { + type: Object, + required: true, + }, + ratio: { + type: Object, + required: true, + }, + selected: { + type: Object, + required: true, + }, + readonly: { + type: Object, + required: true, + }, + setAttr: { + type: Function, + required: true, + }, + config: { + type: Object, + required: true, + }, + }, + setup(props) { + const { src } = props + + return () => { + if (!src.value?.length) { + return + } + return + } + }, +}) diff --git a/packages/components/src/image-block/view/components/image-input.tsx b/packages/components/src/image-block/view/components/image-input.tsx new file mode 100644 index 00000000000..54049c4df2a --- /dev/null +++ b/packages/components/src/image-block/view/components/image-input.tsx @@ -0,0 +1,131 @@ +import { defineComponent, ref, h } from 'vue' +import type { MilkdownImageBlockProps } from './image-block' +import clsx from 'clsx' +import { customAlphabet } from 'nanoid' +import { Icon } from '../../../__internal__/icon' + +h + +const nanoid = customAlphabet('abcdefg', 8) + +export const ImageInput = defineComponent({ + props: { + src: { + type: Object, + required: true, + }, + caption: { + type: Object, + required: true, + }, + ratio: { + type: Object, + required: true, + }, + selected: { + type: Object, + required: true, + }, + readonly: { + type: Object, + required: true, + }, + setAttr: { + type: Function, + required: true, + }, + config: { + type: Object, + required: true, + }, + }, + setup({ config, readonly, src, setAttr }) { + const focusLinkInput = ref(false) + const linkInputRef = ref() + const currentLink = ref(src.value ?? '') + const uuid = ref(nanoid()) + const hidePlaceholder = ref(src.value?.length !== 0) + const onEditLink = (e: Event) => { + const target = e.target as HTMLInputElement + const value = target.value + hidePlaceholder.value = value.length !== 0 + currentLink.value = value + } + + const onKeydown = (e: KeyboardEvent) => { + if (e.key === 'Enter') { + setAttr('src', linkInputRef.value?.value ?? '') + } + } + + const onConfirmLinkInput = () => { + setAttr('src', linkInputRef.value?.value ?? '') + } + + const onUpload = (e: Event) => { + const file = (e.target as HTMLInputElement).files?.[0] + if (!file) return + + config + .onUpload(file) + .then((url) => { + if (!url) return + + setAttr('src', url) + hidePlaceholder.value = true + }) + .catch((err) => { + console.error('An error occurred while uploading image') + console.error(err) + }) + } + + return () => { + return ( +
+ + + {currentLink.value && ( +
onConfirmLinkInput()}> + +
+ )} +
+ ) + } + }, +}) diff --git a/packages/components/src/image-block/view/components/image-viewer.tsx b/packages/components/src/image-block/view/components/image-viewer.tsx new file mode 100644 index 00000000000..2a90bde6e18 --- /dev/null +++ b/packages/components/src/image-block/view/components/image-viewer.tsx @@ -0,0 +1,169 @@ +import { defineComponent, ref, h, Fragment } from 'vue' +import type { MilkdownImageBlockProps } from './image-block' +import { IMAGE_DATA_TYPE } from '../../schema' +import { Icon } from '../../../__internal__/icon' + +h +Fragment + +export const ImageViewer = defineComponent({ + props: { + src: { + type: Object, + required: true, + }, + caption: { + type: Object, + required: true, + }, + ratio: { + type: Object, + required: true, + }, + selected: { + type: Object, + required: true, + }, + readonly: { + type: Object, + required: true, + }, + setAttr: { + type: Function, + required: true, + }, + config: { + type: Object, + required: true, + }, + }, + setup({ src, caption, ratio, readonly, setAttr, config }) { + const imageRef = ref() + const resizeHandle = ref() + const showCaption = ref(Boolean(caption.value?.length)) + const timer = ref(0) + + const onImageLoad = () => { + const image = imageRef.value + if (!image) return + const host = image.closest('.milkdown-image-block') + if (!host) return + + const maxWidth = host.getBoundingClientRect().width + if (!maxWidth) return + + const height = image.height + const width = image.width + const transformedHeight = + width < maxWidth ? height : maxWidth * (height / width) + const h = (transformedHeight * (ratio.value ?? 1)).toFixed(2) + image.dataset.origin = transformedHeight.toFixed(2) + image.dataset.height = h + image.style.height = `${h}px` + } + + const onToggleCaption = (e: PointerEvent) => { + e.preventDefault() + e.stopPropagation() + if (readonly.value) return + showCaption.value = !showCaption.value + } + + const onInputCaption = (e: Event) => { + const target = e.target as HTMLInputElement + const value = target.value + if (timer.value) window.clearTimeout(timer.value) + + timer.value = window.setTimeout(() => { + setAttr('caption', value) + }, 1000) + } + + const onBlurCaption = (e: Event) => { + const target = e.target as HTMLInputElement + const value = target.value + if (timer.value) { + window.clearTimeout(timer.value) + timer.value = 0 + } + + setAttr('caption', value) + } + + const onResizeHandlePointerMove = (e: PointerEvent) => { + e.preventDefault() + const image = imageRef.value + if (!image) return + const top = image.getBoundingClientRect().top + const height = e.clientY - top + const h = Number(height < 100 ? 100 : height).toFixed(2) + image.dataset.height = h + image.style.height = `${h}px` + } + + const onResizeHandlePointerUp = () => { + window.removeEventListener('pointermove', onResizeHandlePointerMove) + window.removeEventListener('pointerup', onResizeHandlePointerUp) + + const image = imageRef.value + if (!image) return + + const originHeight = Number(image.dataset.origin) + const currentHeight = Number(image.dataset.height) + const ratio = Number.parseFloat( + Number(currentHeight / originHeight).toFixed(2) + ) + if (Number.isNaN(ratio)) return + + setAttr('ratio', ratio) + } + + const onResizeHandlePointerDown = (e: PointerEvent) => { + if (readonly.value) return + e.preventDefault() + e.stopPropagation() + window.addEventListener('pointermove', onResizeHandlePointerMove) + window.addEventListener('pointerup', onResizeHandlePointerUp) + } + + return () => { + return ( + <> +
+
+
+ +
+
+ {caption.value} +
+
+ {showCaption.value && ( + { + e.preventDefault() + e.stopPropagation() + }} + class="caption-input" + placeholder={config?.captionPlaceholderText} + onInput={onInputCaption} + onBlur={onBlurCaption} + value={caption.value} + /> + )} + + ) + } + }, +}) diff --git a/packages/components/src/image-block/view/index.ts b/packages/components/src/image-block/view/index.ts index 3c4a8aec874..a806c4549e1 100644 --- a/packages/components/src/image-block/view/index.ts +++ b/packages/components/src/image-block/view/index.ts @@ -4,48 +4,66 @@ 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' +import { createApp, ref, watchEffect } from 'vue' +import { MilkdownImageBlock } from './components/image-block' -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 + const src = ref(initialNode.attrs.src) + const caption = ref(initialNode.attrs.caption) + const ratio = ref(initialNode.attrs.ratio) + const selected = ref(false) + const readonly = ref(!view.editable) + const setAttr = (attr: string, value: string) => { + const pos = getPos() + if (pos == null) return + view.dispatch(view.state.tr.setNodeAttribute(pos, attr, value)) + } const config = ctx.get(imageBlockConfig.key) + const app = createApp(MilkdownImageBlock, { + src, + caption, + ratio, + selected, + readonly, + setAttr, + config, + }) + const dom = document.createElement('div') + dom.className = 'milkdown-image-block' + app.mount(dom) + const selectedWatcher = watchEffect(() => { + const isSelected = selected.value + if (isSelected) { + dom.classList.add('selected') + } else { + dom.classList.remove('selected') + } + }) const proxyDomURL = config.proxyDomURL const bindAttrs = (node: Node) => { if (!proxyDomURL) { - dom.src = node.attrs.src + src.value = node.attrs.src } else { const proxiedURL = proxyDomURL(node.attrs.src) if (typeof proxiedURL === 'string') { - dom.src = proxiedURL + src.value = proxiedURL } else { proxiedURL.then((url) => { - dom.src = url + src.value = url }) } } - dom.ratio = node.attrs.ratio - dom.caption = node.attrs.caption + ratio.value = node.attrs.ratio + caption.value = node.attrs.caption - dom.readonly = !view.editable + readonly.value = !view.editable } bindAttrs(initialNode) - dom.selected = false - dom.setAttr = (attr, value) => { - const pos = getPos() - if (pos == null) return - - view.dispatch(view.state.tr.setNodeAttribute(pos, attr, value)) - } - dom.config = config + selected.value = false return { dom, update: (updatedNode) => { @@ -60,12 +78,14 @@ export const imageBlockView = $view( return false }, selectNode: () => { - dom.selected = true + selected.value = true }, deselectNode: () => { - dom.selected = false + selected.value = false }, destroy: () => { + selectedWatcher() + app.unmount() dom.remove() }, } diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json index a9ccbb0ac1b..725c8cf59f0 100644 --- a/packages/components/tsconfig.json +++ b/packages/components/tsconfig.json @@ -1,6 +1,10 @@ { "extends": "../../tsconfig.json", "compilerOptions": { + "jsx": "preserve", + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment", + "jsxImportSource": "vue", "rootDir": "src", "outDir": "lib", "tsBuildInfoFile": "./lib/tsconfig.tsbuildinfo" @@ -10,12 +14,12 @@ "references": [ { "path": "../core" }, { "path": "../ctx" }, + { "path": "../exception" }, { "path": "../plugins/plugin-tooltip" }, { "path": "../plugins/preset-commonmark" }, { "path": "../plugins/preset-gfm" }, { "path": "../prose" }, { "path": "../transformer" }, - { "path": "../utils" }, - { "path": "../exception" } + { "path": "../utils" } ] } diff --git a/packages/crepe/package.json b/packages/crepe/package.json index dc0011d17cf..9a9a5c2022f 100644 --- a/packages/crepe/package.json +++ b/packages/crepe/package.json @@ -71,6 +71,7 @@ "prosemirror-virtual-cursor": "^0.4.2", "remark-math": "^6.0.0", "tslib": "^2.8.1", - "unist-util-visit": "^5.0.0" + "unist-util-visit": "^5.0.0", + "vue": "^3.5.13" } } diff --git a/packages/crepe/src/feature/block-edit/menu/config.ts b/packages/crepe/src/feature/block-edit/menu/config.ts index 66e78317f1f..95366884c8e 100644 --- a/packages/crepe/src/feature/block-edit/menu/config.ts +++ b/packages/crepe/src/feature/block-edit/menu/config.ts @@ -26,7 +26,7 @@ import { h4Icon, h5Icon, h6Icon, - imageIcon, + legacyImageIcon, orderedListIcon, quoteIcon, tableIcon, @@ -221,7 +221,7 @@ export function getGroups( if (isImageBlockEnabled) { advancedGroup.addItem('image', { label: config?.slashMenuImageLabel ?? 'Image', - icon: config?.slashMenuImageIcon?.() ?? imageIcon, + icon: config?.slashMenuImageIcon?.() ?? legacyImageIcon, onRun: (ctx) => { const view = ctx.get(editorViewCtx) const { dispatch, state } = view diff --git a/packages/crepe/src/feature/image-block/index.ts b/packages/crepe/src/feature/image-block/index.ts index ae0c1ef119e..1edf1452007 100644 --- a/packages/crepe/src/feature/image-block/index.ts +++ b/packages/crepe/src/feature/image-block/index.ts @@ -7,7 +7,12 @@ import { inlineImageConfig, } from '@milkdown/kit/component/image-inline' import type { DefineFeature, Icon } from '../shared' -import { captionIcon, confirmIcon, imageIcon } from '../../icons' +import { + captionIcon, + confirmIcon, + imageIcon, + legacyImageIcon, +} from '../../icons' interface ImageBlockConfig { onUpload: (file: File) => Promise @@ -38,7 +43,7 @@ export const defineFeature: DefineFeature = ( .config((ctx) => { ctx.update(inlineImageConfig.key, (value) => ({ uploadButton: config?.inlineUploadButton ?? (() => 'Upload'), - imageIcon: config?.inlineImageIcon ?? (() => imageIcon), + imageIcon: config?.inlineImageIcon ?? (() => legacyImageIcon), confirmButton: config?.inlineConfirmButton ?? (() => confirmIcon), uploadPlaceholderText: config?.inlineUploadPlaceholderText ?? 'or paste link', diff --git a/packages/crepe/src/feature/shared.ts b/packages/crepe/src/feature/shared.ts index cc53712778f..2cd5b24726f 100644 --- a/packages/crepe/src/feature/shared.ts +++ b/packages/crepe/src/feature/shared.ts @@ -1,9 +1,8 @@ import type { Editor } from '@milkdown/kit/core' -import type { html } from 'atomico' export type DefineFeature = ( editor: Editor, config?: Config ) => void | Promise -export type Icon = () => ReturnType | string | null +export type Icon = () => string | null diff --git a/packages/crepe/src/icons/caption.ts b/packages/crepe/src/icons/caption.ts index 2c15d6d1161..d740303c6e1 100644 --- a/packages/crepe/src/icons/caption.ts +++ b/packages/crepe/src/icons/caption.ts @@ -1,6 +1,4 @@ -import { html } from 'atomico' - -export const captionIcon = html` +export const captionIcon = ` + + + + + + + + + +` + +export const legacyImageIcon = html` .image-edit { + .milkdown-image-block { + user-select: none !important; + } + + .milkdown-image-block.selected > .image-edit { outline: 2px solid fuchsia; } - milkdown-image-block.selected > .image-wrapper img { + .milkdown-image-block.selected > .image-wrapper img { outline: 2px solid fuchsia; } - milkdown-image-block > .image-wrapper .operation { + .milkdown-image-block > .image-wrapper .operation { gap: 16px; right: 16px; top: 16px; @@ -15,11 +19,11 @@ transition: all 0.2s; } - milkdown-image-block:hover > .image-wrapper .operation { + .milkdown-image-block:hover > .image-wrapper .operation { opacity: 1; } - milkdown-image-block > .image-wrapper .operation > .operation-item { + .milkdown-image-block > .image-wrapper .operation > .operation-item { color: antiquewhite; padding: 8px; background: rgba(0, 0, 0, 0.4); @@ -31,7 +35,7 @@ align-items: center; } - milkdown-image-block > .image-wrapper .image-resize-handle { + .milkdown-image-block > .image-wrapper .image-resize-handle { height: 8px; bottom: -4px; width: 160px; @@ -40,15 +44,15 @@ transition: all 0.2s; } - milkdown-image-block:hover > .image-wrapper .image-resize-handle { + .milkdown-image-block:hover > .image-wrapper .image-resize-handle { opacity: 1; } - milkdown-image-block > .caption-input { + .milkdown-image-block > .caption-input { margin: 16px auto; } - milkdown-image-block > .image-edit { + .milkdown-image-block > .image-edit { align-items: center; padding: 16px 24px; gap: 16px; @@ -56,32 +60,32 @@ height: 56px; } - milkdown-image-block > .image-edit .image-icon { + .milkdown-image-block > .image-edit .image-icon { color: darkgray; } - milkdown-image-block > .image-edit .image-icon svg { + .milkdown-image-block > .image-edit .image-icon svg { width: 24px; height: 24px; } - milkdown-image-block > .image-edit .link-importer .placeholder { + .milkdown-image-block > .image-edit .link-importer .placeholder { color: darkgray; } - milkdown-image-block > .image-edit .link-importer .link-input-area { + .milkdown-image-block > .image-edit .link-importer .link-input-area { line-height: 24px; padding: 8px 0; } - milkdown-image-block > .image-edit .link-importer .placeholder .uploader { + .milkdown-image-block > .image-edit .link-importer .placeholder .uploader { gap: 8px; color: fuchsia; justify-content: center; transition: color 0.2s; } - milkdown-image-block + .milkdown-image-block > .image-edit .link-importer.focus .placeholder @@ -89,7 +93,7 @@ color: unset; } - milkdown-image-block + .milkdown-image-block > .image-edit .link-importer .placeholder @@ -97,11 +101,11 @@ color: fuchsia; } - milkdown-image-block > .image-edit .link-importer .placeholder .text { + .milkdown-image-block > .image-edit .link-importer .placeholder .text { margin-left: 8px; } - milkdown-image-block > .image-edit .confirm { + .milkdown-image-block > .image-edit .confirm { background: darkgray; color: antiquewhite; line-height: 40px; @@ -110,75 +114,75 @@ font-size: 14px; } - milkdown-image-block { + .milkdown-image-block { outline: none; margin: 16px 0; display: block; } - milkdown-image-block > .image-wrapper { + .milkdown-image-block > .image-wrapper { position: relative; width: fit-content; margin: 0 auto; min-width: 100px; } - milkdown-image-block > .image-wrapper .operation { + .milkdown-image-block > .image-wrapper .operation { position: absolute; display: flex; } - milkdown-image-block > .image-wrapper .operation > .operation-item { + .milkdown-image-block > .image-wrapper .operation > .operation-item { cursor: pointer; } - milkdown-image-block > .image-wrapper img { + .milkdown-image-block > .image-wrapper img { max-width: 100%; min-height: 100px; display: block; object-fit: cover; } - milkdown-image-block > .image-wrapper > .image-resize-handle { + .milkdown-image-block > .image-wrapper > .image-resize-handle { position: absolute; left: 50%; transform: translateX(-50%); } - milkdown-image-block > .image-wrapper > .image-resize-handle:hover { + .milkdown-image-block > .image-wrapper > .image-resize-handle:hover { cursor: row-resize; } - milkdown-image-block input { + .milkdown-image-block input { background: transparent; outline: none; border: 0; } - milkdown-image-block > .caption-input { + .milkdown-image-block > .caption-input { display: block; width: 100%; text-align: center; } - milkdown-image-block > .image-edit { + .milkdown-image-block > .image-edit { display: flex; } - milkdown-image-block > .image-edit .confirm { + .milkdown-image-block > .image-edit .confirm { cursor: pointer; } - milkdown-image-block > .image-edit .link-importer { + .milkdown-image-block > .image-edit .link-importer { position: relative; flex: 1; } - milkdown-image-block > .image-edit .link-importer > .link-input-area { + .milkdown-image-block > .image-edit .link-importer > .link-input-area { width: 100%; } - milkdown-image-block > .image-edit .link-importer .placeholder { + .milkdown-image-block > .image-edit .link-importer .placeholder { position: absolute; top: 0; left: 0; @@ -188,12 +192,12 @@ cursor: text; } - milkdown-image-block > .image-edit .link-importer .placeholder .uploader { + .milkdown-image-block > .image-edit .link-importer .placeholder .uploader { cursor: pointer; display: flex; } - milkdown-image-block .hidden { + .milkdown-image-block .hidden { display: none !important; } } diff --git a/storybook/stories/crepe/setup.ts b/storybook/stories/crepe/setup.ts index 9889f9f7039..e0e3e5216df 100644 --- a/storybook/stories/crepe/setup.ts +++ b/storybook/stories/crepe/setup.ts @@ -45,7 +45,6 @@ export function setup({ args, style, theme }: setupConfig) { language === 'JA' ? 'をップロード' : 'Upload', inlineUploadPlaceholderText: language === 'JA' ? 'またはγƒͺγƒ³γ‚―γ‚’θ²Όγ‚Šδ»˜γ‘γ‚‹' : 'or paste link', - inlineConfirmButton: () => (language === 'JA' ? 'η’Ίθͺ' : 'Confirm'), blockUploadButton: () => language === 'JA' ? 'フゑむルををップロード' : 'Upload file', blockUploadPlaceholderText: diff --git a/tsconfig.base.json b/tsconfig.base.json index cdf30aede48..b5ef9019025 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -7,6 +7,8 @@ "moduleResolution": "Bundler", "resolveJsonModule": true, + "verbatimModuleSyntax": true, + "strict": true, "noFallthroughCasesInSwitch": true, "noImplicitOverride": true,