From 22758560ef3498505de156ce561e94a3f4b9c3cc Mon Sep 17 00:00:00 2001 From: rojer Date: Wed, 9 Aug 2023 19:15:28 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20:boom:=20=E9=A2=98=E7=9B=AE=E7=BC=96?= =?UTF-8?q?=E6=8E=92&latex=E6=94=AF=E6=8C=81&=E4=BF=AE=E5=A4=8Dlink?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .dumirc.ts | 1 + docs/demos/math.tsx | 114 ++++++++++ docs/getting-started/math.md | 17 ++ packages/antd/lib/components/index.tsx | 13 +- packages/antd/lib/components/input.tsx | 19 ++ packages/antd/lib/components/select.tsx | 5 +- packages/antd/lib/components/textarea.tsx | 19 ++ packages/antd/lib/index.tsx | 7 +- .../component/lib/components/editable.tsx | 57 ++--- packages/component/lib/components/index.tsx | 21 +- .../component/lib/components/textarea.tsx | 28 +++ packages/component/lib/element.tsx | 4 +- packages/core/lib/components/core.tsx | 43 +++- packages/core/lib/typing.ts | 1 + packages/plugin/lib/index.tsx | 26 ++- packages/plugin/lib/plugins/blockquote.tsx | 11 + packages/plugin/lib/plugins/hr.tsx | 36 ++- packages/plugin/lib/plugins/img/img.tsx | 87 +++++++- packages/plugin/lib/plugins/img/index.tsx | 45 ++++ packages/plugin/lib/plugins/latex.tsx | 207 ++++++++++++++++++ packages/plugin/lib/plugins/link.tsx | 11 + packages/plugin/lib/plugins/list.tsx | 58 +++++ packages/plugin/lib/plugins/paragraph.tsx | 33 +++ packages/plugin/lib/plugins/todo-list.tsx | 10 + packages/plugin/package.json | 3 + packages/semi/lib/index.tsx | 4 +- pnpm-lock.yaml | 45 +++- 27 files changed, 833 insertions(+), 92 deletions(-) create mode 100644 docs/demos/math.tsx create mode 100644 docs/getting-started/math.md create mode 100644 packages/antd/lib/components/input.tsx create mode 100644 packages/antd/lib/components/textarea.tsx create mode 100644 packages/component/lib/components/textarea.tsx create mode 100755 packages/plugin/lib/plugins/latex.tsx diff --git a/.dumirc.ts b/.dumirc.ts index 81c140c..765c435 100644 --- a/.dumirc.ts +++ b/.dumirc.ts @@ -11,6 +11,7 @@ export default defineConfig({ github: 'https://github.com/rojer95/dslate', }, }, + mfsu: false, monorepoRedirect: { srcDir: ['packages'], peerDeps: true, diff --git a/docs/demos/math.tsx b/docs/demos/math.tsx new file mode 100644 index 0000000..0988753 --- /dev/null +++ b/docs/demos/math.tsx @@ -0,0 +1,114 @@ +/** + * defaultShowCode: true + */ +import DSlate, { DefaultToolbar, DSlateRef } from '@dslate/antd'; +import { Button, ConfigProvider, Input, Space, theme } from 'antd'; +import React, { useEffect, useRef, useState } from 'react'; +import type { Descendant } from 'slate'; + +export default () => { + const [resultWeapp, setResultWeapp] = useState(); + const [resultHtml, setResultHtml] = useState(); + const [value, setValue] = useState([ + { + type: 'paragraph', + children: [ + { text: '当' }, + { type: 'latex', children: [{ text: '' }], formula: 'b=1' }, + { text: '、' }, + { type: 'latex', children: [{ text: '' }], formula: 'c=2' }, + { text: '时,请计算' }, + { type: 'latex', children: [{ text: '' }], formula: 'a=b+c' }, + { text: '的结果?' }, + ], + }, + ]); + + const ref = useRef(null); + const [mode, setMode] = useState( + document.documentElement.getAttribute('data-prefers-color') ?? 'light', + ); + + const switchMode = () => { + const doc = document.documentElement; + const attrName = 'data-prefers-color'; + if (!doc.hasAttribute(attrName) || doc.getAttribute(attrName) === 'dark') { + doc.setAttribute(attrName, 'light'); + setMode('light'); + } else { + doc.setAttribute(attrName, 'dark'); + setMode('dark'); + } + }; + + const watch = (mutationsList: any[]) => { + const attrName = 'data-prefers-color'; + for (const mutation of mutationsList) { + if (mutation.attributeName === attrName) { + setMode(document.documentElement.getAttribute(attrName) ?? 'light'); + } + } + }; + + useEffect(() => { + let observer: any; + if (MutationObserver) { + observer = new MutationObserver(watch); + // 以上述配置开始观察目标节点 + observer.observe(document.documentElement, { attributes: true }); + // 之后,可停止观察 + } + return () => observer?.disconnect?.(); + }, []); + + return ( + +
+ +
+ + + + +
+

小程序结果:

+ + +
+

html结果:

+ +
+
+ ); +}; diff --git a/docs/getting-started/math.md b/docs/getting-started/math.md new file mode 100644 index 0000000..746e329 --- /dev/null +++ b/docs/getting-started/math.md @@ -0,0 +1,17 @@ +--- +title: 题目编排 +order: 7 +nav: 文档 +--- + +# 题目编排 + +基于 DSlate 的 Antd 风格包,支持 html 渲染与小程序 RichNode 渲染模式 + +> 小程序请手动引入 katex-mini 包的 css ,具体请看链接:https://github.com/rojer95/katex-mini + +> Antd 版本要求:antd >= 5.0 + +## Demo + + diff --git a/packages/antd/lib/components/index.tsx b/packages/antd/lib/components/index.tsx index 435a6a2..ad8cc35 100644 --- a/packages/antd/lib/components/index.tsx +++ b/packages/antd/lib/components/index.tsx @@ -1,6 +1,7 @@ -export * from "./editor"; -export * from "./button"; -export * from "./select"; -export * from "./divider"; -export * from "./tooltip"; -export * from "./popover"; +export * from './button'; +export * from './divider'; +export * from './editor'; +export * from './input'; +export * from './popover'; +export * from './select'; +export * from './tooltip'; diff --git a/packages/antd/lib/components/input.tsx b/packages/antd/lib/components/input.tsx new file mode 100644 index 0000000..c742fef --- /dev/null +++ b/packages/antd/lib/components/input.tsx @@ -0,0 +1,19 @@ +import { Input as AntdInput } from 'antd'; +import { PropsWithChildren } from 'react'; + +export const Input = ({ + children, + onChange, + ...props +}: PropsWithChildren) => { + return ( + { + onChange?.(e.target.value); + }} + > + {children} + + ); +}; diff --git a/packages/antd/lib/components/select.tsx b/packages/antd/lib/components/select.tsx index 580ca1b..b6cf4d8 100644 --- a/packages/antd/lib/components/select.tsx +++ b/packages/antd/lib/components/select.tsx @@ -1,5 +1,6 @@ -import React, { PropsWithChildren } from "react"; -import { Select as AntdSelect } from "antd"; +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { Select as AntdSelect } from 'antd'; +import { PropsWithChildren } from 'react'; export const Select = ({ children, diff --git a/packages/antd/lib/components/textarea.tsx b/packages/antd/lib/components/textarea.tsx new file mode 100644 index 0000000..924a19f --- /dev/null +++ b/packages/antd/lib/components/textarea.tsx @@ -0,0 +1,19 @@ +import { Input as AntdInput } from 'antd'; +import { PropsWithChildren } from 'react'; + +export const Textarea = ({ + children, + onChange, + ...props +}: PropsWithChildren) => { + return ( + { + onChange?.(e.target.value); + }} + > + {children} + + ); +}; diff --git a/packages/antd/lib/index.tsx b/packages/antd/lib/index.tsx index 3c449fc..fc6e26b 100755 --- a/packages/antd/lib/index.tsx +++ b/packages/antd/lib/index.tsx @@ -10,11 +10,12 @@ import { registerElement } from '@dslate/component'; import DefaultPlugin from '@dslate/plugin'; -import { Input, InputNumber, Progress } from 'antd'; +import { InputNumber, Progress } from 'antd'; import { AntdEditor, Button, Divider, + Input, Popover, Select, Tooltip, @@ -22,6 +23,7 @@ import { import type { AntdStyleDSlateProps } from './typing'; +import { Textarea } from './components/textarea'; import EN_US from './locale/en_US'; import ZH_CN from './locale/zh_CN'; @@ -33,6 +35,7 @@ registerElement('input', Input); registerElement('input-number', InputNumber); registerElement('button', Button); registerElement('select', Select); +registerElement('textarea', Textarea); export const DefaultLocales = [ZH_CN, EN_US]; @@ -81,7 +84,7 @@ export default forwardRef( locales: mergeLocalteFromPlugins(locales, plugins), plugins, iconScriptUrl: - '//at.alicdn.com/t/c/font_3062978_atuqwazgoap.js', + '//at.alicdn.com/t/c/font_3062978_igshjiflyft.js', }} > diff --git a/packages/component/lib/components/editable.tsx b/packages/component/lib/components/editable.tsx index 197ed94..a8a6f71 100755 --- a/packages/component/lib/components/editable.tsx +++ b/packages/component/lib/components/editable.tsx @@ -1,20 +1,20 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import React, { useCallback } from "react"; +import { + mergeStyle, + PluginUuidContext, + useConfig, + useMessage, + usePluginHelper, +} from '@dslate/core'; +import React, { useCallback } from 'react'; import { DefaultElement, Editable as SlateEditable, RenderLeafProps, useSlate, -} from "slate-react"; -import { - PluginUuidContext, - usePluginHelper, - mergeStyle, - useConfig, - useMessage, -} from "@dslate/core"; +} from 'slate-react'; -import type { DSlatePlugin, RenderElementPropsWithStyle } from "@dslate/core"; +import type { DSlatePlugin, RenderElementPropsWithStyle } from '@dslate/core'; interface EditableProps { disabled?: boolean; @@ -33,10 +33,10 @@ const Editable = ({ const getMessage = useMessage(); const renderElement = useCallback((props: RenderElementPropsWithStyle) => { - const style = mergeStyle(props.element, plugins, "element", editor); + const style = mergeStyle(props.element, plugins, 'element', editor); const plugin = plugins.find( (i: DSlatePlugin) => - i.nodeType === "element" && i.type === props.element.type + i.nodeType === 'element' && i.type === props.element.type, ) as DSlatePlugin | undefined; let dom; @@ -53,7 +53,7 @@ const Editable = ({ ); } else { const defaultElementPlugin = plugins.find( - (p: DSlatePlugin) => p.isDefaultElement + (p: DSlatePlugin) => p.isDefaultElement, ); if (defaultElementPlugin && defaultElementPlugin.renderElement) { @@ -77,10 +77,10 @@ const Editable = ({ const { attributes, children, leaf } = props; const needRenderPlugin = plugins.find( (i: DSlatePlugin) => - i.nodeType === "text" && i.type in leaf && !!i.renderLeaf + i.nodeType === 'text' && i.type in leaf && !!i.renderLeaf, ) as DSlatePlugin | undefined; - const style = mergeStyle(leaf, plugins, "text", editor); + const style = mergeStyle(leaf, plugins, 'text', editor); if (needRenderPlugin && needRenderPlugin.renderLeaf) { return needRenderPlugin.renderLeaf({ ...props, style }, editor); @@ -95,24 +95,27 @@ const Editable = ({ const onKeyDown = useCallback((e: React.KeyboardEvent) => { for (const plugin of plugins) { - if (typeof plugin.onKeyDown === "function") { + if (typeof plugin.onKeyDown === 'function') { plugin.onKeyDown(e, editor); } } }, []); return ( - { - setVisibleKey?.(undefined); - }} - onKeyDown={onKeyDown} - readOnly={disabled} - placeholder={placeholder ?? getMessage("placeholder", "")} - /> + <> + { + setVisibleKey?.(undefined); + }} + onKeyDown={onKeyDown} + readOnly={disabled} + placeholder={placeholder ?? getMessage('placeholder', '')} + /> +
+ ); }; diff --git a/packages/component/lib/components/index.tsx b/packages/component/lib/components/index.tsx index 0e9ccb8..8a72360 100644 --- a/packages/component/lib/components/index.tsx +++ b/packages/component/lib/components/index.tsx @@ -1,10 +1,11 @@ -export * from "./counter"; -export * from "./divider"; -export * from "./input-number"; -export * from "./input"; -export * from "./popover"; -export * from "./progress"; -export * from "./tooltip"; -export * from "./icon"; -export * from "./toolbar"; -export * from "./editable"; +export * from './counter'; +export * from './divider'; +export * from './editable'; +export * from './icon'; +export * from './input'; +export * from './input-number'; +export * from './popover'; +export * from './progress'; +export * from './textarea'; +export * from './toolbar'; +export * from './tooltip'; diff --git a/packages/component/lib/components/textarea.tsx b/packages/component/lib/components/textarea.tsx new file mode 100644 index 0000000..1f4ff19 --- /dev/null +++ b/packages/component/lib/components/textarea.tsx @@ -0,0 +1,28 @@ +import React, { PropsWithChildren } from 'react'; +import { getElement } from '../element'; + +type InputProps = { + value?: string; + onChange: (value: string) => void; + autosize?: boolean; + [index: string]: any; +}; + +const Textarea = ({ + children, + autosize, + ...props +}: PropsWithChildren) => { + const InputElement = getElement('textarea'); + if (!InputElement) return null; + return React.createElement( + InputElement, + { + ...(props || {}), + autoSize: autosize, + } as any, + children, + ); +}; + +export { Textarea }; diff --git a/packages/component/lib/element.tsx b/packages/component/lib/element.tsx index f935d0c..02707df 100644 --- a/packages/component/lib/element.tsx +++ b/packages/component/lib/element.tsx @@ -8,7 +8,8 @@ export type ElementType = | 'input' | 'input-number' | 'button' - | 'select'; + | 'select' + | 'textarea'; export const elements: Record< ElementType, @@ -22,6 +23,7 @@ export const elements: Record< 'input-number': undefined, button: undefined, select: undefined, + textarea: undefined, }; export const registerElement = ( diff --git a/packages/core/lib/components/core.tsx b/packages/core/lib/components/core.tsx index cb6439d..300e608 100755 --- a/packages/core/lib/components/core.tsx +++ b/packages/core/lib/components/core.tsx @@ -23,6 +23,7 @@ export interface DSlateProps { export type DSlateRef = { serialize: (v: any) => string; + serializeWeapp: (v: any) => any; getEditor: () => Editor; }; @@ -88,11 +89,51 @@ const DSlateCore = forwardRef>( [plugins, pluginProps], ); + const serializeWeapp = useCallback( + (node: any) => { + if (Text.isText(node)) { + const style = mergeStyle(node, plugins, 'text', editor); + return { + type: 'node', + name: 'span', + attrs: { + style: style2string(style), + }, + children: [ + { + type: 'text', + text: escapeHtml(node.text), + }, + ], + }; + } + + const childrens = node.children.map((n: any) => serializeWeapp(n)); + const style = mergeStyle(node, plugins, 'element', editor); + const match = Object.values(plugins).find( + (i) => i.type === node.type && i.nodeType === 'element', + ); + + if (match && match.serializeWeapp) { + const matchPluginProps = { + ...(match?.props ?? {}), + ...(pluginProps?.[match.type ?? ''] ?? {}), + style: style2string(style), + }; + return match.serializeWeapp(node, matchPluginProps, childrens); + } + + return childrens; + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [plugins, pluginProps], + ); + const getEditor = useCallback(() => { return editor; }, [editor]); - useImperativeHandle(ref, () => ({ serialize, getEditor }), [ + useImperativeHandle(ref, () => ({ serialize, getEditor, serializeWeapp }), [ serialize, getEditor, ]); diff --git a/packages/core/lib/typing.ts b/packages/core/lib/typing.ts index 7f0fc41..d9dcd4d 100755 --- a/packages/core/lib/typing.ts +++ b/packages/core/lib/typing.ts @@ -67,6 +67,7 @@ export type DSlatePlugin = { locale?: Locale[]; props?: Record; serialize?: (element: any, pluginProps: any, children: any[]) => string; + serializeWeapp?: (element: any, pluginProps: any, children: any[]) => any; }; export type Locale = { diff --git a/packages/plugin/lib/index.tsx b/packages/plugin/lib/index.tsx index f12b0ae..695de53 100755 --- a/packages/plugin/lib/index.tsx +++ b/packages/plugin/lib/index.tsx @@ -1,21 +1,22 @@ -import { ParagraphPlugin } from './plugins/paragraph'; +import { BackgroundColorPlugin } from './plugins/background-color'; +import { BlockquotePlugin } from './plugins/blockquote'; import { BoldPlugin } from './plugins/bold'; -import { DecorationPlugin } from './plugins/decoration'; -import { ItalicPlugin } from './plugins/italic'; -import { ColorPlugin } from './plugins/color'; import { ClearPlugin } from './plugins/clear'; -import { HistoryPlugin } from './plugins/history'; -import { BackgroundColorPlugin } from './plugins/background-color'; +import { ColorPlugin } from './plugins/color'; +import { DecorationPlugin } from './plugins/decoration'; import { FontSizePlugin } from './plugins/font-size'; -import { TextAlignPlugin } from './plugins/text-align'; +import { HistoryPlugin } from './plugins/history'; +import { HrPlugin } from './plugins/hr'; +import { ImgPlugin } from './plugins/img'; import { TextIndentPlugin } from './plugins/indent'; +import { ItalicPlugin } from './plugins/italic'; +import { LatexPlugin } from './plugins/latex'; +import { LineHeightPlugin } from './plugins/line-height'; +import { LinkPlugin } from './plugins/link'; import { ListPlugin } from './plugins/list'; +import { ParagraphPlugin } from './plugins/paragraph'; +import { TextAlignPlugin } from './plugins/text-align'; import { TodoListPlugin } from './plugins/todo-list'; -import { ImgPlugin } from './plugins/img'; -import { LinkPlugin } from './plugins/link'; -import { BlockquotePlugin } from './plugins/blockquote'; -import { HrPlugin } from './plugins/hr'; -import { LineHeightPlugin } from './plugins/line-height'; export default { HistoryPlugin, @@ -36,4 +37,5 @@ export default { BlockquotePlugin, HrPlugin, LineHeightPlugin, + LatexPlugin, }; diff --git a/packages/plugin/lib/plugins/blockquote.tsx b/packages/plugin/lib/plugins/blockquote.tsx index db460d4..e445e41 100755 --- a/packages/plugin/lib/plugins/blockquote.tsx +++ b/packages/plugin/lib/plugins/blockquote.tsx @@ -160,6 +160,17 @@ const BlockquotePlugin: DSlatePlugin = { '', )}`; }, + + serializeWeapp: (element, props, children) => { + return { + type: 'node', + name: 'blockquote', + attrs: { + style: props.style, + }, + children, + }; + }, }; export { BlockquotePlugin }; diff --git a/packages/plugin/lib/plugins/hr.tsx b/packages/plugin/lib/plugins/hr.tsx index 154c4e7..41712c4 100755 --- a/packages/plugin/lib/plugins/hr.tsx +++ b/packages/plugin/lib/plugins/hr.tsx @@ -98,7 +98,7 @@ const HrWrap = (props: RenderElementPropsWithStyle) => { const getMessage = useMessage(); return ( -
+
{ >
-
@@ -154,12 +154,15 @@ const renderElement = (props: RenderElementPropsWithStyle) => ( ); -const renderStyle = (node: Descendant) => { +const renderStyle = (node: Descendant, _: Editor, props?: any) => { if (node.type === TYPE) { return { - padding: '10px 0px', - border: '1px solid transparent', - borderRadius: 4, + border: 'none', + margin: '10px 0px', + borderBottomWidth: 1, + borderBottomStyle: 'solid', + borderBottomColor: props.color, + height: 1, } as CSSProperties; } return {}; @@ -189,8 +192,19 @@ const HrPlugin: DSlatePlugin = { remove: 'remove', }, ], - serialize: (e, p) => - `
`, + serialize: (e, p) => { + return `
`; + }, + + serializeWeapp: (e, p) => { + return { + type: 'node', + name: 'hr', + attrs: { + style: p.style, + }, + }; + }, }; export { HrPlugin }; diff --git a/packages/plugin/lib/plugins/img/img.tsx b/packages/plugin/lib/plugins/img/img.tsx index aaecf7d..ec788c7 100755 --- a/packages/plugin/lib/plugins/img/img.tsx +++ b/packages/plugin/lib/plugins/img/img.tsx @@ -1,5 +1,5 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import { Icon, Input, Popover, Toolbar } from '@dslate/component'; +import { Icon, Input, InputNumber, Popover, Toolbar } from '@dslate/component'; import type { RenderElementPropsWithStyle } from '@dslate/core'; import { promiseUploadFunc, @@ -82,7 +82,9 @@ const Img = ({ }); useEffect(() => { - setLoading(true); + if (element.url.indexOf(';base64,') === -1) { + setLoading(true); + } }, [element.url]); const selected = useSelected(); @@ -120,6 +122,21 @@ const Img = ({ ); }; + const updateMargin = (position: string, margin: number) => { + Transforms.setNodes( + editor, + { + margin: { + ...(element.margin || {}), + [position]: margin, + }, + }, + { + at: path, + }, + ); + }; + const updateEditableSizeEnd = () => { updateSize(editable); }; @@ -129,7 +146,10 @@ const Img = ({ const width = String(image.current?.naturalWidth ?? 1); const height = String(image.current?.naturalHeight ?? 1); const nSize = resize({ width, height }, key, value); - setEditable(nSize); + setEditable((pre) => ({ + ...pre, + ...nSize, + })); }; useEffect(() => { @@ -139,9 +159,12 @@ const Img = ({ */ const width = element.imgWidth ?? image.current?.width ?? ''; const height = element.imgHeight ?? image.current?.height ?? ''; - setEditable({ - width: width, - height: height, + setEditable((pre) => { + return { + ...pre, + width: width, + height: height, + }; }); } @@ -172,9 +195,12 @@ const Img = ({ if (element.imgWidth) width = element.imgWidth; if (element.imgHeight) height = element.imgHeight; - setEditable({ - width: width, - height: height, + setEditable((pre) => { + return { + ...pre, + width: width, + height: height, + }; }); setDraggable({ @@ -329,8 +355,10 @@ const Img = ({ display: 'flex', alignItems: 'center', gap: '8px', + marginBottom: 8, }} > + {getMessage('float', '浮动方式')} { @@ -353,6 +381,47 @@ const Img = ({ icon={} />
+
+ {getMessage('margin', '外边距')} + { + updateMargin('top', number as number); + }} + placeholder={getMessage('top', '上')} + /> + { + updateMargin('right', number as number); + }} + placeholder={getMessage('right', '右')} + /> + { + updateMargin('bottom', number as number); + }} + placeholder={getMessage('bottom', '下')} + /> + { + updateMargin('left', number as number); + }} + placeholder={getMessage('left', '左')} + /> +
} > diff --git a/packages/plugin/lib/plugins/img/index.tsx b/packages/plugin/lib/plugins/img/index.tsx index 3c723e8..2f66bec 100755 --- a/packages/plugin/lib/plugins/img/index.tsx +++ b/packages/plugin/lib/plugins/img/index.tsx @@ -35,6 +35,19 @@ const renderStyle = (node: Descendant) => { if (node.align === 'left') style.float = 'left'; if (node.align === 'right') style.float = 'right'; if (node.align === '') style.float = undefined; + for (const position of ['Left', 'Top', 'Bottom', 'Right']) { + const cssKey = `margin${position}` as + | 'marginLeft' + | 'marginTop' + | 'marginBottom' + | 'marginRight'; + if (node.margin?.[position.toLowerCase()]) { + style[cssKey] = `${node.margin[position.toLowerCase()]}px`; + } else { + style[cssKey] = undefined; + } + } + return style; } return {}; @@ -107,6 +120,15 @@ const ImgPlugin: DSlatePlugin = { width: '宽', loading: '图片加载中', remove: '删除', + float: '浮动方式', + ['float-left']: '左浮动', + ['float-right']: '右浮动', + ['float-default']: '默认', + margin: '外边距', + top: '上', + left: '左', + right: '右', + bottom: '下', }, { locale: Locales.enUS, @@ -117,6 +139,15 @@ const ImgPlugin: DSlatePlugin = { width: 'width', loading: 'loading', remove: 'remove', + float: 'float', + ['float-left']: 'float left', + ['float-right']: 'float right', + ['float-default']: 'float default', + margin: 'margin', + top: 'top', + left: 'left', + right: 'right', + bottom: 'bottom', }, ], serialize: (element, props) => { @@ -126,6 +157,20 @@ const ImgPlugin: DSlatePlugin = { style.push(`max-width: ${props.maxWidth}; height: auto;`); return ``; }, + serializeWeapp: (element, props) => { + const style = []; + if (props?.style) style.push(props.style); + if (props?.maxWidth) + style.push(`max-width: ${props.maxWidth}; height: auto;`); + return { + type: 'node', + name: 'img', + attrs: { + style: style.join(''), + src: element.url, + }, + }; + }, }; export { ImgPlugin }; diff --git a/packages/plugin/lib/plugins/latex.tsx b/packages/plugin/lib/plugins/latex.tsx new file mode 100755 index 0000000..475ff22 --- /dev/null +++ b/packages/plugin/lib/plugins/latex.tsx @@ -0,0 +1,207 @@ +import { Icon, Popover, Textarea, Toolbar } from '@dslate/component'; +import { + DSlatePlugin, + isBlockActive, + Locales, + RenderElementPropsWithStyle, + useMessage, + usePlugin, +} from '@dslate/core'; +import parseKatexWeapp from '@rojer/katex-mini'; +import katex from 'katex'; +import 'katex/dist/katex.min.css'; +import { useMemo } from 'react'; +import { Editor, Text, Transforms } from 'slate'; +import { ReactEditor, useSelected, useSlate } from 'slate-react'; +const TYPE = 'latex'; + +const remove = (editor: Editor) => { + if (!editor.selection) return; + Transforms.unwrapNodes(editor, { + match: (n) => n.type === TYPE, + split: true, + }); +}; + +const add = (editor: Editor) => { + if (!editor.selection) return; + Transforms.wrapNodes( + editor, + { + type: TYPE, + children: [], + }, + { + at: editor.selection, + match: (n) => Text.isText(n), + split: true, + }, + ); +}; + +const ToolbarButton = () => { + const editor = useSlate(); + const getMessage = useMessage(); + + const toggle = () => { + if (!editor.selection) return; + const isActive = isBlockActive(editor, TYPE); + if (isActive) { + remove(editor); + } else { + add(editor); + } + }; + + return ( + } + /> + ); +}; + +const LatexWrap = ({ + attributes, + element, + children, +}: RenderElementPropsWithStyle) => { + const selected = useSelected(); + const editor = useSlate(); + const path = ReactEditor.findPath(editor, element); + const getMessage = useMessage(); + const { props } = usePlugin(); + + const LatexDom = useMemo(() => { + if (typeof element.formula !== 'string' || !element.formula) { + return ( + + {getMessage('edit', '点击编辑Latex')} + + ); + } + + const html = katex.renderToString(element.formula, { + displayMode: props?.displayMode, + throwOnError: false, + }); + + return ( + + ); + }, [element.formula, props?.displayMode]); + + return ( + + + +
{getMessage('latex', 'Latex')}:
+