From 13c68411b00ca0b74deda40314df02042a68d55f Mon Sep 17 00:00:00 2001 From: drl990114 Date: Sun, 21 Apr 2024 17:34:32 +0800 Subject: [PATCH] feat: preview mode --- package.json | 4 +-- src/App.tsx | 33 ++++++----------- src/editor/components/Editor.tsx | 35 ++++++++++++++----- src/editor/components/Preview/preview.tsx | 14 ++++++-- .../Inline/inline-mark-extensions.ts | 5 +++ src/editor/theme/dark.ts | 2 +- src/editor/theme/light.ts | 2 +- src/editor/types/index.ts | 2 +- src/playground/content.ts | 3 ++ yarn.lock | 12 +++---- 10 files changed, 67 insertions(+), 45 deletions(-) diff --git a/package.json b/package.json index 0513d45..adf43a9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rme", - "version": "0.0.51", + "version": "0.0.55", "type": "module", "scripts": { "build": "yarn clear && yarn build-esbuild && yarn types", @@ -180,6 +180,6 @@ "svgmoji": "^3.2.0", "void-elements": "^3.1.0", "yjs": "^13.6.14", - "zens": "^0.0.20" + "zens": "^0.0.21" } } diff --git a/src/App.tsx b/src/App.tsx index 50da886..a9eaeba 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,8 +5,9 @@ import { createWysiwygDelegate, WysiwygThemeWrapper, Preview, + createSourceCodeDelegate, } from '.' -import React, { FC, useLayoutEffect, useMemo, useRef } from 'react' +import React, { FC, useLayoutEffect, useMemo, useRef, useState } from 'react' import useDevTools from './playground/hooks/use-devtools' import useContent from './playground/hooks/use-content' import { DebugConsole } from './playground/components/DebugConsole' @@ -34,9 +35,8 @@ function App() { const { contentId, content, hasUnsavedChanges, setContentId, setContent } = useContent() const { enableDevTools, setEnableDevTools } = useDevTools() const [theme, setTheme] = React.useState<'light' | 'dark'>('dark') - const editorDelegate = useMemo(() => createWysiwygDelegate(), []) + const [editorDelegate, setEditorDelegate] = useState(createWysiwygDelegate()) const [previewMode, setPreviewMode] = React.useState(false) - const docRef = useRef() const debounce = (fn: (...args: any) => void, delay: number) => { let timer: number @@ -47,13 +47,13 @@ function App() { } const debounceChange = debounce((params) => { - docRef.current = params.state.doc setContent(editorDelegate.docToString(params.state.doc) || '') }, 300) const editor = (
{ const value = e.target.value if (value === 'wysiwyg') { + setEditorDelegate(createWysiwygDelegate()) editorRef.current?.toggleType('wysiwyg') - } else { + } else if (value === 'sourceCode') { + setEditorDelegate(createSourceCodeDelegate()) editorRef.current?.toggleType('sourceCode') + } else { + editorRef.current?.toggleType('preview') } }} > + - - } - label="Preview" - labelPlacement="start" - onChange={(e) => { - setPreviewMode(e.target.checked) - }} - /> -
setEnableDevTools(!enableDevTools)} />
- {previewMode ? ( -
- -
- ) : ( - editor - )} + {editor} {debugConsole}
diff --git a/src/editor/components/Editor.tsx b/src/editor/components/Editor.tsx index aca0c72..666fb78 100644 --- a/src/editor/components/Editor.tsx +++ b/src/editor/components/Editor.tsx @@ -2,20 +2,26 @@ import WysiwygEditor from './WysiwygEditor' import SourceEditor from './SourceEditor' import { forwardRef, memo, useImperativeHandle, useMemo, useState } from 'react' -import type { EditorContext, EditorDelegate, EditorViewType } from '../..' +import { + Preview, + type CreateWysiwygDelegateOptions, + type EditorContext, + type EditorDelegate, + type EditorViewType, +} from '../..' import { useContextMounted } from './useContextMounted' import type { Extension, RemirrorEventListenerProps } from 'remirror' -import "prosemirror-flat-list/dist/style.css" - -export type EditorRef = { - toggleType: (targetType: EditorViewType) => void - getType: () => EditorViewType -} +import 'prosemirror-flat-list/dist/style.css' export const Editor = memo( forwardRef((props, ref) => { - const { hooks = [], onContextMounted, ...otherProps } = props - const [type, setType] = useState('wysiwyg') + const { + initialType = 'wysiwyg', + hooks = [], + onContextMounted, + ...otherProps + } = props + const [type, setType] = useState(initialType) useImperativeHandle(ref, () => ({ getType: () => type, @@ -28,6 +34,10 @@ export const Editor = memo( return [() => useContextMounted(onContextMounted), ...hooks] }, [hooks, onContextMounted]) + if (type === 'preview') { + return + } + return type === 'sourceCode' ? ( ) : ( @@ -39,11 +49,18 @@ export const Editor = memo( export type EditorChangeEventParams = RemirrorEventListenerProps export type EditorChangeHandler = (params: EditorChangeEventParams) => void +export type EditorRef = { + toggleType: (targetType: EditorViewType) => void + getType: () => EditorViewType +} + export interface EditorProps { + initialType?: EditorViewType delegate?: EditorDelegate content: string isTesting?: boolean editable?: boolean + delegateOptions?: CreateWysiwygDelegateOptions onChange?: EditorChangeHandler hooks?: (() => void)[] markdownToolBar?: React.ReactNode[] diff --git a/src/editor/components/Preview/preview.tsx b/src/editor/components/Preview/preview.tsx index 5e04e15..63fc294 100644 --- a/src/editor/components/Preview/preview.tsx +++ b/src/editor/components/Preview/preview.tsx @@ -1,15 +1,23 @@ import { WysiwygThemeWrapper } from '../../theme' import { prosemirrorNodeToHtml } from 'remirror' import type { Node } from '@remirror/pm/model' +import { EditorProps } from '../Editor' +import { createWysiwygDelegate } from '../WysiwygEditor' interface PreviewProps { - doc: Node + doc: Node | string + delegateOptions?: EditorProps['delegateOptions'] } export const Preview: React.FC = (props) => { - const { doc } = props + const { doc, delegateOptions } = props + let targetDoc: PreviewProps['doc'] = doc - const html = prosemirrorNodeToHtml(doc) + if (typeof targetDoc === 'string') { + targetDoc = createWysiwygDelegate(delegateOptions).stringToDoc(targetDoc) + } + + const html = prosemirrorNodeToHtml(targetDoc) return } diff --git a/src/editor/extensions/Inline/inline-mark-extensions.ts b/src/editor/extensions/Inline/inline-mark-extensions.ts index 865d120..61b1877 100644 --- a/src/editor/extensions/Inline/inline-mark-extensions.ts +++ b/src/editor/extensions/Inline/inline-mark-extensions.ts @@ -189,6 +189,8 @@ export interface MfImgOptions { handleViewImgSrcUrl?: (src: string) => Promise } +const globalImageHrefCache: Map = new Map() + @extension({ defaultOptions: { handleViewImgSrcUrl: async (src: string) => src, @@ -214,8 +216,10 @@ class ImgUri extends MarkExtension { default: '', }, }, + toDOM: (mark) => ['img', { src: globalImageHrefCache.get(mark.attrs.href) || mark.attrs.href }, 0] } } + createNodeViews = (): NodeViewMethod => { return (mark): NodeView => { const innerContainer = document.createElement('span') @@ -224,6 +228,7 @@ class ImgUri extends MarkExtension { if (this.options.handleViewImgSrcUrl) { this.options.handleViewImgSrcUrl(mark.attrs.href).then((newHref) => { img.setAttribute('src', formatHref(newHref)) + globalImageHrefCache.set(mark.attrs.href, newHref) }) } else { img.setAttribute('src', formatHref(mark.attrs.href)) diff --git a/src/editor/theme/dark.ts b/src/editor/theme/dark.ts index 558a169..11b7b20 100644 --- a/src/editor/theme/dark.ts +++ b/src/editor/theme/dark.ts @@ -1,7 +1,7 @@ export const styledDarkTheme = { primaryFontColor: '#c8d1d9', labelFontColor: 'rgba(255, 255, 255, 0.5)', - accentColor: '#1c78aa', + accentColor: '#016ab3', borderColor: '#363b41', bgColor: '#05010d', hoverColor: '#1f2225', diff --git a/src/editor/theme/light.ts b/src/editor/theme/light.ts index 3c0fd1d..77ed068 100644 --- a/src/editor/theme/light.ts +++ b/src/editor/theme/light.ts @@ -1,7 +1,7 @@ export const styledLightTheme = { primaryFontColor: '#000000', labelFontColor: '#9ca3af', - accentColor: '#0369a1', + accentColor: '#007acc', borderColor: '#d7d7dc', bgColor: '#fdfdfd', hoverColor: '#d7d7dc', diff --git a/src/editor/types/index.ts b/src/editor/types/index.ts index 559f384..14f0242 100644 --- a/src/editor/types/index.ts +++ b/src/editor/types/index.ts @@ -18,7 +18,7 @@ export interface Note { deleted: boolean } -export type EditorViewType = 'wysiwyg' | 'sourceCode' +export type EditorViewType = 'wysiwyg' | 'sourceCode' | 'preview' type BaseEditorState = { mode: EditorViewType diff --git a/src/playground/content.ts b/src/playground/content.ts index 765800a..002f95b 100644 --- a/src/playground/content.ts +++ b/src/playground/content.ts @@ -12,6 +12,9 @@ const defaultContent = [ ` # hello world! +![img](https://www.gstatic.com/images/branding/googlelogo/svg/googlelogo_clr_74x24px.svg) + +a `.trim(), ].join("\n") diff --git a/yarn.lock b/yarn.lock index d25e131..3dc2068 100644 --- a/yarn.lock +++ b/yarn.lock @@ -34,7 +34,7 @@ "@floating-ui/dom" "^1.0.0" use-sync-external-store "^1.2.0" -"@ariakit/react@^0.4.3": +"@ariakit/react@^0.4.5": version "0.4.5" resolved "https://registry.yarnpkg.com/@ariakit/react/-/react-0.4.5.tgz#f6e356665cc45fa317c10fa041009acbb290d92f" integrity sha512-GUHxaOY1JZrJUHkuV20IY4NWcgknhqTQM0qCQcVZDCi+pJiWchUjTG+UyIr/Of02hU569qnQ7yovskCf+V3tNg== @@ -10322,12 +10322,12 @@ yocto-queue@^1.0.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== -zens@^0.0.20: - version "0.0.20" - resolved "https://registry.yarnpkg.com/zens/-/zens-0.0.20.tgz#8b6595a2a9ba4a5837723f6f58b3b246f8dea708" - integrity sha512-vcnMK7O/x1p5HT8CTN43PeQ8oTPxub1cBdpLejoJ9sMLMOp3hEowqTNsRiGYRC/A5eXLsio2Z0WpzpiJSDVbZg== +zens@^0.0.21: + version "0.0.21" + resolved "https://registry.yarnpkg.com/zens/-/zens-0.0.21.tgz#bbe42a1fb46bee023c5e6125c4cad314430b332f" + integrity sha512-K5bFEHMn2w2EzFR+dshUT5f8l/Uc31VNoO/XDfZjOh4TGfHq0bMJ92I8q+al1+L/ooIYOXqImkwEmGmcB3H2+Q== dependencies: - "@ariakit/react" "^0.4.3" + "@ariakit/react" "^0.4.5" "@babel/runtime" "^7" "@emotion/is-prop-valid" "^1.2.2" "@floating-ui/dom" "^1.0.0"