@burger-editor/frozen-patty 4.0.0-alpha.7 → 4.0.0-alpha.71
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 +120 -495
- package/dist/get-values.js +5 -3
- package/dist/normalize-indent.d.ts +19 -0
- package/dist/normalize-indent.js +29 -0
- package/dist/set-component.js +10 -0
- package/dist/set-value.d.ts +6 -5
- package/dist/set-value.js +250 -441
- package/dist/utils.js +18 -10
- package/package.json +10 -4
- package/dist/field-name-parser.d.ts +0 -8
- package/dist/field-name-parser.js +0 -18
- package/dist/field-name-stringifier.d.ts +0 -14
- package/dist/field-name-stringifier.js +0 -26
- package/dist/get.d.ts +0 -9
- package/dist/get.js +0 -19
- package/dist/handle-picture-element.d.ts +0 -6
- package/dist/handle-picture-element.js +0 -31
- package/dist/set-comonent.d.ts +0 -10
- package/dist/set-comonent.js +0 -71
- package/dist/set.d.ts +0 -9
- package/dist/set.js +0 -60
package/dist/utils.js
CHANGED
|
@@ -96,22 +96,30 @@ export function sanitizeHtml(html) {
|
|
|
96
96
|
sanitized = sanitized.replace(regex, '$1');
|
|
97
97
|
}
|
|
98
98
|
// Parse HTML safely using DOM parser for remaining sanitization
|
|
99
|
-
const
|
|
100
|
-
const body = doc.body;
|
|
99
|
+
const fragment = new Range().createContextualFragment(sanitized);
|
|
101
100
|
// Remove event handler attributes from all elements
|
|
102
|
-
sanitizeElementAndChildren(
|
|
103
|
-
|
|
101
|
+
sanitizeElementAndChildren(fragment);
|
|
102
|
+
const newHtml = [...fragment.childNodes]
|
|
103
|
+
.map((node) => {
|
|
104
|
+
if (node instanceof Element) {
|
|
105
|
+
return node.outerHTML;
|
|
106
|
+
}
|
|
107
|
+
return node.textContent ?? node.nodeValue ?? '';
|
|
108
|
+
})
|
|
109
|
+
.join('');
|
|
110
|
+
return newHtml;
|
|
104
111
|
}
|
|
105
112
|
/**
|
|
106
113
|
* Sanitizes an element and all its child elements
|
|
107
|
-
* @param
|
|
114
|
+
* @param node Target element
|
|
108
115
|
*/
|
|
109
|
-
function sanitizeElementAndChildren(
|
|
110
|
-
|
|
111
|
-
|
|
116
|
+
function sanitizeElementAndChildren(node) {
|
|
117
|
+
if (node instanceof Element) {
|
|
118
|
+
// Remove dangerous attributes from current element
|
|
119
|
+
sanitizeAttributes(node);
|
|
120
|
+
}
|
|
112
121
|
// Process child elements recursively
|
|
113
|
-
const
|
|
114
|
-
for (const child of children) {
|
|
122
|
+
for (const child of node.childNodes) {
|
|
115
123
|
sanitizeElementAndChildren(child);
|
|
116
124
|
}
|
|
117
125
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burger-editor/frozen-patty",
|
|
3
|
-
"version": "4.0.0-alpha.
|
|
3
|
+
"version": "4.0.0-alpha.71",
|
|
4
4
|
"description": "Pure HTML to JSON converter that does not use a template engine.",
|
|
5
5
|
"author": "D-ZERO",
|
|
6
6
|
"license": "(MIT OR Apache-2.0)",
|
|
7
7
|
"repository": {
|
|
8
|
-
"
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/d-zero-dev/BurgerEditor.git",
|
|
10
|
+
"directory": "packages/@burger-editor/frozen-patty"
|
|
9
11
|
},
|
|
10
12
|
"publishConfig": {
|
|
11
13
|
"access": "public"
|
|
@@ -30,6 +32,10 @@
|
|
|
30
32
|
},
|
|
31
33
|
"./types": {
|
|
32
34
|
"types": "./dist/types.d.ts"
|
|
35
|
+
},
|
|
36
|
+
"./parse-fields": {
|
|
37
|
+
"import": "./dist/parse-fields.js",
|
|
38
|
+
"types": "./dist/parse-fields.d.ts"
|
|
33
39
|
}
|
|
34
40
|
},
|
|
35
41
|
"files": [
|
|
@@ -41,7 +47,7 @@
|
|
|
41
47
|
"clean": "tsc --build --clean"
|
|
42
48
|
},
|
|
43
49
|
"dependencies": {
|
|
44
|
-
"@burger-editor/utils": "4.0.0-alpha.
|
|
50
|
+
"@burger-editor/utils": "4.0.0-alpha.71"
|
|
45
51
|
},
|
|
46
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "c1f332e5de38a97b5fb901ccd3b28e2d49e65f53"
|
|
47
53
|
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { camelCase } from './utils.js';
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
4
|
-
* @param field
|
|
5
|
-
*/
|
|
6
|
-
export function fieldNameParser(field) {
|
|
7
|
-
field = field.trim().toLowerCase();
|
|
8
|
-
if (!field) {
|
|
9
|
-
throw new Error('Field name is empty.');
|
|
10
|
-
}
|
|
11
|
-
let [fieldName, propName] = field.split(':');
|
|
12
|
-
propName = propName?.trim() || '';
|
|
13
|
-
fieldName = fieldName?.trim() || propName || field;
|
|
14
|
-
return {
|
|
15
|
-
fieldName: camelCase(fieldName),
|
|
16
|
-
propName,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { FieldDefinition } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* フィールド名と属性名からフィールド定義文字列を生成します
|
|
4
|
-
* @param fieldName フィールド名
|
|
5
|
-
* @param propName 属性名(オプション)
|
|
6
|
-
* @returns フィールド定義文字列
|
|
7
|
-
*/
|
|
8
|
-
export declare function fieldNameStringify(fieldName: string, propName?: string): string;
|
|
9
|
-
/**
|
|
10
|
-
* 複数のフィールド定義をカンマ区切りで結合します
|
|
11
|
-
* @param fields フィールド定義の配列
|
|
12
|
-
* @returns カンマ区切りのフィールド定義文字列
|
|
13
|
-
*/
|
|
14
|
-
export declare function fieldsStringify(fields: readonly FieldDefinition[]): string;
|
|
@@ -1,26 +0,0 @@
|
|
|
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/get.d.ts
DELETED
package/dist/get.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { getValues } from './get-values.js';
|
|
2
|
-
import { arrayToHash } from './utils.js';
|
|
3
|
-
/**
|
|
4
|
-
*
|
|
5
|
-
* @param el
|
|
6
|
-
* @param attr
|
|
7
|
-
* @param typeConvert
|
|
8
|
-
* @param filter
|
|
9
|
-
*/
|
|
10
|
-
export default function (el, attr, typeConvert, filter) {
|
|
11
|
-
// eslint-disable-next-line unicorn/prefer-spread
|
|
12
|
-
const filedElements = Array.from(el.querySelectorAll(`[data-${attr}]`));
|
|
13
|
-
let values = [];
|
|
14
|
-
for (const _el of filedElements) {
|
|
15
|
-
values = [...values, ...getValues(_el, typeConvert, attr, filter)];
|
|
16
|
-
}
|
|
17
|
-
const result = arrayToHash(values);
|
|
18
|
-
return result;
|
|
19
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
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/set-comonent.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
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;
|
package/dist/set-comonent.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
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
|
-
}
|
package/dist/set.d.ts
DELETED
package/dist/set.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { setValue } from './set-value.js';
|
|
2
|
-
import { kebabCase } from './utils.js';
|
|
3
|
-
/**
|
|
4
|
-
*
|
|
5
|
-
* @param el
|
|
6
|
-
* @param data
|
|
7
|
-
* @param attr
|
|
8
|
-
* @param filter
|
|
9
|
-
*/
|
|
10
|
-
export default function (el, data, attr, filter) {
|
|
11
|
-
el = el.cloneNode(true);
|
|
12
|
-
for (const dataKeyName in data) {
|
|
13
|
-
if (dataKeyName in data) {
|
|
14
|
-
const datum = data[dataKeyName];
|
|
15
|
-
const selector = `[data-${attr}*="${kebabCase(dataKeyName)}"]`;
|
|
16
|
-
// eslint-disable-next-line unicorn/prefer-spread
|
|
17
|
-
const targetList = Array.from(el.querySelectorAll(selector));
|
|
18
|
-
if (Array.isArray(datum)) {
|
|
19
|
-
const targetEl = targetList[0];
|
|
20
|
-
if (!targetEl) {
|
|
21
|
-
continue;
|
|
22
|
-
}
|
|
23
|
-
const listRoot = targetEl.closest(`[data-${attr}-list]`);
|
|
24
|
-
if (!listRoot || (listRoot && listRoot.children.length === 0)) {
|
|
25
|
-
continue;
|
|
26
|
-
}
|
|
27
|
-
const listItem = listRoot.children[0]?.cloneNode(true);
|
|
28
|
-
while (listItem && datum.length > listRoot.children.length) {
|
|
29
|
-
listRoot.append(listItem.cloneNode(true));
|
|
30
|
-
}
|
|
31
|
-
// eslint-disable-next-line unicorn/prefer-spread
|
|
32
|
-
const newChildren = Array.from(listRoot.querySelectorAll(selector));
|
|
33
|
-
// eslint-disable-next-line unicorn/prefer-spread
|
|
34
|
-
const oldChildList = Array.from(listRoot.children);
|
|
35
|
-
let deleteNodeList = [];
|
|
36
|
-
for (const [i, child] of [...newChildren].entries()) {
|
|
37
|
-
if (datum[i] == null) {
|
|
38
|
-
setValue(child, dataKeyName, '', attr, filter);
|
|
39
|
-
const oldChild = oldChildList[i];
|
|
40
|
-
if (oldChild) {
|
|
41
|
-
deleteNodeList.push(oldChild);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
setValue(child, dataKeyName, datum[i], attr, filter);
|
|
46
|
-
deleteNodeList = [];
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
for (const node of deleteNodeList)
|
|
50
|
-
node.remove();
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
for (const [, targetEl] of [...targetList].entries()) {
|
|
54
|
-
setValue(targetEl, dataKeyName, datum, attr, filter);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return el;
|
|
60
|
-
}
|