Browse Source

Allow to import HTML string as document

pull/5895/head
Artur Arseniev 2 years ago
parent
commit
8512f3eeff
  1. 7
      src/common/index.ts
  2. 18
      src/dom_components/model/ComponentWrapper.ts
  3. 24
      src/dom_components/model/Components.ts
  4. 9
      src/dom_components/model/types.ts
  5. 6
      src/utils/dom.ts

7
src/common/index.ts

@ -25,6 +25,13 @@ export type ObjectStrings = Record<string, string>;
export type Nullable = undefined | null | false; export type Nullable = undefined | null | false;
export interface OptionAsDocument {
/**
* Treat the HTML string as document (option valid on the root component, eg. will include `<head>`, `<html>`, etc.).
*/
asDocument?: boolean;
}
// https://github.com/microsoft/TypeScript/issues/29729#issuecomment-1483854699 // https://github.com/microsoft/TypeScript/issues/29729#issuecomment-1483854699
export type LiteralUnion<T, U> = T | (U & NOOP); export type LiteralUnion<T, U> = T | (U & NOOP);

18
src/dom_components/model/ComponentWrapper.ts

@ -1,3 +1,4 @@
import { attrToString } from '../../utils/dom';
import Component from './Component'; import Component from './Component';
import ComponentHead, { type as typeHead } from './ComponentHead'; import ComponentHead, { type as typeHead } from './ComponentHead';
import { ToHTMLOptions } from './types'; import { ToHTMLOptions } from './types';
@ -15,6 +16,7 @@ export default class ComponentWrapper extends Component {
traits: [], traits: [],
doctype: '', doctype: '',
head: null, head: null,
docEl: null,
stylable: [ stylable: [
'background', 'background',
'background-color', 'background-color',
@ -30,10 +32,13 @@ export default class ComponentWrapper extends Component {
constructor(...args: ConstructorParameters<typeof Component>) { constructor(...args: ConstructorParameters<typeof Component>) {
super(...args); super(...args);
const opts = args[1]; const opts = args[1];
const CmpHead = opts?.em?.Components.getType(typeHead)?.model; const cmp = opts?.em?.Components;
const CmpHead = cmp?.getType(typeHead)?.model;
const CmpDef = cmp?.getType('default').model;
if (CmpHead) { if (CmpHead) {
this.set({ this.set({
head: new CmpHead({}, opts), head: new CmpHead({}, opts),
docEl: new CmpDef({ tagName: 'html' }, opts),
}); });
} }
} }
@ -42,12 +47,19 @@ export default class ComponentWrapper extends Component {
return this.get('head'); return this.get('head');
} }
get docEl(): Component {
return this.get('docEl');
}
toHTML(opts: ToHTMLOptions = {}) { toHTML(opts: ToHTMLOptions = {}) {
const { asDocument } = opts; const { asDocument } = opts;
const { head, docEl } = this;
const { doctype = '' } = this.attributes; const { doctype = '' } = this.attributes;
const body = super.toHTML(opts); const body = super.toHTML(opts);
const head = (asDocument && this.head?.toHTML(opts)) || ''; const headStr = (asDocument && head?.toHTML(opts)) || '';
return asDocument ? `${doctype}${head}${body}` : body; const docElAttr = (asDocument && attrToString(docEl?.getAttrToHTML())) || '';
const docElAttrStr = docElAttr ? ` ${docElAttr}` : '';
return asDocument ? `${doctype}<html${docElAttrStr}>${headStr}${body}</html>` : body;
} }
__postAdd() { __postAdd() {

24
src/dom_components/model/Components.ts

@ -1,12 +1,13 @@
import { isEmpty, isArray, isString, isFunction, each, includes, extend, flatten, keys } from 'underscore'; import { isEmpty, isArray, isString, isFunction, each, includes, extend, flatten, keys } from 'underscore';
import Component from './Component'; import Component from './Component';
import { AddOptions, Collection, ObjectAny } from '../../common'; import { AddOptions, Collection, ObjectAny, OptionAsDocument } from '../../common';
import { DomComponentsConfig } from '../config/config'; import { DomComponentsConfig } from '../config/config';
import EditorModel from '../../editor/model/Editor'; import EditorModel from '../../editor/model/Editor';
import ComponentManager from '..'; import ComponentManager from '..';
import CssRule from '../../css_composer/model/CssRule'; import CssRule from '../../css_composer/model/CssRule';
import { ComponentAdd, ComponentProperties } from './types'; import { ComponentAdd, ComponentProperties } from './types';
import ComponentText from './ComponentText'; import ComponentText from './ComponentText';
import ComponentWrapper from './ComponentWrapper';
export const getComponentIds = (cmp?: Component | Component[] | Components, res: string[] = []) => { export const getComponentIds = (cmp?: Component | Component[] | Components, res: string[] = []) => {
if (!cmp) return []; if (!cmp) return [];
@ -228,12 +229,23 @@ Component> {
return new model(attrs, options) as Component; return new model(attrs, options) as Component;
} }
parseString(value: string, opt: AddOptions & { temporary?: boolean; keepIds?: string[] } = {}) { parseString(value: string, opt: AddOptions & OptionAsDocument & { temporary?: boolean; keepIds?: string[] } = {}) {
const { em, domc } = this; const { em, domc, parent } = this;
const asDocument = opt.asDocument && parent?.is('wrapper');
const cssc = em.Css; const cssc = em.Css;
const parsed = em.Parser.parseHtml(value); const parsed = em.Parser.parseHtml(value, { asDocument });
let components = parsed.html;
if (asDocument) {
const root = parent as ComponentWrapper;
components = (parsed.html as any).components;
root.head.set(parsed.head as any, opt);
root.docEl.set(parsed.root as any, opt);
root.set({ doctype: parsed.doctype });
}
// We need this to avoid duplicate IDs // We need this to avoid duplicate IDs
Component.checkId(parsed.html!, parsed.css, domc!.componentsById, opt); Component.checkId(components, parsed.css, domc!.componentsById, opt);
if (parsed.css && cssc && !opt.temporary) { if (parsed.css && cssc && !opt.temporary) {
const { at, ...optsToPass } = opt; const { at, ...optsToPass } = opt;
@ -243,7 +255,7 @@ Component> {
}); });
} }
return parsed.html; return components;
} }
/** @ts-ignore */ /** @ts-ignore */

9
src/dom_components/model/types.ts

@ -1,5 +1,5 @@
import Frame from '../../canvas/model/Frame'; import Frame from '../../canvas/model/Frame';
import { Nullable } from '../../common'; import { Nullable, OptionAsDocument } from '../../common';
import EditorModel from '../../editor/model/Editor'; import EditorModel from '../../editor/model/Editor';
import Selectors from '../../selector_manager/model/Selectors'; import Selectors from '../../selector_manager/model/Selectors';
import { TraitProperties } from '../../trait_manager/types'; import { TraitProperties } from '../../trait_manager/types';
@ -264,7 +264,7 @@ type ComponentAddType = Component | ComponentDefinition | ComponentDefinitionDef
export type ComponentAdd = ComponentAddType | ComponentAddType[]; export type ComponentAdd = ComponentAddType | ComponentAddType[];
export interface ToHTMLOptions { export interface ToHTMLOptions extends OptionAsDocument {
/** /**
* Custom tagName. * Custom tagName.
*/ */
@ -280,11 +280,6 @@ export interface ToHTMLOptions {
*/ */
altQuoteAttr?: boolean; altQuoteAttr?: boolean;
/**
* Return the HTML string as document (option valid on the root component, eg. will include the <head>).
*/
asDocument?: boolean;
/** /**
* You can pass an object of custom attributes to replace with the current ones * You can pass an object of custom attributes to replace with the current ones
* or you can even pass a function to generate attributes dynamically. * or you can even pass a function to generate attributes dynamically.

6
src/utils/dom.ts

@ -214,6 +214,12 @@ export const doctypeToString = (dt?: DocumentType | null) => {
return `<!DOCTYPE ${name}${pubId}${sysId}>`; return `<!DOCTYPE ${name}${pubId}${sysId}>`;
}; };
export const attrToString = (attrs: ObjectAny = {}) => {
const res: string[] = [];
each(attrs, (value, key) => res.push(`${key}="${value}"`));
return res.join(' ');
};
export const on = <E extends Event = Event>( export const on = <E extends Event = Event>(
el: EventTarget | EventTarget[], el: EventTarget | EventTarget[],
ev: string, ev: string,

Loading…
Cancel
Save