@burger-editor/frozen-patty 0.11.0 → 4.0.0-alpha.10
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/README.md +553 -30
- package/dist/field-name-parser.d.ts +8 -0
- package/dist/field-name-parser.js +18 -0
- package/dist/field-name-stringifier.d.ts +14 -0
- package/dist/field-name-stringifier.js +26 -0
- package/dist/frozen-patty.d.ts +7 -2
- package/dist/frozen-patty.js +18 -5
- package/dist/get-component.d.ts +9 -0
- package/dist/get-component.js +29 -0
- package/dist/get-value.d.ts +1 -1
- package/dist/get-value.js +1 -1
- package/dist/get-values.d.ts +3 -4
- package/dist/get-values.js +114 -77
- package/dist/get.d.ts +7 -0
- package/dist/get.js +7 -0
- package/dist/handle-picture-element.d.ts +6 -0
- package/dist/handle-picture-element.js +31 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/parse-fields.d.ts +6 -0
- package/dist/parse-fields.js +38 -0
- package/dist/set-comonent.d.ts +10 -0
- package/dist/set-comonent.js +71 -0
- package/dist/set-component.d.ts +10 -0
- package/dist/set-component.js +91 -0
- package/dist/set-value.d.ts +11 -2
- package/dist/set-value.js +259 -197
- package/dist/set.d.ts +7 -0
- package/dist/set.js +9 -1
- package/dist/stringify-fields.d.ts +14 -0
- package/dist/stringify-fields.js +26 -0
- package/dist/types.d.ts +7 -0
- package/dist/utils.d.ts +82 -1
- package/dist/utils.js +265 -9
- package/package.json +10 -7
- package/LICENSE +0 -21
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* フィールド名と属性名からフィールド定義文字列を生成します
|
|
3
|
+
* @param fieldName フィールド名
|
|
4
|
+
* @param propName 属性名(オプション)
|
|
5
|
+
* @returns フィールド定義文字列
|
|
6
|
+
*/
|
|
7
|
+
export function fieldNameStringify(fieldName, propName) {
|
|
8
|
+
if (!propName) {
|
|
9
|
+
return fieldName;
|
|
10
|
+
}
|
|
11
|
+
// fieldNameとpropNameが同じ場合は省略記法を使用
|
|
12
|
+
if (fieldName === propName) {
|
|
13
|
+
return `:${propName}`;
|
|
14
|
+
}
|
|
15
|
+
return `${fieldName}:${propName}`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 複数のフィールド定義をカンマ区切りで結合します
|
|
19
|
+
* @param fields フィールド定義の配列
|
|
20
|
+
* @returns カンマ区切りのフィールド定義文字列
|
|
21
|
+
*/
|
|
22
|
+
export function fieldsStringify(fields) {
|
|
23
|
+
return fields
|
|
24
|
+
.map((field) => fieldNameStringify(field.fieldName, field.propName))
|
|
25
|
+
.join(', ');
|
|
26
|
+
}
|
package/dist/frozen-patty.d.ts
CHANGED
|
@@ -15,7 +15,6 @@ export default class FrozenPatty {
|
|
|
15
15
|
export interface FrozenPattyOptions {
|
|
16
16
|
/**
|
|
17
17
|
* **Data attribute** name for specifying the node that FrozenPatty treats as a _field_
|
|
18
|
-
*
|
|
19
18
|
* @default `"field"`
|
|
20
19
|
*/
|
|
21
20
|
attr?: string;
|
|
@@ -28,7 +27,6 @@ export interface FrozenPattyOptions {
|
|
|
28
27
|
* - `"1"` => `1`
|
|
29
28
|
* - `"1.0"` => `1`
|
|
30
29
|
* - `"0.1"` => `0.1`
|
|
31
|
-
*
|
|
32
30
|
* @default `false`
|
|
33
31
|
*/
|
|
34
32
|
typeConvert?: boolean;
|
|
@@ -36,4 +34,11 @@ export interface FrozenPattyOptions {
|
|
|
36
34
|
* Value filter
|
|
37
35
|
*/
|
|
38
36
|
valueFilter?: Filter;
|
|
37
|
+
/**
|
|
38
|
+
* Enable XSS protection
|
|
39
|
+
* - When enabled, removes potentially malicious code that may be contained in HTML
|
|
40
|
+
* - Prevents script elements, dangerous event handler attributes, javascript protocol, etc.
|
|
41
|
+
* @default `true`
|
|
42
|
+
*/
|
|
43
|
+
xssSanitize?: boolean;
|
|
39
44
|
}
|
package/dist/frozen-patty.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { getComponent } from './get-component.js';
|
|
2
|
+
import { setComponent } from './set-component.js';
|
|
3
|
+
import { sanitizeHtml } from './utils.js';
|
|
3
4
|
export default class FrozenPatty {
|
|
4
5
|
#attr = 'field';
|
|
5
6
|
#dom;
|
|
@@ -8,6 +9,10 @@ export default class FrozenPatty {
|
|
|
8
9
|
*/
|
|
9
10
|
#filter;
|
|
10
11
|
#typeConvert = false;
|
|
12
|
+
/**
|
|
13
|
+
* Enable XSS protection
|
|
14
|
+
*/
|
|
15
|
+
#xssSanitize = true;
|
|
11
16
|
/**
|
|
12
17
|
*
|
|
13
18
|
* @param html Original HTML
|
|
@@ -15,7 +20,14 @@ export default class FrozenPatty {
|
|
|
15
20
|
*/
|
|
16
21
|
constructor(html, options) {
|
|
17
22
|
this.#dom = document.createElement('fp-placeholer');
|
|
18
|
-
|
|
23
|
+
// Sanitize initial HTML if XSS protection is enabled
|
|
24
|
+
if (options?.xssSanitize === false) {
|
|
25
|
+
this.#dom.innerHTML = html;
|
|
26
|
+
this.#xssSanitize = false;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
this.#dom.innerHTML = sanitizeHtml(html);
|
|
30
|
+
}
|
|
19
31
|
if (options) {
|
|
20
32
|
if (options.attr) {
|
|
21
33
|
this.#attr = options.attr;
|
|
@@ -27,7 +39,8 @@ export default class FrozenPatty {
|
|
|
27
39
|
merge(data) {
|
|
28
40
|
const currentData = this.toJSON(false);
|
|
29
41
|
const newData = Object.assign(currentData, data);
|
|
30
|
-
|
|
42
|
+
// Pass XSS protection flag to set (default is true if not set)
|
|
43
|
+
this.#dom = setComponent(this.#dom, newData, this.#attr, this.#filter, this.#xssSanitize);
|
|
31
44
|
return this;
|
|
32
45
|
}
|
|
33
46
|
toDOM() {
|
|
@@ -38,6 +51,6 @@ export default class FrozenPatty {
|
|
|
38
51
|
}
|
|
39
52
|
toJSON(filtering = true) {
|
|
40
53
|
const filter = filtering ? this.#filter : undefined;
|
|
41
|
-
return
|
|
54
|
+
return getComponent(this.#dom, this.#attr, this.#typeConvert, filter);
|
|
42
55
|
}
|
|
43
56
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Filter, FrozenPattyData } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
*
|
|
4
|
+
* @param el
|
|
5
|
+
* @param attr
|
|
6
|
+
* @param typeConvert
|
|
7
|
+
* @param filter
|
|
8
|
+
*/
|
|
9
|
+
export declare function getComponent(el: Element, attr: string, typeConvert: boolean, filter?: Filter): FrozenPattyData;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getValues } from './get-values.js';
|
|
2
|
+
import { arrayToHash } from './utils.js';
|
|
3
|
+
const reverseListElementSelectors = ['picture'];
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param el
|
|
7
|
+
* @param attr
|
|
8
|
+
* @param typeConvert
|
|
9
|
+
* @param filter
|
|
10
|
+
*/
|
|
11
|
+
export function getComponent(el, attr, typeConvert, filter) {
|
|
12
|
+
el = el.cloneNode(true);
|
|
13
|
+
const reverseListElements = el.querySelectorAll(`:is(${reverseListElementSelectors.join(',')})[data-${attr}-list]`);
|
|
14
|
+
for (const reverseListElement of reverseListElements) {
|
|
15
|
+
const children = [];
|
|
16
|
+
while (reverseListElement.lastElementChild) {
|
|
17
|
+
children.push(reverseListElement.lastElementChild);
|
|
18
|
+
reverseListElement.lastElementChild.remove();
|
|
19
|
+
}
|
|
20
|
+
reverseListElement.append(...children);
|
|
21
|
+
}
|
|
22
|
+
const filedElements = el.querySelectorAll(`[data-${attr}]`);
|
|
23
|
+
let values = [];
|
|
24
|
+
for (const _el of filedElements) {
|
|
25
|
+
values = [...values, ...getValues(_el, typeConvert, attr, filter)];
|
|
26
|
+
}
|
|
27
|
+
const result = arrayToHash(values);
|
|
28
|
+
return result;
|
|
29
|
+
}
|
package/dist/get-value.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { Filter, PrimitiveDatum } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Get value from an element
|
|
4
|
-
*
|
|
5
4
|
* @param el A target element
|
|
6
5
|
* @param name A label name
|
|
7
6
|
* @param typeConvert Auto covert type of value
|
|
8
7
|
* @param attr Data attribute name for specifying the node that FrozenPatty treats as a field
|
|
8
|
+
* @param filter
|
|
9
9
|
*/
|
|
10
10
|
export declare function getValue(el: Element, name: string, typeConvert?: boolean, attr?: string, filter?: Filter): PrimitiveDatum | PrimitiveDatum[];
|
package/dist/get-value.js
CHANGED
|
@@ -2,11 +2,11 @@ import { getValues } from './get-values.js';
|
|
|
2
2
|
import { arrayToHash } from './utils.js';
|
|
3
3
|
/**
|
|
4
4
|
* Get value from an element
|
|
5
|
-
*
|
|
6
5
|
* @param el A target element
|
|
7
6
|
* @param name A label name
|
|
8
7
|
* @param typeConvert Auto covert type of value
|
|
9
8
|
* @param attr Data attribute name for specifying the node that FrozenPatty treats as a field
|
|
9
|
+
* @param filter
|
|
10
10
|
*/
|
|
11
11
|
export function getValue(el, name, typeConvert = false, attr = 'field', filter) {
|
|
12
12
|
const value = getValues(el, typeConvert, attr, filter);
|
package/dist/get-values.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import type { Filter, PrimitiveDatum } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Get value from an element
|
|
4
|
-
*
|
|
5
4
|
* @param el A target element
|
|
6
|
-
* @param
|
|
7
|
-
* @param typeConvert Auto covert type of value
|
|
5
|
+
* @param convertType Auto covert type of value
|
|
8
6
|
* @param attr Data attribute name for specifying the node that FrozenPatty treats as a field
|
|
7
|
+
* @param filter
|
|
9
8
|
*/
|
|
10
|
-
export declare function getValues(el: Element,
|
|
9
|
+
export declare function getValues(el: Element, convertType?: boolean, attr?: string, filter?: Filter): [key: string | number, value: PrimitiveDatum, forceArray: boolean][];
|
package/dist/get-values.js
CHANGED
|
@@ -1,81 +1,93 @@
|
|
|
1
|
+
import { kebabCase } from '@burger-editor/utils';
|
|
2
|
+
import { parseFields } from './parse-fields.js';
|
|
1
3
|
/**
|
|
2
4
|
* Get value from an element
|
|
3
|
-
*
|
|
4
5
|
* @param el A target element
|
|
5
|
-
* @param
|
|
6
|
-
* @param typeConvert Auto covert type of value
|
|
6
|
+
* @param convertType Auto covert type of value
|
|
7
7
|
* @param attr Data attribute name for specifying the node that FrozenPatty treats as a field
|
|
8
|
+
* @param filter
|
|
8
9
|
*/
|
|
9
|
-
export function getValues(el,
|
|
10
|
-
/**
|
|
11
|
-
* [key, value, forceArray]
|
|
12
|
-
*/
|
|
10
|
+
export function getValues(el, convertType = false, attr = 'field', filter) {
|
|
13
11
|
const result = [];
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
const forceArray = !!listRoot;
|
|
17
|
-
if (rawValue == null) {
|
|
12
|
+
const query = el.getAttribute(`data-${attr}`);
|
|
13
|
+
if (query == null) {
|
|
18
14
|
throw new Error(`data-${attr} attriblute is empty.`);
|
|
19
15
|
}
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
let keyAttr = '';
|
|
16
|
+
const listRoot = el.closest(`[data-${attr}-list]`);
|
|
17
|
+
const forceArray = !!listRoot;
|
|
18
|
+
const fields = parseFields(query);
|
|
19
|
+
for (const field of fields) {
|
|
25
20
|
let value;
|
|
26
|
-
fieldName =
|
|
27
|
-
if (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
21
|
+
const { fieldName, propName } = field;
|
|
22
|
+
if (propName) {
|
|
23
|
+
switch (propName) {
|
|
24
|
+
case 'node': {
|
|
25
|
+
value = el.localName ?? el.nodeName.toLowerCase();
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
case 'text': {
|
|
29
|
+
value = el.textContent?.trim() ?? '';
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
case 'html': {
|
|
33
|
+
value = el.innerHTML.trim();
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
case 'style': {
|
|
37
|
+
if (el instanceof HTMLElement) {
|
|
38
|
+
value = el.style.cssText;
|
|
39
|
+
}
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
default: {
|
|
43
|
+
if (/^style\([a-z-]+\)$/i.test(propName)) {
|
|
44
|
+
const css = propName.replace(/^style\(([a-z-]+)\)$/i, '$1');
|
|
45
|
+
let style;
|
|
46
|
+
if (el instanceof HTMLElement) {
|
|
47
|
+
style = el.style;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
style = window.getComputedStyle(el);
|
|
51
|
+
}
|
|
52
|
+
value = style.getPropertyValue(css);
|
|
53
|
+
if (css === 'background-image') {
|
|
54
|
+
value = getBackgroundImagePath(value);
|
|
55
|
+
}
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
value = getAttribute(el, propName, attr, convertType);
|
|
59
|
+
}
|
|
48
60
|
}
|
|
49
61
|
}
|
|
50
|
-
else if (keyAttr) {
|
|
51
|
-
value = getAttribute(el, keyAttr, typeConvert);
|
|
52
|
-
}
|
|
53
62
|
else {
|
|
54
63
|
if (el instanceof HTMLInputElement ||
|
|
55
64
|
el instanceof HTMLSelectElement ||
|
|
56
65
|
el instanceof HTMLTextAreaElement) {
|
|
57
66
|
const val = el.value;
|
|
58
|
-
|
|
59
|
-
value = cast(val[0]);
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
value = cast(val);
|
|
63
|
-
}
|
|
67
|
+
value = convertType ? convert(val) : val;
|
|
64
68
|
}
|
|
65
69
|
else {
|
|
66
|
-
value = el.innerHTML;
|
|
70
|
+
value = el.innerHTML.trim();
|
|
67
71
|
}
|
|
68
72
|
}
|
|
69
|
-
// console.log({fieldName, value});
|
|
70
73
|
if (filter) {
|
|
71
74
|
value = filter(value);
|
|
72
75
|
}
|
|
76
|
+
if (value === undefined) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
73
79
|
result.push([fieldName, value, forceArray]);
|
|
74
80
|
}
|
|
75
|
-
// console.log({result});
|
|
76
81
|
return result;
|
|
77
82
|
}
|
|
78
|
-
|
|
83
|
+
/**
|
|
84
|
+
*
|
|
85
|
+
* @param el
|
|
86
|
+
* @param keyAttr
|
|
87
|
+
* @param attr
|
|
88
|
+
* @param typeConvert
|
|
89
|
+
*/
|
|
90
|
+
function getAttribute(el, keyAttr, attr, typeConvert) {
|
|
79
91
|
switch (keyAttr) {
|
|
80
92
|
case 'contenteditable': {
|
|
81
93
|
if (el instanceof HTMLElement) {
|
|
@@ -96,43 +108,68 @@ function getAttribute(el, keyAttr, typeConvert) {
|
|
|
96
108
|
// So to get inactive it is necessary to use the "hasAttribute" method.
|
|
97
109
|
return el.hasAttribute('download') ? el.getAttribute('download') : null;
|
|
98
110
|
}
|
|
99
|
-
case 'href':
|
|
100
|
-
|
|
101
|
-
|
|
111
|
+
case 'href':
|
|
112
|
+
case 'src': {
|
|
113
|
+
// Example: (el as HTMLAnchorElement).href;
|
|
114
|
+
// Expected: return defined value of plain string
|
|
115
|
+
return el.getAttribute(keyAttr) ?? '';
|
|
102
116
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
return el.getAttribute(keyAttr) || '';
|
|
117
|
+
}
|
|
118
|
+
let value =
|
|
119
|
+
// @ts-ignore
|
|
120
|
+
el[keyAttr];
|
|
121
|
+
if (value !== undefined) {
|
|
122
|
+
if (typeConvert) {
|
|
123
|
+
value = convert(value);
|
|
112
124
|
}
|
|
125
|
+
return value;
|
|
113
126
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (
|
|
127
|
+
// For shorthand notation, get value from data-field-* attribute
|
|
128
|
+
const dataAttr = ['data', attr, kebabCase(keyAttr)].join('-');
|
|
129
|
+
if (el.hasAttribute(dataAttr)) {
|
|
130
|
+
return el.getAttribute(dataAttr) ?? '';
|
|
131
|
+
}
|
|
132
|
+
value = el.getAttribute(keyAttr) ?? null;
|
|
133
|
+
if ((value === '' || value == null) &&
|
|
134
|
+
typeof keyAttr === 'string' &&
|
|
135
|
+
keyAttr.startsWith('data-')) {
|
|
117
136
|
return '';
|
|
118
137
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
return true;
|
|
122
|
-
}
|
|
123
|
-
case 'false': {
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
138
|
+
if (typeConvert) {
|
|
139
|
+
value = convert(value);
|
|
126
140
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
141
|
+
return value;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
*
|
|
145
|
+
* @param value
|
|
146
|
+
*/
|
|
147
|
+
function convert(value) {
|
|
148
|
+
value = parse(value);
|
|
149
|
+
const str = `${value}`;
|
|
150
|
+
if (URL.canParse(str)) {
|
|
151
|
+
const url = new URL(str, location.href);
|
|
152
|
+
if (url.origin === location.origin) {
|
|
153
|
+
return url.pathname;
|
|
154
|
+
}
|
|
130
155
|
}
|
|
131
156
|
return value;
|
|
132
157
|
}
|
|
133
158
|
/**
|
|
134
|
-
* Get path from value of "background-image"
|
|
135
159
|
*
|
|
160
|
+
* @param value
|
|
161
|
+
*/
|
|
162
|
+
function parse(value) {
|
|
163
|
+
try {
|
|
164
|
+
return JSON.parse(`${value}`);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Get path from value of "background-image"
|
|
172
|
+
* @param value
|
|
136
173
|
*/
|
|
137
174
|
function getBackgroundImagePath(value) {
|
|
138
175
|
const origin = `${location.protocol}//${location.hostname}${location.port ? `:${location.port}` : ''}`;
|
package/dist/get.d.ts
CHANGED
package/dist/get.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { getValues } from './get-values.js';
|
|
2
2
|
import { arrayToHash } from './utils.js';
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @param el
|
|
6
|
+
* @param attr
|
|
7
|
+
* @param typeConvert
|
|
8
|
+
* @param filter
|
|
9
|
+
*/
|
|
3
10
|
export default function (el, attr, typeConvert, filter) {
|
|
4
11
|
// eslint-disable-next-line unicorn/prefer-spread
|
|
5
12
|
const filedElements = Array.from(el.querySelectorAll(`[data-${attr}]`));
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { parseFields } from './parse-fields.js';
|
|
2
|
+
import { setValue } from './set-value.js';
|
|
3
|
+
/**
|
|
4
|
+
* picture要素の特別な処理を行います
|
|
5
|
+
* @param pictureElement picture要素
|
|
6
|
+
* @param attr
|
|
7
|
+
*/
|
|
8
|
+
export function handlePictureElement(pictureElement, attr) {
|
|
9
|
+
const children = [...pictureElement.children];
|
|
10
|
+
const fieldAttrName = `data-${attr}`;
|
|
11
|
+
for (const [i, child] of children.entries()) {
|
|
12
|
+
const fields = parseFields(child.getAttribute(fieldAttrName) ?? '');
|
|
13
|
+
const newChild = child.cloneNode(false);
|
|
14
|
+
child.remove();
|
|
15
|
+
if (i > 0) {
|
|
16
|
+
setValue(newChild, 'node', 'source', attr);
|
|
17
|
+
}
|
|
18
|
+
switch (newChild.localName) {
|
|
19
|
+
case 'source': {
|
|
20
|
+
// newChild.removeAttribute('alt');
|
|
21
|
+
// setValue(newChild, name, attr.value);
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
case 'img': {
|
|
25
|
+
//
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
pictureElement.insertBefore(newChild, pictureElement.firstChild);
|
|
30
|
+
}
|
|
31
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,12 @@ import type { FrozenPattyOptions } from './frozen-patty.js';
|
|
|
2
2
|
import FrozenPatty from './frozen-patty.js';
|
|
3
3
|
/**
|
|
4
4
|
* Pure HTML to JSON converter that not use template engine.
|
|
5
|
-
*
|
|
6
5
|
* @param html Original HTML
|
|
7
6
|
* @param options Options
|
|
8
7
|
*/
|
|
8
|
+
/**
|
|
9
|
+
*
|
|
10
|
+
* @param html
|
|
11
|
+
* @param options
|
|
12
|
+
*/
|
|
9
13
|
export default function frozenPatty(html: string, options?: FrozenPattyOptions): FrozenPatty;
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import FrozenPatty from './frozen-patty.js';
|
|
2
2
|
/**
|
|
3
3
|
* Pure HTML to JSON converter that not use template engine.
|
|
4
|
-
*
|
|
5
4
|
* @param html Original HTML
|
|
6
5
|
* @param options Options
|
|
7
6
|
*/
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* @param html
|
|
10
|
+
* @param options
|
|
11
|
+
*/
|
|
8
12
|
export default function frozenPatty(html, options) {
|
|
9
13
|
return new FrozenPatty(html, options);
|
|
10
14
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { camelCase } from '@burger-editor/utils';
|
|
2
|
+
const cache = new Map();
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @param query
|
|
6
|
+
*/
|
|
7
|
+
export function parseFields(query) {
|
|
8
|
+
const cached = cache.get(query);
|
|
9
|
+
if (cached) {
|
|
10
|
+
return cached;
|
|
11
|
+
}
|
|
12
|
+
const fields = query.split(',');
|
|
13
|
+
const result = fields.map(parseField);
|
|
14
|
+
cache.set(query, result);
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
*
|
|
19
|
+
* @param field
|
|
20
|
+
*/
|
|
21
|
+
function parseField(field) {
|
|
22
|
+
field = field.trim().toLowerCase();
|
|
23
|
+
if (!field) {
|
|
24
|
+
throw new Error('Field name is empty.');
|
|
25
|
+
}
|
|
26
|
+
let [fieldName, propName,
|
|
27
|
+
// eslint-disable-next-line prefer-const
|
|
28
|
+
...rest] = field.split(':');
|
|
29
|
+
if (rest.length > 0) {
|
|
30
|
+
throw new Error('Invalid field format.');
|
|
31
|
+
}
|
|
32
|
+
propName = propName?.trim() || '';
|
|
33
|
+
fieldName = fieldName?.trim() || propName || field;
|
|
34
|
+
return {
|
|
35
|
+
fieldName: camelCase(fieldName),
|
|
36
|
+
propName,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Filter, FrozenPattyData } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
*
|
|
4
|
+
* @param el
|
|
5
|
+
* @param data
|
|
6
|
+
* @param attr
|
|
7
|
+
* @param filter
|
|
8
|
+
* @param xssSanitize Enable XSS protection
|
|
9
|
+
*/
|
|
10
|
+
export declare function setComponent(el: Element, data: FrozenPattyData, attr: string, filter?: Filter, xssSanitize?: boolean): Element;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { setValue } from './set-value.js';
|
|
2
|
+
import { stringifyFields } from './stringify-fields.js';
|
|
3
|
+
import { definedFields, flattenData, getFields, hasField, kebabCase, maxLengthOf, removeProp, replaceNode, replaceProp, } from './utils.js';
|
|
4
|
+
/**
|
|
5
|
+
*
|
|
6
|
+
* @param el
|
|
7
|
+
* @param data
|
|
8
|
+
* @param attr
|
|
9
|
+
* @param filter
|
|
10
|
+
* @param xssSanitize Enable XSS protection
|
|
11
|
+
*/
|
|
12
|
+
export function setComponent(el, data, attr, filter, xssSanitize = true) {
|
|
13
|
+
el = el.cloneNode(true);
|
|
14
|
+
for (const dataKeyName in data) {
|
|
15
|
+
const datum = data[dataKeyName];
|
|
16
|
+
if (Array.isArray(datum)) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const targetList = [
|
|
20
|
+
// 自身を含める
|
|
21
|
+
el,
|
|
22
|
+
// パフォーマンスのために雑なフィルタリングを行う
|
|
23
|
+
...el.querySelectorAll(`[data-${attr}*="${kebabCase(dataKeyName)}"]`),
|
|
24
|
+
]
|
|
25
|
+
// より正確なフィルタリングを行う
|
|
26
|
+
.filter((el) => hasField(el, attr, dataKeyName));
|
|
27
|
+
for (const targetEl of targetList) {
|
|
28
|
+
setValue(targetEl, dataKeyName, datum, attr, filter, xssSanitize);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
for (const listRoot of el.querySelectorAll(`[data-${attr}-list]`)) {
|
|
32
|
+
const decendants = listRoot.querySelectorAll('*');
|
|
33
|
+
const definedFieldsOfDecendants = new Set([...decendants].flatMap((el) => definedFields(el, attr)));
|
|
34
|
+
const maxLength = maxLengthOf(data, [...definedFieldsOfDecendants]);
|
|
35
|
+
const listItem = listRoot.children[0]?.cloneNode(true);
|
|
36
|
+
if (!listItem) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
while (listRoot.firstChild) {
|
|
40
|
+
listRoot.firstChild.remove();
|
|
41
|
+
}
|
|
42
|
+
for (let i = 0; i < maxLength; i++) {
|
|
43
|
+
let item = listItem.cloneNode(true);
|
|
44
|
+
const itemData = flattenData(data, i);
|
|
45
|
+
let reverse = false;
|
|
46
|
+
switch (listRoot.localName) {
|
|
47
|
+
case 'picture': {
|
|
48
|
+
reverse = true;
|
|
49
|
+
if (i > 0) {
|
|
50
|
+
// 2つ目以降はsource要素に変換
|
|
51
|
+
let fields = getFields(item, attr);
|
|
52
|
+
item = replaceNode(item, 'source', attr, xssSanitize);
|
|
53
|
+
fields = replaceProp(fields, 'src', 'srcset');
|
|
54
|
+
fields = removeProp(fields, 'alt');
|
|
55
|
+
const fieldQuery = stringifyFields(fields);
|
|
56
|
+
item.setAttribute(`data-${attr}`, fieldQuery);
|
|
57
|
+
}
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const newEl = setComponent(item, itemData, attr, filter, xssSanitize);
|
|
62
|
+
if (reverse) {
|
|
63
|
+
listRoot.prepend(newEl);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
listRoot.append(newEl);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return el;
|
|
71
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Filter, FrozenPattyData } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
*
|
|
4
|
+
* @param el
|
|
5
|
+
* @param data
|
|
6
|
+
* @param attr
|
|
7
|
+
* @param filter
|
|
8
|
+
* @param xssSanitize Enable XSS protection
|
|
9
|
+
*/
|
|
10
|
+
export declare function setComponent(el: Element, data: FrozenPattyData, attr: string, filter?: Filter, xssSanitize?: boolean): Element;
|