diff --git a/apps/client/package.json b/apps/client/package.json index f849241c..c5f6ecea 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -9,9 +9,9 @@ "preview": "vite preview" }, "dependencies": { - "@docmost/editor-ext": "workspace:*", "@casl/ability": "^6.7.1", "@casl/react": "^4.0.0", + "@docmost/editor-ext": "workspace:*", "@emoji-mart/data": "^1.2.1", "@emoji-mart/react": "^1.1.1", "@excalidraw/excalidraw": "^0.17.6", diff --git a/apps/client/src/components/icons/airtable-icon.tsx b/apps/client/src/components/icons/airtable-icon.tsx new file mode 100644 index 00000000..8ea04736 --- /dev/null +++ b/apps/client/src/components/icons/airtable-icon.tsx @@ -0,0 +1,32 @@ +import { rem } from '@mantine/core'; + +interface Props { + size?: number | string; +} + +export function AirtableIcon({ size }: Props) { + return ( + + + + + + + ); +} diff --git a/apps/client/src/components/icons/figma-icon.tsx b/apps/client/src/components/icons/figma-icon.tsx new file mode 100644 index 00000000..2ddd1ce0 --- /dev/null +++ b/apps/client/src/components/icons/figma-icon.tsx @@ -0,0 +1,23 @@ +import { rem } from '@mantine/core'; + +interface Props { + size?: number | string; +} + +export function FigmaIcon({ size }: Props) { + return ( + + + + + + + + + + ); +} diff --git a/apps/client/src/components/icons/framer-icon.tsx b/apps/client/src/components/icons/framer-icon.tsx new file mode 100644 index 00000000..4b76a0f3 --- /dev/null +++ b/apps/client/src/components/icons/framer-icon.tsx @@ -0,0 +1,17 @@ +import { rem } from '@mantine/core'; + +interface Props { + size?: number | string; +} + +export function FramerIcon({ size }: Props) { + return ( + + + + ); +} diff --git a/apps/client/src/components/icons/google-drive-icon.tsx b/apps/client/src/components/icons/google-drive-icon.tsx new file mode 100644 index 00000000..f19cecad --- /dev/null +++ b/apps/client/src/components/icons/google-drive-icon.tsx @@ -0,0 +1,24 @@ +import { rem } from '@mantine/core'; + +interface Props { + size?: number | string; +} + +export function GoogleDriveIcon({ size }: Props) { + return ( + + + + + + + + + ); +} diff --git a/apps/client/src/components/icons/index.ts b/apps/client/src/components/icons/index.ts new file mode 100644 index 00000000..089bf20e --- /dev/null +++ b/apps/client/src/components/icons/index.ts @@ -0,0 +1,10 @@ +export { AirtableIcon } from "./airtable-icon.tsx"; +export { FigmaIcon } from "./figma-icon.tsx"; +export { TypeformIcon } from "./typeform-icon.tsx"; +export { VimeoIcon } from "./vimeo-icon.tsx"; +export { MiroIcon } from "./miro-icon.tsx"; +export { GoogleDriveIcon } from "./google-drive-icon.tsx"; +export { FramerIcon } from "./framer-icon.tsx"; +export { LoomIcon } from "./loom-icon.tsx"; +export { YoutubeIcon } from "./youtube-icon.tsx"; + diff --git a/apps/client/src/components/icons/loom-icon.tsx b/apps/client/src/components/icons/loom-icon.tsx new file mode 100644 index 00000000..9899d701 --- /dev/null +++ b/apps/client/src/components/icons/loom-icon.tsx @@ -0,0 +1,19 @@ +import { rem } from '@mantine/core'; + +interface Props { + size?: number | string; +} + +export function LoomIcon({ size }: Props) { + return ( + + + + ); +} diff --git a/apps/client/src/components/icons/miro-icon.tsx b/apps/client/src/components/icons/miro-icon.tsx new file mode 100644 index 00000000..9d07898a --- /dev/null +++ b/apps/client/src/components/icons/miro-icon.tsx @@ -0,0 +1,18 @@ +import { rem } from '@mantine/core'; + +interface Props { + size?: number | string; +} + +export function MiroIcon({ size }: Props) { + return ( + + + + ); +} diff --git a/apps/client/src/components/icons/typeform-icon.tsx b/apps/client/src/components/icons/typeform-icon.tsx new file mode 100644 index 00000000..9d4b20a7 --- /dev/null +++ b/apps/client/src/components/icons/typeform-icon.tsx @@ -0,0 +1,18 @@ +import { rem } from '@mantine/core'; + +interface Props { + size?: number | string; +} + +export function TypeformIcon({ size }: Props) { + return ( + + + + ); +} diff --git a/apps/client/src/components/icons/vimeo-icon.tsx b/apps/client/src/components/icons/vimeo-icon.tsx new file mode 100644 index 00000000..a9c5c539 --- /dev/null +++ b/apps/client/src/components/icons/vimeo-icon.tsx @@ -0,0 +1,19 @@ +import { rem } from '@mantine/core'; + +interface Props { + size?: number | string; +} + +export function VimeoIcon({ size }: Props) { + return ( + + + + ); +} diff --git a/apps/client/src/components/icons/youtube-icon.tsx b/apps/client/src/components/icons/youtube-icon.tsx new file mode 100644 index 00000000..20111911 --- /dev/null +++ b/apps/client/src/components/icons/youtube-icon.tsx @@ -0,0 +1,19 @@ +import { rem } from '@mantine/core'; + +interface Props { + size?: number | string; +} + +export function YoutubeIcon({ size }: Props) { + return ( + + + + ); +} diff --git a/apps/client/src/features/editor/components/embed/embed-view.tsx b/apps/client/src/features/editor/components/embed/embed-view.tsx new file mode 100644 index 00000000..57b8de07 --- /dev/null +++ b/apps/client/src/features/editor/components/embed/embed-view.tsx @@ -0,0 +1,111 @@ +import { NodeViewProps, NodeViewWrapper } from "@tiptap/react"; +import { useMemo } from "react"; +import clsx from "clsx"; +import { ActionIcon, AspectRatio, Button, Card, FocusTrap, Group, Popover, Text, TextInput } from "@mantine/core"; +import { IconEdit } from "@tabler/icons-react"; +import { z } from "zod"; +import { useForm, zodResolver } from "@mantine/form"; +import { + getEmbedProviderById, + getEmbedUrlAndProvider +} from "@/features/editor/components/embed/providers.ts"; +import { notifications } from '@mantine/notifications'; + +const schema = z.object({ + url: z + .string().trim().url({ message: 'please enter a valid url' }), +}); + +export default function EmbedView(props: NodeViewProps) { + const { node, selected, updateAttributes } = props; + const { src, provider } = node.attrs; + + const embedUrl = useMemo(() => { + if (src) { + return getEmbedUrlAndProvider(src).embedUrl; + } + return null; + }, [src]); + + const embedForm = useForm<{ url: string }>({ + initialValues: { + url: "", + }, + validate: zodResolver(schema), + }); + + async function onSubmit(data: { url: string }) { + if (provider) { + const embedProvider = getEmbedProviderById(provider); + if (embedProvider.regex.test(data.url)) { + updateAttributes({ src: data.url }); + } else { + notifications.show({ + message: `Invalid ${provider} embed link`, + position: 'top-right', + color: 'red' + }); + } + } + } + + return ( + + {embedUrl ? ( + <> + + + + + + ) : ( + + + +
+ + + + + + Embed {getEmbedProviderById(provider).name} + +
+
+
+ +
+ + + + + + + +
+
+
+ )} +
+ ); +} diff --git a/apps/client/src/features/editor/components/embed/providers.ts b/apps/client/src/features/editor/components/embed/providers.ts new file mode 100644 index 00000000..6d928507 --- /dev/null +++ b/apps/client/src/features/editor/components/embed/providers.ts @@ -0,0 +1,109 @@ +export interface IEmbedProvider { + id: string; + name: string; + regex: RegExp; + getEmbedUrl: (match: RegExpMatchArray, url?: string) => string; +} + +export const embedProviders: IEmbedProvider[] = [ + { + id: 'loom', + name: 'Loom', + regex: /^https?:\/\/(?:www\.)?loom\.com\/(?:share|embed)\/([\da-zA-Z]+)\/?/, + getEmbedUrl: (match) => { + return `https://loom.com/embed/${match[1]}`; + } + }, + { + id: 'airtable', + name: 'Airtable', + regex: /^https:\/\/(www.)?airtable.com\/([a-zA-Z0-9]{2,})\/.*/, + getEmbedUrl: (match, url: string) => { + const path = url.split('airtable.com/'); + return `https://airtable.com/embed/${path[1]}`; + } + }, + { + id: 'figma', + name: 'Figma', + regex: /^https:\/\/[\w\.-]+\.?figma.com\/(file|proto|board|design|slides|deck)\/([0-9a-zA-Z]{22,128})/, + getEmbedUrl: (match, url: string) => { + return `https://www.figma.com/embed?url=${url}&embed_host=docmost`; + } + }, + { + 'id': 'typeform', + name: 'Typeform', + regex: /^(https?:)?(\/\/)?[\w\.]+\.typeform\.com\/to\/.+/, + getEmbedUrl: (match, url: string) => { + return url; + } + }, + { + id: 'miro', + name: 'Miro', + regex: /^https:\/\/(www\.)?miro\.com\/app\/board\/([\w-]+=)/, + getEmbedUrl: (match) => { + return `https://miro.com/app/live-embed/${match[2]}?embedMode=view_only_without_ui&autoplay=true&embedSource=docmost`; + } + }, + { + id: 'youtube', + name: 'YouTube', + regex: /^((?:https?:)?\/\/)?((?:www|m|music)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/, + getEmbedUrl: (match) => { + return `https://www.youtube-nocookie.com/embed/${match[5]}`; + } + }, + { + id: 'vimeo', + name: 'Vimeo', + regex: /^(https:)?\/\/(?:www\.|player\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|album\/(\d+)\/video\/|video\/|)(\d+)/, + getEmbedUrl: (match) => { + return `https://player.vimeo.com/video/${match[4]}`; + } + }, + { + id: 'framer', + name: 'Framer', + regex: /^https:\/\/(www\.)?framer\.com\/embed\/([\w-]+)/, + getEmbedUrl: (match, url: string) => { + return url; + } + }, + { + id: 'gdrive', + name: 'Google Drive', + regex: /^((?:https?:)?\/\/)?((?:www|m)\.)?(drive\.google\.com)\/file\/d\/([a-zA-Z0-9_-]+)\/.*$/, + getEmbedUrl: (match) => { + return `https://drive.google.com/file/d/${match[4]}/preview`; + } + }, +]; + +export function getEmbedProviderById(id: string) { + return embedProviders.find(provider => provider.id.toLowerCase() === id.toLowerCase()); +} + +export interface IEmbedResult { + embedUrl: string; + provider: string; +} + +export function getEmbedUrlAndProvider(url: string): IEmbedResult { + for (const provider of embedProviders) { + const match = url.match(provider.regex); + if (match) { + return { + embedUrl: provider.getEmbedUrl(match, url), + provider: provider.name.toLowerCase() + }; + } + } + return { + embedUrl: url, + provider: 'iframe', + }; +} + + diff --git a/apps/client/src/features/editor/components/slash-menu/menu-items.ts b/apps/client/src/features/editor/components/slash-menu/menu-items.ts index 00d993d0..c0a2b34a 100644 --- a/apps/client/src/features/editor/components/slash-menu/menu-items.ts +++ b/apps/client/src/features/editor/components/slash-menu/menu-items.ts @@ -29,6 +29,16 @@ import { uploadAttachmentAction } from "@/features/editor/components/attachment/ import IconExcalidraw from "@/components/icons/icon-excalidraw"; import IconMermaid from "@/components/icons/icon-mermaid"; import IconDrawio from "@/components/icons/icon-drawio"; +import { + AirtableIcon, + FigmaIcon, + FramerIcon, + GoogleDriveIcon, + LoomIcon, + MiroIcon, + TypeformIcon, + VimeoIcon, YoutubeIcon +} from "@/components/icons"; const CommandGroups: SlashMenuGroupedItemsType = { basic: [ @@ -343,7 +353,7 @@ const CommandGroups: SlashMenuGroupedItemsType = { day: "numeric", }); - return editor + editor .chain() .focus() .deleteRange(range) @@ -351,6 +361,87 @@ const CommandGroups: SlashMenuGroupedItemsType = { .run(); }, }, + { + title: "Airtable", + description: "Embed Airtable", + searchTerms: ["airtable"], + icon: AirtableIcon, + command: ({ editor, range }: CommandProps) => { + editor.chain().focus().deleteRange(range).setEmbed({ provider: 'airtable' }).run(); + }, + }, + { + title: "Loom", + description: "Embed Loom video", + searchTerms: ["loom"], + icon: LoomIcon, + command: ({ editor, range }: CommandProps) => { + editor.chain().focus().deleteRange(range).setEmbed({ provider: 'loom' }).run(); + }, + }, + { + title: "Figma", + description: "Embed Figma files", + searchTerms: ["figma"], + icon: FigmaIcon, + command: ({ editor, range }: CommandProps) => { + editor.chain().focus().deleteRange(range).setEmbed({ provider: 'figma' }).run(); + }, + }, + { + title: "Typeform", + description: "Embed Typeform", + searchTerms: ["typeform"], + icon: TypeformIcon, + command: ({ editor, range }: CommandProps) => { + editor.chain().focus().deleteRange(range).setEmbed({ provider: 'typeform' }).run(); + }, + }, + { + title: "Miro", + description: "Embed Miro board", + searchTerms: ["miro"], + icon: MiroIcon, + command: ({ editor, range }: CommandProps) => { + editor.chain().focus().deleteRange(range).setEmbed({ provider: 'miro' }).run(); + }, + }, + { + title: "YouTube", + description: "Embed YouTube video", + searchTerms: ["youtube", "yt"], + icon: YoutubeIcon, + command: ({ editor, range }: CommandProps) => { + editor.chain().focus().deleteRange(range).setEmbed({ provider: 'youtube' }).run(); + }, + }, + { + title: "Vimeo", + description: "Embed Vimeo video", + searchTerms: ["vimeo"], + icon: VimeoIcon, + command: ({ editor, range }: CommandProps) => { + editor.chain().focus().deleteRange(range).setEmbed({ provider: 'vimeo' }).run(); + }, + }, + { + title: "Framer", + description: "Embed Framer prototype", + searchTerms: ["framer"], + icon: FramerIcon, + command: ({ editor, range }: CommandProps) => { + editor.chain().focus().deleteRange(range).setEmbed({ provider: 'framer' }).run(); + }, + }, + { + title: "Google Drive", + description: "Embed Google Drive content", + searchTerms: ["google drive", "gdrive"], + icon: GoogleDriveIcon, + command: ({ editor, range }: CommandProps) => { + editor.chain().focus().deleteRange(range).setEmbed({ provider: 'gdrive' }).run(); + }, + }, ], }; @@ -362,7 +453,7 @@ export const getSuggestionItems = ({ const search = query.toLowerCase(); const filteredGroups: SlashMenuGroupedItemsType = {}; - const fuzzyMatch = (query, target) => { + const fuzzyMatch = (query: string, target: string) => { let queryIndex = 0; target = target.toLowerCase(); for (let char of target) { diff --git a/apps/client/src/features/editor/extensions/extensions.ts b/apps/client/src/features/editor/extensions/extensions.ts index 3399873e..573902dd 100644 --- a/apps/client/src/features/editor/extensions/extensions.ts +++ b/apps/client/src/features/editor/extensions/extensions.ts @@ -35,6 +35,7 @@ import { CustomCodeBlock, Drawio, Excalidraw, + Embed } from "@docmost/editor-ext"; import { randomElement, @@ -53,6 +54,7 @@ import AttachmentView from "@/features/editor/components/attachment/attachment-v import CodeBlockView from "@/features/editor/components/code-block/code-block-view.tsx"; import DrawioView from "../components/drawio/drawio-view"; import ExcalidrawView from "@/features/editor/components/excalidraw/excalidraw-view.tsx"; +import EmbedView from "@/features/editor/components/embed/embed-view.tsx"; import plaintext from "highlight.js/lib/languages/plaintext"; import powershell from "highlight.js/lib/languages/powershell"; import elixir from "highlight.js/lib/languages/elixir"; @@ -149,6 +151,7 @@ export const mainExtensions = [ DetailsSummary, DetailsContent, Youtube.configure({ + addPasteHandler: false, controls: true, nocookie: true, }), @@ -179,6 +182,9 @@ export const mainExtensions = [ Excalidraw.configure({ view: ExcalidrawView, }), + Embed.configure({ + view: EmbedView, + }) ] as any; type CollabExtensions = (provider: HocuspocusProvider, user: IUser) => any[]; diff --git a/apps/client/src/lib/utils.ts b/apps/client/src/lib/utils.ts index 9ce7d353..077c9d47 100644 --- a/apps/client/src/lib/utils.ts +++ b/apps/client/src/lib/utils.ts @@ -71,3 +71,7 @@ export function decodeBase64ToSvgString(base64Data: string): string { return decodeBase64(base64Data); } + +export function capitalizeFirstChar(string: string) { + return string.charAt(0).toUpperCase() + string.slice(1); +} \ No newline at end of file diff --git a/apps/server/src/collaboration/collaboration.util.ts b/apps/server/src/collaboration/collaboration.util.ts index bb956d91..0ceafe88 100644 --- a/apps/server/src/collaboration/collaboration.util.ts +++ b/apps/server/src/collaboration/collaboration.util.ts @@ -1,15 +1,15 @@ -import { StarterKit } from '@tiptap/starter-kit'; -import { TextAlign } from '@tiptap/extension-text-align'; -import { TaskList } from '@tiptap/extension-task-list'; -import { TaskItem } from '@tiptap/extension-task-item'; -import { Underline } from '@tiptap/extension-underline'; -import { Superscript } from '@tiptap/extension-superscript'; +import {StarterKit} from '@tiptap/starter-kit'; +import {TextAlign} from '@tiptap/extension-text-align'; +import {TaskList} from '@tiptap/extension-task-list'; +import {TaskItem} from '@tiptap/extension-task-item'; +import {Underline} from '@tiptap/extension-underline'; +import {Superscript} from '@tiptap/extension-superscript'; import SubScript from '@tiptap/extension-subscript'; -import { Highlight } from '@tiptap/extension-highlight'; -import { Typography } from '@tiptap/extension-typography'; -import { TextStyle } from '@tiptap/extension-text-style'; -import { Color } from '@tiptap/extension-color'; -import { Youtube } from '@tiptap/extension-youtube'; +import {Highlight} from '@tiptap/extension-highlight'; +import {Typography} from '@tiptap/extension-typography'; +import {TextStyle} from '@tiptap/extension-text-style'; +import {Color} from '@tiptap/extension-color'; +import {Youtube} from '@tiptap/extension-youtube'; import Table from '@tiptap/extension-table'; import TableHeader from '@tiptap/extension-table-header'; import { @@ -30,13 +30,14 @@ import { Attachment, Drawio, Excalidraw, + Embed } from '@docmost/editor-ext'; -import { generateText, JSONContent } from '@tiptap/core'; -import { generateHTML } from '../common/helpers/prosemirror/html'; +import {generateText, JSONContent} from '@tiptap/core'; +import {generateHTML} from '../common/helpers/prosemirror/html'; // @tiptap/html library works best for generating prosemirror json state but not HTML // see: https://github.com/ueberdosis/tiptap/issues/5352 // see:https://github.com/ueberdosis/tiptap/issues/4089 -import { generateJSON } from '@tiptap/html'; +import {generateJSON} from '@tiptap/html'; export const tiptapExtensions = [ StarterKit.configure({ @@ -72,6 +73,7 @@ export const tiptapExtensions = [ CustomCodeBlock, Drawio, Excalidraw, + Embed ] as any; export function jsonToHtml(tiptapJson: any) { diff --git a/packages/editor-ext/src/index.ts b/packages/editor-ext/src/index.ts index 80e2035f..a9095ce2 100644 --- a/packages/editor-ext/src/index.ts +++ b/packages/editor-ext/src/index.ts @@ -14,4 +14,5 @@ export * from "./lib/attachment"; export * from "./lib/custom-code-block" export * from "./lib/drawio"; export * from "./lib/excalidraw"; +export * from "./lib/embed"; diff --git a/packages/editor-ext/src/lib/embed.ts b/packages/editor-ext/src/lib/embed.ts new file mode 100644 index 00000000..04181fa5 --- /dev/null +++ b/packages/editor-ext/src/lib/embed.ts @@ -0,0 +1,122 @@ +import { Node, mergeAttributes } from '@tiptap/core'; +import { ReactNodeViewRenderer } from '@tiptap/react'; + +export interface EmbedOptions { + HTMLAttributes: Record; + view: any; +} +export interface EmbedAttributes { + src?: string; + provider: string; + align?: string; + width?: number; + height?: number; +} + +declare module '@tiptap/core' { + interface Commands { + embeds: { + setEmbed: (attributes?: EmbedAttributes) => ReturnType; + }; + } +} + +export const Embed = Node.create({ + name: 'embed', + inline: false, + group: 'block', + isolating: true, + atom: true, + defining: true, + draggable: true, + + addOptions() { + return { + HTMLAttributes: {}, + view: null, + }; + }, + addAttributes() { + return { + src: { + default: '', + parseHTML: (element) => element.getAttribute('data-src'), + renderHTML: (attributes: EmbedAttributes) => ({ + 'data-src': attributes.src, + }), + }, + provider: { + default: '', + parseHTML: (element) => element.getAttribute('data-provider'), + renderHTML: (attributes: EmbedAttributes) => ({ + 'data-provider': attributes.provider, + }), + }, + align: { + default: 'center', + parseHTML: (element) => element.getAttribute('data-align'), + renderHTML: (attributes: EmbedAttributes) => ({ + 'data-align': attributes.align, + }), + }, + width: { + default: 640, + parseHTML: (element) => element.getAttribute('data-width'), + renderHTML: (attributes: EmbedAttributes) => ({ + 'data-width': attributes.width, + }), + }, + height: { + default: 480, + parseHTML: (element) => element.getAttribute('data-height'), + renderHTML: (attributes: EmbedAttributes) => ({ + 'data-height': attributes.height, + }), + }, + }; + }, + + parseHTML() { + return [ + { + tag: `div[data-type="${this.name}"]`, + }, + ]; + }, + + renderHTML({ HTMLAttributes }) { + return [ + "div", + mergeAttributes( + { "data-type": this.name }, + this.options.HTMLAttributes, + HTMLAttributes, + ), + [ + "a", + { + href: HTMLAttributes["data-src"], + target: "blank", + }, + `${HTMLAttributes["data-src"]}`, + ], + ]; + }, + + addCommands() { + return { + setEmbed: + (attrs: EmbedAttributes) => + ({ commands }) => { + return commands.insertContent({ + type: 'embed', + attrs: attrs, + }); + }, + }; + }, + + addNodeView() { + return ReactNodeViewRenderer(this.options.view); + }, +}); diff --git a/packages/editor-ext/src/lib/link.ts b/packages/editor-ext/src/lib/link.ts index 7aa4aac7..6fe3e5a2 100644 --- a/packages/editor-ext/src/lib/link.ts +++ b/packages/editor-ext/src/lib/link.ts @@ -1,4 +1,3 @@ -import { mergeAttributes } from "@tiptap/core"; import TiptapLink from "@tiptap/extension-link"; import { Plugin } from "@tiptap/pm/state"; import { EditorView } from "@tiptap/pm/view"; @@ -6,24 +5,6 @@ import { EditorView } from "@tiptap/pm/view"; export const LinkExtension = TiptapLink.extend({ inclusive: false, - parseHTML() { - return [ - { - tag: 'a[href]:not([data-type="button"]):not([href *= "javascript:" i])', - }, - ]; - }, - - renderHTML({ HTMLAttributes }) { - return [ - "a", - mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, { - class: "link", - }), - 0, - ]; - }, - addProseMirrorPlugins() { const { editor } = this; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ab9aa933..b201c7e1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3480,10 +3480,10 @@ packages: peerDependencies: '@tiptap/core': ^2.6.6 - '@tiptap/extension-paragraph@2.6.6': - resolution: {integrity: sha512-fD/onCr16UQWx+/xEmuFC2MccZZ7J5u4YaENh8LMnAnBXf78iwU7CAcmuc9rfAEO3qiLoYGXgLKiHlh2ZfD4wA==} + '@tiptap/extension-paragraph@2.8.0': + resolution: {integrity: sha512-XgxxNNbuBF48rAGwv7/s6as92/xjm/lTZIGTq9aG13ClUKFtgdel7C33SpUCcxg3cO2WkEyllXVyKUiauFZw/A==} peerDependencies: - '@tiptap/core': ^2.6.6 + '@tiptap/core': ^2.7.0 '@tiptap/extension-placeholder@2.6.6': resolution: {integrity: sha512-J0ZMvF93NsRrt+R7IQ3GhxNq32vq+88g25oV/YFJiwvC48HMu1tQB6kG1I3LJpu5b8lN+LnfANNqDOEhiBfjaA==} @@ -11543,7 +11543,7 @@ snapshots: dependencies: '@tiptap/core': 2.6.6(@tiptap/pm@2.6.6) - '@tiptap/extension-paragraph@2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))': + '@tiptap/extension-paragraph@2.8.0(@tiptap/core@2.6.6(@tiptap/pm@2.6.6))': dependencies: '@tiptap/core': 2.6.6(@tiptap/pm@2.6.6) @@ -11670,7 +11670,7 @@ snapshots: '@tiptap/extension-italic': 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6)) '@tiptap/extension-list-item': 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6)) '@tiptap/extension-ordered-list': 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6)) - '@tiptap/extension-paragraph': 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6)) + '@tiptap/extension-paragraph': 2.8.0(@tiptap/core@2.6.6(@tiptap/pm@2.6.6)) '@tiptap/extension-strike': 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6)) '@tiptap/extension-text': 2.6.6(@tiptap/core@2.6.6(@tiptap/pm@2.6.6)) '@tiptap/pm': 2.6.6