@sitecore-feaas/clientside 0.1.27 → 0.1.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cdn.js +115 -0
- package/build/cdn.js.map +1 -0
- package/build/components/FEAASBase.js +212 -79
- package/build/components/FEAASBase.js.map +1 -1
- package/build/components/FEAASComponent.js +51 -17
- package/build/components/FEAASComponent.js.map +1 -1
- package/build/components/FEAASLoader.js +6 -0
- package/build/components/FEAASLoader.js.map +1 -1
- package/build/components/FEAASStylesheet.js +18 -13
- package/build/components/FEAASStylesheet.js.map +1 -1
- package/build/dom/component.js +34 -0
- package/build/dom/component.js.map +1 -0
- package/build/dom/data.js +105 -0
- package/build/dom/data.js.map +1 -0
- package/build/{dom.js → dom/rendering.js} +3 -194
- package/build/dom/rendering.js.map +1 -0
- package/build/dom/scripts.js +48 -0
- package/build/dom/scripts.js.map +1 -0
- package/build/dom/stylesheet.js +39 -0
- package/build/dom/stylesheet.js.map +1 -0
- package/build/fetch.js +72 -22
- package/build/fetch.js.map +1 -1
- package/build/{basic.js → headless.js} +11 -3
- package/build/headless.js.map +1 -0
- package/build/ui/FEAASContext.js +10 -3
- package/build/ui/FEAASContext.js.map +1 -1
- package/build/ui/FEAASEditor.js +5 -0
- package/build/ui/FEAASEditor.js.map +1 -1
- package/build/ui/FEAASPicker.js +7 -1
- package/build/ui/FEAASPicker.js.map +1 -1
- package/build/ui/index.js +1 -1
- package/build/ui/index.js.map +1 -1
- package/build/ui/react.js +6 -0
- package/build/ui/react.js.map +1 -0
- package/build/utils/promise.js +26 -0
- package/build/utils/promise.js.map +1 -0
- package/dist/browser/headless.esm.js +69 -0
- package/dist/browser/headless.esm.js.map +1 -0
- package/dist/browser/headless.js +69 -0
- package/dist/browser/headless.js.map +1 -0
- package/dist/browser/index.esm.js +3 -5
- package/dist/browser/index.esm.js.map +1 -1
- package/dist/browser/index.js +5 -5
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/react.js +67 -0
- package/dist/browser/react.js.map +1 -0
- package/dist/node/headless.esm.js +67 -0
- package/dist/node/headless.esm.js.map +7 -0
- package/dist/node/headless.js +67 -0
- package/dist/node/headless.js.map +7 -0
- package/dist/node/index.esm.js +3 -3
- package/dist/node/index.esm.js.map +4 -4
- package/dist/node/index.js +3 -3
- package/dist/node/index.js.map +4 -4
- package/dist/node/react.js +67 -0
- package/dist/node/react.js.map +7 -0
- package/examples/index.html +0 -5
- package/package.json +23 -12
- package/scripts/build.js +17 -6
- package/scripts/test-setup.ts +1 -0
- package/src/cdn.ts +188 -0
- package/src/components/FEAASBase.tsx +234 -82
- package/src/components/FEAASComponent.tsx +59 -21
- package/src/components/FEAASLoader.tsx +4 -0
- package/src/components/FEAASStylesheet.tsx +14 -13
- package/src/dom/component.ts +35 -0
- package/src/dom/data.ts +124 -0
- package/src/{dom.ts → dom/rendering.ts} +4 -218
- package/src/dom/scripts.ts +47 -0
- package/src/dom/stylesheet.ts +48 -0
- package/src/fetch.ts +79 -25
- package/src/{basic.ts → headless.ts} +11 -2
- package/src/ui/FEAASContext.tsx +12 -4
- package/src/ui/FEAASEditor.tsx +5 -0
- package/src/ui/FEAASPicker.tsx +6 -2
- package/src/ui/index.ts +1 -1
- package/src/ui/react.tsx +8 -0
- package/src/utils/promise.ts +34 -0
- package/test/FEAASBase.test.tsx +195 -0
- package/test/FEAASComponent.test.tsx +284 -0
- package/test/FEAASContext.test.tsx +76 -0
- package/test/FEAASPicker.test.tsx +40 -0
- package/test/FEAASStylesheet.test.tsx +159 -0
- package/test/cdn.ts +39 -0
- package/test/{render.ts → dom.test.ts} +12 -6
- package/test/extras.ts +1 -1
- package/test/fetch.ts +254 -0
- package/test/tsconfig.json +4 -1
- package/types/cdn.d.ts +57 -0
- package/types/cdn.d.ts.map +1 -0
- package/types/components/FEAASBase.d.ts +220 -16
- package/types/components/FEAASBase.d.ts.map +1 -1
- package/types/components/FEAASComponent.d.ts +34 -7
- package/types/components/FEAASComponent.d.ts.map +1 -1
- package/types/components/FEAASLoader.d.ts +4 -0
- package/types/components/FEAASLoader.d.ts.map +1 -1
- package/types/components/FEAASStylesheet.d.ts +5 -13
- package/types/components/FEAASStylesheet.d.ts.map +1 -1
- package/types/dom/component.d.ts +6 -0
- package/types/dom/component.d.ts.map +1 -0
- package/types/dom/data.d.ts +15 -0
- package/types/dom/data.d.ts.map +1 -0
- package/types/dom/rendering.d.ts +20 -0
- package/types/dom/rendering.d.ts.map +1 -0
- package/types/dom/scripts.d.ts +4 -0
- package/types/dom/scripts.d.ts.map +1 -0
- package/types/dom/stylesheet.d.ts +8 -0
- package/types/dom/stylesheet.d.ts.map +1 -0
- package/types/fetch.d.ts +25 -11
- package/types/fetch.d.ts.map +1 -1
- package/types/headless.d.ts +24 -0
- package/types/headless.d.ts.map +1 -0
- package/types/tsbuildinfo +1 -1
- package/types/tsbuildinfo_ui +1 -1
- package/types/ui/FEAASContext.d.ts +15 -5
- package/types/ui/FEAASContext.d.ts.map +1 -1
- package/types/ui/FEAASEditor.d.ts +18 -0
- package/types/ui/FEAASEditor.d.ts.map +1 -1
- package/types/ui/FEAASPicker.d.ts +15 -3
- package/types/ui/FEAASPicker.d.ts.map +1 -1
- package/types/ui/index.d.ts +1 -1
- package/types/ui/index.d.ts.map +1 -1
- package/types/ui/react.d.ts +7 -0
- package/types/ui/react.d.ts.map +1 -0
- package/types/utils/promise.d.ts +8 -0
- package/types/utils/promise.d.ts.map +1 -0
- package/build/basic.js.map +0 -1
- package/build/cloud.js +0 -71
- package/build/cloud.js.map +0 -1
- package/build/dom.js.map +0 -1
- package/build/node.js +0 -10
- package/build/node.js.map +0 -1
- package/build/ui/node.js +0 -3
- package/build/ui/node.js.map +0 -1
- package/dist/browser/basic.esm.js +0 -69
- package/dist/browser/basic.esm.js.map +0 -1
- package/dist/browser/basic.js +0 -69
- package/dist/browser/basic.js.map +0 -1
- package/dist/node/basic.esm.js +0 -67
- package/dist/node/basic.esm.js.map +0 -7
- package/dist/node/basic.js +0 -67
- package/dist/node/basic.js.map +0 -7
- package/src/cloud.ts +0 -118
- package/src/node.ts +0 -10
- package/src/ui/node.ts +0 -2
- package/test/components.ts +0 -31
- package/types/basic.d.ts +0 -16
- package/types/basic.d.ts.map +0 -1
- package/types/cloud.d.ts +0 -28
- package/types/cloud.d.ts.map +0 -1
- package/types/dom.d.ts +0 -41
- package/types/dom.d.ts.map +0 -1
- package/types/node.d.ts +0 -4
- package/types/node.d.ts.map +0 -1
- package/types/ui/node.d.ts +0 -3
- package/types/ui/node.d.ts.map +0 -1
|
@@ -1,28 +1,34 @@
|
|
|
1
|
-
import { fetchAndRevalidateComponent } from '../
|
|
2
|
-
import { renderDOMContent, renderDOMElement } from '../dom.js'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { DEFAULT_CDN_URL, FEAASCDNComponentParams, fetchAndRevalidateComponent, parseComponentSource } from '../cdn.js'
|
|
2
|
+
import { renderDOMContent, renderDOMElement } from '../dom/rendering.js'
|
|
3
|
+
import { loadStylesheetAllowStale } from '../headless.js'
|
|
4
|
+
import { FEAASBase } from './FEAASBase.js'
|
|
5
5
|
|
|
6
6
|
export function FEAASComponentsProps(element: FEAASBase = this) {
|
|
7
7
|
let data: any
|
|
8
|
+
const src = element.getAttribute('src')
|
|
9
|
+
if (src) {
|
|
10
|
+
var { cdn, library, component, version, revision } = parseComponentSource(src)
|
|
11
|
+
}
|
|
8
12
|
const dataValue = element.getAttribute('data')
|
|
9
|
-
if (dataValue != null) {
|
|
13
|
+
if (dataValue != null && dataValue != '') {
|
|
10
14
|
try {
|
|
11
15
|
data = typeof dataValue == 'string' ? JSON.parse(dataValue) : {}
|
|
12
16
|
} catch (e) {}
|
|
13
17
|
}
|
|
14
18
|
return {
|
|
15
|
-
data: data
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
data: data || {},
|
|
20
|
+
cdn: element.getContextAttribute('cdn') ?? cdn ?? DEFAULT_CDN_URL,
|
|
21
|
+
library: element.getAttribute('library') ?? library,
|
|
22
|
+
component: element.getAttribute('component') ?? component,
|
|
23
|
+
version: element.getAttribute('version') ?? version ?? 'responsive',
|
|
24
|
+
revision: (element.getAttribute('revision') ?? revision ?? 'published') as FEAASCDNComponentParams['revision'],
|
|
20
25
|
hostname: element.getAttribute('hostname'),
|
|
21
|
-
cdn: element.getContextAttribute('cdn'),
|
|
22
26
|
template: element.getAttribute('template'),
|
|
23
27
|
instance: element.getAttribute('instance'),
|
|
24
28
|
editable: element.getAttribute('editable') != null,
|
|
25
|
-
suspended: element.getAttribute('suspended') != null
|
|
29
|
+
suspended: element.getAttribute('suspended') != null,
|
|
30
|
+
lastModified: element.getAttribute('last-modified'),
|
|
31
|
+
withStylesheet: element.getAttribute('with-stylesheet') != 'false'
|
|
26
32
|
}
|
|
27
33
|
}
|
|
28
34
|
export type FEAASComponentsProps = ReturnType<typeof FEAASComponentsProps>
|
|
@@ -37,9 +43,19 @@ export class FEAASComponent extends FEAASBase<FEAASComponentsProps, string> {
|
|
|
37
43
|
'template',
|
|
38
44
|
'data',
|
|
39
45
|
'instance',
|
|
40
|
-
'editable'
|
|
46
|
+
'editable',
|
|
47
|
+
'with-stylesheet'
|
|
41
48
|
]
|
|
42
49
|
|
|
50
|
+
defaultProps = {
|
|
51
|
+
cdn: DEFAULT_CDN_URL,
|
|
52
|
+
revision: 'published',
|
|
53
|
+
version: 'responsive',
|
|
54
|
+
withStylesheet: true,
|
|
55
|
+
editable: false,
|
|
56
|
+
suspended: false
|
|
57
|
+
} as Partial<FEAASComponentsProps>
|
|
58
|
+
|
|
43
59
|
needsRefresh: boolean
|
|
44
60
|
setData(data: any) {
|
|
45
61
|
this.set({ data })
|
|
@@ -51,7 +67,10 @@ export class FEAASComponent extends FEAASBase<FEAASComponentsProps, string> {
|
|
|
51
67
|
|
|
52
68
|
attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
|
|
53
69
|
if (oldValue === newValue) return
|
|
54
|
-
|
|
70
|
+
// force component to be refreshed
|
|
71
|
+
if (name == 'library' || name == 'component' || name == 'revision' || name == 'version') {
|
|
72
|
+
this.payload = undefined
|
|
73
|
+
}
|
|
55
74
|
this.needsRefresh = true
|
|
56
75
|
super.attributeChangedCallback(name, oldValue, newValue)
|
|
57
76
|
}
|
|
@@ -97,18 +116,31 @@ export class FEAASComponent extends FEAASBase<FEAASComponentsProps, string> {
|
|
|
97
116
|
)
|
|
98
117
|
}
|
|
99
118
|
|
|
119
|
+
onLoad = (html: string) => {
|
|
120
|
+
this.payload = html
|
|
121
|
+
this.advance()
|
|
122
|
+
}
|
|
123
|
+
|
|
100
124
|
async load() {
|
|
101
125
|
// defer loading to original element
|
|
102
126
|
if (this.getOriginal()) {
|
|
103
127
|
return
|
|
104
128
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
129
|
+
if (this.props.withStylesheet) {
|
|
130
|
+
var stylesheetPromise = loadStylesheetAllowStale(this.props).then(() => {
|
|
131
|
+
stylesheetPromise = null
|
|
132
|
+
if (this.payload) {
|
|
133
|
+
this.onLoad(this.payload)
|
|
134
|
+
}
|
|
135
|
+
})
|
|
111
136
|
}
|
|
137
|
+
return Promise.all([
|
|
138
|
+
stylesheetPromise,
|
|
139
|
+
fetchAndRevalidateComponent(this.props, (html) => {
|
|
140
|
+
this.payload = html
|
|
141
|
+
if (!stylesheetPromise) this.onLoad(html)
|
|
142
|
+
})
|
|
143
|
+
]).then(() => this.payload)
|
|
112
144
|
}
|
|
113
145
|
|
|
114
146
|
update() {
|
|
@@ -128,8 +160,14 @@ export class FEAASComponent extends FEAASBase<FEAASComponentsProps, string> {
|
|
|
128
160
|
declare global {
|
|
129
161
|
namespace JSX {
|
|
130
162
|
interface IntrinsicElements {
|
|
131
|
-
'feaas-component':
|
|
163
|
+
'feaas-component': (Partial<
|
|
164
|
+
Omit<FEAASComponentsProps, 'component' | 'version' | 'revision' | 'lastModified' | 'withStylesheet'>
|
|
165
|
+
> &
|
|
166
|
+
(Pick<FEAASComponentsProps, 'component' | 'version' | 'revision'> | { src: string })) & {
|
|
132
167
|
children?: any
|
|
168
|
+
dangerouslySetInnerHTML?: { __html: string }
|
|
169
|
+
'last-modified'?: string
|
|
170
|
+
'with-stylesheet'?: 'false' | 'true' | ''
|
|
133
171
|
}
|
|
134
172
|
}
|
|
135
173
|
}
|
|
@@ -16,6 +16,10 @@ export class FEAASLoader extends FEAASBase<FEAASLoaderProps, string> {
|
|
|
16
16
|
return FEAASLoaderProps(this)
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
defaultProps = {
|
|
20
|
+
hidden: false
|
|
21
|
+
} as Partial<FEAASLoaderProps>
|
|
22
|
+
|
|
19
23
|
static observedAttributes = ['hidden', 'opacity']
|
|
20
24
|
|
|
21
25
|
attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
|
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DEFAULT_CDN_URL, FEAASCDNStylesheetParams, parseStylesheetSource } from '../cdn.js'
|
|
2
|
+
import { loadStylesheet } from '../dom/stylesheet.js'
|
|
2
3
|
import { FEAASBase } from './FEAASBase.js'
|
|
3
4
|
|
|
4
5
|
export function FEAASStylesheetProps(element: FEAASBase = this) {
|
|
6
|
+
const src = element.getAttribute('src')
|
|
7
|
+
if (src) {
|
|
8
|
+
var { cdn, library, revision } = parseStylesheetSource(src)
|
|
9
|
+
}
|
|
5
10
|
return {
|
|
6
11
|
library: element?.getContextAttribute('library'),
|
|
7
|
-
revision:
|
|
8
|
-
cdn: element?.getContextAttribute('cdn')
|
|
9
|
-
}
|
|
12
|
+
revision: element?.getAttribute('revision') ?? revision ?? 'published',
|
|
13
|
+
cdn: element?.getContextAttribute('cdn') ?? element?.getContextAttribute('hostname') ?? DEFAULT_CDN_URL
|
|
14
|
+
} as FEAASCDNStylesheetParams
|
|
10
15
|
}
|
|
11
16
|
export type FEAASStylesheetProps = ReturnType<typeof FEAASStylesheetProps>
|
|
12
17
|
|
|
13
18
|
export class FEAASStylesheet extends FEAASBase<FEAASStylesheetProps, string> {
|
|
14
|
-
stylesheet: HTMLStyleElement
|
|
15
19
|
getProps() {
|
|
16
20
|
return FEAASStylesheetProps(this)
|
|
17
21
|
}
|
|
@@ -20,16 +24,13 @@ export class FEAASStylesheet extends FEAASBase<FEAASStylesheetProps, string> {
|
|
|
20
24
|
return this.props.library != null && this.props.revision != null && this.props.cdn != null
|
|
21
25
|
}
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
onLoad = (html: string) => {
|
|
28
|
+
this.payload = html
|
|
29
|
+
this.advance()
|
|
25
30
|
}
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
this.stylesheet = document.createElement('style')
|
|
30
|
-
this.appendChild(this.stylesheet)
|
|
31
|
-
}
|
|
32
|
-
this.stylesheet.textContent = this.payload
|
|
32
|
+
load() {
|
|
33
|
+
return loadStylesheet(this.props, this.onLoad)
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { fetchAndRevalidateComponent } from '../cdn.js'
|
|
2
|
+
import { Datascopes } from './data.js'
|
|
3
|
+
import { FEAASComponentProps, renderDOMContent, renderDOMElement } from './rendering.js'
|
|
4
|
+
|
|
5
|
+
export function renderComponentElement(content: string, datasources?: Datascopes) {
|
|
6
|
+
const wrapper = document.createElement('div')
|
|
7
|
+
wrapper.innerHTML = content
|
|
8
|
+
const fragment = document.createDocumentFragment()
|
|
9
|
+
while (wrapper.firstChild) {
|
|
10
|
+
fragment.appendChild(wrapper.firstChild)
|
|
11
|
+
}
|
|
12
|
+
renderDOMElement(fragment, datasources)
|
|
13
|
+
return wrapper
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function renderComponent(props: FEAASComponentProps, root?: HTMLElement) {
|
|
17
|
+
renderComponentPromise(props, (root ||= document.createElement('div')))
|
|
18
|
+
return root
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function renderComponentPromise(props: FEAASComponentProps, root?: HTMLElement) {
|
|
22
|
+
root ||= document.createElement('div')
|
|
23
|
+
if (!root.childNodes.length) {
|
|
24
|
+
if ('template' in props) {
|
|
25
|
+
return renderDOMContent(root, props.template, props.data, props)
|
|
26
|
+
} else {
|
|
27
|
+
return fetchAndRevalidateComponent(props, (template) => {
|
|
28
|
+
return renderDOMContent(root, template, props.data, props)
|
|
29
|
+
}).then(() => root)
|
|
30
|
+
}
|
|
31
|
+
/* update element with new data */
|
|
32
|
+
} else {
|
|
33
|
+
return renderDOMElement(root, props.data, props)
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/dom/data.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { JSONPath } from 'jsonpath-plus'
|
|
2
|
+
|
|
3
|
+
export type Datascopes = Record<string, any>
|
|
4
|
+
|
|
5
|
+
export function cleanCollectionBit(path: string) {
|
|
6
|
+
return path.replace(/(\[[^\]\[]+\]|\.\*)$/g, '')
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function normalizeCollectionScope(path: string) {
|
|
10
|
+
return cleanCollectionBit(path) + '.*'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function queryScopes(scopes: any, datapath: string, singleValue = false, json: any = scopes) {
|
|
14
|
+
var path = datapath
|
|
15
|
+
/* find closest parent scope */
|
|
16
|
+
const keys = Object.keys(scopes).sort((b, a) => a.length - b.length)
|
|
17
|
+
for (const scope of keys) {
|
|
18
|
+
if (datapath.startsWith(scope) && 'ends with it') {
|
|
19
|
+
if (scope == datapath) {
|
|
20
|
+
if (singleValue) {
|
|
21
|
+
return scopes[scope]
|
|
22
|
+
} else {
|
|
23
|
+
continue
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
path = datapath.substring(scope.length + 1)
|
|
27
|
+
json = scopes[scope]
|
|
28
|
+
break
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
var collection = JSONPath({ path, json })
|
|
33
|
+
|
|
34
|
+
// If regular path didn't match any data, try fallbacks
|
|
35
|
+
// datasourceId.wrapper.$.path
|
|
36
|
+
if (Array.isArray(collection) && collection?.length == 0) {
|
|
37
|
+
collection = [
|
|
38
|
+
// look up in `_` datasource id (catch-all id is used in JSS)
|
|
39
|
+
// _.wrapper.$.path
|
|
40
|
+
path.replace(/^.*?\./, '_.'),
|
|
41
|
+
// look up directly on data root
|
|
42
|
+
// wrapper.$.path
|
|
43
|
+
path.replace(/^.*?\./, ''),
|
|
44
|
+
// look up trimmed path in datasource
|
|
45
|
+
// datasourceId.path
|
|
46
|
+
path.replace(/^([^.]+).*?\.\$\./, '$1.'),
|
|
47
|
+
// look up trimmed path in catch all datasource
|
|
48
|
+
// _.path
|
|
49
|
+
path.replace(/^.*?\.\$\./, '_.'),
|
|
50
|
+
// look up trimmed path on root
|
|
51
|
+
// path
|
|
52
|
+
path.replace(/^.*?\.\$\./, ''),
|
|
53
|
+
// for case of top-level object data mapping full path can be replaced to _
|
|
54
|
+
path.replace(/^[^.]+$/, '_')
|
|
55
|
+
]
|
|
56
|
+
.filter((v, i, a) => a.indexOf(v) === i)
|
|
57
|
+
.reduce((v, path) => {
|
|
58
|
+
return v.length > 0 ? v : JSONPath({ path, json })
|
|
59
|
+
}, [] as any[])
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (singleValue && Array.isArray(collection)) {
|
|
63
|
+
return collection[0]
|
|
64
|
+
} else {
|
|
65
|
+
return collection
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type CH1Node =
|
|
70
|
+
| {
|
|
71
|
+
type: string
|
|
72
|
+
text?: string
|
|
73
|
+
content?: CH1Node[]
|
|
74
|
+
marks?: CH1Node[]
|
|
75
|
+
attrs?: Record<string, any>
|
|
76
|
+
}
|
|
77
|
+
| CH1Node[]
|
|
78
|
+
function serializeCH1Tag(data: any, attrs: Record<string, string> = {}, tag: string): string {
|
|
79
|
+
if (!tag) return serializeCH1RichText(data)
|
|
80
|
+
return `<${tag}${Object.entries(attrs)
|
|
81
|
+
.map(([k, v]) => v != null && ` ${k}="${v}"`)
|
|
82
|
+
.filter(Boolean)
|
|
83
|
+
.join('')}>${serializeCH1RichText(data)}</${tag}>`.replace(/^<([a-z0-9][^>]*)><\/[^>]+>$/, '<$1 />')
|
|
84
|
+
}
|
|
85
|
+
export function serializeCH1RichText(data: CH1Node | string | null | undefined): string {
|
|
86
|
+
if (Array.isArray(data)) return data.map(serializeCH1RichText).join('\n').trim()
|
|
87
|
+
if (typeof data == 'string') return data
|
|
88
|
+
if (data == null) return ''
|
|
89
|
+
switch (data.type) {
|
|
90
|
+
case 'heading':
|
|
91
|
+
const { level, ...attrs } = data.attrs
|
|
92
|
+
return serializeCH1Tag(data.content, attrs, 'h' + level)
|
|
93
|
+
case 'text':
|
|
94
|
+
return ((data.marks || []) as any[]).reduce((text, mark) => {
|
|
95
|
+
return serializeCH1Tag(
|
|
96
|
+
text,
|
|
97
|
+
mark.attrs,
|
|
98
|
+
{
|
|
99
|
+
link: 'a',
|
|
100
|
+
bold: 'strong',
|
|
101
|
+
italic: 'em',
|
|
102
|
+
strike: 's',
|
|
103
|
+
underline: 'u'
|
|
104
|
+
}[mark.type as string]
|
|
105
|
+
)
|
|
106
|
+
}, data.text?.replace(/\s+/, ' '))
|
|
107
|
+
default:
|
|
108
|
+
return serializeCH1Tag(
|
|
109
|
+
data.content || data.text,
|
|
110
|
+
data.attrs,
|
|
111
|
+
{
|
|
112
|
+
bulletList: 'ul',
|
|
113
|
+
orderedList: 'ol',
|
|
114
|
+
listItem: 'li',
|
|
115
|
+
horizontalRule: 'hr',
|
|
116
|
+
paragraph: 'p',
|
|
117
|
+
codeBlock: 'code',
|
|
118
|
+
blockquote: 'blockquote'
|
|
119
|
+
}[data.type]
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export { JSONPath }
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { FEAASCDNComponentProps } from '../cdn.js'
|
|
2
|
+
import { Datascopes, normalizeCollectionScope, queryScopes, serializeCH1RichText } from './data.js'
|
|
3
|
+
import { loadScript } from './scripts.js'
|
|
3
4
|
|
|
4
5
|
export interface FEAASCustomizations {
|
|
5
6
|
processTemplate?: (element: HTMLElement, template: string, data: any) => string
|
|
@@ -13,32 +14,11 @@ export interface ExplicitTemplate {
|
|
|
13
14
|
template: string
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
export type Datascopes = Record<string, any>
|
|
17
|
-
|
|
18
17
|
export type FEAASComponentProps = {
|
|
19
18
|
data?: Datascopes
|
|
20
|
-
} & (ExplicitTemplate |
|
|
19
|
+
} & (ExplicitTemplate | FEAASCDNComponentProps) &
|
|
21
20
|
FEAASCustomizations
|
|
22
21
|
|
|
23
|
-
export function renderComponentElement(content: string, datasources?: Datascopes) {
|
|
24
|
-
const wrapper = document.createElement('div')
|
|
25
|
-
wrapper.innerHTML = content
|
|
26
|
-
const fragment = document.createDocumentFragment()
|
|
27
|
-
while (wrapper.firstChild) {
|
|
28
|
-
fragment.appendChild(wrapper.firstChild)
|
|
29
|
-
}
|
|
30
|
-
renderDOMElement(fragment, datasources)
|
|
31
|
-
return wrapper
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function cleanCollectionBit(path: string) {
|
|
35
|
-
return path.replace(/(\[[^\]\[]+\]|\.\*)$/g, '')
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function normalizeCollectionScope(path: string) {
|
|
39
|
-
return cleanCollectionBit(path) + '.*'
|
|
40
|
-
}
|
|
41
|
-
|
|
42
22
|
function getDOMCollectionIndex(element: HTMLElement) {
|
|
43
23
|
const datascope = element.getAttribute('data-path-scope')
|
|
44
24
|
var index = 0
|
|
@@ -91,62 +71,6 @@ function updateDOMClones(element: HTMLElement, targetCount: number, config?: FEA
|
|
|
91
71
|
}
|
|
92
72
|
}
|
|
93
73
|
|
|
94
|
-
export function queryScopes(scopes: any, datapath: string, singleValue = false, json: any = scopes) {
|
|
95
|
-
var path = datapath
|
|
96
|
-
/* find closest parent scope */
|
|
97
|
-
const keys = Object.keys(scopes).sort((b, a) => a.length - b.length)
|
|
98
|
-
for (const scope of keys) {
|
|
99
|
-
if (datapath.startsWith(scope) && 'ends with it') {
|
|
100
|
-
if (scope == datapath) {
|
|
101
|
-
if (singleValue) {
|
|
102
|
-
return scopes[scope]
|
|
103
|
-
} else {
|
|
104
|
-
continue
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
path = datapath.substring(scope.length + 1)
|
|
108
|
-
json = scopes[scope]
|
|
109
|
-
break
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
var collection = JSONPath({ path, json })
|
|
114
|
-
|
|
115
|
-
// If regular path didn't match any data, try fallbacks
|
|
116
|
-
// datasourceId.wrapper.$.path
|
|
117
|
-
if (Array.isArray(collection) && collection?.length == 0) {
|
|
118
|
-
collection = [
|
|
119
|
-
// look up in `_` datasource id (catch-all id is used in JSS)
|
|
120
|
-
// _.wrapper.$.path
|
|
121
|
-
path.replace(/^.*?\./, '_.'),
|
|
122
|
-
// look up directly on data root
|
|
123
|
-
// wrapper.$.path
|
|
124
|
-
path.replace(/^.*?\./, ''),
|
|
125
|
-
// look up trimmed path in datasource
|
|
126
|
-
// datasourceId.path
|
|
127
|
-
path.replace(/^([^.]+).*?\.\$\./, '$1.'),
|
|
128
|
-
// look up trimmed path in catch all datasource
|
|
129
|
-
// _.path
|
|
130
|
-
path.replace(/^.*?\.\$\./, '_.'),
|
|
131
|
-
// look up trimmed path on root
|
|
132
|
-
// path
|
|
133
|
-
path.replace(/^.*?\.\$\./, ''),
|
|
134
|
-
// for case of top-level object data mapping full path can be replaced to _
|
|
135
|
-
path.replace(/^[^.]+$/, '_')
|
|
136
|
-
]
|
|
137
|
-
.filter((v, i, a) => a.indexOf(v) === i)
|
|
138
|
-
.reduce((v, path) => {
|
|
139
|
-
return v.length > 0 ? v : JSONPath({ path, json })
|
|
140
|
-
}, [] as any[])
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (singleValue && Array.isArray(collection)) {
|
|
144
|
-
return collection[0]
|
|
145
|
-
} else {
|
|
146
|
-
return collection
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
74
|
export function setDOMAttribute(element: HTMLElement, attribute: string, value: any, config?: FEAASCustomizations) {
|
|
151
75
|
if (!element) return
|
|
152
76
|
var target = element
|
|
@@ -213,54 +137,6 @@ export function setDOMAttribute(element: HTMLElement, attribute: string, value:
|
|
|
213
137
|
}
|
|
214
138
|
}
|
|
215
139
|
|
|
216
|
-
export function observeDOMElement(element: HTMLElement) {
|
|
217
|
-
const observer = new element.ownerDocument.defaultView.MutationObserver((changes) => {
|
|
218
|
-
changes.forEach((change) => {
|
|
219
|
-
const target = change.target as HTMLElement
|
|
220
|
-
const src = target.getAttribute('data-embed-src')
|
|
221
|
-
if (src) {
|
|
222
|
-
loadScript(target, src)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (change.addedNodes) {
|
|
226
|
-
change.addedNodes.forEach((node) => {
|
|
227
|
-
if (node.nodeType == 1) autoloadScripts(node as HTMLElement, false)
|
|
228
|
-
})
|
|
229
|
-
}
|
|
230
|
-
})
|
|
231
|
-
})
|
|
232
|
-
observer.observe(element, { attributes: true, subtree: true, childList: true })
|
|
233
|
-
return observer
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
export function autoloadScripts(element: HTMLElement, observe = true) {
|
|
237
|
-
const embeds = element.querySelectorAll('[data-embed-src]')
|
|
238
|
-
for (var i = 0; i < embeds.length; i++) {
|
|
239
|
-
const embed = embeds[i]
|
|
240
|
-
loadScript(embed as HTMLElement, embed.getAttribute('data-embed-src'))
|
|
241
|
-
}
|
|
242
|
-
if (observe) return observeDOMElement(element)
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
export function loadScript(element: HTMLElement, src: string) {
|
|
246
|
-
for (var p = element; p; p = p.parentElement) {
|
|
247
|
-
if (!p.parentElement) {
|
|
248
|
-
const scripts = p.querySelectorAll('script')
|
|
249
|
-
for (var i = 0; i < scripts.length; i++) {
|
|
250
|
-
if (scripts[i].getAttribute('src') == src) {
|
|
251
|
-
return true
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
if (p.tagName == 'HTML') p = p.querySelector('head') || p
|
|
255
|
-
const script = p.ownerDocument.createElement('script')
|
|
256
|
-
script.type = 'module'
|
|
257
|
-
script.src = src
|
|
258
|
-
p.appendChild(script)
|
|
259
|
-
break
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
140
|
// order in which attributes are proccesed. Rightmost are more important
|
|
265
141
|
var sortOrder = ['data-attributes-keys', 'data-path-attributes', 'data-path-scope']
|
|
266
142
|
export function renderDOMElement(
|
|
@@ -343,93 +219,3 @@ export function renderHTMLContent(template: string, data?: Datascopes, config?:
|
|
|
343
219
|
return root.innerHTML
|
|
344
220
|
}
|
|
345
221
|
}
|
|
346
|
-
|
|
347
|
-
export function renderStylesheet(props: CloudStylesheet, style?: HTMLStyleElement) {
|
|
348
|
-
renderStylesheetPromise(props, (style ||= document.createElement('style')))
|
|
349
|
-
return style
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
export function renderStylesheetPromise(props: CloudStylesheet, style?: HTMLStyleElement) {
|
|
353
|
-
style ||= document.createElement('style')
|
|
354
|
-
return fetchStylesheet(props, (cssText) => {
|
|
355
|
-
style.textContent = cssText
|
|
356
|
-
}).then(() => style)
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
export function renderComponent(props: FEAASComponentProps, root?: HTMLElement) {
|
|
360
|
-
renderComponentPromise(props, (root ||= document.createElement('div')))
|
|
361
|
-
return root
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
export async function renderComponentPromise(props: FEAASComponentProps, root?: HTMLElement) {
|
|
365
|
-
root ||= document.createElement('div')
|
|
366
|
-
if (!root.childNodes.length) {
|
|
367
|
-
if ('template' in props) {
|
|
368
|
-
return renderDOMContent(root, props.template, props.data, props)
|
|
369
|
-
} else {
|
|
370
|
-
return fetchAndRevalidateComponent(props, (template) => {
|
|
371
|
-
return renderDOMContent(root, template, props.data, props)
|
|
372
|
-
}).then(() => root)
|
|
373
|
-
}
|
|
374
|
-
/* update element with new data */
|
|
375
|
-
} else {
|
|
376
|
-
return renderDOMElement(root, props.data, props)
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
export type CH1Node =
|
|
381
|
-
| {
|
|
382
|
-
type: string
|
|
383
|
-
text?: string
|
|
384
|
-
content?: CH1Node[]
|
|
385
|
-
marks?: CH1Node[]
|
|
386
|
-
attrs?: Record<string, any>
|
|
387
|
-
}
|
|
388
|
-
| CH1Node[]
|
|
389
|
-
function serializeCH1Tag(data: any, attrs: Record<string, string> = {}, tag: string): string {
|
|
390
|
-
if (!tag) return serializeCH1RichText(data)
|
|
391
|
-
return `<${tag}${Object.entries(attrs)
|
|
392
|
-
.map(([k, v]) => v != null && ` ${k}="${v}"`)
|
|
393
|
-
.filter(Boolean)
|
|
394
|
-
.join('')}>${serializeCH1RichText(data)}</${tag}>`.replace(/^<([a-z0-9][^>]*)><\/[^>]+>$/, '<$1 />')
|
|
395
|
-
}
|
|
396
|
-
export function serializeCH1RichText(data: CH1Node | string | null | undefined): string {
|
|
397
|
-
if (Array.isArray(data)) return data.map(serializeCH1RichText).join('\n').trim()
|
|
398
|
-
if (typeof data == 'string') return data
|
|
399
|
-
if (data == null) return ''
|
|
400
|
-
switch (data.type) {
|
|
401
|
-
case 'heading':
|
|
402
|
-
const { level, ...attrs } = data.attrs
|
|
403
|
-
return serializeCH1Tag(data.content, attrs, 'h' + level)
|
|
404
|
-
case 'text':
|
|
405
|
-
return ((data.marks || []) as any[]).reduce((text, mark) => {
|
|
406
|
-
return serializeCH1Tag(
|
|
407
|
-
text,
|
|
408
|
-
mark.attrs,
|
|
409
|
-
{
|
|
410
|
-
link: 'a',
|
|
411
|
-
bold: 'strong',
|
|
412
|
-
italic: 'em',
|
|
413
|
-
strike: 's',
|
|
414
|
-
underline: 'u'
|
|
415
|
-
}[mark.type as string]
|
|
416
|
-
)
|
|
417
|
-
}, data.text?.replace(/\s+/, ' '))
|
|
418
|
-
default:
|
|
419
|
-
return serializeCH1Tag(
|
|
420
|
-
data.content || data.text,
|
|
421
|
-
data.attrs,
|
|
422
|
-
{
|
|
423
|
-
bulletList: 'ul',
|
|
424
|
-
orderedList: 'ol',
|
|
425
|
-
listItem: 'li',
|
|
426
|
-
horizontalRule: 'hr',
|
|
427
|
-
paragraph: 'p',
|
|
428
|
-
codeBlock: 'code',
|
|
429
|
-
blockquote: 'blockquote'
|
|
430
|
-
}[data.type]
|
|
431
|
-
)
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
export { JSONPath }
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export function observeDOMElement(element: HTMLElement) {
|
|
2
|
+
const observer = new element.ownerDocument.defaultView.MutationObserver((changes) => {
|
|
3
|
+
changes.forEach((change) => {
|
|
4
|
+
const target = change.target as HTMLElement
|
|
5
|
+
const src = target.getAttribute('data-embed-src')
|
|
6
|
+
if (src) {
|
|
7
|
+
loadScript(target, src)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (change.addedNodes) {
|
|
11
|
+
change.addedNodes.forEach((node) => {
|
|
12
|
+
if (node.nodeType == 1) autoloadScripts(node as HTMLElement, false)
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
observer.observe(element, { attributes: true, subtree: true, childList: true })
|
|
18
|
+
return observer
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function autoloadScripts(element: HTMLElement, observe = true) {
|
|
22
|
+
const embeds = element.querySelectorAll('[data-embed-src]')
|
|
23
|
+
for (var i = 0; i < embeds.length; i++) {
|
|
24
|
+
const embed = embeds[i]
|
|
25
|
+
loadScript(embed as HTMLElement, embed.getAttribute('data-embed-src'))
|
|
26
|
+
}
|
|
27
|
+
if (observe) return observeDOMElement(element)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function loadScript(element: HTMLElement, src: string) {
|
|
31
|
+
for (var p = element; p; p = p.parentElement) {
|
|
32
|
+
if (!p.parentElement) {
|
|
33
|
+
const scripts = p.querySelectorAll('script')
|
|
34
|
+
for (var i = 0; i < scripts.length; i++) {
|
|
35
|
+
if (scripts[i].getAttribute('src') == src) {
|
|
36
|
+
return true
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (p.tagName == 'HTML') p = p.querySelector('head') || p
|
|
40
|
+
const script = p.ownerDocument.createElement('script')
|
|
41
|
+
script.type = 'module'
|
|
42
|
+
script.src = src
|
|
43
|
+
p.appendChild(script)
|
|
44
|
+
break
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|