Skip to content

Commit

Permalink
feat: add script support to isolated template (including formatting, …
Browse files Browse the repository at this point in the history
…rendering, store and snippets support)
  • Loading branch information
ala-n committed Jan 23, 2024
1 parent e435b3c commit 1c0aac8
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 22 deletions.
25 changes: 23 additions & 2 deletions src/core/base/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {SyntheticEventTarget} from '@exadel/esl/modules/esl-utils/dom';
import {decorate} from '@exadel/esl/modules/esl-utils/decorators';
import {microtask} from '@exadel/esl/modules/esl-utils/async';

import {UIPHtmlNormalizationService} from '../processors/normalization';
import {UIPNormalizationService} from '../processors/normalization';
import {UIPSnippetItem} from './snippet';

import type {UIPRoot} from './root';
Expand Down Expand Up @@ -42,18 +42,33 @@ export class UIPStateModel extends SyntheticEventTarget {
/** Snippets {@link UIPSnippetItem} value objects */
private _snippets: UIPSnippetItem[];

/** Current js state */
private _js: string = '';
/** Current markup state */
private _html = new DOMParser().parseFromString('', 'text/html').body;
/** Last {@link UIPPlugin} element which changed markup */
private _lastModifier: UIPPlugin | UIPRoot;

/**
* Sets current js state to the passed one
* @param js - new state
* @param modifier - plugin, that initiates the change
*/
public setJS(js: string, modifier: UIPPlugin | UIPRoot): void {
const script = UIPNormalizationService.normalize(js, false);
if (this._js === script) return;
this._js = script;
this._lastModifier = modifier;
this.dispatchChange();
}

/**
* Sets current markup state to the passed one
* @param markup - new state
* @param modifier - plugin, that initiates the change
*/
public setHtml(markup: string, modifier: UIPPlugin | UIPRoot): void {
const html = UIPHtmlNormalizationService.normalize(markup);
const html = UIPNormalizationService.normalize(markup);
const root = new DOMParser().parseFromString(html, 'text/html').body;
if (!root || root.innerHTML.trim() !== this.html.trim()) {
this._html = root;
Expand All @@ -62,6 +77,11 @@ export class UIPStateModel extends SyntheticEventTarget {
}
}

/** Current js state getter */
public get js(): string {
return this._js;
}

/** Current markup state getter */
public get html(): string {
return this._html ? this._html.innerHTML : '';
Expand Down Expand Up @@ -98,6 +118,7 @@ export class UIPStateModel extends SyntheticEventTarget {
if (!snippet) return;
this._snippets.forEach((s) => (s.active = s === snippet));
this.setHtml(snippet.html, modifier);
this.setJS(snippet.js, modifier);
this.dispatchEvent(
new CustomEvent('uip:model:snippet:change', {detail: this})
);
Expand Down
18 changes: 16 additions & 2 deletions src/core/base/snippet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ export type UIPSnippetTemplate = HTMLTemplateElement | HTMLScriptElement;
export class UIPSnippetItem {
public constructor(protected readonly $element: UIPSnippetTemplate) {}

@memoize()
public get $elementJS(): UIPSnippetTemplate | null {
const $root = this.$element.closest('uip-root') || document.body;
const selectors = [];
if (this.$element.id) selectors.push(`[uip-js-snippet="${this.$element.id}"]`);
if (this.label) selectors.push(`[uip-js-snippet="${this.label}"]`, `[uip-js-snippet][label="${this.label}"]`);
return $root.querySelector(selectors.join(',')) as UIPSnippetTemplate;
}

/** @returns snippet's label */
@memoize()
public get label(): string {
Expand All @@ -19,14 +28,19 @@ export class UIPSnippetItem {
return this.$element.innerHTML;
}

@memoize()
public get js(): string {
return this.$elementJS ? this.$elementJS.innerHTML : '';
}

/** @returns if the snippet is in active state */
public get active(): boolean {
return this.$element.hasAttribute('active');
}

/** Sets the snippet active state */
public set active(active: boolean) {
if (active) this.$element.setAttribute('active', '');
else this.$element.removeAttribute('active');
this.$element.toggleAttribute('active', active);
this.$elementJS?.toggleAttribute('active', active);
}
}
3 changes: 2 additions & 1 deletion src/core/preview/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ export class UIPPreview extends UIPPlugin {
}

const title = this.model!.activeSnippet?.label || 'UI Playground';
const script = this.model!.js;
const content = UIPRenderingPreprocessorService.preprocess(this.model!.html);
const html = UIPRenderingTemplatesService.render(this.isolationTemplate, {title, content});
const html = UIPRenderingTemplatesService.render(this.isolationTemplate, {title, content, script});

this.$iframe.contentWindow?.document.open();
this.$iframe.contentWindow?.document.write(html);
Expand Down
35 changes: 21 additions & 14 deletions src/core/processors/normalization.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
/** Function to process html string for normalization*/
export type UIPNormalizationProcessor = (input: string) => string;
export type UIPNormalizationProcessor = ((input: string) => string) & {
htmlOnly?: boolean;
};

export class UIPHtmlNormalizationService {
export class UIPNormalizationService {
/** Processor storage */
protected static processors: Record<string, UIPNormalizationProcessor> = {};

/** Add processor function to normalization process */
public static addProcessor(
name: string,
processor: UIPNormalizationProcessor
processor: UIPNormalizationProcessor,
htmlOnly = false
): void {
UIPHtmlNormalizationService.processors[name] = processor;
Object.assign(processor, {htmlOnly});
UIPNormalizationService.processors[name] = processor;
}

/** Normalizes passes html string by running all registered processors in chain */
public static normalize(html: string): string {
return Object.keys(UIPHtmlNormalizationService.processors)
.map((name) => UIPHtmlNormalizationService.processors[name])
/** Normalizes passes content string by running all registered processors in chain */
public static normalize(content: string, isHtml = true): string {
return Object.keys(UIPNormalizationService.processors)
.map((name) => UIPNormalizationService.processors[name])
.filter((processor) => typeof processor === 'function')
.reduce((input, processor) => processor(input), html);
.filter((processor) => !processor.htmlOnly || isHtml)
.reduce((input, processor) => processor(input), content);
}
}

/** Removes extra indents */
UIPHtmlNormalizationService.addProcessor(
UIPNormalizationService.addProcessor(
'remove-indent',
(input: string): string => {
// Get all indents from text
Expand All @@ -40,14 +45,16 @@ UIPHtmlNormalizationService.addProcessor(
);
/** Removes extra spaces */
// TODO: handle case with inline script with literal double spaces
UIPHtmlNormalizationService.addProcessor('remove-trailing', (input: string) =>
input.replace(/\s*?$/gm, '')
UIPNormalizationService.addProcessor(
'remove-trailing',
(input: string) => input.replace(/\s*?$/gm, ''),
true
);
/** Remove beginning spaces */
UIPHtmlNormalizationService.addProcessor('left-trim', (input: string) =>
UIPNormalizationService.addProcessor('left-trim', (input: string) =>
input.replace(/^\s+/, '')
);
/** Remove ending spaces */
UIPHtmlNormalizationService.addProcessor('right-trim', (input: string) =>
UIPNormalizationService.addProcessor('right-trim', (input: string) =>
input.replace(/\s+$/, '')
);
6 changes: 3 additions & 3 deletions src/core/processors/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {format} from '@exadel/esl/modules/esl-utils/misc';

interface UIPRenderingTemplateParams {
title?: string;
script?: string;
content: string;
[additional: string]: string | number | boolean | undefined | null;
}
Expand Down Expand Up @@ -31,9 +32,8 @@ UIPRenderingTemplatesService.add('default', `
<html>
<head>
<title>{title}</title>
<script type="module">{script}</script>
</head>
<body>
{content}
</body>
<body>{content}</body>
</html>
`);

0 comments on commit 1c0aac8

Please sign in to comment.