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