From c18a327b80e5aaad047a5076096ce94dd8aa1da2 Mon Sep 17 00:00:00 2001 From: fantasticit Date: Sun, 3 Apr 2022 13:10:14 +0800 Subject: [PATCH] feat: now we can insert countdown in editor --- packages/client/package.json | 1 + packages/client/src/tiptap/basekit.tsx | 2 + .../client/src/tiptap/extensions/countdown.ts | 83 +++++++++++++++++++ packages/client/src/tiptap/menubar.tsx | 6 ++ .../src/tiptap/menus/base-bubble-menu.tsx | 2 + .../src/tiptap/menus/countdown-setting.tsx | 43 ++++++++++ .../client/src/tiptap/menus/countdown.tsx | 66 +++++++++++++++ packages/client/src/tiptap/menus/event.ts | 8 ++ packages/client/src/tiptap/menus/iframe.tsx | 2 +- .../client/src/tiptap/menus/media-insert.tsx | 7 ++ .../client/src/tiptap/styles/selection.scss | 4 +- .../wrappers/countdown/index.module.scss | 13 +++ .../src/tiptap/wrappers/countdown/index.tsx | 20 +++++ pnpm-lock.yaml | 13 +++ 14 files changed, 268 insertions(+), 2 deletions(-) create mode 100644 packages/client/src/tiptap/extensions/countdown.ts create mode 100644 packages/client/src/tiptap/menus/countdown-setting.tsx create mode 100644 packages/client/src/tiptap/menus/countdown.tsx create mode 100644 packages/client/src/tiptap/menus/event.ts create mode 100644 packages/client/src/tiptap/wrappers/countdown/index.module.scss create mode 100644 packages/client/src/tiptap/wrappers/countdown/index.tsx diff --git a/packages/client/package.json b/packages/client/package.json index 14b36b6..3f12c98 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -74,6 +74,7 @@ "prosemirror-utils": "^0.9.6", "prosemirror-view": "^1.23.6", "react": "17.0.2", + "react-countdown": "^2.3.2", "react-dom": "17.0.2", "react-helmet": "^6.1.0", "react-pdf": "^5.7.2", diff --git a/packages/client/src/tiptap/basekit.tsx b/packages/client/src/tiptap/basekit.tsx index 0c8531b..582c698 100644 --- a/packages/client/src/tiptap/basekit.tsx +++ b/packages/client/src/tiptap/basekit.tsx @@ -8,6 +8,7 @@ import { Code } from './extensions/code'; import { CodeBlock } from './extensions/code-block'; import { Color } from './extensions/color'; import { ColorHighlighter } from './extensions/color-highlighter'; +import { Countdown } from './extensions/countdown'; import { DocumentChildren } from './extensions/document-children'; import { DocumentReference } from './extensions/document-reference'; import { Dropcursor } from './extensions/dropcursor'; @@ -63,6 +64,7 @@ export const BaseKit = [ CodeBlock, Color, ColorHighlighter, + Countdown, DocumentChildren, DocumentReference, Dropcursor, diff --git a/packages/client/src/tiptap/extensions/countdown.ts b/packages/client/src/tiptap/extensions/countdown.ts new file mode 100644 index 0000000..a508984 --- /dev/null +++ b/packages/client/src/tiptap/extensions/countdown.ts @@ -0,0 +1,83 @@ +import { Node, mergeAttributes } from '@tiptap/core'; +import { ReactNodeViewRenderer } from '@tiptap/react'; +import { CountdownWrapper } from '../wrappers/countdown'; +import { getDatasetAttribute } from '../services/dataset'; + +declare module '@tiptap/core' { + interface Commands { + countdown: { + setCountdown: (attrs) => ReturnType; + }; + } +} + +export const Countdown = Node.create({ + name: 'countdown', + content: '', + marks: '', + group: 'block', + selectable: true, + atom: true, + + addOptions() { + return { + HTMLAttributes: { + class: 'countdown', + }, + }; + }, + + addAttributes() { + return { + title: { + default: '倒计时', + parseHTML: getDatasetAttribute('title'), + }, + date: { + default: Date.now().valueOf() + 60 * 1000, + parseHTML: getDatasetAttribute('date'), + }, + }; + }, + + parseHTML() { + return [ + { + tag: 'div', + }, + ]; + }, + + renderHTML({ HTMLAttributes }) { + return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)]; + }, + + addCommands() { + return { + setCountdown: + (options) => + ({ tr, commands, chain, editor }) => { + // @ts-ignore + if (tr.selection?.node?.type?.name == this.name) { + return commands.updateAttributes(this.name, options); + } + + const { selection } = editor.state; + const pos = selection.$head; + + return chain() + .insertContentAt(pos.before(), [ + { + type: this.name, + attrs: options, + }, + ]) + .run(); + }, + }; + }, + + addNodeView() { + return ReactNodeViewRenderer(CountdownWrapper); + }, +}); diff --git a/packages/client/src/tiptap/menubar.tsx b/packages/client/src/tiptap/menubar.tsx index 2de6181..1f164c4 100644 --- a/packages/client/src/tiptap/menubar.tsx +++ b/packages/client/src/tiptap/menubar.tsx @@ -18,6 +18,9 @@ import { LinkBubbleMenu } from './menus/link'; import { IframeBubbleMenu } from './menus/iframe'; import { TableBubbleMenu } from './menus/table'; +import { CountdownBubbleMenu } from './menus/countdown'; +import { CountdownSettingModal } from './menus/countdown-setting'; + export const MenuBar: React.FC<{ editor: any }> = ({ editor }) => { if (!editor) { return null; @@ -80,6 +83,9 @@ export const MenuBar: React.FC<{ editor: any }> = ({ editor }) => { + + + ); }; diff --git a/packages/client/src/tiptap/menus/base-bubble-menu.tsx b/packages/client/src/tiptap/menus/base-bubble-menu.tsx index 9135ae8..01e331c 100644 --- a/packages/client/src/tiptap/menus/base-bubble-menu.tsx +++ b/packages/client/src/tiptap/menus/base-bubble-menu.tsx @@ -17,6 +17,7 @@ import { TaskItem } from '../extensions/task-item'; import { Katex } from '../extensions/katex'; import { DocumentReference } from '../extensions/document-reference'; import { DocumentChildren } from '../extensions/document-children'; +import { Countdown } from '../extensions/countdown'; import { BaseMenu } from './base-menu'; const OTHER_BUBBLE_MENU_TYPES = [ @@ -36,6 +37,7 @@ const OTHER_BUBBLE_MENU_TYPES = [ DocumentChildren.name, Katex.name, HorizontalRule.name, + Countdown.name, ]; export const BaseBubbleMenu: React.FC<{ editor: Editor }> = ({ editor }) => { diff --git a/packages/client/src/tiptap/menus/countdown-setting.tsx b/packages/client/src/tiptap/menus/countdown-setting.tsx new file mode 100644 index 0000000..c216f3b --- /dev/null +++ b/packages/client/src/tiptap/menus/countdown-setting.tsx @@ -0,0 +1,43 @@ +import { useCallback, useEffect, useRef, useState } from 'react'; +import { Form, Modal } from '@douyinfe/semi-ui'; +import { FormApi } from '@douyinfe/semi-ui/lib/es/form'; +import { Editor } from '@tiptap/core'; +import { useToggle } from 'hooks/use-toggle'; +import { event, OPEN_COUNT_SETTING_MODAL } from './event'; + +type IProps = { editor: Editor }; + +export const CountdownSettingModal: React.FC = ({ editor, children }) => { + const $form = useRef(); + const [initialState, setInitialState] = useState({ date: Date.now() }); + const [visible, toggleVisible] = useToggle(false); + + const handleOk = useCallback(() => { + $form.current.validate().then((values) => { + editor.chain().focus().setCountdown({ title: values.title, date: values.date.valueOf() }).run(); + toggleVisible(false); + }); + }, []); + + useEffect(() => { + const handler = (data) => { + toggleVisible(true); + data && setInitialState(data); + }; + + event.on(OPEN_COUNT_SETTING_MODAL, handler); + + return () => { + event.off(OPEN_COUNT_SETTING_MODAL, handler); + }; + }, []); + + return ( + toggleVisible(false)}> +
($form.current = formApi)} labelPosition="left"> + + + +
+ ); +}; diff --git a/packages/client/src/tiptap/menus/countdown.tsx b/packages/client/src/tiptap/menus/countdown.tsx new file mode 100644 index 0000000..5a28f90 --- /dev/null +++ b/packages/client/src/tiptap/menus/countdown.tsx @@ -0,0 +1,66 @@ +import { useCallback, useRef } from 'react'; +import { Space, Button, Modal, Form, Typography } from '@douyinfe/semi-ui'; +import { FormApi } from '@douyinfe/semi-ui/lib/es/form'; +import { IconEdit, IconExternalOpen, IconLineHeight, IconDelete } from '@douyinfe/semi-icons'; +import { useToggle } from 'hooks/use-toggle'; +import { Tooltip } from 'components/tooltip'; +import { BubbleMenu } from '../views/bubble-menu'; +import { Countdown } from '../extensions/countdown'; +import { Divider } from '../divider'; +import { event, triggerOpenCountSettingModal } from './event'; + +export const CountdownBubbleMenu = ({ editor }) => { + const attrs = editor.getAttributes(Countdown.name); + const $form = useRef(); + // const [visible, toggleVisible] = useToggle(false); + + // const useExample = useCallback(() => { + // $form.current.setValue('url', EXAMPLE_LINK); + // }, []); + + // const handleCancel = useCallback(() => { + // toggleVisible(false); + // }, []); + + // const handleOk = useCallback(() => { + // $form.current.validate().then((values) => { + // editor + // .chain() + // .updateAttributes(Countdown.name, { + // url: values.url, + // }) + // .setNodeSelection(editor.state.selection.from) + // .focus() + // .run(); + // toggleVisible(false); + // }); + // }, []); + + const openEditLinkModal = useCallback(() => { + triggerOpenCountSettingModal(attrs); + }, [attrs]); + + const deleteNode = useCallback(() => editor.chain().deleteSelection().run(), [editor]); + + return ( + editor.isActive(Countdown.name)} + tippyOptions={{ maxWidth: 456 }} + > + + +