diff --git a/packages/core/client/package.json b/packages/core/client/package.json index ea5efa407cc..a09964abe23 100644 --- a/packages/core/client/package.json +++ b/packages/core/client/package.json @@ -76,7 +76,9 @@ "react-to-print": "^2.14.7", "sanitize-html": "2.13.0", "tabulator-tables": "^6.3.1", - "use-deep-compare-effect": "^1.8.1" + "use-deep-compare-effect": "^1.8.1", + "vditor": "^3.10.3", + "liquidjs": "^10.0.0" }, "peerDependencies": { "react": ">=18.0.0", diff --git a/packages/core/client/src/flow/common/Liquid.tsx b/packages/core/client/src/flow/common/Liquid.tsx new file mode 100644 index 00000000000..c8d69706db0 --- /dev/null +++ b/packages/core/client/src/flow/common/Liquid.tsx @@ -0,0 +1,102 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ +import { Liquid } from 'liquidjs'; + +export class LiquidEngine extends Liquid { + constructor(options = {}) { + super({ + extname: '.liquid', + cache: true, + ...options, + }); + + // 注册国际化过滤器 + this.registerFilter('t', (key, locale = 'en', dict = {}) => { + if (!key) return ''; + if (!dict) return key; + + // 优先当前语言,否则 fallback 到英文 + return dict[key]?.[locale] || dict[key]?.['en'] || key; + }); + + // (可选)注册一个日志过滤器,方便调试 + this.registerFilter('log', (value) => { + console.log('[Liquid log]', value); + return value; + }); + } + + /** + * 将路径数组转为 Liquid 模板上下文对象 + * @param {string[]} paths - 如 ['ctx.user.name', 'ctx.order.total'] + * @returns {object} 形如 { user: { name: '{{ctx.user.name}}' }, order: {...} } + */ + transformLiquidContext(paths = []) { + const result = {}; + + for (const fullPath of paths) { + const path = fullPath.replace(/^ctx\./, ''); + const keys = path.split('.'); + + let current = result; + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const isLast = i === keys.length - 1; + + if (isLast) { + current[key] = `{{${fullPath}}}`; + } else { + current[key] = current[key] || {}; + current = current[key]; + } + } + } + + return result; + } + + /** + * 渲染模板 + * @param {string} template - Liquid 模板字符串 + * @param {object} context - 模板上下文变量 + * @returns {Promise} 渲染后的字符串 + */ + async render(template, context = {}) { + try { + return await this.parseAndRender(template, context); + } catch (err) { + console.error('[Liquid] 模板解析失败:', err); + return `
Liquid 模板错误:${err.message}
`; + } + } + + /** + * 合并步骤:获取变量 -> 构建 context -> 解析 -> 渲染 + * @param {string} template Liquid 模板字符串 + * @param {context} ctx flowContext + */ + async renderWithFullContext(template, ctx) { + try { + // 1️⃣ 分析模板中的变量 + const vars = await this.fullVariables(template); + + // 2️⃣ 构造 Liquid context + const liquidContext = this.transformLiquidContext(vars); + + // 3️⃣ 只解析变量 + const resolvedCtx = await ctx.resolveJsonTemplate(liquidContext); + + // 4️⃣ 渲染模板 + return await this.render(template, { ctx: resolvedCtx }); + } catch (err) { + console.error('[Liquid] renderWithFullContext 错误:', err); + return `
Liquid 渲染错误:${err.message}
`; + } + } +} diff --git a/packages/core/client/src/flow/common/Markdown/Display.tsx b/packages/core/client/src/flow/common/Markdown/Display.tsx new file mode 100644 index 00000000000..1cd1b673a4c --- /dev/null +++ b/packages/core/client/src/flow/common/Markdown/Display.tsx @@ -0,0 +1,183 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Popover } from 'antd'; +import { css } from '@emotion/css'; +import React, { CSSProperties, useCallback, useEffect, useRef, useState } from 'react'; +import Vditor from 'vditor'; +import { useCDN } from './useCDN'; +import useStyle from './style'; + +function convertToText(markdownText: string) { + const content = markdownText; + let temp = document.createElement('div'); + temp.innerHTML = content; + const text = temp.innerText; + temp = null; + return text?.replace(/[\n\r]/g, '') || ''; +} + +const getContentWidth = (element) => { + if (element) { + const range = document.createRange(); + range.selectNodeContents(element); + const contentWidth = range.getBoundingClientRect().width; + return contentWidth; + } +}; + +function DisplayInner(props: { value: string; style?: CSSProperties }) { + const containerRef = useRef(null); + const { wrapSSR, componentCls, hashId } = useStyle(); + const cdn = useCDN(); + + useEffect(() => { + Vditor.preview(containerRef.current, props.value ?? '', { + mode: 'light', + cdn, + }); + setTimeout(() => { + containerRef.current?.querySelectorAll('img').forEach((img: HTMLImageElement) => { + img.style.cursor = 'zoom-in'; + img.addEventListener('click', () => { + openCustomPreview(img.src); + }); + }); + }, 0); + }, [props.value]); + + return wrapSSR( + + + , + ); +} + +function openCustomPreview(src: string) { + if (document.getElementById('custom-image-preview')) return; + + // 创建容器 + const overlay = document.createElement('span'); + overlay.id = 'custom-image-preview'; + Object.assign(overlay.style, { + position: 'fixed', + inset: '0', + backgroundColor: 'rgba(0,0,0,0.85)', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + zIndex: '9999', + cursor: 'zoom-out', + }); + + const img = document.createElement('img'); + img.src = src; + Object.assign(img.style, { + maxWidth: '90%', + maxHeight: '90%', + borderRadius: '8px', + boxShadow: '0 0 20px rgba(0,0,0,0.5)', + transition: 'transform 0.2s', + cursor: 'zoom-out', + }); + + overlay.addEventListener('click', () => { + document.body.removeChild(overlay); + }); + + overlay.appendChild(img); + document.body.appendChild(overlay); +} + +export const Display = (props) => { + const { value, textOnly = true } = props; + const cdn = useCDN(); + const [popoverVisible, setPopoverVisible] = useState(false); + const [ellipsis, setEllipsis] = useState(false); + + const [text, setText] = useState(''); + + const elRef = useRef(); + useEffect(() => { + if (!props.value) return; + if (textOnly) { + Vditor.md2html(props.value, { + mode: 'light', + cdn, + }) + .then((html) => { + setText(convertToText(html)); + }) + .catch(() => setText('')); + } + }, [props.value, textOnly]); + + const isOverflowTooltip = useCallback(() => { + if (!elRef.current) return false; + const contentWidth = getContentWidth(elRef.current); + const offsetWidth = elRef.current?.offsetWidth; + return contentWidth > offsetWidth; + }, [elRef]); + + if (props.ellipsis) { + return ( + document.getElementsByClassName('ant-drawer-content')?.[0] as HTMLElement} + onOpenChange={(visible) => { + setPopoverVisible(ellipsis && visible); + }} + overlayStyle={{ maxWidth: 400, maxHeight: 450, overflow: 'auto' }} + content={} + > +
{ + const el = e.target as any; + const isShowTooltips = isOverflowTooltip(); + if (isShowTooltips) { + setEllipsis(el.scrollWidth >= el.clientWidth); + } + }} + > + {textOnly ? ( + text + ) : ( +
+ +
+ )} +
+
+ ); + } + if (textOnly) { + return text; + } + return ; +}; diff --git a/packages/core/client/src/flow/common/Markdown/Edit.tsx b/packages/core/client/src/flow/common/Markdown/Edit.tsx new file mode 100644 index 00000000000..636af10fb86 --- /dev/null +++ b/packages/core/client/src/flow/common/Markdown/Edit.tsx @@ -0,0 +1,368 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { useAPIClient, useCompile, usePlugin, useZIndexContext, getZIndex } from '@nocobase/client'; +import { Button } from 'antd'; +import { css } from '@emotion/css'; +import type { TextAreaRef } from 'antd/es/input/TextArea'; +import { FlowContextSelector, useFlowContext, useFlowModel } from '@nocobase/flow-engine'; +import React, { useEffect, useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import Vditor from 'vditor'; +import 'vditor/dist/index.css'; +import { useCDN } from './useCDN'; +import useStyle from './style'; + +const defaultToolbar = [ + 'headings', + 'bold', + 'italic', + 'strike', + 'link', + 'list', + 'ordered-list', + 'check', + 'quote', + 'line', + 'code', + 'inline-code', + 'upload', + 'fullscreen', +]; + +const NAMESPACE = 'block-markdown'; + +const locales = ['en_US', 'fr_FR', 'pt_BR', 'ja_JP', 'ko_KR', 'ru_RU', 'sv_SE', 'zh_CN', 'zh_TW']; + +const Edit = (props) => { + const { disabled, onChange, value, fileCollection, toolbar, vditorRef } = props; + + const [editorReady, setEditorReady] = useState(false); + const vdRef = useRef(); + const vdFullscreen = useRef(false); + const containerRef = useRef(null); + const containerParentRef = useRef(null); + const apiClient = useAPIClient(); + const cdn = useCDN(); + const { wrapSSR, hashId, componentCls: containerClassName } = useStyle(); + const locale = apiClient.auth.locale || 'en-US'; + const fileManagerPlugin: any = usePlugin('@nocobase/plugin-file-manager'); + const compile = useCompile(); + const compileRef = useRef(compile); + compileRef.current = compile; + const { t } = useTranslation(); + const parentZIndex = useZIndexContext(); + + const zIndex = getZIndex('drawer', parentZIndex + 1000, 0); + + const lang: any = useMemo(() => { + const currentLang = locale.replace(/-/g, '_'); + if (locales.includes(currentLang)) { + return currentLang; + } + return 'en_US'; + }, [locale]); + + useEffect(() => { + if (!containerRef.current) return; + + const toolbarConfig = toolbar ?? defaultToolbar; + + const vditor = new Vditor(containerRef.current, { + value: value ?? '', + lang, + cache: { enable: false }, + undoDelay: 0, + preview: { math: { engine: 'KaTeX' } }, + toolbar: toolbarConfig, + fullscreen: { index: 1200 }, + cdn, + minHeight: 200, + after: () => { + vdRef.current = vditor; + setEditorReady(true); // Notify that the editor is ready + vditor.setValue(value ?? ''); + if (disabled) { + vditor.disabled(); + } else { + vditor.enable(); + } + }, + input(value) { + onChange(value); + }, + upload: { + multiple: false, + fieldName: 'file', + async handler(files: File[]) { + const file = files[0]; + + // Need to ensure focus is in the current input box before uploading + vditor.focus(); + + const { data: checkData } = await apiClient.resource('vditor').check({ + fileCollectionName: fileCollection, + }); + + if (!checkData?.data?.isSupportToUploadFiles) { + vditor.tip( + t('vditor.uploadError.message', { ns: NAMESPACE, storageTitle: checkData.data.storage?.title }), + 0, + ); + return; + } + + vditor.tip(t('uploading'), 0); + const { data, errorMessage } = await fileManagerPlugin.uploadFile({ + file, + fileCollectionName: fileCollection, + storageId: checkData?.data?.storage?.id, + storageType: checkData?.data?.storage?.type, + storageRules: checkData?.data?.storage?.rules, + }); + + if (errorMessage) { + vditor.tip(compileRef.current(errorMessage), 3000); + return; + } + + if (!data) { + vditor.tip(t('Response data is empty', { ns: NAMESPACE }), 3000); + return; + } + + const fileName = data.filename; + const fileUrl = data.url; + + // Check if the uploaded file is an image + const isImage = file.type.startsWith('image/'); + + if (isImage) { + // Insert as an image - will be displayed in the editor + vditor.insertValue(`![${fileName}](${fileUrl})`); + } else { + // For non-image files, insert as a download link + vditor.insertValue(`[${fileName}](${fileUrl})`); + } + + // hide the tip + vditor.tip(t(''), 10); + + return null; + }, + }, + }); + vditorRef.current = vditor; + + return () => { + vdRef.current?.destroy(); + vdRef.current = undefined; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [toolbar?.join(',')]); + + useEffect(() => { + if (editorReady && vdRef.current) { + const editor = vdRef.current; + if (value !== editor.getValue()) { + editor.setValue(value ?? ''); + // editor.focus(); + + const preArea = containerRef.current?.querySelector( + 'div.vditor-content > div.vditor-ir > pre', + ) as HTMLPreElement; + if (preArea) { + const range = document.createRange(); + const selection = window.getSelection(); + if (selection) { + range.selectNodeContents(preArea); + range.collapse(false); // Move cursor to the end + selection.removeAllRanges(); + selection.addRange(range); + } + } + } + } + }, [value, editorReady]); + + useEffect(() => { + if (editorReady && vdRef.current) { + if (disabled) { + vdRef.current.disabled(); + } else { + vdRef.current.enable(); + } + } + }, [disabled, editorReady]); + + useEffect(() => { + if (!containerParentRef.current) return; + + const observer = new MutationObserver((mutations) => { + for (const mutation of mutations) { + for (const node of Array.from(mutation.addedNodes)) { + if (node instanceof HTMLElement && node.classList.contains('vditor-img')) { + // 移动图片预览层到弹窗容器 + containerParentRef.current.appendChild(node); + node.style.zIndex = String(zIndex); + } + } + } + }); + + observer.observe(document.body, { childList: true }); + + return () => { + observer.disconnect(); + }; + }, [zIndex]); + + useLayoutEffect(() => { + if (!containerRef.current) return; + + const observer = new ResizeObserver((entries) => { + for (const entry of entries) { + const target = entry.target; + if (target.className.includes('vditor--fullscreen')) { + document.body.appendChild(target); + vdFullscreen.current = true; + } else if (vdFullscreen.current) { + containerParentRef.current?.appendChild(target); + vdFullscreen.current = false; + } + } + }); + + observer.observe(containerRef.current); + + return () => { + observer.unobserve(containerRef.current); + }; + }, []); + + return wrapSSR( +
+
+
, + ); +}; + +export interface MarkdownWithContextSelectorProps { + value?: string; + onChange?: (v: string) => void; + placeholder?: string; + rows?: number; + style?: React.CSSProperties; +} + +/** + * markdown 与变量选择器的组合,紧凑排版,边框无缝拼接。 + */ +export const MarkdownWithContextSelector: React.FC = ({ + value = '', + onChange, + placeholder, + style, +}) => { + const flowCtx = useFlowContext(); + const [innerValue, setInnerValue] = useState(value || ''); + const ref = useRef(null); + const isConfigMode = !!flowCtx.model.flowEngine?.flowSettings?.enabled; + // 外部 value 变化时同步内部显示 + useEffect(() => { + setInnerValue(value || ''); + }, [value]); + + const handleTextChange = useCallback( + (e) => { + const next = e ?? ''; + setInnerValue(next); + onChange?.(next); + }, + [onChange], + ); + + // 将指定文本插入到当前光标位置 + const insertAtCaret = useCallback( + (toInsert: string) => { + const editor = ref.current as any; + if (!editor) { + console.warn('Vditor 尚未初始化,无法插入文本'); + return; + } + + // 🔹 在当前光标位置插入文本 + editor.insertValue(toInsert); + + // 🔹 同步外部状态 + const next = editor.getValue(); + setInnerValue(next); + onChange?.(next); + + // 🔹 保持聚焦 + requestAnimationFrame(() => { + editor.focus(); + }); + }, + [innerValue, onChange], + ); + + const handleVariableSelected = useCallback( + (varValue: string) => { + if (!varValue) return; + insertAtCaret(varValue); + }, + [insertAtCaret], + ); + + // 使用函数形式提供变量树,保证与运行时上下文一致 + const metaTree = useMemo(() => () => flowCtx.getPropertyMetaTree?.(), [flowCtx]); + + return ( +
+ + {isConfigMode && ( +
+ {/* 参考 1.0:小号按钮 + 非 hover 去掉右/上边框,背景透明,贴合右上角 */} + handleVariableSelected(val)} onlyLeafSelectable> + + +
+ )} +
+ ); +}; diff --git a/packages/core/client/src/flow/common/Markdown/Markdown.tsx b/packages/core/client/src/flow/common/Markdown/Markdown.tsx new file mode 100644 index 00000000000..ff39ff629b2 --- /dev/null +++ b/packages/core/client/src/flow/common/Markdown/Markdown.tsx @@ -0,0 +1,44 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React from 'react'; +import { Display } from './Display'; +import { MarkdownWithContextSelector as Edit } from './Edit'; + +export class Markdown { + /** + * 渲染 Markdown + * @param {string} text - Markdown 文本 + * @param {object} props - 其他属性 + * @returns {JSX.Element} + */ + render(text, props) { + if (!text) return null; + + try { + return ; + } catch (err) { + console.error('渲染失败:', err); + return
Markdown 渲染错误:{err.message}
; + } + } + + /** + * 渲染可编辑的 Markdown 组件 + * @param {object} props - 编辑器属性 + * @returns {JSX.Element} + */ + edit(props) { + try { + return ; + } catch (err) { + return
Markdown 编辑器加载错误:{err.message}
; + } + } +} diff --git a/packages/core/client/src/flow/common/Markdown/style.ts b/packages/core/client/src/flow/common/Markdown/style.ts new file mode 100644 index 00000000000..c9b4d6b1a53 --- /dev/null +++ b/packages/core/client/src/flow/common/Markdown/style.ts @@ -0,0 +1,27 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { genStyleHook } from '../../../schema-component/antd/__builtins__'; + +export default genStyleHook('nb-markdown-vditor', (token) => { + const { componentCls } = token; + + return { + [componentCls]: { + '.vditor-reset': { fontSize: `${token.fontSize}px !important`, color: 'unset', padding: `10px !important` }, + '.vditor': { borderRadius: 8 }, + '.vditor .vditor-content': { borderRadius: '0 0 8px 8px', overflow: 'hidden' }, + '.vditor .vditor-toolbar': { paddingLeft: ' 16px !important', borderRadius: '8px 8px 0 0' }, + '.vditor .vditor-content .vditor-ir .vditor-reset': { paddingLeft: ' 16px !important' }, + '.vditor-ir pre.vditor-reset': { + backgroundColor: `${token.colorBgContainer}!important`, + }, + }, + }; +}); diff --git a/packages/core/client/src/flow/common/Markdown/useCDN.ts b/packages/core/client/src/flow/common/Markdown/useCDN.ts new file mode 100644 index 00000000000..46700cca14d --- /dev/null +++ b/packages/core/client/src/flow/common/Markdown/useCDN.ts @@ -0,0 +1,22 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { usePlugin } from '@nocobase/client'; +import { useFlowEngine } from '@nocobase/flow-engine'; + +export const useCDN = () => { + const flowEngine = useFlowEngine(); + const app = flowEngine.context.app; + const plugin: any = usePlugin('@nocobase/plugin-block-markdown'); + if (!plugin.dependencyLoaded) { + plugin.initVditorDependency(app); + plugin.dependencyLoaded = true; + } + return plugin.getCDN(app); +}; diff --git a/packages/core/client/src/flow/index.ts b/packages/core/client/src/flow/index.ts index 97e8b8d1a4a..438ddbe1d7b 100644 --- a/packages/core/client/src/flow/index.ts +++ b/packages/core/client/src/flow/index.ts @@ -17,6 +17,8 @@ import { FlowRoute } from './FlowPage'; import * as models from './models'; import * as filterFormActions from './models/blocks/filter-manager/flow-actions'; import { DynamicFlowsIcon } from './components/DynamicFlowsIcon'; +import { Markdown } from './common/Markdown/Markdown'; +import { LiquidEngine } from './common/Liquid'; export class PluginFlowEngine extends Plugin { async load() { @@ -44,6 +46,19 @@ export class PluginFlowEngine extends Plugin { }, sort: 0, }); + // 实例化一个全局 Markdown 解析器 + const markdownInstance = new Markdown(); + this.flowEngine.context.defineProperty('markdown', { + get: () => markdownInstance, + }); + + // 创建全局实例 + const liquidInstance = new LiquidEngine(); + + // 注册到全局上下文 + this.flowEngine.context.defineProperty('liquid', { + get: () => liquidInstance, + }); } } diff --git a/packages/core/client/src/flow/internal/components/Markdown/DisplayMarkdown.tsx b/packages/core/client/src/flow/internal/components/Markdown/DisplayMarkdown.tsx index 18349fc2077..4ce6a638ebd 100644 --- a/packages/core/client/src/flow/internal/components/Markdown/DisplayMarkdown.tsx +++ b/packages/core/client/src/flow/internal/components/Markdown/DisplayMarkdown.tsx @@ -7,22 +7,79 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import React from 'react'; +import React, { useRef, useState, useEffect } from 'react'; import { useParseMarkdown, convertToText } from './util'; import { useMarkdownStyles } from './style'; +import { Tooltip } from 'antd'; export const DisplayMarkdown = (props) => { - const { textOnly } = props; + const { textOnly, overflowMode, style } = props; const markdownClass = useMarkdownStyles(); const { html = '' } = useParseMarkdown(props.value); - const text = convertToText(html); - const value = ( + const text: any = convertToText(html); + const isEllipsis = overflowMode === 'ellipsis'; + + const contentRef = useRef(null); + const [isOverflowed, setIsOverflowed] = useState(false); + + // 检测内容是否溢出 + useEffect(() => { + if (contentRef.current) { + const el = contentRef.current; + const overflowed = el.scrollWidth > el.clientWidth || el.scrollHeight > el.clientHeight; + setIsOverflowed(overflowed); + } + }, [html, style, overflowMode]); + + // 通用样式(用于 ellipsis 模式) + const baseStyle: React.CSSProperties = { + ...(style || {}), + ...(isEllipsis + ? { + display: '-webkit-box', + WebkitLineClamp: 1, // 可改为多行省略 + WebkitBoxOrient: 'vertical', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'normal', + wordBreak: 'break-word', + cursor: 'default', + } + : {}), + }; + + const content = (
); - return <>{textOnly ? text : value}; + return isEllipsis && isOverflowed ? ( + + } + overlayInnerStyle={{ + background: '#fff', + color: '#000', + boxShadow: '0 2px 8px rgba(0,0,0,0.15)', + }} + color="#fff" + > + {content} + + ) : ( + content + ); }; diff --git a/packages/core/client/src/flow/models/fields/DisplayTitleFieldModel.tsx b/packages/core/client/src/flow/models/fields/DisplayTitleFieldModel.tsx index 4e1b45476b0..ad7c89bc55f 100644 --- a/packages/core/client/src/flow/models/fields/DisplayTitleFieldModel.tsx +++ b/packages/core/client/src/flow/models/fields/DisplayTitleFieldModel.tsx @@ -37,6 +37,9 @@ export class DisplayTitleFieldModel extends FieldModel { rootClassName: css` .ant-tooltip-inner { color: #000; + max-height: 500px; + overflow-y: auto; + padding: 10px; } `, color: '#fff', diff --git a/packages/plugins/@nocobase/plugin-block-markdown/.npmignore b/packages/plugins/@nocobase/plugin-block-markdown/.npmignore new file mode 100644 index 00000000000..65f5e8779f4 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-block-markdown/.npmignore @@ -0,0 +1,2 @@ +/node_modules +/src diff --git a/packages/plugins/@nocobase/plugin-block-markdown/README.md b/packages/plugins/@nocobase/plugin-block-markdown/README.md new file mode 100644 index 00000000000..82e5b08814d --- /dev/null +++ b/packages/plugins/@nocobase/plugin-block-markdown/README.md @@ -0,0 +1 @@ +# @nocobase/plugin-block-markdown diff --git a/packages/plugins/@nocobase/plugin-block-markdown/build.config.ts b/packages/plugins/@nocobase/plugin-block-markdown/build.config.ts new file mode 100644 index 00000000000..696749bedfd --- /dev/null +++ b/packages/plugins/@nocobase/plugin-block-markdown/build.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from '@nocobase/build'; +import fs from 'fs/promises'; +import path from 'path'; + +const vditor = path.dirname(require.resolve('vditor')); + +export default defineConfig({ + afterBuild: async (log) => { + log('coping vditor dist'); + await fs.cp(vditor, path.resolve(__dirname, 'dist/client/vditor/dist'), { + recursive: true, + force: true, + }); + }, +}); diff --git a/packages/plugins/@nocobase/plugin-block-markdown/client.d.ts b/packages/plugins/@nocobase/plugin-block-markdown/client.d.ts new file mode 100644 index 00000000000..6c459cbac4c --- /dev/null +++ b/packages/plugins/@nocobase/plugin-block-markdown/client.d.ts @@ -0,0 +1,2 @@ +export * from './dist/client'; +export { default } from './dist/client'; diff --git a/packages/plugins/@nocobase/plugin-block-markdown/client.js b/packages/plugins/@nocobase/plugin-block-markdown/client.js new file mode 100644 index 00000000000..b6e3be70e63 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-block-markdown/client.js @@ -0,0 +1 @@ +module.exports = require('./dist/client/index.js'); diff --git a/packages/plugins/@nocobase/plugin-block-markdown/package.json b/packages/plugins/@nocobase/plugin-block-markdown/package.json new file mode 100644 index 00000000000..b1fff3892e2 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-block-markdown/package.json @@ -0,0 +1,21 @@ +{ + "name": "@nocobase/plugin-block-markdown", + "version": "2.0.0-alpha.21", + "main": "dist/server/index.js", + "displayName": "Block: Markdown", + "displayName.zh-CN": "Markdown", + "description": "Provide Markdown block", + "description.zh-CN": "提供Markdown 区块", + "license": "AGPL-3.0", + "homepage": "https://docs.nocobase.com/handbook/block-markdown", + "homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/block-markdown", + "dependencies": {}, + "peerDependencies": { + "@nocobase/client": "1.x", + "@nocobase/server": "1.x", + "@nocobase/test": "1.x" + }, + "keywords": [ + "Blocks" + ] +} diff --git a/packages/plugins/@nocobase/plugin-block-markdown/server.d.ts b/packages/plugins/@nocobase/plugin-block-markdown/server.d.ts new file mode 100644 index 00000000000..c41081ddc6d --- /dev/null +++ b/packages/plugins/@nocobase/plugin-block-markdown/server.d.ts @@ -0,0 +1,2 @@ +export * from './dist/server'; +export { default } from './dist/server'; diff --git a/packages/plugins/@nocobase/plugin-block-markdown/server.js b/packages/plugins/@nocobase/plugin-block-markdown/server.js new file mode 100644 index 00000000000..972842039a6 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-block-markdown/server.js @@ -0,0 +1 @@ +module.exports = require('./dist/server/index.js'); diff --git a/packages/plugins/@nocobase/plugin-block-markdown/src/client/client.d.ts b/packages/plugins/@nocobase/plugin-block-markdown/src/client/client.d.ts new file mode 100644 index 00000000000..4e96f83fa12 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-block-markdown/src/client/client.d.ts @@ -0,0 +1,249 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +// CSS modules +type CSSModuleClasses = { readonly [key: string]: string }; + +declare module '*.module.css' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.scss' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.sass' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.less' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.styl' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.stylus' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.pcss' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.sss' { + const classes: CSSModuleClasses; + export default classes; +} + +// CSS +declare module '*.css' { } +declare module '*.scss' { } +declare module '*.sass' { } +declare module '*.less' { } +declare module '*.styl' { } +declare module '*.stylus' { } +declare module '*.pcss' { } +declare module '*.sss' { } + +// Built-in asset types +// see `src/node/constants.ts` + +// images +declare module '*.apng' { + const src: string; + export default src; +} +declare module '*.png' { + const src: string; + export default src; +} +declare module '*.jpg' { + const src: string; + export default src; +} +declare module '*.jpeg' { + const src: string; + export default src; +} +declare module '*.jfif' { + const src: string; + export default src; +} +declare module '*.pjpeg' { + const src: string; + export default src; +} +declare module '*.pjp' { + const src: string; + export default src; +} +declare module '*.gif' { + const src: string; + export default src; +} +declare module '*.svg' { + const src: string; + export default src; +} +declare module '*.ico' { + const src: string; + export default src; +} +declare module '*.webp' { + const src: string; + export default src; +} +declare module '*.avif' { + const src: string; + export default src; +} + +// media +declare module '*.mp4' { + const src: string; + export default src; +} +declare module '*.webm' { + const src: string; + export default src; +} +declare module '*.ogg' { + const src: string; + export default src; +} +declare module '*.mp3' { + const src: string; + export default src; +} +declare module '*.wav' { + const src: string; + export default src; +} +declare module '*.flac' { + const src: string; + export default src; +} +declare module '*.aac' { + const src: string; + export default src; +} +declare module '*.opus' { + const src: string; + export default src; +} +declare module '*.mov' { + const src: string; + export default src; +} +declare module '*.m4a' { + const src: string; + export default src; +} +declare module '*.vtt' { + const src: string; + export default src; +} + +// fonts +declare module '*.woff' { + const src: string; + export default src; +} +declare module '*.woff2' { + const src: string; + export default src; +} +declare module '*.eot' { + const src: string; + export default src; +} +declare module '*.ttf' { + const src: string; + export default src; +} +declare module '*.otf' { + const src: string; + export default src; +} + +// other +declare module '*.webmanifest' { + const src: string; + export default src; +} +declare module '*.pdf' { + const src: string; + export default src; +} +declare module '*.txt' { + const src: string; + export default src; +} + +// wasm?init +declare module '*.wasm?init' { + const initWasm: (options?: WebAssembly.Imports) => Promise; + export default initWasm; +} + +// web worker +declare module '*?worker' { + const workerConstructor: { + new(options?: { name?: string }): Worker; + }; + export default workerConstructor; +} + +declare module '*?worker&inline' { + const workerConstructor: { + new(options?: { name?: string }): Worker; + }; + export default workerConstructor; +} + +declare module '*?worker&url' { + const src: string; + export default src; +} + +declare module '*?sharedworker' { + const sharedWorkerConstructor: { + new(options?: { name?: string }): SharedWorker; + }; + export default sharedWorkerConstructor; +} + +declare module '*?sharedworker&inline' { + const sharedWorkerConstructor: { + new(options?: { name?: string }): SharedWorker; + }; + export default sharedWorkerConstructor; +} + +declare module '*?sharedworker&url' { + const src: string; + export default src; +} + +declare module '*?raw' { + const src: string; + export default src; +} + +declare module '*?url' { + const src: string; + export default src; +} + +declare module '*?inline' { + const src: string; + export default src; +} diff --git a/packages/plugins/@nocobase/plugin-block-markdown/src/client/index.tsx b/packages/plugins/@nocobase/plugin-block-markdown/src/client/index.tsx new file mode 100644 index 00000000000..be989de7c3c --- /dev/null +++ b/packages/plugins/@nocobase/plugin-block-markdown/src/client/index.tsx @@ -0,0 +1,10 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export { default } from './plugin'; diff --git a/packages/plugins/@nocobase/plugin-block-markdown/src/client/locale.ts b/packages/plugins/@nocobase/plugin-block-markdown/src/client/locale.ts new file mode 100644 index 00000000000..9f256d1b5c8 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-block-markdown/src/client/locale.ts @@ -0,0 +1,21 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { tExpr as _tExpr, useFlowEngine } from '@nocobase/flow-engine'; +// @ts-ignore +import pkg from './../../package.json'; + +export function useT() { + const engine = useFlowEngine(); + return (str: string) => engine.context.t(str, { ns: [pkg.name, 'client'] }); +} + +export function tExpr(key: string) { + return _tExpr(key, { ns: [pkg.name, 'client'] }); +} diff --git a/packages/plugins/@nocobase/plugin-block-markdown/src/client/models/MarkdownBlockModel.tsx b/packages/plugins/@nocobase/plugin-block-markdown/src/client/models/MarkdownBlockModel.tsx new file mode 100644 index 00000000000..8f9b1c970cc --- /dev/null +++ b/packages/plugins/@nocobase/plugin-block-markdown/src/client/models/MarkdownBlockModel.tsx @@ -0,0 +1,85 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { BlockModel, css } from '@nocobase/client'; +import { escapeT } from '@nocobase/flow-engine'; +import { Card } from 'antd'; +import React from 'react'; + +export class MarkdownBlockModel extends BlockModel { + render() { + const { content } = this.props; + return ( + + {content} + + ); + } +} + +MarkdownBlockModel.define({ + label: escapeT('Markdown'), +}); + +MarkdownBlockModel.registerFlow({ + key: 'markdownBlockSettings', + title: escapeT('Markdown block settings', { ns: 'block-markdown' }), + steps: { + editMarkdown: { + title: escapeT('Edit markdown'), + uiSchema(ctx) { + const t = ctx.t; + const descriptionContent = ( + <> + + {t('Syntax references')}: + + + Liquid + + + ); + + return { + content: { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': (props) => ctx.markdown.edit(props), + description: descriptionContent, + }, + }; + }, + useRawParams: true, + defaultParams: { + content: "{{ 'This is a demo text, **supports Markdown syntax**.' | t: locale, i18n }}", + }, + async handler(ctx, params) { + const content = params.content; + try { + const result = await ctx.liquid.renderWithFullContext(content, ctx); + // 解析 Markdown + const mdContent = ctx.markdown.render(ctx.t(result), { textOnly: false }); + ctx.model.setProps({ + content: mdContent, + }); + } catch (error) { + ctx.model.setProps({ + content:
{`渲染失败: ${error}`}
, + }); + } + }, + }, + }, +}); diff --git a/packages/plugins/@nocobase/plugin-block-markdown/src/client/models/index.ts b/packages/plugins/@nocobase/plugin-block-markdown/src/client/models/index.ts new file mode 100644 index 00000000000..4adedaed0be --- /dev/null +++ b/packages/plugins/@nocobase/plugin-block-markdown/src/client/models/index.ts @@ -0,0 +1,12 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { MarkdownBlockModel } from './MarkdownBlockModel'; + +export { MarkdownBlockModel }; diff --git a/packages/plugins/@nocobase/plugin-block-markdown/src/client/plugin.tsx b/packages/plugins/@nocobase/plugin-block-markdown/src/client/plugin.tsx new file mode 100644 index 00000000000..6f8020abfb8 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-block-markdown/src/client/plugin.tsx @@ -0,0 +1,52 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Plugin } from '@nocobase/client'; +import { MarkdownBlockModel } from './models'; + +export class PluginBlockMarkdownClient extends Plugin { + async load() { + this.flowEngine.registerModels({ + MarkdownBlockModel, + }); + } + + getCDN() { + return this.app.getPublicPath() + 'static/plugins/@nocobase/plugin-block-markdown/dist/client/vditor'; + } + + initVditorDependency() { + const cdn = this.getCDN(); + try { + const vditorDepdencePrefix = 'plugin-block-markdown-dep'; + const vditorDepdence = { + [`${vditorDepdencePrefix}.katex`]: `${cdn}/dist/js/katex/katex.min.js?v=0.16.9`, + [`${vditorDepdencePrefix}.ABCJS`]: `${cdn}/dist/js/abcjs/abcjs_basic.min`, + [`${vditorDepdencePrefix}.plantumlEncoder`]: `${cdn}/dist/js/plantuml/plantuml-encoder.min`, + [`${vditorDepdencePrefix}.echarts`]: `${cdn}/dist/js/echarts/echarts.min`, + [`${vditorDepdencePrefix}.flowchart`]: `${cdn}/dist/js/flowchart.js/flowchart.min`, + [`${vditorDepdencePrefix}.Viz`]: `${cdn}/dist/js/graphviz/viz`, + [`${vditorDepdencePrefix}.mermaid`]: `${cdn}/dist/js/mermaid/mermaid.min`, + }; + this.app.requirejs.require.config({ + waitSeconds: 120, + paths: vditorDepdence, + }); + Object.keys(vditorDepdence).forEach((key) => { + this.app.requirejs.require([key], (m) => { + window[key.split('.')[1]] = m; + }); + }); + } catch (e) { + console.log('initVditorDependency failed', e); + } + } +} + +export default PluginBlockMarkdownClient; diff --git a/packages/plugins/@nocobase/plugin-block-markdown/src/index.ts b/packages/plugins/@nocobase/plugin-block-markdown/src/index.ts new file mode 100644 index 00000000000..be99a2ff1ae --- /dev/null +++ b/packages/plugins/@nocobase/plugin-block-markdown/src/index.ts @@ -0,0 +1,11 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export * from './server'; +export { default } from './server'; diff --git a/packages/plugins/@nocobase/plugin-block-markdown/src/locale/en-US.json b/packages/plugins/@nocobase/plugin-block-markdown/src/locale/en-US.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/packages/plugins/@nocobase/plugin-block-markdown/src/locale/en-US.json @@ -0,0 +1 @@ +{} diff --git a/packages/plugins/@nocobase/plugin-block-markdown/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-block-markdown/src/locale/zh-CN.json new file mode 100644 index 00000000000..6bcef7d1171 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-block-markdown/src/locale/zh-CN.json @@ -0,0 +1 @@ +{ "Markdown block settings": "Markdown 区块设置" } diff --git a/packages/plugins/@nocobase/plugin-block-markdown/src/server/collections/.gitkeep b/packages/plugins/@nocobase/plugin-block-markdown/src/server/collections/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/plugins/@nocobase/plugin-block-markdown/src/server/index.ts b/packages/plugins/@nocobase/plugin-block-markdown/src/server/index.ts new file mode 100644 index 00000000000..be989de7c3c --- /dev/null +++ b/packages/plugins/@nocobase/plugin-block-markdown/src/server/index.ts @@ -0,0 +1,10 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +export { default } from './plugin'; diff --git a/packages/plugins/@nocobase/plugin-block-markdown/src/server/plugin.ts b/packages/plugins/@nocobase/plugin-block-markdown/src/server/plugin.ts new file mode 100644 index 00000000000..b001ccdf15a --- /dev/null +++ b/packages/plugins/@nocobase/plugin-block-markdown/src/server/plugin.ts @@ -0,0 +1,28 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { Plugin } from '@nocobase/server'; + +export class PluginBlockMarkdownServer extends Plugin { + async afterAdd() {} + + async beforeLoad() {} + + async load() {} + + async install() {} + + async afterEnable() {} + + async afterDisable() {} + + async remove() {} +} + +export default PluginBlockMarkdownServer; diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/DisplayVditorFieldModel.tsx b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/DisplayVditorFieldModel.tsx deleted file mode 100644 index 15e1094be3a..00000000000 --- a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/DisplayVditorFieldModel.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -import React from 'react'; -import { DisplayItemModel, escapeT } from '@nocobase/flow-engine'; -import { DisplayTitleFieldModel, tval } from '@nocobase/client'; -import { Display } from './components/Display'; - -export class DisplayVditorFieldModel extends DisplayTitleFieldModel { - public renderComponent(value) { - if (!value) { - return; - } - return ; - } -} -DisplayVditorFieldModel.define({ - label: escapeT('MarkdownVditor'), -}); - -DisplayVditorFieldModel.registerFlow({ - key: 'markdownVditorSettings', - title: tval('Content settings'), - sort: 200, - steps: { - renderMode: { - use: 'renderMode', - }, - }, -}); - -DisplayItemModel.bindModelToInterface('DisplayVditorFieldModel', ['vditor'], { isDefault: true }); diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/index.tsx b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/index.tsx index 0734434d0dc..84d88b6aac6 100644 --- a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/index.tsx +++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/index.tsx @@ -9,10 +9,9 @@ import { Plugin } from '@nocobase/client'; import 'vditor/dist/index.css'; -// import { MarkdownVditor } from './components'; import { lazy } from '@nocobase/client'; -import { VditorFieldModel } from './VditorFieldModel'; -import { DisplayVditorFieldModel } from './DisplayVditorFieldModel'; +import { VditorFieldModel } from './models/VditorFieldModel'; +import { DisplayVditorFieldModel } from './models/DisplayVditorFieldModel'; const { MarkdownVditor } = lazy(() => import('./components'), 'MarkdownVditor'); import { MarkdownVditorFieldInterface } from './interfaces/markdown-vditor'; diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/models/DisplayVditorFieldModel.tsx b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/models/DisplayVditorFieldModel.tsx new file mode 100644 index 00000000000..e1ca1cc8326 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/models/DisplayVditorFieldModel.tsx @@ -0,0 +1,68 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { DisplayItemModel, escapeT } from '@nocobase/flow-engine'; +import { DisplayTitleFieldModel, tval } from '@nocobase/client'; +import React, { useState, useEffect } from 'react'; + +const Display = ({ value, markdown, liquid, t, textOnly, ctx, overflowMode }) => { + const [content, setContent] = useState(null); + + useEffect(() => { + if (!value) return; + + (async () => { + try { + const result = await liquid.renderWithFullContext(value, ctx); + const html = markdown.render(t(result), { ellipsis: overflowMode === 'ellipsis', textOnly }); + setContent(html); + } catch (err) { + setContent(`
 渲染错误: ${err.message}
