feat: third-party embeds (#423)

* wip

* Add more providers

* icons

* unify embed providers (Youtube)

* fix case

* YT music

* remove redundant code
This commit is contained in:
Philip Okugbe
2024-10-29 18:13:20 +00:00
committed by GitHub
parent 978fadd6b9
commit ab70cee278
21 changed files with 667 additions and 41 deletions

View File

@ -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";

View File

@ -0,0 +1,122 @@
import { Node, mergeAttributes } from '@tiptap/core';
import { ReactNodeViewRenderer } from '@tiptap/react';
export interface EmbedOptions {
HTMLAttributes: Record<string, any>;
view: any;
}
export interface EmbedAttributes {
src?: string;
provider: string;
align?: string;
width?: number;
height?: number;
}
declare module '@tiptap/core' {
interface Commands<ReturnType> {
embeds: {
setEmbed: (attributes?: EmbedAttributes) => ReturnType;
};
}
}
export const Embed = Node.create<EmbedOptions>({
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);
},
});

View File

@ -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;