Dojo NG is being built in the open, one section at a time — follow along on Heptapod.

API REFERENCE · RICH TEXT PLUGINS

@dojo-ng/rich-text-embed

Embedded media for <dj-rich-text>: YouTube and Vimeo videos, and video or audio files, from a pasted URL.

Try it in the playground

@dojo-ng/rich-text-embed · v0.1.1 · A plugin for <dj-rich-text>, not a custom element

npm install @dojo-ng/rich-text-embed

Compose the embed plugin with the default set. Load video.js at the document level for dj-video. Click the embed button and paste a YouTube/Vimeo URL or a direct media file URL.

<!-- App prerequisite for dj-video: load video.js's stylesheet once, in the page head. -->
<link rel="stylesheet" href="https://vjs.zencdn.net/8.10.0/video-js.css" />

<dj-rich-text id="editor" label="Article"></dj-rich-text>
<script type="module">
  import "@dojo-ng/rich-text";
  import { defaultPlugins } from "@dojo-ng/rich-text";
  import { embedPlugin } from "@dojo-ng/rich-text-embed";
  document.getElementById("editor").plugins = [...defaultPlugins, embedPlugin];
</script>

Inserting

  • The toolbar button opens a dialog for a media URL. An unsupported link shows an error and the dialog stays open.
  • Matchers are tried in order. YouTube (watch, youtu.be, shorts, and embed URLs) and Vimeo become privacy-enhanced iframes (youtube-nocookie.com, player.vimeo.com).
  • A video file (.mp4, .webm, .m3u8, .mov) uses dj-video, and an audio file (.mp3, .m4a, .ogg, .wav, .flac) uses dj-audio.
  • dj-video needs the video.js stylesheet and video.js loaded at the document level.

Setup

  • Exports embedPlugin, createEmbedPlugin({ matchers? }), EmbedNode, $createEmbedNode, $isEmbedNode, INSERT_EMBED_COMMAND, defaultMatchers, and the EmbedMatcher and EmbedPayload types.
  • There is no matcher for any iframe. Whether to allow other iframes is your decision: add your own matcher with matchers.
  • Setting plugins replaces the default set, so spread ...defaultPlugins to keep bold, italic, underline, undo, and redo.

HTML and pasting

  • An embed exports as <div data-dj-embed data-src [data-title]> around a plain <a href>, so a site can render it from the data attributes or show the link.
  • Embeds survive the value round trip.
  • Paste cleaning removes iframes and the embed's attributes, so a pasted embed becomes a plain link. Embeds come in through the dialog, the command, or value.

Not built

  • Titles and thumbnails (oEmbed), autoplay options, a matcher for any iframe, and resizing or alignment.

Need one of these? Make a request on Discord or add an issue (work item) on Heptapod.

Examples

Add a custom matcher

Pass matchers to support more hosts. A matcher is { kind, match(url) } returning { kind, src, title? } or undefined; defaultMatchers are the built-ins.

import { createEmbedPlugin, defaultMatchers } from "@dojo-ng/rich-text-embed";

const loom = {
  kind: "loom",
  match: (url) => {
    const m = /loom\.com\/share\/(\w+)/.exec(url);
    return m ? { kind: "video", src: url } : undefined;
  },
};
const embed = createEmbedPlugin({ matchers: [loom, ...defaultMatchers] });

Functions

createEmbedPlugin

function createEmbedPlugin(options?: EmbedPluginOptions): RichTextPlugin;

embedHref

function embedHref(payload: EmbedPayload): string;

matchEmbed

function matchEmbed(url: string, matchers?: EmbedMatcher[]): EmbedPayload | undefined;

Classes

EmbedNode

class EmbedNode extends DecoratorNode<HTMLElement> {
  __kind: string;
  __src: string;
  __title?: string;
  static getType(): string;
  static clone(node: EmbedNode): EmbedNode;
  static importJSON(serialized: SerializedEmbedNode): EmbedNode;
  static importDOM(): DOMConversionMap | null;
  constructor(payload: EmbedNodePayload);
  exportJSON(): SerializedEmbedNode;
  createDOM(_config: EditorConfig): HTMLElement;
  updateDOM(): boolean;
  exportDOM(): DOMExportOutput;
  decorate(editor: LexicalEditor): HTMLElement;
  getKindValue(): string;
  getSrc(): string;
  getTitle(): string | undefined;
}

Values

defaultMatchers

const defaultMatchers: EmbedMatcher[];

embedPlugin

const embedPlugin: RichTextPlugin;

INSERT_EMBED_COMMAND

const INSERT_EMBED_COMMAND: LexicalCommand<EmbedPayload>;

Types

EmbedMatcher

interface EmbedMatcher {
  kind: string;
  match(url: string): EmbedPayload | undefined;
}

EmbedNodePayload

interface EmbedNodePayload extends EmbedPayload {
  key?: NodeKey;
}

EmbedPayload

interface EmbedPayload {
  kind: string;
  src: string;
  title?: string;
}

EmbedPluginOptions

interface EmbedPluginOptions {
  matchers?: EmbedMatcher[];
}

SerializedEmbedNode

type SerializedEmbedNode = Spread<{
  kind: string;
  src: string;
  title?: string;
}, SerializedLexicalNode>;