Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | 2x 2x 2x 16x 2x 4x 4x 4x 4x 4x 4x 3x 2x 2x 2x 2x 1x 1x 1x 1x 1x 4x 2x 7x 7x 7x 14x 7x 3x 2x 4x 4x 4x 4x 4x 4x 4x 2x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 1x 1x 1x 1x 1x 2x 1x 1x 1x 2x 8x 13x 8x 1x 8x 1x 8x 1x 1x 1x 8x | import {Editor, Element as SlateElement, Node, Path, Transforms} from 'slate';
import {insertVoidBlock} from './voidInsert';
import type {CustomElement, CustomText, EmbedElement} from '../types';
export const isEmbedElement = (node: unknown): node is EmbedElement =>
SlateElement.isElement(node) && node.type === 'embed';
export const normalizeEmbedUrl = (href: string): string => {
const value = href.trim();
Iif (!value) return '';
const nextValue = /^[a-z][a-z0-9+.-]*:/i.test(value) ? value : `https://${value}`;
try {
const url = new URL(nextValue);
if (url.protocol !== 'http:' && url.protocol !== 'https:') return '';
return url.toString();
} catch {
return '';
}
};
const normalizeCaption = (caption?: string): string | undefined => {
const value = caption?.trim();
return value ? value : undefined;
};
const createEmbedElement = (url: string, caption?: string): EmbedElement => {
const children: CustomText[] = [{text: ''}];
const element: EmbedElement = {
type: 'embed',
url,
children,
};
const nextCaption = normalizeCaption(caption);
Iif (nextCaption) element.caption = nextCaption;
return element;
};
const createParagraphElement = (): CustomElement => ({
type: 'p',
children: [{text: ''}],
});
export const getActiveEmbedEntry = (editor: Editor): [EmbedElement, Path] | null => {
const {selection} = editor;
Iif (!selection) return null;
const match = Editor.above(editor, {
at: Editor.unhangRange(editor, selection),
match: (node) => isEmbedElement(node),
});
return (match as [EmbedElement, Path] | undefined) ?? null;
};
export const getActiveEmbed = (editor: Editor): EmbedElement | null => getActiveEmbedEntry(editor)?.[0] ?? null;
export const insertParagraphNearActiveEmbed = (editor: Editor, position: 'above' | 'below' = 'below'): Path | null => {
const entry = getActiveEmbedEntry(editor);
Iif (!entry) return null;
const [, path] = entry;
const targetPath = position === 'above' ? path : Path.next(path);
Transforms.insertNodes(editor, createParagraphElement(), {at: targetPath});
Transforms.select(editor, Editor.start(editor, targetPath));
return targetPath;
};
export const updateEmbedAtPath = (editor: Editor, path: Path, url: string, caption?: string): boolean => {
const normalizedUrl = normalizeEmbedUrl(url);
Iif (!normalizedUrl) return false;
Iif (!Node.has(editor, path)) return false;
const node = Node.get(editor, path);
Iif (!isEmbedElement(node)) return false;
Transforms.setNodes(editor, {url: normalizedUrl} as Partial<EmbedElement>, {at: path});
const nextCaption = normalizeCaption(caption);
if (nextCaption) Transforms.setNodes(editor, {caption: nextCaption} as Partial<EmbedElement>, {at: path});
else ETransforms.unsetNodes(editor, 'caption', {at: path});
return true;
};
export const removeEmbedAtPath = (editor: Editor, path: Path): boolean => {
Iif (!Node.has(editor, path)) return false;
const node = Node.get(editor, path);
Iif (!isEmbedElement(node)) return false;
Transforms.removeNodes(editor, {at: path});
return true;
};
export const insertEmbed = (editor: Editor, url: string, caption?: string): EmbedElement | null => {
const normalizedUrl = normalizeEmbedUrl(url);
Iif (!normalizedUrl) return null;
return insertVoidBlock(editor, createEmbedElement(normalizedUrl, caption));
};
export const withEmbeds = <T extends Editor>(editor: T): T => {
const {isVoid, insertBreak, insertSoftBreak, insertText} = editor;
editor.isVoid = (element) => (element.type === 'embed' ? true : isVoid(element));
editor.insertBreak = () => {
Eif (insertParagraphNearActiveEmbed(editor, 'below')) return;
insertBreak();
};
editor.insertSoftBreak = () => {
Eif (insertParagraphNearActiveEmbed(editor, 'above')) return;
insertSoftBreak();
};
editor.insertText = (text) => {
Eif (text && insertParagraphNearActiveEmbed(editor, 'below')) {
insertText(text);
return;
}
insertText(text);
};
return editor;
};
|