`); + } + })(); + }, [value, textOnly, overflowMode]); + + return content; +}; + +export class DisplayVditorFieldModel extends DisplayTitleFieldModel { + public renderComponent(value) { + if (!value) return null; + const { markdown, liquid, t } = this.context; + const { textOnly, overflowMode } = this.props; + return ( + + ); + } +} + +DisplayVditorFieldModel.define({ + label: escapeT('MarkdownVditor'), +}); + +DisplayVditorFieldModel.registerFlow({ + key: 'markdownVditorSettings', + title: tval('Content settings'), + sort: 200, + steps: { + renderMode: { + use: 'renderMode', + }, + }, +}); + +DisplayItemModel.bindModelToInterface('DisplayVditorFieldModel', ['vditor'], { isDefault: true }); diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/VditorFieldModel.tsx b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/models/VditorFieldModel.tsx similarity index 84% rename from packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/VditorFieldModel.tsx rename to packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/models/VditorFieldModel.tsx index 7a93ae2b033..ad12acf26a3 100644 --- a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/VditorFieldModel.tsx +++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/models/VditorFieldModel.tsx @@ -7,13 +7,13 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ import { FieldModel } from '@nocobase/client'; -import React from 'react'; import { largeField, EditableItemModel } from '@nocobase/flow-engine'; -import { Edit } from './components/Edit'; @largeField() export class VditorFieldModel extends FieldModel { render() { - return ; + const markdown = this.context.markdown; + + return markdown.edit(this.props); } } EditableItemModel.bindModelToInterface('VditorFieldModel', ['vditor'], { isDefault: true }); diff --git a/packages/presets/nocobase/package.json b/packages/presets/nocobase/package.json index 874928cd76b..394dcb4f9e9 100644 --- a/packages/presets/nocobase/package.json +++ b/packages/presets/nocobase/package.json @@ -24,6 +24,7 @@ "@nocobase/plugin-block-grid-card": "2.0.0-alpha.21", "@nocobase/plugin-block-iframe": "2.0.0-alpha.21", "@nocobase/plugin-block-list": "2.0.0-alpha.21", + "@nocobase/plugin-block-markdown": "2.0.0-alpha.21", "@nocobase/plugin-block-reference": "2.0.0-alpha.21", "@nocobase/plugin-block-template": "2.0.0-alpha.21", "@nocobase/plugin-block-workbench": "2.0.0-alpha.21", @@ -113,6 +114,7 @@ "@nocobase/plugin-block-workbench", "@nocobase/plugin-block-list", "@nocobase/plugin-block-grid-card", + "@nocobase/plugin-block-markdown", "@nocobase/plugin-calendar", "@nocobase/plugin-client", "@nocobase/plugin-collection-sql",