generated from diplodoc-platform/package-template
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathEmbeddedContentRootController.ts
128 lines (102 loc) · 4.49 KB
/
EmbeddedContentRootController.ts
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import {nanoid} from 'nanoid';
import {EmbeddingMode, EmbedsConfig, HTMLRuntimeConfig} from '../types';
import {Disposable} from '../utils';
import {HTML_RUNTIME_CONFIG_SYMBOL} from '../constants';
import {EmbeddedIFrameController} from './EmbeddedIFrameController';
import {IEmbeddedContentController} from './IEmbeddedContentController';
import {ShadowRootController} from './ShadowRootController';
import {SrcDocIFrameController} from './SrcDocIFrameController';
import {IHTMLIFrameElementConfig} from '.';
const findAllSrcDocEmbeds = (scope: ParentNode) =>
scope.querySelectorAll<HTMLIFrameElement>('iframe[data-yfm-sandbox-mode=srcdoc]');
const findAllShadowContainers = (scope: ParentNode) =>
scope.querySelectorAll<HTMLDivElement>('div[data-yfm-sandbox-mode=shadow]');
const findAllIFrameEmbeds = (scope: ParentNode) =>
scope.querySelectorAll<HTMLIFrameElement>('iframe[data-yfm-sandbox-mode=isolated]');
const embedFinders: Record<EmbeddingMode, (scope: ParentNode) => NodeListOf<HTMLElement>> = {
srcdoc: findAllSrcDocEmbeds,
shadow: findAllShadowContainers,
isolated: findAllIFrameEmbeds,
};
const modeToController: Record<
EmbeddingMode,
new (node: HTMLElement, config: IHTMLIFrameElementConfig) => IEmbeddedContentController
> = {
srcdoc: SrcDocIFrameController,
shadow: ShadowRootController,
isolated: EmbeddedIFrameController,
};
// Finds all iframes and creates controllers for each iframe
export class EmbeddedContentRootController extends Disposable {
private children: Map<string, IEmbeddedContentController> = new Map();
private config: EmbedsConfig;
private document: Document;
private runtimeConfig: HTMLRuntimeConfig;
constructor(
document: Document,
config: EmbedsConfig = {
classNames: [],
styles: {},
},
) {
super();
this.config = config;
this.document = document;
this.runtimeConfig = window[HTML_RUNTIME_CONFIG_SYMBOL] || {};
// initialize on DOM ready
this.document.addEventListener('DOMContentLoaded', () => this.initialize());
this.dispose.add(() => {
this.document.removeEventListener('DOMContentLoaded', () => this.initialize());
});
this.dispose.add(() => this.disposeChildren());
}
initialize = async (configOverrideForThisInitCycle?: EmbedsConfig) => {
const {disabledModes} = this.runtimeConfig;
// MAJOR: separate runtime controllers and chunks, so the consumer could
// import only one runtime mode: import('@diplodoc/html-extension/runtime/srcdoc');
const embeds = Object.keys(embedFinders).reduce<HTMLElement[]>((result, current) => {
const modeKey = current as EmbeddingMode;
if (!disabledModes?.includes(modeKey)) {
return result.concat(...embedFinders[modeKey](this.document));
}
return result;
}, []);
const dirtyEmbeds = embeds.filter(
(el) =>
typeof el.dataset.yfmEmbedId !== 'string' ||
!this.children.has(el.dataset.yfmEmbedId),
);
const instantiatedControllers = dirtyEmbeds.map((embed) => {
const embedId = nanoid();
const mode = embed.dataset.yfmSandboxMode as EmbeddingMode; // this cast is safe at this point
const ControllerCtor = modeToController[mode];
const instance = new ControllerCtor(
embed,
configOverrideForThisInitCycle ?? this.config,
);
embed.dataset.yfmEmbedId = embedId;
this.children.set(embedId, instance);
return instance;
});
return Promise.all(instantiatedControllers.map((ctrller) => ctrller.initialize()));
};
get blocks(): IEmbeddedContentController[] {
return Array.from(this.children.values());
}
/**
* Set the config object that will be passed to child embeds during an initialization cycle.
* Please note that changes made via a call to this method would be only reflected during next initialization cycles.
* @param config
* @returns {void}
*/
setConfig(config: EmbedsConfig) {
this.config = config;
}
forEach(callback: (controller: IEmbeddedContentController) => void) {
return this.children.forEach((controller) => callback(controller));
}
disposeChildren = () => {
this.children.forEach((controller) => controller.dispose());
this.children.clear();
};
}