2023-06-01 12:20:42 +05:00
import ' package:appflowy_editor/appflowy_editor.dart ' ;
2023-07-20 09:06:00 +05:00
import ' package:flutter_test/flutter_test.dart ' ;
2023-06-01 12:20:42 +05:00
void main ( ) {
2023-08-17 00:01:29 +08:00
group ( ' html_document_test.dart ' , ( ) {
// Foo Bar
test ( ' sample 1 ' , ( ) {
const html = ' <p>Foo Bar</p> ' ;
final document = htmlToDocument ( html ) ;
expect ( document . root . children . length , 1 ) ;
expect ( document . nodeAtPath ( [ 0 ] ) ! . type , ParagraphBlockKeys . type ) ;
expect ( document . nodeAtPath ( [ 0 ] ) ! . delta ! . toPlainText ( ) , ' Foo Bar ' ) ;
2023-06-01 12:20:42 +05:00
} ) ;
2023-06-11 11:21:21 +05:00
2023-08-17 00:01:29 +08:00
// * Foo
// * Bar
// * Baz
test ( ' sample 2 ' , ( ) {
const html = ''' <ul>
< li > Foo < / li >
< li > Bar < / li >
< li > Baz < / li >
< / ul > ''' ;
final document = htmlToDocument ( html ) ;
expect ( document . root . children . length , 3 ) ;
expect ( document . nodeAtPath ( [ 0 ] ) ! . type , BulletedListBlockKeys . type ) ;
expect ( document . nodeAtPath ( [ 0 ] ) ! . delta ! . toPlainText ( ) , ' Foo ' ) ;
expect ( document . nodeAtPath ( [ 1 ] ) ! . type , BulletedListBlockKeys . type ) ;
expect ( document . nodeAtPath ( [ 1 ] ) ! . delta ! . toPlainText ( ) , ' Bar ' ) ;
expect ( document . nodeAtPath ( [ 2 ] ) ! . type , BulletedListBlockKeys . type ) ;
expect ( document . nodeAtPath ( [ 2 ] ) ! . delta ! . toPlainText ( ) , ' Baz ' ) ;
2023-06-09 11:30:29 +05:00
} ) ;
2023-06-11 11:21:21 +05:00
2023-08-17 00:01:29 +08:00
// Hello
// * Foo
// * Bar
// * Baz
test ( ' sample 3 ' , ( ) {
const html = ''' <p>Hello</p>
< ul >
< li > Foo < / li >
< li > Bar < / li >
< li > Baz < / li >
< / ul > ''' ;
final document = htmlToDocument ( html ) ;
expect ( document . root . children . length , 4 ) ;
expect ( document . nodeAtPath ( [ 0 ] ) ! . type , ParagraphBlockKeys . type ) ;
expect ( document . nodeAtPath ( [ 0 ] ) ! . delta ! . toPlainText ( ) , ' Hello ' ) ;
expect ( document . nodeAtPath ( [ 1 ] ) ! . type , BulletedListBlockKeys . type ) ;
expect ( document . nodeAtPath ( [ 1 ] ) ! . delta ! . toPlainText ( ) , ' Foo ' ) ;
expect ( document . nodeAtPath ( [ 2 ] ) ! . type , BulletedListBlockKeys . type ) ;
expect ( document . nodeAtPath ( [ 2 ] ) ! . delta ! . toPlainText ( ) , ' Bar ' ) ;
expect ( document . nodeAtPath ( [ 3 ] ) ! . type , BulletedListBlockKeys . type ) ;
expect ( document . nodeAtPath ( [ 3 ] ) ! . delta ! . toPlainText ( ) , ' Baz ' ) ;
2023-06-11 11:21:21 +05:00
} ) ;
2023-06-01 12:20:42 +05:00
2023-08-17 00:01:29 +08:00
// * Foo
// * Bar
// * Baz
test ( ' sample 4 ' , ( ) {
const html = ''' <ul>
< li > Foo
< ul >
< li > Bar
< ul >
< li > Baz < / li >
< / ul >
< / li >
< / ul >
< / li >
< / ul > ''' ;
final document = htmlToDocument ( html ) ;
expect ( document . root . children . length , 1 ) ;
final foo = document . nodeAtPath ( [ 0 ] ) ! ;
final bar = document . nodeAtPath ( [ 0 , 0 ] ) ! ;
final baz = document . nodeAtPath ( [ 0 , 0 , 0 ] ) ! ;
expect ( foo . type , BulletedListBlockKeys . type ) ;
expect ( foo . delta ! . toPlainText ( ) , ' Foo ' ) ;
expect ( bar . parent , foo ) ;
expect ( bar . type , BulletedListBlockKeys . type ) ;
expect ( bar . delta ! . toPlainText ( ) , ' Bar ' ) ;
expect ( baz . parent , bar ) ;
expect ( baz . type , BulletedListBlockKeys . type ) ;
expect ( baz . delta ! . toPlainText ( ) , ' Baz ' ) ;
} ) ;
2023-06-11 11:21:21 +05:00
2023-08-17 00:01:29 +08:00
// copy from Notion
// There's a line with different formats, such as **bold**, __italic__, underline, ~~strikethrough~~, and `inline code`.
test ( ' sample 5 ' , ( ) {
const html =
''' <meta charset= ' utf-8 ' >There's a line with different formats, such as <span style="font-weight:600" data-token-index="1" class="notion-enable-hover">bold</span>, <span style="font-style:italic" data-token-index="3" class="notion-enable-hover">italic</span>, <span style="color:inherit;border-bottom:0.05em solid;word-wrap:break-word" data-token-index="5" class="notion-enable-hover">underline</span>, <span style="text-decoration:line-through" data-token-index="7" class="notion-enable-hover">strikethrough</span>, and <span style="font-family:"SFMono-Regular", Menlo, Consolas, "PT Mono", "Liberation Mono", Courier, monospace;line-height:normal;background:rgba(135,131,120,0.15);color:#EB5757;border-radius:3px;font-size:85%;padding:0.2em 0.4em" data-token-index="9" spellcheck="false" class="notion-enable-hover">inline code</span>. ''' ;
final document = htmlToDocument ( html ) ;
final delta = document . nodeAtPath ( [ 0 ] ) ! . delta ! ;
expect (
delta . toJson ( ) ,
[
{ " insert " : " There's a line with different formats, such as " } ,
2023-06-11 11:21:21 +05:00
{
2023-08-17 00:01:29 +08:00
" insert " : " bold " ,
2023-09-13 15:26:29 +08:00
" attributes " : { " bold " : true } ,
2023-08-17 00:01:29 +08:00
} ,
{ " insert " : " , " } ,
{
" insert " : " italic " ,
2023-09-13 15:26:29 +08:00
" attributes " : { " italic " : true } ,
2023-08-17 00:01:29 +08:00
} ,
{ " insert " : " , underline, " } ,
{
" insert " : " strikethrough " ,
2023-09-13 15:26:29 +08:00
" attributes " : { " strikethrough " : true } ,
2023-08-17 00:01:29 +08:00
} ,
{ " insert " : " , and " } ,
{
" insert " : " inline code " ,
2023-09-13 15:40:36 +08:00
" attributes " : {
" bg_color " : " 0x26878378 " ,
2023-09-15 16:13:49 +08:00
" font_color " : " 0xffeb5757 " ,
2023-09-13 15:40:36 +08:00
} ,
2023-08-17 00:01:29 +08:00
} ,
2023-09-13 15:26:29 +08:00
{ " insert " : " . " } ,
2023-06-11 11:21:21 +05:00
] ,
2023-08-17 00:01:29 +08:00
) ;
} ) ;
// sample 6
// 1. Foo
// 2. Bar
// 3. Baz
test ( ' sample 6 ' , ( ) {
const html = ''' <ol>
< li > Foo < / li >
< li > Bar < / li >
< li > Baz < / li >
< / ol > ''' ;
final document = htmlToDocument ( html ) ;
expect ( document . nodeAtPath ( [ 0 ] ) ! . type , NumberedListBlockKeys . type ) ;
expect ( document . nodeAtPath ( [ 0 ] ) ! . delta ! . toPlainText ( ) , ' Foo ' ) ;
expect ( document . nodeAtPath ( [ 1 ] ) ! . type , NumberedListBlockKeys . type ) ;
expect ( document . nodeAtPath ( [ 1 ] ) ! . delta ! . toPlainText ( ) , ' Bar ' ) ;
expect ( document . nodeAtPath ( [ 2 ] ) ! . type , NumberedListBlockKeys . type ) ;
expect ( document . nodeAtPath ( [ 2 ] ) ! . delta ! . toPlainText ( ) , ' Baz ' ) ;
} ) ;
test ( ' sample 7 ' , ( ) {
const html = ''' <ol>
< li > Foo
< ol >
< li > Bar
< ol >
< li > Baz < / li >
< / ol >
< / li >
< / ol >
< / li >
< / ol > ''' ;
final document = htmlToDocument ( html ) ;
expect ( document . root . children . length , 1 ) ;
final foo = document . nodeAtPath ( [ 0 ] ) ! ;
final bar = document . nodeAtPath ( [ 0 , 0 ] ) ! ;
final baz = document . nodeAtPath ( [ 0 , 0 , 0 ] ) ! ;
expect ( foo . type , NumberedListBlockKeys . type ) ;
expect ( foo . delta ! . toPlainText ( ) , ' Foo ' ) ;
expect ( bar . parent , foo ) ;
expect ( bar . type , NumberedListBlockKeys . type ) ;
expect ( bar . delta ! . toPlainText ( ) , ' Bar ' ) ;
expect ( baz . parent , bar ) ;
expect ( baz . type , NumberedListBlockKeys . type ) ;
expect ( baz . delta ! . toPlainText ( ) , ' Baz ' ) ;
} ) ;
} ) ;
test ( ' sample 8 ' , ( ) {
const html = ''' <ol>
< li > < strong > Foo < / strong > < / li >
< li > < em > Bar < / em > < / li >
< li > < s > Baz < / s > < / li >
< / ol >
< ul >
< li > < strong > Foo < / strong > < / li >
< li > < em > Bar < / em > < / li >
< li > < s > Baz < / s > < / li >
< / ul > ''' ;
final document = htmlToDocument ( html ) ;
expect ( document . root . children . length , 6 ) ;
final foo1 = document . nodeAtPath ( [ 0 ] ) ! ;
final bar1 = document . nodeAtPath ( [ 1 ] ) ! ;
final baz1 = document . nodeAtPath ( [ 2 ] ) ! ;
final foo2 = document . nodeAtPath ( [ 3 ] ) ! ;
final bar2 = document . nodeAtPath ( [ 4 ] ) ! ;
final baz2 = document . nodeAtPath ( [ 5 ] ) ! ;
expect ( foo1 . type , NumberedListBlockKeys . type ) ;
expect ( foo1 . delta ! . toPlainText ( ) , ' Foo ' ) ;
expect ( bar1 . type , NumberedListBlockKeys . type ) ;
expect ( bar1 . delta ! . toPlainText ( ) , ' Bar ' ) ;
expect ( baz1 . type , NumberedListBlockKeys . type ) ;
expect ( baz1 . delta ! . toPlainText ( ) , ' Baz ' ) ;
expect ( foo2 . type , BulletedListBlockKeys . type ) ;
expect ( foo2 . delta ! . toPlainText ( ) , ' Foo ' ) ;
expect ( bar2 . type , BulletedListBlockKeys . type ) ;
expect ( bar2 . delta ! . toPlainText ( ) , ' Bar ' ) ;
expect ( baz2 . type , BulletedListBlockKeys . type ) ;
expect ( baz2 . delta ! . toPlainText ( ) , ' Baz ' ) ;
} ) ;
2023-09-25 15:06:53 +08:00
// copy from Notion
2023-08-17 00:01:29 +08:00
// There's a line with different formats, such as **bold**, __italic__, underline, ~~strikethrough~~, and `inline code`.
test ( ' sample 9 ' , ( ) {
const html =
''' <p>Hello</p><p>There ' s a line with different formats, such as <strong>bold</strong>, <em>italic</em>, underline, <s>strikethrough</s>, and <code>inline code</code>.</p> ''' ;
final document = htmlToDocument ( html ) ;
expect ( document . root . children . length , 2 ) ;
final delta = document . nodeAtPath ( [ 1 ] ) ! . delta ! ;
expect (
delta . toJson ( ) ,
[
{ " insert " : " There's a line with different formats, such as " } ,
{
" insert " : " bold " ,
2023-09-13 15:26:29 +08:00
" attributes " : { " bold " : true } ,
2023-08-17 00:01:29 +08:00
} ,
{ " insert " : " , " } ,
{
" insert " : " italic " ,
2023-09-13 15:26:29 +08:00
" attributes " : { " italic " : true } ,
2023-08-17 00:01:29 +08:00
} ,
{ " insert " : " , underline, " } ,
{
" insert " : " strikethrough " ,
2023-09-13 15:26:29 +08:00
" attributes " : { " strikethrough " : true } ,
2023-08-17 00:01:29 +08:00
} ,
{ " insert " : " , and " } ,
{
" insert " : " inline code " ,
2023-09-13 15:26:29 +08:00
" attributes " : { " code " : true } ,
2023-08-17 00:01:29 +08:00
} ,
2023-09-13 15:26:29 +08:00
{ " insert " : " . " } ,
2023-08-17 00:01:29 +08:00
] ,
) ;
} ) ;
2023-09-25 15:06:53 +08:00
test ( ' sample 10 ' , ( ) {
const html =
''' <meta charset= ' utf-8 ' ><span style="color: rgb(36, 36, 36); font-family: source-serif-pro, Georgia, Cambria, "Times New Roman", Times, serif; font-size: 20px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: -0.06px; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;">DTrace is a<span> </span></span><mark class="ael aco ao" style="box-sizing: inherit; cursor: pointer; color: currentcolor; background-color: rgb(187, 219, 186); font-family: source-serif-pro, Georgia, Cambria, "Times New Roman", Times, serif; font-size: 20px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: -0.06px; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><strong class="mq fp" style="box-sizing: inherit; font-weight: 700; font-family: source-serif-pro, Georgia, Cambria, "Times New Roman", Times, serif;"><em class="mp" style="box-sizing: inherit; font-style: italic;">dynamic tracing</em></strong></mark><mark class="ael aco ao" style="box-sizing: inherit; cursor: pointer; color: currentcolor; background-color: rgb(187, 219, 186); font-family: source-serif-pro, Georgia, Cambria, "Times New Roman", Times, serif; font-size: 20px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: -0.06px; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span> </span>technology</mark><span style="color: rgb(36, 36, 36); font-family: source-serif-pro, Georgia, Cambria, "Times New Roman", Times, serif; font-size: 20px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: -0.06px; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: none;"><span> </span>that can be used to locate<span> </span></span><mark class="acn aco ao" style="box-sizing: inherit; cursor: pointer; background-color: rgb(232, 243, 232); color: currentcolor; font-family: source-serif-pro, Georgia, Cambria, "Times New Roman", Times, serif; font-size: 20px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: -0.06px; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">system performance issues</mark><span style="color: rgb(36, 36, 36); font-family: source-serif-pro, Georgia, Cambria, "Times New Roman", Times, serif; font-size: 20px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: -0.06px; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(255, 255, 255); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; display: inline !important; float: n
final document = htmlToDocument ( html ) ;
expect ( document . root . children . length , 1 ) ;
final delta = document . nodeAtPath ( [ 0 ] ) ! . delta ! ;
expect (
delta . toJson ( ) ,
[
{
" insert " : " DTrace is a " ,
2023-11-08 13:46:33 +08:00
" attributes " : { " bg_color " : " 0xffffffff " , " font_color " : " 0xff242424 " } ,
2023-09-25 15:06:53 +08:00
} ,
{
" insert " : " dynamic tracing " ,
2023-11-08 13:46:33 +08:00
" attributes " : {
" bg_color " : " 0xffbbdbba " ,
" bold " : true ,
" italic " : true ,
} ,
2023-09-25 15:06:53 +08:00
} ,
{
" insert " : " technology " ,
2023-11-08 13:46:33 +08:00
" attributes " : { " bg_color " : " 0xffbbdbba " } ,
2023-09-25 15:06:53 +08:00
} ,
{
" insert " : " that can be used to locate " ,
2023-11-08 13:46:33 +08:00
" attributes " : { " bg_color " : " 0xffffffff " , " font_color " : " 0xff242424 " } ,
2023-09-25 15:06:53 +08:00
} ,
{
" insert " : " system performance issues " ,
2023-11-08 13:46:33 +08:00
" attributes " : { " bg_color " : " 0xffe8f3e8 " } ,
2023-09-25 15:06:53 +08:00
} ,
{
" insert " : " , obtain information about " ,
2023-11-08 13:46:33 +08:00
" attributes " : { " bg_color " : " 0xffffffff " , " font_color " : " 0xff242424 " } ,
2023-09-25 15:06:53 +08:00
} ,
{
" insert " : " system function calls " ,
2023-11-08 13:46:33 +08:00
" attributes " : { " bg_color " : " 0xffe8f3e8 " } ,
2023-09-25 15:06:53 +08:00
} ,
{
" insert " :
" , or monitor system runtime information. And worth noting is that DTrace is " ,
2023-11-08 13:46:33 +08:00
" attributes " : { " bg_color " : " 0xffffffff " , " font_color " : " 0xff242424 " } ,
2023-09-25 15:06:53 +08:00
} ,
{
" insert " : " non-intrusive " ,
2023-11-08 13:46:33 +08:00
" attributes " : { " bg_color " : " 0xffe8f3e8 " , " bold " : true } ,
2023-09-25 15:06:53 +08:00
} ,
{
" insert " :
" to existing code. Also, there is no need to modify the existing code or use " ,
2023-11-08 13:46:33 +08:00
" attributes " : { " bg_color " : " 0xffffffff " , " font_color " : " 0xff242424 " } ,
2023-09-25 15:06:53 +08:00
} ,
{
" insert " : " instrumentation technology " ,
2023-11-08 13:46:33 +08:00
" attributes " : { " bg_color " : " 0xffe8f3e8 " } ,
2023-09-25 15:06:53 +08:00
} ,
{
" insert " : " to obtain more system information. " ,
2023-11-08 13:46:33 +08:00
" attributes " : { " bg_color " : " 0xffffffff " , " font_color " : " 0xff242424 " } ,
2023-09-25 15:06:53 +08:00
}
] ,
) ;
} ) ;
2024-10-16 10:22:40 +08:00
test ( ' sample 11 ' , ( ) {
const html = '''
< h1 > Heading 1 < / h1 >
< h2 > Heading 2 < / h2 >
< h3 > Heading 3 < / h3 >
< h4 > Heading 4 < / h4 >
< h5 > Heading 5 < / h5 >
< h6 > Heading 6 < / h6 >
''' ;
final document = htmlToDocument ( html ) ;
for ( var i = 1 ; i < = 6 ; i + + ) {
final node = document . nodeAtPath ( [ i - 1 ] ) ;
expect ( node ! . type , HeadingBlockKeys . type ) ;
expect ( node . delta ! . toPlainText ( ) , ' Heading $ i ' ) ;
expect ( node . attributes [ HeadingBlockKeys . level ] , i ) ;
}
} ) ;
2024-12-17 14:59:11 +08:00
test ( ' sample 12 ' , ( ) {
// Checkout conversion is too low: only 18% of users who click on “Book” checkout.
// Comparable funnels for hotel booking sites achieve ~30% (source).
const html =
''' <meta charset= ' utf-8 ' ><meta charset="utf-8"><b style="font-weight:normal;" id="docs-internal-guid-30b67c3b-7fff-e5ec-0b19-376448d34980"><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Checkout conversion is too low: only 18% of users who click on “Book” checkout.</span></p><p dir="ltr" style="line-height:1.38;margin-top:0pt;margin-bottom:0pt;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">Comparable funnels for hotel booking sites achieve ~30% (</span><a href="https://docs.google.com/document/d/147-eAtY305IGAghvmQzn0NXUFlh2zQs50GbmBNlgnaA/edit?tab=t.0" style="text-decoration:none;"><span style="font-size:11pt;font-family:Arial,sans-serif;color:#1155cc;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:underline;-webkit-text-decoration-skip:none;text-decoration-skip-ink:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">source</span></a><span style="font-size:11pt;font-family:Arial,sans-serif;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;">).</span></p></b><br class="Apple-interchange-newline"> ''' ;
final document = htmlToDocument ( html ) ;
final node = document . nodeAtPath ( [ 0 ] ) ! ;
expect ( node . type , ParagraphBlockKeys . type ) ;
expect (
node . delta ! . toPlainText ( ) ,
' Checkout conversion is too low: only 18% of users who click on “Book” checkout. ' ,
) ;
final node2 = document . nodeAtPath ( [ 1 ] ) ! ;
expect ( node2 . type , ParagraphBlockKeys . type ) ;
final jsonList = node2 . delta ! . toJson ( ) ;
final cleanJson = jsonList . map ( ( e ) {
final map = e as Map ;
map . removeWhere ( ( k , v ) = > k ! = ' insert ' & & k ! = ' attributes ' ) ;
2025-12-23 15:12:57 +08:00
2024-12-17 14:59:11 +08:00
return map ;
} ) . toList ( ) ;
expect (
cleanJson ,
[
{
' insert ' : ' Comparable funnels for hotel booking sites achieve ~30% ( ' ,
' attributes ' : { ' font_color ' : ' 0xff000000 ' } ,
} ,
{
' insert ' : ' source ' ,
' attributes ' : {
' href ' :
' https://docs.google.com/document/d/147-eAtY305IGAghvmQzn0NXUFlh2zQs50GbmBNlgnaA/edit?tab=t.0 ' ,
' underline ' : true ,
' font_color ' : ' 0xff1155cc ' ,
} ,
} ,
{
' insert ' : ' ). ' ,
' attributes ' : { ' font_color ' : ' 0xff000000 ' } ,
}
] ,
) ;
} ) ;
2023-08-17 00:01:29 +08:00
}