flex-html-render 1.0.0 → 1.0.1

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 CHANGED
@@ -42,8 +42,6 @@ console.log(JSON.stringify(flexMessage, null, 2));
42
42
  - **參數**:`htmlString` (string) - Flex Message 對應的 HTML 字串
43
43
  - **回傳**:Flex Message JSON 物件陣列
44
44
 
45
- ### 其他匯出
46
- - `officialDemoString`:官方範例 HTML 字串
47
45
 
48
46
  ## 支援的 Flex Message 元素
49
47
  本工具支援大部分 [LINE Flex Message](https://developers.line.biz/en/reference/messaging-api/#flex-message) 元素,包括:
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const f=require("htmlparser2"),N={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",SPACE:"space",STRONG:"strong",BASELINE:"baseline",ROW:"row",VERTICAL:"vertical",ACTION:"action",BACKGROUND:"background"},b=[e.HEADER,e.HERO,e.BODY,e.FOOTER],p=[e.BOX,e.VERTICAL,e.ROW,e.BASELINE];function E(t){const r=Number(t);return isNaN(r)?t:r}function O(t){return t==="true"?!0:t==="false"?!1:t}function T(t){return[E,O].reduce((a,n)=>n(a),t)}function A(t){const r={type:"root",children:[]},a=[r],n=new f.Parser({onopentag(s,u){const g={type:N.ELEMENT,tagName:s,attributes:Object.fromEntries(Object.entries(u).map(([h,m])=>[h,T(m)])),children:[]};a[a.length-1].children.push(g),a.push(g)},ontext(s){s.trim()&&a[a.length-1].children.push({type:N.TEXT,content:s.replace(/\n/g,"").trim()})},onclosetag(){a.pop()},onerror(s){console.error("解析錯誤:",s)}},{xmlMode:!0,lowerCaseTags:!1,lowerCaseAttributeNames:!1});return n.write(t),n.end(),r.children}const c=t=>{throw Error(`[Flex Html Render]: ${t}`)},l={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}),uri:(t={})=>({type:"uri",altUri:t.altUri?{desktop:t.altUri}:void 0,...t})};function B(t){if(t.tagName!==e.BACKGROUND&&t.children&&t.children.length>0){const r=t.children.filter(a=>a.tagName===e.BACKGROUND);return r.length>1&&c("A box node can only allowed with one background node inside"),t.children=t.children.filter(a=>a.tagName!==e.BACKGROUND),r[0]}return null}function y(t){if(t.tagName!==e.ACTION&&t.children&&t.children.length>0){const r=t.children.filter(a=>a.tagName===e.ACTION);return r.length>1&&c("A node can only allowed with one action node inside"),t.children=t.children.filter(a=>a.tagName!==e.ACTION),r[0]}return null}function i(t){t.attributes=t.attributes||{};const r=y(t);r&&(t.attributes.action=i(r));const a=B(t);if(a&&p.includes(t.tagName)&&(t.attributes.background=i(a)),t.type===N.TEXT)return l.span(t.content,t.attributes);if(t.tagName===e.BUBBLE){const n=t.children.find(o=>o.tagName===e.HEADER),s=t.children.find(o=>o.tagName===e.HERO),u=t.children.find(o=>o.tagName===e.BODY),g=t.children.find(o=>o.tagName===e.FOOTER);return l.bubble({...t.attributes,header:n?i(n):void 0,hero:s?i(s):void 0,body:u?i(u):void 0,footer:g?i(g):void 0})}if(t.tagName===e.CAROUSEL)return t.children.some(n=>n.tagName!==e.BUBBLE)&&c("Carousel can only have Bubble as children"),t.children.length>12&&c("Carousel can have maximum 12 bubbles"),l.carousel(t.children.map(i));if(b.includes(t.tagName))return t.children.length!==1&&c(`${t.tagName} should have exactly one child node`),i(t.children[0]);if(t.tagName===e.BUTTON)return t.attributes.action||c("Button component should contain an action node"),l.button(t.attributes);if(t.tagName===e.SPAN){t.children.length>1&&c("Span component can only have one text child");const n=t.children[0];if(n&&n.type===N.TEXT)return l.span(n.content,t.attributes);c("Span component only allowed text child")}if(t.tagName===e.TEXT)return l.text(t.children.map(i),t.attributes);if(t.tagName===e.SEPARATOR)return l.separator(t.attributes);if(t.tagName===e.IMAGE)return l.image(t.attributes);if(t.tagName===e.VIDEO)return t.children.length!==1&&c("Video component should only has one Box or Image as altContent"),[e.BOX,e.IMAGE].includes(t.children[0]?.tagName)||c("Video component should only has Box or Image child as altContent"),t.attributes.altContent=i(t.children[0]),l.video(t.attributes);if(t.tagName===e.ICON)return l.icon(t.attributes);if(t.tagName===e.BOX)return l.box(t.children.map(i),t.attributes);if(t.tagName===e.SPACE)return l.span(" ".repeat(t.attributes.size||1));if(t.tagName===e.STRONG)return t.tagName=e.TEXT,t.attributes.weight="bold",i(t);if(t.tagName===e.BASELINE)return t.tagName=e.BOX,t.attributes.layout="baseline",i(t);if(t.tagName===e.ROW)return t.tagName=e.BOX,t.attributes.layout="horizontal",i(t);if(t.tagName===e.VERTICAL)return t.tagName=e.BOX,t.attributes.layout="vertical",i(t);if(t.tagName===e.ACTION&&t.attributes.type==="uri")return l.uri(t.attributes);if(t.tagName===e.BACKGROUND)return t.attributes}function R(t){try{return A(t).map(i)}catch(r){console.error("Error converting HTML to Flex Message:",r)}}exports.default=R;
@@ -0,0 +1,244 @@
1
+ import { Parser as f } from "htmlparser2";
2
+ const g = {
3
+ ELEMENT: "element",
4
+ TEXT: "text"
5
+ }, e = {
6
+ // flex message container
7
+ CAROUSEL: "carousel",
8
+ BUBBLE: "bubble",
9
+ // flex message top level sections
10
+ HEADER: "header",
11
+ HERO: "hero",
12
+ BODY: "body",
13
+ FOOTER: "footer",
14
+ // flex message tags
15
+ BOX: "box",
16
+ TEXT: "text",
17
+ SPAN: "span",
18
+ IMAGE: "image",
19
+ VIDEO: "video",
20
+ ICON: "icon",
21
+ SEPARATOR: "separator",
22
+ BUTTON: "button",
23
+ // custom render tags
24
+ SPACE: "space",
25
+ STRONG: "strong",
26
+ // box extends
27
+ BASELINE: "baseline",
28
+ ROW: "row",
29
+ VERTICAL: "vertical",
30
+ // custom utils tags
31
+ ACTION: "action",
32
+ BACKGROUND: "background"
33
+ }, b = [
34
+ e.HEADER,
35
+ e.HERO,
36
+ e.BODY,
37
+ e.FOOTER
38
+ ], p = [
39
+ e.BOX,
40
+ e.VERTICAL,
41
+ e.ROW,
42
+ e.BASELINE
43
+ ];
44
+ function E(t) {
45
+ const r = Number(t);
46
+ return isNaN(r) ? t : r;
47
+ }
48
+ function O(t) {
49
+ return t === "true" ? !0 : t === "false" ? !1 : t;
50
+ }
51
+ function T(t) {
52
+ return [
53
+ E,
54
+ O
55
+ ].reduce((a, n) => n(a), t);
56
+ }
57
+ function A(t) {
58
+ const r = { type: "root", children: [] }, a = [r], n = new f(
59
+ {
60
+ onopentag(s, u) {
61
+ const N = {
62
+ type: g.ELEMENT,
63
+ tagName: s,
64
+ attributes: Object.fromEntries(
65
+ Object.entries(u).map(([h, m]) => [h, T(m)])
66
+ ),
67
+ children: []
68
+ };
69
+ a[a.length - 1].children.push(N), a.push(N);
70
+ },
71
+ ontext(s) {
72
+ s.trim() && a[a.length - 1].children.push({
73
+ type: g.TEXT,
74
+ content: s.replace(/\n/g, "").trim()
75
+ });
76
+ },
77
+ onclosetag() {
78
+ a.pop();
79
+ },
80
+ onerror(s) {
81
+ console.error("解析錯誤:", s);
82
+ }
83
+ },
84
+ {
85
+ xmlMode: !0,
86
+ // 開啟 XML 模式以支援自定義 self-closing tags
87
+ lowerCaseTags: !1,
88
+ // 保留標籤大小寫
89
+ lowerCaseAttributeNames: !1
90
+ }
91
+ );
92
+ return n.write(t), n.end(), r.children;
93
+ }
94
+ const l = (t) => {
95
+ throw Error(`[Flex Html Render]: ${t}`);
96
+ }, c = {
97
+ carousel: (t) => ({
98
+ type: "carousel",
99
+ contents: t
100
+ }),
101
+ bubble: (t = {}) => ({
102
+ type: "bubble",
103
+ ...t
104
+ }),
105
+ box: (t, r = {}) => ({
106
+ type: "box",
107
+ contents: t,
108
+ ...r
109
+ }),
110
+ text: (t, r = {}) => ({
111
+ type: "text",
112
+ ...typeof t == "string" ? { text: t } : {
113
+ contents: t
114
+ },
115
+ ...r
116
+ }),
117
+ span: (t, r = {}) => ({
118
+ type: "span",
119
+ text: t,
120
+ ...r
121
+ }),
122
+ image: (t = {}) => ({
123
+ type: "image",
124
+ ...t
125
+ }),
126
+ video: (t = {}) => ({
127
+ type: "video",
128
+ ...t
129
+ }),
130
+ icon: (t = {}) => ({
131
+ type: "icon",
132
+ ...t
133
+ }),
134
+ button: (t = {}) => ({
135
+ type: "button",
136
+ ...t
137
+ }),
138
+ separator: (t = { color: "#E0E3EA" }) => ({
139
+ type: "separator",
140
+ ...t
141
+ }),
142
+ uri: (t = {}) => ({
143
+ type: "uri",
144
+ altUri: t.altUri ? {
145
+ desktop: t.altUri
146
+ } : void 0,
147
+ ...t
148
+ })
149
+ };
150
+ function B(t) {
151
+ if (t.tagName !== e.BACKGROUND && t.children && t.children.length > 0) {
152
+ const r = t.children.filter((a) => a.tagName === e.BACKGROUND);
153
+ return r.length > 1 && l("A box node can only allowed with one background node inside"), t.children = t.children.filter((a) => a.tagName !== e.BACKGROUND), r[0];
154
+ }
155
+ return null;
156
+ }
157
+ function y(t) {
158
+ if (t.tagName !== e.ACTION && t.children && t.children.length > 0) {
159
+ const r = t.children.filter((a) => a.tagName === e.ACTION);
160
+ return r.length > 1 && l("A node can only allowed with one action node inside"), t.children = t.children.filter((a) => a.tagName !== e.ACTION), r[0];
161
+ }
162
+ return null;
163
+ }
164
+ function i(t) {
165
+ t.attributes = t.attributes || {};
166
+ const r = y(t);
167
+ r && (t.attributes.action = i(r));
168
+ const a = B(t);
169
+ if (a && p.includes(t.tagName) && (t.attributes.background = i(a)), t.type === g.TEXT)
170
+ return c.span(t.content, t.attributes);
171
+ if (t.tagName === e.BUBBLE) {
172
+ const n = t.children.find((o) => o.tagName === e.HEADER), s = t.children.find((o) => o.tagName === e.HERO), u = t.children.find((o) => o.tagName === e.BODY), N = t.children.find((o) => o.tagName === e.FOOTER);
173
+ return c.bubble({
174
+ ...t.attributes,
175
+ header: n ? i(n) : void 0,
176
+ hero: s ? i(s) : void 0,
177
+ body: u ? i(u) : void 0,
178
+ footer: N ? i(N) : void 0
179
+ });
180
+ }
181
+ if (t.tagName === e.CAROUSEL)
182
+ return t.children.some((n) => n.tagName !== e.BUBBLE) && l("Carousel can only have Bubble as children"), t.children.length > 12 && l("Carousel can have maximum 12 bubbles"), c.carousel(
183
+ t.children.map(i)
184
+ );
185
+ if (b.includes(t.tagName))
186
+ return t.children.length !== 1 && l(`${t.tagName} should have exactly one child node`), i(t.children[0]);
187
+ if (t.tagName === e.BUTTON)
188
+ return t.attributes.action || l("Button component should contain an action node"), c.button(t.attributes);
189
+ if (t.tagName === e.SPAN) {
190
+ t.children.length > 1 && l("Span component can only have one text child");
191
+ const n = t.children[0];
192
+ if (n && n.type === g.TEXT)
193
+ return c.span(
194
+ n.content,
195
+ t.attributes
196
+ );
197
+ l("Span component only allowed text child");
198
+ }
199
+ if (t.tagName === e.TEXT)
200
+ return c.text(
201
+ t.children.map(i),
202
+ t.attributes
203
+ );
204
+ if (t.tagName === e.SEPARATOR)
205
+ return c.separator(t.attributes);
206
+ if (t.tagName === e.IMAGE)
207
+ return c.image(t.attributes);
208
+ if (t.tagName === e.VIDEO)
209
+ return t.children.length !== 1 && l("Video component should only has one Box or Image as altContent"), [
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);
213
+ if (t.tagName === e.ICON)
214
+ return c.icon(t.attributes);
215
+ if (t.tagName === e.BOX)
216
+ return c.box(
217
+ t.children.map(i),
218
+ t.attributes
219
+ );
220
+ if (t.tagName === e.SPACE)
221
+ return c.span(" ".repeat(t.attributes.size || 1));
222
+ if (t.tagName === e.STRONG)
223
+ return t.tagName = e.TEXT, t.attributes.weight = "bold", i(t);
224
+ if (t.tagName === e.BASELINE)
225
+ return t.tagName = e.BOX, t.attributes.layout = "baseline", i(t);
226
+ if (t.tagName === e.ROW)
227
+ return t.tagName = e.BOX, t.attributes.layout = "horizontal", i(t);
228
+ if (t.tagName === e.VERTICAL)
229
+ return t.tagName = e.BOX, t.attributes.layout = "vertical", i(t);
230
+ if (t.tagName === e.ACTION && t.attributes.type === "uri")
231
+ return c.uri(t.attributes);
232
+ if (t.tagName === e.BACKGROUND)
233
+ return t.attributes;
234
+ }
235
+ function C(t) {
236
+ try {
237
+ return A(t).map(i);
238
+ } catch (r) {
239
+ console.error("Error converting HTML to Flex Message:", r);
240
+ }
241
+ }
242
+ export {
243
+ C as default
244
+ };
@@ -0,0 +1 @@
1
+ (function(u,f){typeof exports=="object"&&typeof module<"u"?f(exports,require("htmlparser2")):typeof define=="function"&&define.amd?define(["exports","htmlparser2"],f):(u=typeof globalThis<"u"?globalThis:u||self,f(u.FlexHtmlRender={},u.htmlparser2))})(this,(function(u,f){"use strict";const g={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",SPACE:"space",STRONG:"strong",BASELINE:"baseline",ROW:"row",VERTICAL:"vertical",ACTION:"action",BACKGROUND:"background"},N=[e.HEADER,e.HERO,e.BODY,e.FOOTER],p=[e.BOX,e.VERTICAL,e.ROW,e.BASELINE];function b(t){const r=Number(t);return isNaN(r)?t:r}function E(t){return t==="true"?!0:t==="false"?!1:t}function T(t){return[b,E].reduce((a,l)=>l(a),t)}function O(t){const r={type:"root",children:[]},a=[r],l=new f.Parser({onopentag(c,h){const m={type:g.ELEMENT,tagName:c,attributes:Object.fromEntries(Object.entries(h).map(([B,R])=>[B,T(R)])),children:[]};a[a.length-1].children.push(m),a.push(m)},ontext(c){c.trim()&&a[a.length-1].children.push({type:g.TEXT,content:c.replace(/\n/g,"").trim()})},onclosetag(){a.pop()},onerror(c){console.error("解析錯誤:",c)}},{xmlMode:!0,lowerCaseTags:!1,lowerCaseAttributeNames:!1});return l.write(t),l.end(),r.children}const s=t=>{throw Error(`[Flex Html Render]: ${t}`)},n={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}),uri:(t={})=>({type:"uri",altUri:t.altUri?{desktop:t.altUri}:void 0,...t})};function y(t){if(t.tagName!==e.BACKGROUND&&t.children&&t.children.length>0){const r=t.children.filter(a=>a.tagName===e.BACKGROUND);return r.length>1&&s("A box node can only allowed with one background node inside"),t.children=t.children.filter(a=>a.tagName!==e.BACKGROUND),r[0]}return null}function A(t){if(t.tagName!==e.ACTION&&t.children&&t.children.length>0){const r=t.children.filter(a=>a.tagName===e.ACTION);return r.length>1&&s("A node can only allowed with one action node inside"),t.children=t.children.filter(a=>a.tagName!==e.ACTION),r[0]}return null}function i(t){t.attributes=t.attributes||{};const r=A(t);r&&(t.attributes.action=i(r));const a=y(t);if(a&&p.includes(t.tagName)&&(t.attributes.background=i(a)),t.type===g.TEXT)return n.span(t.content,t.attributes);if(t.tagName===e.BUBBLE){const l=t.children.find(o=>o.tagName===e.HEADER),c=t.children.find(o=>o.tagName===e.HERO),h=t.children.find(o=>o.tagName===e.BODY),m=t.children.find(o=>o.tagName===e.FOOTER);return n.bubble({...t.attributes,header:l?i(l):void 0,hero:c?i(c):void 0,body:h?i(h):void 0,footer:m?i(m):void 0})}if(t.tagName===e.CAROUSEL)return t.children.some(l=>l.tagName!==e.BUBBLE)&&s("Carousel can only have Bubble as children"),t.children.length>12&&s("Carousel can have maximum 12 bubbles"),n.carousel(t.children.map(i));if(N.includes(t.tagName))return t.children.length!==1&&s(`${t.tagName} should have exactly one child node`),i(t.children[0]);if(t.tagName===e.BUTTON)return t.attributes.action||s("Button component should contain an action node"),n.button(t.attributes);if(t.tagName===e.SPAN){t.children.length>1&&s("Span component can only have one text child");const l=t.children[0];if(l&&l.type===g.TEXT)return n.span(l.content,t.attributes);s("Span component only allowed text child")}if(t.tagName===e.TEXT)return n.text(t.children.map(i),t.attributes);if(t.tagName===e.SEPARATOR)return n.separator(t.attributes);if(t.tagName===e.IMAGE)return n.image(t.attributes);if(t.tagName===e.VIDEO)return t.children.length!==1&&s("Video component should only has one Box or Image as altContent"),[e.BOX,e.IMAGE].includes(t.children[0]?.tagName)||s("Video component should only has Box or Image child as altContent"),t.attributes.altContent=i(t.children[0]),n.video(t.attributes);if(t.tagName===e.ICON)return n.icon(t.attributes);if(t.tagName===e.BOX)return n.box(t.children.map(i),t.attributes);if(t.tagName===e.SPACE)return n.span(" ".repeat(t.attributes.size||1));if(t.tagName===e.STRONG)return t.tagName=e.TEXT,t.attributes.weight="bold",i(t);if(t.tagName===e.BASELINE)return t.tagName=e.BOX,t.attributes.layout="baseline",i(t);if(t.tagName===e.ROW)return t.tagName=e.BOX,t.attributes.layout="horizontal",i(t);if(t.tagName===e.VERTICAL)return t.tagName=e.BOX,t.attributes.layout="vertical",i(t);if(t.tagName===e.ACTION&&t.attributes.type==="uri")return n.uri(t.attributes);if(t.tagName===e.BACKGROUND)return t.attributes}function d(t){try{return O(t).map(i)}catch(r){console.error("Error converting HTML to Flex Message:",r)}}u.default=d,Object.defineProperties(u,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flex-html-render",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "將 HTML 字串轉換為 LINE Flex Message JSON 結構的工具",
5
5
  "type": "module",
6
6
  "main": "dist/flex-html-render.cjs.js",