flex-html-render 1.0.1 → 1.0.3
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 +7 -0
- package/dist/flex-html-render.cjs.js +6 -1
- package/dist/flex-html-render.es.js +225 -73
- package/dist/flex-html-render.umd.js +6 -1
- package/index.d.ts +1 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,6 +41,12 @@ console.log(JSON.stringify(flexMessage, null, 2));
|
|
|
41
41
|
### `convertHtmlToFlexMessage(htmlString)`
|
|
42
42
|
- **參數**:`htmlString` (string) - Flex Message 對應的 HTML 字串
|
|
43
43
|
- **回傳**:Flex Message JSON 物件陣列
|
|
44
|
+
- **說明**:此函數將 Flex Message HTML 字串轉為 JSON 結構,可用於編輯或顯示目的
|
|
45
|
+
|
|
46
|
+
### `convertJsonToHtml(json)`
|
|
47
|
+
- **參數**:`json` (object) - Flex Message JSON 物件
|
|
48
|
+
- **回傳**:對應的格式化 HTML 字串
|
|
49
|
+
- **說明**:此函數將 Flex Message JSON 結構轉為 HTML 字串,可用於編輯或顯示目的
|
|
44
50
|
|
|
45
51
|
|
|
46
52
|
## 支援的 Flex Message 元素
|
|
@@ -82,6 +88,7 @@ console.log(JSON.stringify(flexMessage, null, 2));
|
|
|
82
88
|
|
|
83
89
|
### 便捷標籤 (Convenience Tags)
|
|
84
90
|
這些是對常用設定的簡寫標籤:
|
|
91
|
+
- `<div>`, `<article>` - 等同於 `<box>`
|
|
85
92
|
- `<baseline>` - 等同於 `<box layout="baseline">`
|
|
86
93
|
- `<row>` - 等同於 `<box layout="horizontal">`
|
|
87
94
|
- `<vertical>` - 等同於 `<box layout="vertical">`
|
|
@@ -1 +1,6 @@
|
|
|
1
|
-
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const J=require("htmlparser2"),E={ELEMENT:"element",TEXT:"text"},e={CAROUSEL:"carousel",BUBBLE:"bubble",HEADER:"header",HERO:"hero",BODY:"body",FOOTER:"footer",BOX:"box",TEXT:"text",SPAN:"span",IMAGE:"image",VIDEO:"video",ICON:"icon",SEPARATOR:"separator",BUTTON:"button",FILLER:"filler",SPACE:"space",STRONG:"strong",BASELINE:"baseline",ROW:"row",VERTICAL:"vertical",DIV:"div",ARTICLE:"article",ACTION:"action",BACKGROUND:"background"},Y=[e.HEADER,e.HERO,e.BODY,e.FOOTER],W=[e.BOX,e.VERTICAL,e.ROW,e.BASELINE,e.DIV,e.ARTICLE],q=[e.TEXT,e.STRONG];function z(t){const r=Number(t);return isNaN(r)?t:r}function _(t){return t==="true"?!0:t==="false"?!1:t}function Q(t){return[z,_].reduce((n,s)=>s(n),t)}function Z(t){const r={type:"root",children:[]},n=[r],s=new J.Parser({onopentag(l,i){const a={type:E.ELEMENT,tagName:l,attributes:Object.fromEntries(Object.entries(i).map(([p,f])=>[p,Q(f)])),children:[]};n[n.length-1].children.push(a),n.push(a)},ontext(l){const i=n[n.length-1];if(i.tagName==="span"){i.children.push({type:E.TEXT,content:l});return}l.trim()&&i.children.push({type:E.TEXT,content:l.replace(/\n/g,"").trim()})},onclosetag(){n.pop()},onerror(l){console.error("解析錯誤:",l)}},{xmlMode:!0,lowerCaseTags:!1,lowerCaseAttributeNames:!1});return s.write(t),s.end(),r.children}const h=t=>{throw Error(`[Flex Html Render]: ${t}`)},c={carousel:t=>({type:"carousel",contents:t}),bubble:(t={})=>({type:"bubble",...t}),box:(t,r={})=>({type:"box",contents:t,...r}),text:(t,r={})=>({type:"text",...typeof t=="string"?{text:t}:{contents:t},...r}),span:(t,r={})=>({type:"span",text:t,...r}),image:(t={})=>({type:"image",...t}),video:(t={})=>({type:"video",...t}),icon:(t={})=>({type:"icon",...t}),button:(t={})=>({type:"button",...t}),separator:(t={color:"#E0E3EA"})=>({type:"separator",...t}),filler:(t={})=>({type:"filler",...t}),uri:(t={})=>({type:"uri",altUri:t.altUri?{desktop:t.altUri}:void 0,...t}),postback:(t={})=>({type:"postback",...t}),message:(t={})=>({type:"message",...t}),datetimepicker:(t={})=>({type:"datetimepicker",...t}),camera:(t={})=>({type:"camera",...t}),cameraRoll:(t={})=>({type:"cameraRoll",...t}),location:(t={})=>({type:"location",...t}),richmenuswitch:(t={})=>({type:"richmenuswitch",...t}),clipboard:(t={})=>({type:"clipboard",...t})};function tt(t){if(t.tagName!==e.BACKGROUND&&t.children&&t.children.length>0){const r=t.children.filter(n=>n.tagName===e.BACKGROUND);return r.length>1&&h("A box node can only allowed with one background node inside"),t.children=t.children.filter(n=>n.tagName!==e.BACKGROUND),r[0]}return null}function et(t){if(t.tagName!==e.ACTION&&t.children&&t.children.length>0){const r=t.children.filter(n=>n.tagName===e.ACTION);return r.length>1&&h("A node can only allowed with one action node inside"),t.children=t.children.filter(n=>n.tagName!==e.ACTION),r[0]}return null}function rt(t){![...q,e.SPAN].includes(t.tagName)&&t.children?.some(l=>l.type===E.TEXT)&&(console.log(t),h("Span component only allowed inside Text component")),t.tagName===e.VIDEO&&t.children?.some(l=>![e.BOX,e.IMAGE].includes(l.tagName))&&h("Video component only allowed Box or Image as altContent child"),!W.includes(t.tagName)&&t.children?.some(l=>l.tagName===e.BACKGROUND)&&h("Only Box component allowed to have Background child")}function m(t){t.attributes=t.attributes||{},rt(t);const r=et(t);r&&(t.attributes.action=m(r));const n=tt(t);if(n&&(t.attributes.background=m(n)),t.type===E.TEXT)return c.span(t.content,t.attributes);if(t.tagName===e.BUBBLE){const s=t.children.find(u=>u.tagName===e.HEADER),l=t.children.find(u=>u.tagName===e.HERO),i=t.children.find(u=>u.tagName===e.BODY),a=t.children.find(u=>u.tagName===e.FOOTER);return c.bubble({...t.attributes,header:s?m(s):void 0,hero:l?m(l):void 0,body:i?m(i):void 0,footer:a?m(a):void 0})}if(t.tagName===e.CAROUSEL)return t.children.some(s=>s.tagName!==e.BUBBLE)&&h("Carousel can only have Bubble as children"),t.children.length>12&&h("Carousel can have maximum 12 bubbles"),c.carousel(t.children.map(m));if(Y.includes(t.tagName))return t.children.length!==1&&h(`${t.tagName} should have exactly one child node`),m(t.children[0]);if(t.tagName===e.BUTTON)return t.attributes.action||h("Button component should contain an action node"),c.button(t.attributes);if(t.tagName===e.SPAN){t.children.length>1&&h("Span component can only have one text child");const s=t.children[0];if(s&&s.type===E.TEXT)return c.span(s.content,t.attributes);h(s?"Span component only allowed text child":"Span component can not be empty")}if(t.tagName===e.TEXT)return c.text(t.children.map(m),t.attributes);if(t.tagName===e.SEPARATOR)return c.separator(t.attributes);if(t.tagName===e.IMAGE)return c.image(t.attributes);if(t.tagName===e.VIDEO)return t.children.length!==1&&h("Video component should only has one Box or Image as altContent"),t.attributes.altContent=m(t.children[0]),c.video(t.attributes);if(t.tagName===e.ICON)return c.icon(t.attributes);if(t.tagName===e.FILLER)return c.filler(t.attributes);if(t.tagName===e.BOX||t.tagName===e.DIV||t.tagName===e.ARTICLE)return t.attributes.layout||(t.attributes.layout="vertical"),c.box(t.children.map(m),t.attributes);if(t.tagName===e.SPACE)return c.span(" ".repeat(t.attributes.size||1));if(t.tagName===e.STRONG)return t.tagName=e.TEXT,t.attributes.weight="bold",m(t);if(t.tagName===e.BASELINE)return t.tagName=e.BOX,t.attributes.layout="baseline",m(t);if(t.tagName===e.ROW)return t.tagName=e.BOX,t.attributes.layout="horizontal",m(t);if(t.tagName===e.VERTICAL)return t.tagName=e.BOX,t.attributes.layout="vertical",m(t);if(t.tagName===e.ACTION){const s=t.attributes.type;switch(s){case"uri":return c.uri(t.attributes);case"postback":return c.postback(t.attributes);case"message":return c.message(t.attributes);case"datetimepicker":return c.datetimepicker(t.attributes);case"camera":return c.camera(t.attributes);case"cameraRoll":return c.cameraRoll(t.attributes);case"location":return c.location(t.attributes);case"richmenuswitch":return c.richmenuswitch(t.attributes);case"clipboard":return c.clipboard(t.attributes);default:h(`Unsupported action type: ${s}`)}}if(t.tagName===e.BACKGROUND)return t.attributes}const o={createElement:(t,r={},n=[],s=0)=>{const i=Object.entries(r).filter(([u,p])=>p!=null).map(([u,p])=>typeof p=="object"?`${u}="${JSON.stringify(p).replace(/"/g,""")}"`:`${u}="${p}"`).join(" "),a=i?` ${i}`:"";if(n.length===0)return` <${t}${a} />`;{const u=n.map(p=>typeof p=="string"?p.split(`
|
|
2
|
+
`).map(f=>f?" "+f:"").join(`
|
|
3
|
+
`):p).join(`
|
|
4
|
+
`);return` <${t}${a}>
|
|
5
|
+
${u}
|
|
6
|
+
</${t}>`}},createText:t=>t};function b(t,r=0){if(!t||typeof t!="object")return"";const{type:n,...s}=t,{action:l,...i}=s,a=l?b(l,r+1):"";switch(n){case"carousel":const{contents:u,...p}=i,f=u.map(N=>b(N,r+1));return a&&f.unshift(a),o.createElement(e.CAROUSEL,p,f,r);case"bubble":const{header:A,hero:C,body:R,footer:B,...L}=i,g=[];return a&&g.push(a),A&&g.push(o.createElement(e.HEADER,{},[b(A,r+1)],r+1)),C&&g.push(o.createElement(e.HERO,{},[b(C,r+1)],r+1)),R&&g.push(o.createElement(e.BODY,{},[b(R,r+1)],r+1)),B&&g.push(o.createElement(e.FOOTER,{},[b(B,r+1)],r+1)),o.createElement(e.BUBBLE,L,g,r);case"box":const{contents:w,background:d,...O}=i;O.layout||(O.layout="vertical");const T=[];return a&&T.push(a),d&&T.push(b({type:"background",...d},r+1)),T.push(...(w||[]).map(N=>b(N,r+1))),o.createElement(e.BOX,O,T,r);case"text":const{text:U,contents:y,...k}=i,x=y&&y.length>0?y.map(N=>b(N,r+1)):[U||""];return a&&x.unshift(a),o.createElement(e.TEXT,k,x,r);case"span":const{text:D,...X}=i,S=[D||""];return a&&S.unshift(a),o.createElement(e.SPAN,X,S,r);case"image":const{...$}=i,H=a?[a]:[];return o.createElement(e.IMAGE,$,H,r);case"video":const{altContent:I,...G}=i,v=I?[b(I,r+1)]:[];return a&&v.unshift(a),o.createElement(e.VIDEO,G,v,r);case"icon":const{...M}=i,F=a?[a]:[];return o.createElement(e.ICON,M,F,r);case"button":const{...P}=i,V=a?[a]:[];return o.createElement(e.BUTTON,P,V,r);case"separator":const j=a?[a]:[];return o.createElement(e.SEPARATOR,i,j,r);case"filler":const K=a?[a]:[];return o.createElement(e.FILLER,i,K,r);case"uri":case"message":case"postback":case"datetimepicker":case"camera":case"cameraRoll":case"location":case"richmenuswitch":case"clipboard":return o.createElement(e.ACTION,{type:n,...i},[],r);case"background":return o.createElement(e.BACKGROUND,i,[],r);default:return""}}function at(t){try{return Z(t).map(m)}catch(r){throw console.error("Error converting HTML to Flex Message:",r),r}}exports.convertJsonToHtml=b;exports.default=at;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Parser as
|
|
2
|
-
const
|
|
1
|
+
import { Parser as Y } from "htmlparser2";
|
|
2
|
+
const E = {
|
|
3
3
|
ELEMENT: "element",
|
|
4
4
|
TEXT: "text"
|
|
5
5
|
}, e = {
|
|
@@ -20,6 +20,7 @@ const g = {
|
|
|
20
20
|
ICON: "icon",
|
|
21
21
|
SEPARATOR: "separator",
|
|
22
22
|
BUTTON: "button",
|
|
23
|
+
FILLER: "filler",
|
|
23
24
|
// custom render tags
|
|
24
25
|
SPACE: "space",
|
|
25
26
|
STRONG: "strong",
|
|
@@ -27,58 +28,73 @@ const g = {
|
|
|
27
28
|
BASELINE: "baseline",
|
|
28
29
|
ROW: "row",
|
|
29
30
|
VERTICAL: "vertical",
|
|
31
|
+
DIV: "div",
|
|
32
|
+
ARTICLE: "article",
|
|
30
33
|
// custom utils tags
|
|
31
34
|
ACTION: "action",
|
|
32
35
|
BACKGROUND: "background"
|
|
33
|
-
},
|
|
36
|
+
}, J = [
|
|
34
37
|
e.HEADER,
|
|
35
38
|
e.HERO,
|
|
36
39
|
e.BODY,
|
|
37
40
|
e.FOOTER
|
|
38
|
-
],
|
|
41
|
+
], W = [
|
|
39
42
|
e.BOX,
|
|
40
43
|
e.VERTICAL,
|
|
41
44
|
e.ROW,
|
|
42
|
-
e.BASELINE
|
|
45
|
+
e.BASELINE,
|
|
46
|
+
e.DIV,
|
|
47
|
+
e.ARTICLE
|
|
48
|
+
], z = [
|
|
49
|
+
e.TEXT,
|
|
50
|
+
e.STRONG
|
|
43
51
|
];
|
|
44
|
-
function
|
|
52
|
+
function q(t) {
|
|
45
53
|
const r = Number(t);
|
|
46
54
|
return isNaN(r) ? t : r;
|
|
47
55
|
}
|
|
48
|
-
function
|
|
56
|
+
function Q(t) {
|
|
49
57
|
return t === "true" ? !0 : t === "false" ? !1 : t;
|
|
50
58
|
}
|
|
51
|
-
function
|
|
59
|
+
function Z(t) {
|
|
52
60
|
return [
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
].reduce((
|
|
61
|
+
q,
|
|
62
|
+
Q
|
|
63
|
+
].reduce((n, s) => s(n), t);
|
|
56
64
|
}
|
|
57
|
-
function
|
|
58
|
-
const r = { type: "root", children: [] },
|
|
65
|
+
function _(t) {
|
|
66
|
+
const r = { type: "root", children: [] }, n = [r], s = new Y(
|
|
59
67
|
{
|
|
60
|
-
onopentag(
|
|
61
|
-
const
|
|
62
|
-
type:
|
|
63
|
-
tagName:
|
|
68
|
+
onopentag(l, i) {
|
|
69
|
+
const a = {
|
|
70
|
+
type: E.ELEMENT,
|
|
71
|
+
tagName: l,
|
|
64
72
|
attributes: Object.fromEntries(
|
|
65
|
-
Object.entries(
|
|
73
|
+
Object.entries(i).map(([p, f]) => [p, Z(f)])
|
|
66
74
|
),
|
|
67
75
|
children: []
|
|
68
76
|
};
|
|
69
|
-
|
|
77
|
+
n[n.length - 1].children.push(a), n.push(a);
|
|
70
78
|
},
|
|
71
|
-
ontext(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
79
|
+
ontext(l) {
|
|
80
|
+
const i = n[n.length - 1];
|
|
81
|
+
if (i.tagName === "span") {
|
|
82
|
+
i.children.push({
|
|
83
|
+
type: E.TEXT,
|
|
84
|
+
content: l
|
|
85
|
+
});
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
l.trim() && i.children.push({
|
|
89
|
+
type: E.TEXT,
|
|
90
|
+
content: l.replace(/\n/g, "").trim()
|
|
75
91
|
});
|
|
76
92
|
},
|
|
77
93
|
onclosetag() {
|
|
78
|
-
|
|
94
|
+
n.pop();
|
|
79
95
|
},
|
|
80
|
-
onerror(
|
|
81
|
-
console.error("解析錯誤:",
|
|
96
|
+
onerror(l) {
|
|
97
|
+
console.error("解析錯誤:", l);
|
|
82
98
|
}
|
|
83
99
|
},
|
|
84
100
|
{
|
|
@@ -89,9 +105,9 @@ function A(t) {
|
|
|
89
105
|
lowerCaseAttributeNames: !1
|
|
90
106
|
}
|
|
91
107
|
);
|
|
92
|
-
return
|
|
108
|
+
return s.write(t), s.end(), r.children;
|
|
93
109
|
}
|
|
94
|
-
const
|
|
110
|
+
const h = (t) => {
|
|
95
111
|
throw Error(`[Flex Html Render]: ${t}`);
|
|
96
112
|
}, c = {
|
|
97
113
|
carousel: (t) => ({
|
|
@@ -139,66 +155,105 @@ const l = (t) => {
|
|
|
139
155
|
type: "separator",
|
|
140
156
|
...t
|
|
141
157
|
}),
|
|
158
|
+
filler: (t = {}) => ({
|
|
159
|
+
type: "filler",
|
|
160
|
+
...t
|
|
161
|
+
}),
|
|
142
162
|
uri: (t = {}) => ({
|
|
143
163
|
type: "uri",
|
|
144
164
|
altUri: t.altUri ? {
|
|
145
165
|
desktop: t.altUri
|
|
146
166
|
} : void 0,
|
|
147
167
|
...t
|
|
168
|
+
}),
|
|
169
|
+
postback: (t = {}) => ({
|
|
170
|
+
type: "postback",
|
|
171
|
+
...t
|
|
172
|
+
}),
|
|
173
|
+
message: (t = {}) => ({
|
|
174
|
+
type: "message",
|
|
175
|
+
...t
|
|
176
|
+
}),
|
|
177
|
+
datetimepicker: (t = {}) => ({
|
|
178
|
+
type: "datetimepicker",
|
|
179
|
+
...t
|
|
180
|
+
}),
|
|
181
|
+
camera: (t = {}) => ({
|
|
182
|
+
type: "camera",
|
|
183
|
+
...t
|
|
184
|
+
}),
|
|
185
|
+
cameraRoll: (t = {}) => ({
|
|
186
|
+
type: "cameraRoll",
|
|
187
|
+
...t
|
|
188
|
+
}),
|
|
189
|
+
location: (t = {}) => ({
|
|
190
|
+
type: "location",
|
|
191
|
+
...t
|
|
192
|
+
}),
|
|
193
|
+
richmenuswitch: (t = {}) => ({
|
|
194
|
+
type: "richmenuswitch",
|
|
195
|
+
...t
|
|
196
|
+
}),
|
|
197
|
+
clipboard: (t = {}) => ({
|
|
198
|
+
type: "clipboard",
|
|
199
|
+
...t
|
|
148
200
|
})
|
|
149
201
|
};
|
|
150
|
-
function
|
|
202
|
+
function tt(t) {
|
|
151
203
|
if (t.tagName !== e.BACKGROUND && t.children && t.children.length > 0) {
|
|
152
|
-
const r = t.children.filter((
|
|
153
|
-
return r.length > 1 &&
|
|
204
|
+
const r = t.children.filter((n) => n.tagName === e.BACKGROUND);
|
|
205
|
+
return r.length > 1 && h("A box node can only allowed with one background node inside"), t.children = t.children.filter((n) => n.tagName !== e.BACKGROUND), r[0];
|
|
154
206
|
}
|
|
155
207
|
return null;
|
|
156
208
|
}
|
|
157
|
-
function
|
|
209
|
+
function et(t) {
|
|
158
210
|
if (t.tagName !== e.ACTION && t.children && t.children.length > 0) {
|
|
159
|
-
const r = t.children.filter((
|
|
160
|
-
return r.length > 1 &&
|
|
211
|
+
const r = t.children.filter((n) => n.tagName === e.ACTION);
|
|
212
|
+
return r.length > 1 && h("A node can only allowed with one action node inside"), t.children = t.children.filter((n) => n.tagName !== e.ACTION), r[0];
|
|
161
213
|
}
|
|
162
214
|
return null;
|
|
163
215
|
}
|
|
164
|
-
function
|
|
165
|
-
t.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
216
|
+
function rt(t) {
|
|
217
|
+
![...z, e.SPAN].includes(t.tagName) && t.children?.some((l) => l.type === E.TEXT) && (console.log(t), h("Span component only allowed inside Text component")), t.tagName === e.VIDEO && t.children?.some((l) => ![e.BOX, e.IMAGE].includes(l.tagName)) && h("Video component only allowed Box or Image as altContent child"), !W.includes(t.tagName) && t.children?.some((l) => l.tagName === e.BACKGROUND) && h("Only Box component allowed to have Background child");
|
|
218
|
+
}
|
|
219
|
+
function m(t) {
|
|
220
|
+
t.attributes = t.attributes || {}, rt(t);
|
|
221
|
+
const r = et(t);
|
|
222
|
+
r && (t.attributes.action = m(r));
|
|
223
|
+
const n = tt(t);
|
|
224
|
+
if (n && (t.attributes.background = m(n)), t.type === E.TEXT)
|
|
170
225
|
return c.span(t.content, t.attributes);
|
|
171
226
|
if (t.tagName === e.BUBBLE) {
|
|
172
|
-
const
|
|
227
|
+
const s = t.children.find((u) => u.tagName === e.HEADER), l = t.children.find((u) => u.tagName === e.HERO), i = t.children.find((u) => u.tagName === e.BODY), a = t.children.find((u) => u.tagName === e.FOOTER);
|
|
173
228
|
return c.bubble({
|
|
174
229
|
...t.attributes,
|
|
175
|
-
header:
|
|
176
|
-
hero:
|
|
177
|
-
body:
|
|
178
|
-
footer:
|
|
230
|
+
header: s ? m(s) : void 0,
|
|
231
|
+
hero: l ? m(l) : void 0,
|
|
232
|
+
body: i ? m(i) : void 0,
|
|
233
|
+
footer: a ? m(a) : void 0
|
|
179
234
|
});
|
|
180
235
|
}
|
|
181
236
|
if (t.tagName === e.CAROUSEL)
|
|
182
|
-
return t.children.some((
|
|
183
|
-
t.children.map(
|
|
237
|
+
return t.children.some((s) => s.tagName !== e.BUBBLE) && h("Carousel can only have Bubble as children"), t.children.length > 12 && h("Carousel can have maximum 12 bubbles"), c.carousel(
|
|
238
|
+
t.children.map(m)
|
|
184
239
|
);
|
|
185
|
-
if (
|
|
186
|
-
return t.children.length !== 1 &&
|
|
240
|
+
if (J.includes(t.tagName))
|
|
241
|
+
return t.children.length !== 1 && h(`${t.tagName} should have exactly one child node`), m(t.children[0]);
|
|
187
242
|
if (t.tagName === e.BUTTON)
|
|
188
|
-
return t.attributes.action ||
|
|
243
|
+
return t.attributes.action || h("Button component should contain an action node"), c.button(t.attributes);
|
|
189
244
|
if (t.tagName === e.SPAN) {
|
|
190
|
-
t.children.length > 1 &&
|
|
191
|
-
const
|
|
192
|
-
if (
|
|
245
|
+
t.children.length > 1 && h("Span component can only have one text child");
|
|
246
|
+
const s = t.children[0];
|
|
247
|
+
if (s && s.type === E.TEXT)
|
|
193
248
|
return c.span(
|
|
194
|
-
|
|
249
|
+
s.content,
|
|
195
250
|
t.attributes
|
|
196
251
|
);
|
|
197
|
-
|
|
252
|
+
h(s ? "Span component only allowed text child" : "Span component can not be empty");
|
|
198
253
|
}
|
|
199
254
|
if (t.tagName === e.TEXT)
|
|
200
255
|
return c.text(
|
|
201
|
-
t.children.map(
|
|
256
|
+
t.children.map(m),
|
|
202
257
|
t.attributes
|
|
203
258
|
);
|
|
204
259
|
if (t.tagName === e.SEPARATOR)
|
|
@@ -206,39 +261,136 @@ function i(t) {
|
|
|
206
261
|
if (t.tagName === e.IMAGE)
|
|
207
262
|
return c.image(t.attributes);
|
|
208
263
|
if (t.tagName === e.VIDEO)
|
|
209
|
-
return t.children.length !== 1 &&
|
|
210
|
-
e.BOX,
|
|
211
|
-
e.IMAGE
|
|
212
|
-
].includes(t.children[0]?.tagName) || l("Video component should only has Box or Image child as altContent"), t.attributes.altContent = i(t.children[0]), c.video(t.attributes);
|
|
264
|
+
return t.children.length !== 1 && h("Video component should only has one Box or Image as altContent"), t.attributes.altContent = m(t.children[0]), c.video(t.attributes);
|
|
213
265
|
if (t.tagName === e.ICON)
|
|
214
266
|
return c.icon(t.attributes);
|
|
215
|
-
if (t.tagName === e.
|
|
216
|
-
return c.
|
|
217
|
-
|
|
267
|
+
if (t.tagName === e.FILLER)
|
|
268
|
+
return c.filler(t.attributes);
|
|
269
|
+
if (t.tagName === e.BOX || t.tagName === e.DIV || t.tagName === e.ARTICLE)
|
|
270
|
+
return t.attributes.layout || (t.attributes.layout = "vertical"), c.box(
|
|
271
|
+
t.children.map(m),
|
|
218
272
|
t.attributes
|
|
219
273
|
);
|
|
220
274
|
if (t.tagName === e.SPACE)
|
|
221
275
|
return c.span(" ".repeat(t.attributes.size || 1));
|
|
222
276
|
if (t.tagName === e.STRONG)
|
|
223
|
-
return t.tagName = e.TEXT, t.attributes.weight = "bold",
|
|
277
|
+
return t.tagName = e.TEXT, t.attributes.weight = "bold", m(t);
|
|
224
278
|
if (t.tagName === e.BASELINE)
|
|
225
|
-
return t.tagName = e.BOX, t.attributes.layout = "baseline",
|
|
279
|
+
return t.tagName = e.BOX, t.attributes.layout = "baseline", m(t);
|
|
226
280
|
if (t.tagName === e.ROW)
|
|
227
|
-
return t.tagName = e.BOX, t.attributes.layout = "horizontal",
|
|
281
|
+
return t.tagName = e.BOX, t.attributes.layout = "horizontal", m(t);
|
|
228
282
|
if (t.tagName === e.VERTICAL)
|
|
229
|
-
return t.tagName = e.BOX, t.attributes.layout = "vertical",
|
|
230
|
-
if (t.tagName === e.ACTION
|
|
231
|
-
|
|
283
|
+
return t.tagName = e.BOX, t.attributes.layout = "vertical", m(t);
|
|
284
|
+
if (t.tagName === e.ACTION) {
|
|
285
|
+
const s = t.attributes.type;
|
|
286
|
+
switch (s) {
|
|
287
|
+
case "uri":
|
|
288
|
+
return c.uri(t.attributes);
|
|
289
|
+
case "postback":
|
|
290
|
+
return c.postback(t.attributes);
|
|
291
|
+
case "message":
|
|
292
|
+
return c.message(t.attributes);
|
|
293
|
+
case "datetimepicker":
|
|
294
|
+
return c.datetimepicker(t.attributes);
|
|
295
|
+
case "camera":
|
|
296
|
+
return c.camera(t.attributes);
|
|
297
|
+
case "cameraRoll":
|
|
298
|
+
return c.cameraRoll(t.attributes);
|
|
299
|
+
case "location":
|
|
300
|
+
return c.location(t.attributes);
|
|
301
|
+
case "richmenuswitch":
|
|
302
|
+
return c.richmenuswitch(t.attributes);
|
|
303
|
+
case "clipboard":
|
|
304
|
+
return c.clipboard(t.attributes);
|
|
305
|
+
default:
|
|
306
|
+
h(`Unsupported action type: ${s}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
232
309
|
if (t.tagName === e.BACKGROUND)
|
|
233
310
|
return t.attributes;
|
|
234
311
|
}
|
|
235
|
-
|
|
312
|
+
const o = {
|
|
313
|
+
createElement: (t, r = {}, n = [], s = 0) => {
|
|
314
|
+
const i = Object.entries(r).filter(([u, p]) => p != null).map(([u, p]) => typeof p == "object" ? `${u}="${JSON.stringify(p).replace(/"/g, """)}"` : `${u}="${p}"`).join(" "), a = i ? ` ${i}` : "";
|
|
315
|
+
if (n.length === 0)
|
|
316
|
+
return ` <${t}${a} />`;
|
|
317
|
+
{
|
|
318
|
+
const u = n.map((p) => typeof p == "string" ? p.split(`
|
|
319
|
+
`).map((f) => f ? " " + f : "").join(`
|
|
320
|
+
`) : p).join(`
|
|
321
|
+
`);
|
|
322
|
+
return ` <${t}${a}>
|
|
323
|
+
${u}
|
|
324
|
+
</${t}>`;
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
createText: (t) => t
|
|
328
|
+
};
|
|
329
|
+
function b(t, r = 0) {
|
|
330
|
+
if (!t || typeof t != "object")
|
|
331
|
+
return "";
|
|
332
|
+
const { type: n, ...s } = t, { action: l, ...i } = s, a = l ? b(l, r + 1) : "";
|
|
333
|
+
switch (n) {
|
|
334
|
+
case "carousel":
|
|
335
|
+
const { contents: u, ...p } = i, f = u.map((N) => b(N, r + 1));
|
|
336
|
+
return a && f.unshift(a), o.createElement(e.CAROUSEL, p, f, r);
|
|
337
|
+
case "bubble":
|
|
338
|
+
const { header: y, hero: C, body: R, footer: B, ...L } = i, g = [];
|
|
339
|
+
return a && g.push(a), y && g.push(o.createElement(e.HEADER, {}, [b(y, r + 1)], r + 1)), C && g.push(o.createElement(e.HERO, {}, [b(C, r + 1)], r + 1)), R && g.push(o.createElement(e.BODY, {}, [b(R, r + 1)], r + 1)), B && g.push(o.createElement(e.FOOTER, {}, [b(B, r + 1)], r + 1)), o.createElement(e.BUBBLE, L, g, r);
|
|
340
|
+
case "box":
|
|
341
|
+
const { contents: w, background: d, ...O } = i;
|
|
342
|
+
O.layout || (O.layout = "vertical");
|
|
343
|
+
const T = [];
|
|
344
|
+
return a && T.push(a), d && T.push(b({ type: "background", ...d }, r + 1)), T.push(...(w || []).map((N) => b(N, r + 1))), o.createElement(e.BOX, O, T, r);
|
|
345
|
+
case "text":
|
|
346
|
+
const { text: U, contents: A, ...k } = i, x = A && A.length > 0 ? A.map((N) => b(N, r + 1)) : [U || ""];
|
|
347
|
+
return a && x.unshift(a), o.createElement(e.TEXT, k, x, r);
|
|
348
|
+
case "span":
|
|
349
|
+
const { text: D, ...X } = i, I = [D || ""];
|
|
350
|
+
return a && I.unshift(a), o.createElement(e.SPAN, X, I, r);
|
|
351
|
+
case "image":
|
|
352
|
+
const { ...$ } = i, G = a ? [a] : [];
|
|
353
|
+
return o.createElement(e.IMAGE, $, G, r);
|
|
354
|
+
case "video":
|
|
355
|
+
const { altContent: S, ...H } = i, v = S ? [b(S, r + 1)] : [];
|
|
356
|
+
return a && v.unshift(a), o.createElement(e.VIDEO, H, v, r);
|
|
357
|
+
case "icon":
|
|
358
|
+
const { ...F } = i, V = a ? [a] : [];
|
|
359
|
+
return o.createElement(e.ICON, F, V, r);
|
|
360
|
+
case "button":
|
|
361
|
+
const { ...M } = i, P = a ? [a] : [];
|
|
362
|
+
return o.createElement(e.BUTTON, M, P, r);
|
|
363
|
+
case "separator":
|
|
364
|
+
const K = a ? [a] : [];
|
|
365
|
+
return o.createElement(e.SEPARATOR, i, K, r);
|
|
366
|
+
case "filler":
|
|
367
|
+
const j = a ? [a] : [];
|
|
368
|
+
return o.createElement(e.FILLER, i, j, r);
|
|
369
|
+
// Action types
|
|
370
|
+
case "uri":
|
|
371
|
+
case "message":
|
|
372
|
+
case "postback":
|
|
373
|
+
case "datetimepicker":
|
|
374
|
+
case "camera":
|
|
375
|
+
case "cameraRoll":
|
|
376
|
+
case "location":
|
|
377
|
+
case "richmenuswitch":
|
|
378
|
+
case "clipboard":
|
|
379
|
+
return o.createElement(e.ACTION, { type: n, ...i }, [], r);
|
|
380
|
+
case "background":
|
|
381
|
+
return o.createElement(e.BACKGROUND, i, [], r);
|
|
382
|
+
default:
|
|
383
|
+
return "";
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
function nt(t) {
|
|
236
387
|
try {
|
|
237
|
-
return
|
|
388
|
+
return _(t).map(m);
|
|
238
389
|
} catch (r) {
|
|
239
|
-
console.error("Error converting HTML to Flex Message:", r);
|
|
390
|
+
throw console.error("Error converting HTML to Flex Message:", r), r;
|
|
240
391
|
}
|
|
241
392
|
}
|
|
242
393
|
export {
|
|
243
|
-
|
|
394
|
+
b as convertJsonToHtml,
|
|
395
|
+
nt as default
|
|
244
396
|
};
|
|
@@ -1 +1,6 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(b,T){typeof exports=="object"&&typeof module<"u"?T(exports,require("htmlparser2")):typeof define=="function"&&define.amd?define(["exports","htmlparser2"],T):(b=typeof globalThis<"u"?globalThis:b||self,T(b.FlexHtmlRender={},b.htmlparser2))})(this,(function(b,T){"use strict";const E={ELEMENT:"element",TEXT:"text"},e={CAROUSEL:"carousel",BUBBLE:"bubble",HEADER:"header",HERO:"hero",BODY:"body",FOOTER:"footer",BOX:"box",TEXT:"text",SPAN:"span",IMAGE:"image",VIDEO:"video",ICON:"icon",SEPARATOR:"separator",BUTTON:"button",FILLER:"filler",SPACE:"space",STRONG:"strong",BASELINE:"baseline",ROW:"row",VERTICAL:"vertical",DIV:"div",ARTICLE:"article",ACTION:"action",BACKGROUND:"background"},U=[e.HEADER,e.HERO,e.BODY,e.FOOTER],k=[e.BOX,e.VERTICAL,e.ROW,e.BASELINE,e.DIV,e.ARTICLE],D=[e.TEXT,e.STRONG];function X(t){const r=Number(t);return isNaN(r)?t:r}function $(t){return t==="true"?!0:t==="false"?!1:t}function H(t){return[X,$].reduce((n,s)=>s(n),t)}function G(t){const r={type:"root",children:[]},n=[r],s=new T.Parser({onopentag(l,i){const a={type:E.ELEMENT,tagName:l,attributes:Object.fromEntries(Object.entries(i).map(([h,g])=>[h,H(g)])),children:[]};n[n.length-1].children.push(a),n.push(a)},ontext(l){const i=n[n.length-1];if(i.tagName==="span"){i.children.push({type:E.TEXT,content:l});return}l.trim()&&i.children.push({type:E.TEXT,content:l.replace(/\n/g,"").trim()})},onclosetag(){n.pop()},onerror(l){console.error("解析錯誤:",l)}},{xmlMode:!0,lowerCaseTags:!1,lowerCaseAttributeNames:!1});return s.write(t),s.end(),r.children}const p=t=>{throw Error(`[Flex Html Render]: ${t}`)},c={carousel:t=>({type:"carousel",contents:t}),bubble:(t={})=>({type:"bubble",...t}),box:(t,r={})=>({type:"box",contents:t,...r}),text:(t,r={})=>({type:"text",...typeof t=="string"?{text:t}:{contents:t},...r}),span:(t,r={})=>({type:"span",text:t,...r}),image:(t={})=>({type:"image",...t}),video:(t={})=>({type:"video",...t}),icon:(t={})=>({type:"icon",...t}),button:(t={})=>({type:"button",...t}),separator:(t={color:"#E0E3EA"})=>({type:"separator",...t}),filler:(t={})=>({type:"filler",...t}),uri:(t={})=>({type:"uri",altUri:t.altUri?{desktop:t.altUri}:void 0,...t}),postback:(t={})=>({type:"postback",...t}),message:(t={})=>({type:"message",...t}),datetimepicker:(t={})=>({type:"datetimepicker",...t}),camera:(t={})=>({type:"camera",...t}),cameraRoll:(t={})=>({type:"cameraRoll",...t}),location:(t={})=>({type:"location",...t}),richmenuswitch:(t={})=>({type:"richmenuswitch",...t}),clipboard:(t={})=>({type:"clipboard",...t})};function F(t){if(t.tagName!==e.BACKGROUND&&t.children&&t.children.length>0){const r=t.children.filter(n=>n.tagName===e.BACKGROUND);return r.length>1&&p("A box node can only allowed with one background node inside"),t.children=t.children.filter(n=>n.tagName!==e.BACKGROUND),r[0]}return null}function M(t){if(t.tagName!==e.ACTION&&t.children&&t.children.length>0){const r=t.children.filter(n=>n.tagName===e.ACTION);return r.length>1&&p("A node can only allowed with one action node inside"),t.children=t.children.filter(n=>n.tagName!==e.ACTION),r[0]}return null}function P(t){![...D,e.SPAN].includes(t.tagName)&&t.children?.some(l=>l.type===E.TEXT)&&(console.log(t),p("Span component only allowed inside Text component")),t.tagName===e.VIDEO&&t.children?.some(l=>![e.BOX,e.IMAGE].includes(l.tagName))&&p("Video component only allowed Box or Image as altContent child"),!k.includes(t.tagName)&&t.children?.some(l=>l.tagName===e.BACKGROUND)&&p("Only Box component allowed to have Background child")}function u(t){t.attributes=t.attributes||{},P(t);const r=M(t);r&&(t.attributes.action=u(r));const n=F(t);if(n&&(t.attributes.background=u(n)),t.type===E.TEXT)return c.span(t.content,t.attributes);if(t.tagName===e.BUBBLE){const s=t.children.find(m=>m.tagName===e.HEADER),l=t.children.find(m=>m.tagName===e.HERO),i=t.children.find(m=>m.tagName===e.BODY),a=t.children.find(m=>m.tagName===e.FOOTER);return c.bubble({...t.attributes,header:s?u(s):void 0,hero:l?u(l):void 0,body:i?u(i):void 0,footer:a?u(a):void 0})}if(t.tagName===e.CAROUSEL)return t.children.some(s=>s.tagName!==e.BUBBLE)&&p("Carousel can only have Bubble as children"),t.children.length>12&&p("Carousel can have maximum 12 bubbles"),c.carousel(t.children.map(u));if(U.includes(t.tagName))return t.children.length!==1&&p(`${t.tagName} should have exactly one child node`),u(t.children[0]);if(t.tagName===e.BUTTON)return t.attributes.action||p("Button component should contain an action node"),c.button(t.attributes);if(t.tagName===e.SPAN){t.children.length>1&&p("Span component can only have one text child");const s=t.children[0];if(s&&s.type===E.TEXT)return c.span(s.content,t.attributes);p(s?"Span component only allowed text child":"Span component can not be empty")}if(t.tagName===e.TEXT)return c.text(t.children.map(u),t.attributes);if(t.tagName===e.SEPARATOR)return c.separator(t.attributes);if(t.tagName===e.IMAGE)return c.image(t.attributes);if(t.tagName===e.VIDEO)return t.children.length!==1&&p("Video component should only has one Box or Image as altContent"),t.attributes.altContent=u(t.children[0]),c.video(t.attributes);if(t.tagName===e.ICON)return c.icon(t.attributes);if(t.tagName===e.FILLER)return c.filler(t.attributes);if(t.tagName===e.BOX||t.tagName===e.DIV||t.tagName===e.ARTICLE)return t.attributes.layout||(t.attributes.layout="vertical"),c.box(t.children.map(u),t.attributes);if(t.tagName===e.SPACE)return c.span(" ".repeat(t.attributes.size||1));if(t.tagName===e.STRONG)return t.tagName=e.TEXT,t.attributes.weight="bold",u(t);if(t.tagName===e.BASELINE)return t.tagName=e.BOX,t.attributes.layout="baseline",u(t);if(t.tagName===e.ROW)return t.tagName=e.BOX,t.attributes.layout="horizontal",u(t);if(t.tagName===e.VERTICAL)return t.tagName=e.BOX,t.attributes.layout="vertical",u(t);if(t.tagName===e.ACTION){const s=t.attributes.type;switch(s){case"uri":return c.uri(t.attributes);case"postback":return c.postback(t.attributes);case"message":return c.message(t.attributes);case"datetimepicker":return c.datetimepicker(t.attributes);case"camera":return c.camera(t.attributes);case"cameraRoll":return c.cameraRoll(t.attributes);case"location":return c.location(t.attributes);case"richmenuswitch":return c.richmenuswitch(t.attributes);case"clipboard":return c.clipboard(t.attributes);default:p(`Unsupported action type: ${s}`)}}if(t.tagName===e.BACKGROUND)return t.attributes}const o={createElement:(t,r={},n=[],s=0)=>{const i=Object.entries(r).filter(([m,h])=>h!=null).map(([m,h])=>typeof h=="object"?`${m}="${JSON.stringify(h).replace(/"/g,""")}"`:`${m}="${h}"`).join(" "),a=i?` ${i}`:"";if(n.length===0)return` <${t}${a} />`;{const m=n.map(h=>typeof h=="string"?h.split(`
|
|
2
|
+
`).map(g=>g?" "+g:"").join(`
|
|
3
|
+
`):h).join(`
|
|
4
|
+
`);return` <${t}${a}>
|
|
5
|
+
${m}
|
|
6
|
+
</${t}>`}},createText:t=>t};function f(t,r=0){if(!t||typeof t!="object")return"";const{type:n,...s}=t,{action:l,...i}=s,a=l?f(l,r+1):"";switch(n){case"carousel":const{contents:m,...h}=i,g=m.map(O=>f(O,r+1));return a&&g.unshift(a),o.createElement(e.CAROUSEL,h,g,r);case"bubble":const{header:d,hero:R,body:B,footer:x,...j}=i,N=[];return a&&N.push(a),d&&N.push(o.createElement(e.HEADER,{},[f(d,r+1)],r+1)),R&&N.push(o.createElement(e.HERO,{},[f(R,r+1)],r+1)),B&&N.push(o.createElement(e.BODY,{},[f(B,r+1)],r+1)),x&&N.push(o.createElement(e.FOOTER,{},[f(x,r+1)],r+1)),o.createElement(e.BUBBLE,j,N,r);case"box":const{contents:K,background:S,...A}=i;A.layout||(A.layout="vertical");const y=[];return a&&y.push(a),S&&y.push(f({type:"background",...S},r+1)),y.push(...(K||[]).map(O=>f(O,r+1))),o.createElement(e.BOX,A,y,r);case"text":const{text:J,contents:C,...Y}=i,I=C&&C.length>0?C.map(O=>f(O,r+1)):[J||""];return a&&I.unshift(a),o.createElement(e.TEXT,Y,I,r);case"span":const{text:W,...q}=i,v=[W||""];return a&&v.unshift(a),o.createElement(e.SPAN,q,v,r);case"image":const{...z}=i,_=a?[a]:[];return o.createElement(e.IMAGE,z,_,r);case"video":const{altContent:L,...Q}=i,w=L?[f(L,r+1)]:[];return a&&w.unshift(a),o.createElement(e.VIDEO,Q,w,r);case"icon":const{...Z}=i,tt=a?[a]:[];return o.createElement(e.ICON,Z,tt,r);case"button":const{...et}=i,rt=a?[a]:[];return o.createElement(e.BUTTON,et,rt,r);case"separator":const at=a?[a]:[];return o.createElement(e.SEPARATOR,i,at,r);case"filler":const nt=a?[a]:[];return o.createElement(e.FILLER,i,nt,r);case"uri":case"message":case"postback":case"datetimepicker":case"camera":case"cameraRoll":case"location":case"richmenuswitch":case"clipboard":return o.createElement(e.ACTION,{type:n,...i},[],r);case"background":return o.createElement(e.BACKGROUND,i,[],r);default:return""}}function V(t){try{return G(t).map(u)}catch(r){throw console.error("Error converting HTML to Flex Message:",r),r}}b.convertJsonToHtml=f,b.default=V,Object.defineProperties(b,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
|
package/index.d.ts
CHANGED
|
@@ -3,9 +3,4 @@
|
|
|
3
3
|
* @param htmlString - Flex Message 對應的 HTML 字串
|
|
4
4
|
* @returns Flex Message JSON 物件陣列
|
|
5
5
|
*/
|
|
6
|
-
export default function convertHtmlToFlexMessage(htmlString: string): any[];
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* 官方範例 HTML 字串
|
|
10
|
-
*/
|
|
11
|
-
export const officialDemoString: string;
|
|
6
|
+
export default function convertHtmlToFlexMessage(htmlString: string): any[];
|