flex-html-render 1.0.0 → 1.0.2

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