import Token from "markdown-it/lib/token"; import { NodeSpec, NodeType, Node as ProsemirrorNode } from "prosemirror-model"; import { Command } from "prosemirror-state"; import * as React from "react"; import { Primitive } from "utility-types"; import { sanitizeUrl } from "../../utils/urls"; import EmbedComponent from "../components/Embed"; import defaultEmbeds from "../embeds"; import { getMatchingEmbed } from "../lib/embeds"; import { MarkdownSerializerState } from "../lib/markdown/serializer"; import embedsRule from "../rules/embeds"; import { ComponentProps } from "../types"; import Node from "./Node"; export default class Embed extends Node { get name() { return "embed"; } get schema(): NodeSpec { return { content: "inline*", group: "block", atom: true, attrs: { href: {}, }, parseDOM: [ { tag: "iframe", getAttrs: (dom: HTMLIFrameElement) => { const embeds = this.editor?.props.embeds ?? defaultEmbeds; const href = dom.getAttribute("data-canonical-url") || ""; const response = getMatchingEmbed(embeds, href); if (response) { return { href, }; } return false; }, }, { tag: "a.embed", getAttrs: (dom: HTMLAnchorElement) => ({ href: dom.getAttribute("href"), }), }, ], toDOM: (node) => { const embeds = this.editor?.props.embeds ?? defaultEmbeds; const response = getMatchingEmbed(embeds, node.attrs.href); const src = response?.embed.transformMatch?.(response.matches); if (src) { return [ "iframe", { class: "embed", frameborder: "0", src: sanitizeUrl(src), contentEditable: "false", allowfullscreen: "true", "data-canonical-url": sanitizeUrl(node.attrs.href), }, ]; } else { return [ "a", { class: "embed", href: sanitizeUrl(node.attrs.href), contentEditable: "false", "data-canonical-url": sanitizeUrl(node.attrs.href), }, response?.embed.title ?? node.attrs.href, ]; } }, toPlainText: (node) => node.attrs.href, }; } get rulePlugins() { return [embedsRule(this.options.embeds)]; } component(props: ComponentProps) { const { embeds, embedsDisabled } = this.editor.props; return ( ); } commands({ type }: { type: NodeType }) { return (attrs: Record): Command => (state, dispatch) => { dispatch?.( state.tr.replaceSelectionWith(type.create(attrs)).scrollIntoView() ); return true; }; } toMarkdown(state: MarkdownSerializerState, node: ProsemirrorNode) { if (!state.inTable) { state.ensureNewLine(); } const href = node.attrs.href.replace(/_/g, "%5F"); state.write( "[" + state.esc(href, false) + "](" + state.esc(href, false) + ")" ); if (!state.inTable) { state.write("\n\n"); } } parseMarkdown() { return { node: "embed", getAttrs: (token: Token) => ({ href: token.attrGet("href"), }), }; } }