import { Arr, Obj, Type } from '@ephox/katamari'; import { Compare, PredicateFilter, PredicateFind, Remove, SelectorFilter, SugarElement, SugarElements, SugarNode, Traverse } from '@ephox/sugar'; import AstNode from '../api/html/Node'; import Schema, { SchemaMap } from '../api/html/Schema'; import * as Empty from '../dom/Empty'; import * as NodeType from '../dom/NodeType'; export const transparentBlockAttr = 'data-mce-block'; // Returns the lowercase element names form a SchemaMap by excluding anyone that has uppercase letters. // This method is to avoid having to specify all possible valid characters other than lowercase a-z such as '-' or ':' etc. export const elementNames = (map: SchemaMap): string[] => Arr.filter(Obj.keys(map), (key) => !/[A-Z]/.test(key)); const makeSelectorFromSchemaMap = (map: SchemaMap) => elementNames(map).join(','); const updateTransparent = (blocksSelector: string, transparent: Element) => { if (Type.isNonNullable(transparent.querySelector(blocksSelector))) { transparent.setAttribute(transparentBlockAttr, 'true'); if (transparent.getAttribute('data-mce-selected') === 'inline-boundary') { transparent.removeAttribute('data-mce-selected'); } return true; } else { transparent.removeAttribute(transparentBlockAttr); return false; } }; const updateBlockStateOnChildren = (schema: Schema, scope: Element): Element[] => { const transparentSelector = makeSelectorFromSchemaMap(schema.getTransparentElements()); const blocksSelector = makeSelectorFromSchemaMap(schema.getBlockElements()); return Arr.filter(scope.querySelectorAll(transparentSelector), (transparent) => updateTransparent(blocksSelector, transparent)); }; const trimEdge = (el: DocumentFragment, leftSide: boolean) => { const childPropertyName = leftSide ? 'lastChild' : 'firstChild'; for (let child = el[childPropertyName]; child; child = child[childPropertyName]) { if (Empty.isEmpty(SugarElement.fromDom(child))) { child.parentNode?.removeChild(child); return; } } }; const split = (parentElm: Element, splitElm: Node) => { const range = document.createRange(); const parentNode = parentElm.parentNode; if (parentNode) { range.setStartBefore(parentElm); range.setEndBefore(splitElm); const beforeFragment = range.extractContents(); trimEdge(beforeFragment, true); range.setStartAfter(splitElm); range.setEndAfter(parentElm); const afterFragment = range.extractContents(); trimEdge(afterFragment, false); if (!Empty.isEmpty(SugarElement.fromDom(beforeFragment))) { parentNode.insertBefore(beforeFragment, parentElm); } if (!Empty.isEmpty(SugarElement.fromDom(splitElm))) { parentNode.insertBefore(splitElm, parentElm); } if (!Empty.isEmpty(SugarElement.fromDom(afterFragment))) { parentNode.insertBefore(afterFragment, parentElm); } parentNode.removeChild(parentElm); } }; // This will find invalid blocks wrapped in anchors and split them out so for example //
|
it would use the P as the scope Arr.get(parents, parents.length - 2).filter(SugarNode.isElement).fold( () => updateChildren(schema, root), (scope) => updateChildren(schema, scope.dom) ); }; export const hasBlockAttr = (el: Element): boolean => el.hasAttribute(transparentBlockAttr); export const isTransparentElementName = (schema: Schema, name: string): boolean => Obj.has(schema.getTransparentElements(), name); const isTransparentElement = (schema: Schema, node: Node | null | undefined): node is Element => NodeType.isElement(node) && isTransparentElementName(schema, node.nodeName); export const isTransparentBlock = (schema: Schema, node: Node | null | undefined): node is Element => isTransparentElement(schema, node) && hasBlockAttr(node); export const isTransparentInline = (schema: Schema, node: Node | null | undefined): node is Element => isTransparentElement(schema, node) && !hasBlockAttr(node); export const isTransparentAstBlock = (schema: Schema, node: AstNode): boolean => node.type === 1 && isTransparentElementName(schema, node.name) && Type.isString(node.attr(transparentBlockAttr)); export const isTransparentAstInline = (schema: Schema, node: AstNode): boolean => node.type === 1 && isTransparentElementName(schema, node.name) && Type.isUndefined(node.attr(transparentBlockAttr));