From 216fa6b82a0e64fcaceca2f2f203c51e084b3d3e Mon Sep 17 00:00:00 2001 From: hunghg255 Date: Thu, 15 Aug 2024 22:54:10 +0700 Subject: [PATCH 1/3] fix: render bubble extension --- src/components/menus/bubble.ts | 40 ++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/components/menus/bubble.ts b/src/components/menus/bubble.ts index f9fb777..adc4624 100644 --- a/src/components/menus/bubble.ts +++ b/src/components/menus/bubble.ts @@ -5,7 +5,6 @@ import { IMAGE_SIZE, VIDEO_SIZE } from '@/constants' import type { ButtonViewParams, ButtonViewReturn, ExtensionNameKeys } from '@/types' import { localeActions } from '@/locales' import ActionButton from '@/components/ActionButton' -import { Bold, Code, Color, Highlight, Italic, Link, Strike, Underline } from '@/extensions' /** Represents the size types for bubble images or videos */ type BubbleImageOrVideoSizeType = 'size-small' | 'size-medium' | 'size-large' @@ -171,20 +170,29 @@ export function getBubbleVideo(editor: Editor): BubbleMenuItem[] { ] } +const bubbleList = ['bold', 'italic', 'underline', 'strike', 'code', 'link', 'divider', 'color', 'highlight'] + export function getBubbleText(editor: Editor, t: any) { - return [ - Bold.configure().options.button({ editor, t } as any), - Italic.configure().options.button({ editor, t } as any), - Underline.configure().options.button({ editor, t } as any), - Strike.configure().options.button({ editor, t } as any), - Code.configure().options.button({ editor, t } as any), - Link.configure().options.button({ editor, t } as any), - { - type: 'divider', - component: undefined, - componentProps: {}, - }, - Color.configure().options.button({ editor, t } as any), - Highlight.configure().options.button({ editor, t } as any), - ] + const bubbleMenu = bubbleList.filter((type) => { + if (type === 'divider') { + return true + } + + const ext = editor.extensionManager.extensions.find(ext => ext.name === type) + return !!ext + }).map((it) => { + const ext: any = editor.extensionManager.extensions.find(ext => ext.name === it) + + if (it === 'divider') { + return { + type: 'divider', + component: undefined, + componentProps: {}, + } + } + + return ext.configure().options.button({ editor, t } as any) + }) + + return bubbleMenu } From 58cf29b0464af42fba043e4fc81eb5d4074a5c3d Mon Sep 17 00:00:00 2001 From: hunghg255 Date: Fri, 16 Aug 2024 14:06:43 +0700 Subject: [PATCH 2/3] chore: add dynamic bubble text extention --- playground/src/App.tsx | 109 +++++++++--------- src/components/menus/bubble.ts | 25 +--- .../menus/components/BubbleMenuText.tsx | 70 ++++++----- src/extensions/Bold/Bold.ts | 1 + src/extensions/Code/Code.ts | 2 + src/extensions/Color/Color.ts | 1 + src/extensions/FontFamily/FontFamily.ts | 7 +- src/extensions/Highlight/Highlight.ts | 1 + src/extensions/Italic/Italic.ts | 1 + src/extensions/Link/Link.ts | 1 + src/extensions/Strike/Strike.ts | 1 + src/extensions/UnderLine/Underline.ts | 1 + src/styles/global.scss | 7 ++ src/types.ts | 2 + 14 files changed, 115 insertions(+), 114 deletions(-) diff --git a/playground/src/App.tsx b/playground/src/App.tsx index b333ed2..a0aa497 100644 --- a/playground/src/App.tsx +++ b/playground/src/App.tsx @@ -1,46 +1,44 @@ -/* eslint-disable unicorn/no-null */ -/* eslint-disable quotes */ -import { useCallback, useState } from 'react'; +import { useCallback, useState } from 'react' -import { createLowlight, common } from 'lowlight'; +import { common, createLowlight } from 'lowlight' import RcTiptapEditor, { BaseKit, - History, - FormatPainter, - Clear, - Heading, - FontSize, + Blockquote, Bold, - Italic, - Underline, - Strike, - MoreMark, + BulletList, + Clear, + Code, + CodeBlock, Color, + ColumnToolbar, + FontFamily, + FontSize, + FormatPainter, + Heading, Highlight, - BulletList, - OrderedList, - TextAlign, + History, + HorizontalRule, + Iframe, + Image, + ImageUpload, Indent, + Italic, LineHeight, - TaskList, Link, - Image, - ImageUpload, - Video, - VideoUpload, - Blockquote, + MoreMark, + OrderedList, SlashCommand, - HorizontalRule, - ColumnToolbar, - FontFamily, - CodeBlock, + Strike, Table, - Code, + TaskList, + TextAlign, + Underline, + Video, + VideoUpload, locale, - Iframe, -} from 'reactjs-tiptap-editor'; +} from 'reactjs-tiptap-editor' -import 'reactjs-tiptap-editor/style.css'; +import 'reactjs-tiptap-editor/style.css' const extensions = [ BaseKit.configure({ @@ -62,12 +60,12 @@ const extensions = [ Italic, Underline, Strike, - MoreMark, + MoreMark.configure({ bubble: true }), Color.configure({ spacer: true }), Highlight, BulletList, OrderedList, - TextAlign.configure({ types: ['heading', 'paragraph'], spacer: true }), + TextAlign.configure({ types: ['heading', 'paragraph'], spacer: true, bubble: true }), Indent, LineHeight, TaskList.configure({ @@ -82,56 +80,59 @@ const extensions = [ upload: (files: File) => { return new Promise((resolve) => { setTimeout(() => { - resolve(URL.createObjectURL(files)); - }, 500); - }); + resolve(URL.createObjectURL(files)) + }, 500) + }) }, }), Video, VideoUpload.configure({ upload: (files: File[]) => { - const f = files.map((file) => ({ + const f = files.map(file => ({ src: URL.createObjectURL(file), alt: file.name, - })); - return Promise.resolve(f); + })) + return Promise.resolve(f) }, }), Blockquote, SlashCommand, HorizontalRule, - Code, + Code.configure({ + toolbar: false, + bubble: true, + }), CodeBlock.configure({ lowlight: createLowlight(common) }), ColumnToolbar, Table, Iframe.configure({ spacer: true }), -]; +] -const DEFAULT = `

Rc Tiptap Editor

A modern WYSIWYG rich text editor based on tiptap and shadcn ui for Reactjs


Demo

👉Demo

Features

Installation

pnpm add reactjs-tiptap-editor

`; +const DEFAULT = `

Rc Tiptap Editor

A modern WYSIWYG rich text editor based on tiptap and shadcn ui for Reactjs


Demo

👉Demo

Features

Installation

pnpm add reactjs-tiptap-editor

` function debounce(func: any, wait: number) { - let timeout: NodeJS.Timeout; + let timeout: NodeJS.Timeout return function (...args: any[]) { - clearTimeout(timeout); + clearTimeout(timeout) // @ts-ignore - timeout = setTimeout(() => func.apply(this, args), wait); - }; + timeout = setTimeout(() => func.apply(this, args), wait) + } } function App() { - const [content, setContent] = useState(DEFAULT); - const [theme, setTheme] = useState('light'); - const [disable, setDisable] = useState(false); + const [content, setContent] = useState(DEFAULT) + const [theme, setTheme] = useState('light') + const [disable, setDisable] = useState(false) const onValueChange = useCallback( debounce((value: any) => { - setContent(value); + setContent(value) }, 300), [], - ); + ) return (
)}
- ); + ) } -export default App; +export default App diff --git a/src/components/menus/bubble.ts b/src/components/menus/bubble.ts index adc4624..64c9c97 100644 --- a/src/components/menus/bubble.ts +++ b/src/components/menus/bubble.ts @@ -170,28 +170,11 @@ export function getBubbleVideo(editor: Editor): BubbleMenuItem[] { ] } -const bubbleList = ['bold', 'italic', 'underline', 'strike', 'code', 'link', 'divider', 'color', 'highlight'] - export function getBubbleText(editor: Editor, t: any) { - const bubbleMenu = bubbleList.filter((type) => { - if (type === 'divider') { - return true - } - - const ext = editor.extensionManager.extensions.find(ext => ext.name === type) - return !!ext - }).map((it) => { - const ext: any = editor.extensionManager.extensions.find(ext => ext.name === it) - - if (it === 'divider') { - return { - type: 'divider', - component: undefined, - componentProps: {}, - } - } - - return ext.configure().options.button({ editor, t } as any) + const bubbleMenu = editor.extensionManager.extensions.filter((ext) => { + return ext.options.bubble + }).sort((a, b) => a.options?.sort - b.options?.sort).map((ext) => { + return ext.configure().options.button({ editor, t, extension: ext } as any) }) return bubbleMenu diff --git a/src/components/menus/components/BubbleMenuText.tsx b/src/components/menus/components/BubbleMenuText.tsx index 582f32b..290a2d4 100644 --- a/src/components/menus/components/BubbleMenuText.tsx +++ b/src/components/menus/components/BubbleMenuText.tsx @@ -28,13 +28,11 @@ function ItemA({ item, disabled, editor }: any) { } return ( - - - + ) } @@ -63,40 +61,38 @@ function BubbleMenuText(props: IPropsBubbleMenuText) { }, [props.disabled, props.editor, lang, t]) return ( - <> - - {items?.length - ? ( -
-
- {items?.map((item: any, key: any) => { - if (item?.type === 'divider') { - return ( - - ) - } - + + {items?.length + ? ( +
+
+ {items?.map((item: any, key: any) => { + if (item?.type === 'divider') { return ( - ) - })} -
+ } + + return ( + + ) + })}
- ) - : ( - <> - )} -
- +
+ ) + : ( + <> + )} + ) } diff --git a/src/extensions/Bold/Bold.ts b/src/extensions/Bold/Bold.ts index 5b9950b..fbf429e 100644 --- a/src/extensions/Bold/Bold.ts +++ b/src/extensions/Bold/Bold.ts @@ -10,6 +10,7 @@ export const Bold = TiptapBold.extend({ addOptions() { return { ...this.parent?.(), + bubble: true, button: ({ editor, t }: any) => ({ component: ActionButton, componentProps: { diff --git a/src/extensions/Code/Code.ts b/src/extensions/Code/Code.ts index 8fe0039..aa744c8 100644 --- a/src/extensions/Code/Code.ts +++ b/src/extensions/Code/Code.ts @@ -10,6 +10,8 @@ export const Code = TiptapCode.extend({ addOptions() { return { ...this.parent?.(), + bubble: true, + button: ({ editor, t }) => ({ component: ActionButton, componentProps: { diff --git a/src/extensions/Color/Color.ts b/src/extensions/Color/Color.ts index 985422e..a2921c9 100644 --- a/src/extensions/Color/Color.ts +++ b/src/extensions/Color/Color.ts @@ -10,6 +10,7 @@ export const Color = TiptapColor.extend({ addOptions() { return { ...this.parent?.(), + bubble: true, button({ editor, t }) { return { component: ColorActionButton, diff --git a/src/extensions/FontFamily/FontFamily.ts b/src/extensions/FontFamily/FontFamily.ts index c263bec..fb5ce49 100644 --- a/src/extensions/FontFamily/FontFamily.ts +++ b/src/extensions/FontFamily/FontFamily.ts @@ -1,21 +1,24 @@ import type { Extension } from '@tiptap/core' +import type { FontFamilyOptions as TiptapFontFamilyOptions } from '@tiptap/extension-font-family' import FontFamilyTiptap from '@tiptap/extension-font-family' // import type { GeneralOptions } from '@/types'; import type { BaseKitOptions } from '../BaseKit' import FontFamilyButton from '@/extensions/FontFamily/components/FontFamilyButton' +import type { GeneralOptions } from '@/types' // export interface FontFamilyOptions extends , GeneralOptions {} +export interface FontFamilyOptions extends TiptapFontFamilyOptions, GeneralOptions {} -export const FontFamily = FontFamilyTiptap.extend({ +export const FontFamily = FontFamilyTiptap.extend({ addOptions() { return { ...this.parent?.(), fonts: ['Inter', 'Comic Sans MS, Comic Sans', 'serif', 'monospace', 'cursive'], button({ editor, extension, t }: any) { const { extensions = [] } = editor.extensionManager ?? [] - const fonts = extension.options?.fonts || [] + const fonts = extension?.options?.fonts || [] const baseKitExt = extensions.find( (k: any) => k.name === 'base-kit', ) as Extension diff --git a/src/extensions/Highlight/Highlight.ts b/src/extensions/Highlight/Highlight.ts index 7a8f46e..79afba0 100644 --- a/src/extensions/Highlight/Highlight.ts +++ b/src/extensions/Highlight/Highlight.ts @@ -12,6 +12,7 @@ export const Highlight = TiptapHighlight.extend({ addOptions() { return { ...this.parent?.(), + bubble: true, multicolor: true, button: ({ editor, t }) => ({ component: HighlightActionButton, diff --git a/src/extensions/Italic/Italic.ts b/src/extensions/Italic/Italic.ts index ba4a6f6..200b53d 100644 --- a/src/extensions/Italic/Italic.ts +++ b/src/extensions/Italic/Italic.ts @@ -11,6 +11,7 @@ export const Italic = TiptapItalic.extend({ addOptions() { return { ...this.parent?.(), + bubble: true, button({ editor, t }: { editor: Editor, t: (...args: any[]) => string }) { return { component: ActionButton, diff --git a/src/extensions/Link/Link.ts b/src/extensions/Link/Link.ts index 8ef8f5d..8869cbf 100644 --- a/src/extensions/Link/Link.ts +++ b/src/extensions/Link/Link.ts @@ -32,6 +32,7 @@ export const Link = TiptapLink.extend({ return { ...this.parent?.(), openOnClick: true, + bubble: true, button: ({ editor, t }) => { return { component: LinkEditPopover, diff --git a/src/extensions/Strike/Strike.ts b/src/extensions/Strike/Strike.ts index f85f756..28c4a4c 100644 --- a/src/extensions/Strike/Strike.ts +++ b/src/extensions/Strike/Strike.ts @@ -10,6 +10,7 @@ export const Strike = TiptapStrike.extend({ addOptions() { return { ...this.parent?.(), + bubble: true, button: ({ editor, t }: any) => ({ component: ActionButton, componentProps: { diff --git a/src/extensions/UnderLine/Underline.ts b/src/extensions/UnderLine/Underline.ts index 691ec62..8f7433e 100644 --- a/src/extensions/UnderLine/Underline.ts +++ b/src/extensions/UnderLine/Underline.ts @@ -12,6 +12,7 @@ export const Underline = TiptapUnderline.extend({ addOptions() { return { ...this.parent?.(), + bubble: true, button({ editor, t }: any) { return { component: ActionButton, diff --git a/src/styles/global.scss b/src/styles/global.scss index 415e410..621c073 100644 --- a/src/styles/global.scss +++ b/src/styles/global.scss @@ -76,3 +76,10 @@ @apply bg-background text-foreground; } } + +// https://github.com/radix-ui/primitives/issues/1496 +html body[data-scroll-locked] { + --removed-body-scroll-bar-size: 0 !important; + // margin-right: 0 !important; + position: initial !important; +} diff --git a/src/types.ts b/src/types.ts index 6e1a2a6..38853ec 100644 --- a/src/types.ts +++ b/src/types.ts @@ -52,6 +52,8 @@ export interface GeneralOptions { button: ButtonView /** Show on Toolbar */ toolbar?: boolean + /** Show on Bubble menu */ + bubble?: boolean } /** From 6e99d75ca8ce5e02f33730d9a55741477d2c3fca Mon Sep 17 00:00:00 2001 From: hunghg255 Date: Fri, 16 Aug 2024 14:51:15 +0700 Subject: [PATCH 3/3] chore: fix crash when not import text-align extensiton --- playground/src/App.tsx | 2 +- src/components/menus/bubble.ts | 4 ++-- .../menus/components/TableBubbleMenu.tsx | 18 +++++++++--------- src/extensions/TextAlign/TextAlign.ts | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/playground/src/App.tsx b/playground/src/App.tsx index a0aa497..8409808 100644 --- a/playground/src/App.tsx +++ b/playground/src/App.tsx @@ -60,7 +60,7 @@ const extensions = [ Italic, Underline, Strike, - MoreMark.configure({ bubble: true }), + MoreMark, Color.configure({ spacer: true }), Highlight, BulletList, diff --git a/src/components/menus/bubble.ts b/src/components/menus/bubble.ts index 64c9c97..0be322c 100644 --- a/src/components/menus/bubble.ts +++ b/src/components/menus/bubble.ts @@ -105,9 +105,9 @@ function imageAlignMenus(editor: Editor): BubbleMenuItem[] { componentProps: { tooltip: localeActions.t(`editor.textalign.${k}.tooltip`), icon: iconMap[k], - action: () => editor.commands.setTextAlign(k), + action: () => editor.commands?.setTextAlign?.(k), isActive: () => editor.isActive({ textAlign: k }) || false, - disabled: !editor.can().setTextAlign(k), + disabled: !editor.can()?.setTextAlign?.(k), }, })) } diff --git a/src/components/menus/components/TableBubbleMenu.tsx b/src/components/menus/components/TableBubbleMenu.tsx index ecc5797..0c2413d 100644 --- a/src/components/menus/components/TableBubbleMenu.tsx +++ b/src/components/menus/components/TableBubbleMenu.tsx @@ -100,7 +100,7 @@ function TableBubbleMenu(props: any) { tooltip-options={{ sideOffset: 15, }} - disabled={!props?.editor?.can().addColumnBefore()} + disabled={!props?.editor?.can()?.addColumnBefore?.()} /> @@ -129,7 +129,7 @@ function TableBubbleMenu(props: any) { tooltip-options={{ sideOffset: 15, }} - disabled={!props?.editor?.can().addRowBefore()} + disabled={!props?.editor?.can().addRowBefore?.()} /> @@ -186,7 +186,7 @@ function TableBubbleMenu(props: any) { tooltip-options={{ sideOffset: 15, }} - disabled={!props?.editor?.can().deleteTable()} + disabled={!props?.editor?.can()?.deleteTable?.()} />
diff --git a/src/extensions/TextAlign/TextAlign.ts b/src/extensions/TextAlign/TextAlign.ts index 21b25e6..d8f0d40 100644 --- a/src/extensions/TextAlign/TextAlign.ts +++ b/src/extensions/TextAlign/TextAlign.ts @@ -49,8 +49,8 @@ export const TextAlign = TiptapTextAlign.extend({ icon: iconMap[k], shortcutKeys: shortcutKeysMap[k], isActive: () => editor.isActive({ textAlign: k }) || false, - action: () => editor.commands.setTextAlign(k), - disabled: !editor.can().setTextAlign(k), + action: () => editor.commands?.setTextAlign?.(k), + disabled: !editor?.can?.()?.setTextAlign?.(k), })) const disabled = items.filter(k => k.disabled).length === items.length return {