From b5d5b71f95cd2a9e0efe602d2247adb481cc2d1e Mon Sep 17 00:00:00 2001 From: DarkSky <25152247+darkskygit@users.noreply.github.com> Date: Sat, 7 Mar 2026 00:14:27 +0800 Subject: [PATCH] feat(server): improve markdown parse (#14580) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### PR Dependency Tree * **PR #14580** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) ## Summary by CodeRabbit * **New Features** * Markdown conversion now reports lists of known-unsupported and unknown block identifiers encountered during parsing, and separates them from the main markdown output. * **Bug Fixes** * Improved error handling and logging around markdown parsing. * **Tests** * Updated tests and snapshots to reflect the new block-list fields and the adjusted markdown output. --- packages/backend/native/index.d.ts | 2 + packages/backend/native/src/doc.rs | 4 + .../__snapshots__/controller.spec.ts.md | 38 +-- .../__snapshots__/controller.spec.ts.snap | Bin 2098 -> 2106 bytes .../doc-renderer/__tests__/controller.spec.ts | 2 + .../doc-service/__tests__/controller.spec.ts | 4 + .../reader-from-database.spec.ts.md | 38 +-- .../reader-from-database.spec.ts.snap | Bin 1954 -> 1968 bytes .../__snapshots__/reader-from-rpc.spec.ts.md | 38 +-- .../reader-from-rpc.spec.ts.snap | Bin 1954 -> 1968 bytes .../backend/server/src/core/doc/reader.ts | 29 ++- .../__snapshots__/blocksute.spec.ts.md | 81 ++----- .../__snapshots__/blocksute.spec.ts.snap | Bin 7948 -> 7961 bytes .../server/src/core/utils/blocksuite.ts | 11 +- .../common/native/src/doc_parser/read/mod.rs | 220 ++++++++++++++++-- 15 files changed, 305 insertions(+), 162 deletions(-) diff --git a/packages/backend/native/index.d.ts b/packages/backend/native/index.d.ts index 785572f201..30f16a4a73 100644 --- a/packages/backend/native/index.d.ts +++ b/packages/backend/native/index.d.ts @@ -83,6 +83,8 @@ export interface NativeCrawlResult { export interface NativeMarkdownResult { title: string markdown: string + knownUnsupportedBlocks: Array + unknownBlocks: Array } export interface NativePageDocContent { diff --git a/packages/backend/native/src/doc.rs b/packages/backend/native/src/doc.rs index d11ba85d3a..743f25ae60 100644 --- a/packages/backend/native/src/doc.rs +++ b/packages/backend/native/src/doc.rs @@ -9,6 +9,8 @@ use napi_derive::napi; pub struct NativeMarkdownResult { pub title: String, pub markdown: String, + pub known_unsupported_blocks: Vec, + pub unknown_blocks: Vec, } impl From for NativeMarkdownResult { @@ -16,6 +18,8 @@ impl From for NativeMarkdownResult { Self { title: result.title, markdown: result.markdown, + known_unsupported_blocks: result.known_unsupported_blocks, + unknown_blocks: result.unknown_blocks, } } } diff --git a/packages/backend/server/src/__tests__/e2e/doc-service/__snapshots__/controller.spec.ts.md b/packages/backend/server/src/__tests__/e2e/doc-service/__snapshots__/controller.spec.ts.md index f05073bd1a..e5b8f7fee8 100644 --- a/packages/backend/server/src/__tests__/e2e/doc-service/__snapshots__/controller.spec.ts.md +++ b/packages/backend/server/src/__tests__/e2e/doc-service/__snapshots__/controller.spec.ts.md @@ -9,6 +9,16 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 { + knownUnsupportedBlocks: [ + 'RX4CG2zsBk:affine:note', + 'S1mkc8zUoU:affine:note', + 'yGlBdshAqN:affine:note', + '6lDiuDqZGL:affine:note', + 'cauvaHOQmh:affine:note', + '2jwCeO8Yot:affine:note', + 'c9MF_JiRgx:affine:note', + '6x7ALjUDjj:affine:surface', + ], markdown: `AFFiNE is an open source all in one workspace, an operating system for all the building blocks of your team wiki, knowledge management and digital assets and a better alternative to Notion and Miro.␊ ␊ ␊ @@ -70,35 +80,9 @@ Generated by [AVA](https://avajs.dev). ␊ ␊ ␊ - ␊ - [](Bookmark,https://affine.pro/)␊ - ␊ - ␊ - [](Bookmark,https://www.youtube.com/@affinepro)␊ - ␊ - ␊ - ␊ - ␊ - ␊ - ␊ - ␊ `, title: 'Write, Draw, Plan all at Once.', + unknownBlocks: [], } ## should get doc markdown return null when doc not exists diff --git a/packages/backend/server/src/__tests__/e2e/doc-service/__snapshots__/controller.spec.ts.snap b/packages/backend/server/src/__tests__/e2e/doc-service/__snapshots__/controller.spec.ts.snap index 7bd07afacd7c1cbfc96a6d66070df1fbbd6277c3..702fef10e3421ff3bb1c16936c6db5a961475f4c 100644 GIT binary patch literal 2106 zcmV-A2*vk7RzV zq|KkmvXTh3Gqp3#?dcwM_v~^79+DjL1OamL!Fhn(a|rSlIpvf?o+GH9*&i}1Sdaq| zAh0uCU0wCnS6}z^H~pS6$-w>LFXW|Hbo^tNn@XjSk%!bIP@o;8W~9MYNkYy^r?GUo z@$UEEq$zrQ@=kPKToU{jU5=JEgb=?FLgf6sEX1v4A>NHHu0$7idqt!K+5SOou>l&#s+Jj6Z9&nQXZ>8 z0@H`FsVsQH0!DHmTfmVjN;852HD*+h_JBHtRA$nn0(6eN3$GA*BMmyUxO#60tP;;$Vj(fBz+FrKw^s0nnF5qaeVC>JTwWac3)cOVGZ^O zRolygATdf|&)Be196u&dhL_9T}b37au!!j3IO0JZ?_MIqGr5)I=tk207#!?Grl?jeRsHjw1>h`IkfX~sxfT(mc z_q-lim&9`~-Nhj2a<2j~)nu8C^Qpu#AT$M({)?W9()yuwY zk%p!nE?E;W`&AavB3#0!@;ue5%{Dv5@1C0HIV>7E%k%rR0F%SNpJK zQVN}x811d>Ro+;~t&miXqj*6bcuX)t?fGr+WGi?kL9%qctTS3?sl1 z85WucLD8l_D?+P*JGN zdH57<0>g|7bZ;DybELgQ#ULox2^zWy-d4m&4zaFWQ$)!sGUhi$96x=;zgbaunGQ9Ynn2%ymc<%7W4tbwPEG{{kyL_unEd|c3DcJ ziL9$@Beg3ft7BEmaOiDr`*91XlXW)Y_y&AY$r9Ekn`EGUUB--Yn6QqWuhNk0xYmdu zbfJQ)@eMezq?DPT{8jQdgVe_50(WafSP@TdUJ-VF8yZA;uVGSPVvXCxe z~V59iMNfdyE7$^Qn?nC ziqH{v1k>Au0zBO_m_EX?=BC?NUuO~OV81UlbxLc(^Lk<|wI@xxZVjddme)fw?33!l zedBx_ho8A|zG>cOKYGYY!9Z_YQ^Y4H2Yh-wIl$~BK54hx)BpHnGb{^T8j><4tKXtm zpSlutK(}4T**0Y;cz88LRne{0&EtJ)|NLjiM_;bi?`xZOVwADBR)=WcZ%?Ls%-W^Y zgX^oe>%c!<=Ou2R#3zhmTCdGi3>xG-D%DUwt7J+uY%{~aHqzBK$#h10{vRTI8xrhD_9$qQy``LZ>+3w+kov&Uk>z=AO=RKxBpX_ES zzpUk7KJbPEd~MSN0PNM{_M(xbC-v{;RvPUL9j z=UaA*bY{8!ypqYF?L1n4oeu_QKFpWGRl?-yM~|)a)Pk~J?!$QSorw= zdhX-@N~Oc+|Kywha`e{UmWBAovJn4S7UJy{A#Sb+@!J(4{je(ZtSGwvP!-E{{HX z+2xTXUs(;hQrxkm!zh0m(6MxW!KG64y}3)Ji?@XkT_MDg5aQ25h`$RV{w2QkVrl6N zFE6k(P9EOcF&?%}rPDK3mkM$Yv#H^gZWC*Nf$;^6#hlCCgJ;{jhdbRfRu`Q|U%Bvj k;oP61vrhcDoH}`D@9zHY;ojEWXH#$g1zvvTQ>7CC07z6F6#xJL literal 2098 zcmV-22+j9FRzVk3**HNQRI%)h5npy~ zXPpZc0X1_p!-&JfkQ~Wc_@V7@DA1=qY#;hB`cL}QUs4>7B+IUi4U!@T3}`syxqRn4 zA36NF-&2$f++Y5IUV4Q`e{eaKDuoO^q?AA*>>#C)23IBtIwzeL#^vPQpMOqM;l-;P z|GXT15%FK`%i86oD2i61D0+7zioUL0yHUHg+k1zJuhnKhuV23&)xL^qm!jHlqOWV0 zepkC3)vn&CeL3|t8Mt))MrUI~?yQ4!AT*Fc4US5iAPA*EY9Ng;B0F$KB-oftvcgN9 zfg3xI1@y^=**-_;l~Seru1XrH4`V7VcoYSURI*+JY=L;G{Ca za0pc>)s(tDWK-y z6UHc+REtYP8rJr@2d;8^krP)ML)N#rWRrl|t{RE41Lw(NTKQ0880AtS6s7XgCslknPf+>SP^Hg!+=1q87F)*ORlqz}(D}Xc}h1PQo@WKro42146vn@OG*dt-n zI6lbH;v`>TGXxrStJAXV#7o10@_XLaqe|IgUYC1dWobe5%_t zS;)l@L4Z+Z7E%k%rQ`tNhkaP16ho&aq`j5B(vx-E3Q6fWil49rhX^B~J&y)kjtcZS z#}z=}l|pJaYt}vrUs`l9n$VnUpHbikg7avgUoo;3T7iR~r8=R5C+Kn1{ZglbTUn@6 zi5Vq)pKNhK2xs+zGd!uxS%nx%R|<8SOAW}$Oj-*Pp~2AA{Ls>plCCo6$-0?J9g6SoRzVZmWk&Umzie1RA@CI zP%1zi+MaZR#=A#xd_b_zfj2-?Ff3AGd}~ex7%CqCk~)<`nU+F1SP0VUV3ZRiVm=Bv z)4-g-V!w0PEK=D_lY5N4z-s;F$l`Ff`)d$+Y%Ycp{Oc1 zDWYT*8S_XH$8WazH!BKn6UMk7Z{^;bNA0!_BHnVe2#FH1z;wo@IhzwK z5Z*F)+f;Q?A391w7YlNYx6q|BQ^LWz&QSG)^$j*>vD!K~Ut=k=q7WCl-8B9VxG{z0#pc(P)O%k#5lSPHC8+fdmW<_Xk}f{q52_C_!u@p#x9uS$Wio} zZYM9QiKY9`pZ?y04k+?$(n!*X)>XBU+8N2}SXMF|dYjvR+yHE4tw(Wu8@?~4fraTN z8E9XXF=HI2tYhPK9HJdp8WDspRInc3h69UA(fs1)K_uhb(2>>$^cKSAJ3mKLGhv}R zg|YC$<%r(Jx2pvUaG1+Nx`dHGS_>+6+|6t*RvVN95bI*5QZ0@}N zu&jHk-7hdNY-y6Fa}KTcjs5+wV)644Tdh>lgW85Wo5H)>EPZe~>q(R^|*WUNB`e z8nwn$`m%?ugo^ebt2M^bVjMq~MHT}%o2=G*ih7UQ?WY^B2g}LQ`krYzCHID%2ixDj zHOo8cAYI$?ORw=x_xp!(>27K|tMwT8JA1XxKSnb3`D*>{@|`7q%u!~!U#&k_TB*nF zC+GTlb~s$y^!pE1x<7sQZEuv6;@hM@Fh6u(47VPxY!08@-`{+`((EK#E9d*V_s#OW zujL2#F7)+!@40bLX>DcY{rYx+^5yfF@5iHl=dRrv9@3V$Z_RrC@aW<0yF2IlTKVQw zT6dRMCV8E?F}WJ8t|PGI(*0o54-%0JiXz0aM2BqMPFJCx>VeT zsKZ-6@8gkl{+yei+OOwsey%-b c%#8eYZ+(CF#opTb+nKfh0&TSz2TBtF0OJ<_Gynhq diff --git a/packages/backend/server/src/core/doc-renderer/__tests__/controller.spec.ts b/packages/backend/server/src/core/doc-renderer/__tests__/controller.spec.ts index 56d75b112f..b76fd06894 100644 --- a/packages/backend/server/src/core/doc-renderer/__tests__/controller.spec.ts +++ b/packages/backend/server/src/core/doc-renderer/__tests__/controller.spec.ts @@ -129,6 +129,8 @@ test('should return markdown content and skip page view when accept is text/mark const markdown = Sinon.stub(docReader, 'getDocMarkdown').resolves({ title: 'markdown-doc', markdown: '# markdown-doc', + knownUnsupportedBlocks: [], + unknownBlocks: [], }); const docContent = Sinon.stub(docReader, 'getDocContent'); const record = Sinon.stub( diff --git a/packages/backend/server/src/core/doc-service/__tests__/controller.spec.ts b/packages/backend/server/src/core/doc-service/__tests__/controller.spec.ts index b03b6e10bb..c8163384d8 100644 --- a/packages/backend/server/src/core/doc-service/__tests__/controller.spec.ts +++ b/packages/backend/server/src/core/doc-service/__tests__/controller.spec.ts @@ -402,6 +402,8 @@ test('should get doc markdown in json format', async t => { return { title: 'test title', markdown: 'test markdown', + knownUnsupportedBlocks: [], + unknownBlocks: [], }; }); @@ -418,6 +420,8 @@ test('should get doc markdown in json format', async t => { .expect({ title: 'test title', markdown: 'test markdown', + knownUnsupportedBlocks: [], + unknownBlocks: [], }); t.pass(); }); diff --git a/packages/backend/server/src/core/doc/__tests__/__snapshots__/reader-from-database.spec.ts.md b/packages/backend/server/src/core/doc/__tests__/__snapshots__/reader-from-database.spec.ts.md index a2672336e2..c0420af59c 100644 --- a/packages/backend/server/src/core/doc/__tests__/__snapshots__/reader-from-database.spec.ts.md +++ b/packages/backend/server/src/core/doc/__tests__/__snapshots__/reader-from-database.spec.ts.md @@ -9,6 +9,16 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 { + knownUnsupportedBlocks: [ + 'RX4CG2zsBk:affine:note', + 'S1mkc8zUoU:affine:note', + 'yGlBdshAqN:affine:note', + '6lDiuDqZGL:affine:note', + 'cauvaHOQmh:affine:note', + '2jwCeO8Yot:affine:note', + 'c9MF_JiRgx:affine:note', + '6x7ALjUDjj:affine:surface', + ], markdown: `AFFiNE is an open source all in one workspace, an operating system for all the building blocks of your team wiki, knowledge management and digital assets and a better alternative to Notion and Miro.␊ ␊ ␊ @@ -70,33 +80,7 @@ Generated by [AVA](https://avajs.dev). ␊ ␊ ␊ - ␊ - [](Bookmark,https://affine.pro/)␊ - ␊ - ␊ - [](Bookmark,https://www.youtube.com/@affinepro)␊ - ␊ - ␊ - ␊ - ␊ - ␊ - ␊ - ␊ `, title: 'Write, Draw, Plan all at Once.', + unknownBlocks: [], } diff --git a/packages/backend/server/src/core/doc/__tests__/__snapshots__/reader-from-database.spec.ts.snap b/packages/backend/server/src/core/doc/__tests__/__snapshots__/reader-from-database.spec.ts.snap index c2e0c3a45cc3ef780217b604a687a1e8f80ab407..dc0fdb39233381a75df2eecadb2f09452d3a1e18 100644 GIT binary patch literal 1968 zcmV;h2T%AxRzVq=`fKwk zQn*7Bk{^o*00000000BMSj}!6#}S^9ELj=E78wTyatR7cfUtz!W#TxBO;|!G(v%cS zV$7e&vXTh3Gqp3#?dckI_v~^79^y;h!UyL8a?dGmkq5{j&k zS66-Y)z>}y{cxZ;9l3A+MqYVMCmmOCtuwIXYil6m6iT$Cj3)+Mou=fRat13`uy@~n zpJnLr@lWE5(YulS#aH4>n^6?~Hj1J`-dCdN=1LTO5MN%6FYgVGDfMwYe_UBviQ@O7 z_?;+zKaT&n5=B3aFGlgD)%e4a;fXmiuC6M!p0e8^&M3YbeGp%mSzVdM)(%{lT;1B; zRS&-a~ZG#O+nR~|K>bL3qZLKu+uB>p{FgIC7yqsH1DN~`qbW)L3WK!xU$r4jQFTsaZrB&K2t}8aMy}xtlnr&aUNlVkd zl&lR{{OSo#9XQXHvStrGg-M|_L0N0BDotQGBL|jBR0iDCq?8Hx^PL(ltgS)Q0S!7r zsdAvH0m#samsWLM@aRSkMra1YY%8028Zg={Ne&CL)aHveC*Uzzjg^qv7o;qp=TfE6 zd4csEVeNz{P{NTQv`LV;rfgi6{f z7YeT0)`AobxB{!?LQA6zHBTBw)-5B%vcyqqsMV0>QX{&foivOwlzL!6Kq6uhntMUf z=0IygYd|`-ZFb43vYgeamljvK5xz8NbsM7F=xytkz+60EN z4s>rFk#l6cLMV*qNRF|oTw6pXDl(OkGD)61kY`a8IVP&g{pq8^`)YGz z!vqoUJH7^~MpaTaXVa3;7E3i&e%glvWm1Bt0^S2|x@ZVnTEh;N?7B~3hwEIUgD*@@ zdVtn<#hs<*=#YFX%elo8MGj_1&O%HJbTE+CMW|})YSl0g4Tu1h=m|>YoQRl2my@A} zhvBRfbONoc3p&(!L@gh~At=}dcM>__Q0TUpXbfxj@4tN4hb_?T#bu?~5LwsMMj2Nr zQOCNG;n3UC_R}s`(2{bu3 zYx5;7z7G3T8uqg1HddG_ugEPg=5Rsk4NwkekF(23vdgyV&Wt=N?YdBDLPydOLT?TQ zc(Tu!J;18rrrX@u5E1I*aHtIRE6d@1BV|jyS<`M<#;nBZT4;s?(nGk*&L>HDEZF(B zdE4{oz9@wNy=%ElPEQZz0(p9f`Dt?6>-FaU$>~7}gM-t3Ige$<;S_n7x8Wk%OJx0=8|-;gbC zohGM(V%F>}R16yAJZjxiKd)6r3v6>1U|Z?h;q;Byzf!KhI4(ZRA5K1btY0lxwrFy? z*15Gak8F|g8~4!v!NV(MeK)`BzT7*y_wbun%X*+|$$4MsFE_hI$}emAmruOq0AE{p z-eBchPVx2h@pYHI<>X#d!(V&dX>(2afpjLx89jPYj?P!3Co?%(`1v2FMYgcqcvh=) z)N>xKza|%h7e34{g_Zm&OFQbP&`4kHu|KBZr{J&S5@cBRc z=D!mE=pQRl^v{(j`p-%fy}ueoH&&zQ&#O`N*VQQc`)U;ZYqkB6;nJ41IE^pNo~~ZK z8pT)6%w0S)m&kPdV~Kw8>%;!npKRax__@11+H6qRl!CQpaXhy>_+>dtKYf10N9Wni z;rMWRN8ip|vGwfXd3G1)b~p5ns&}4!bLakfcJIvXQmn^#ckipRIM43V-0tJ!$u{kM z`k4KBc9$3aK6|kH^ml4MKRM6t-I?9bZk+sP>;Cc4&hhcg&ee8^Dg75OHze~(5&!_t Cs>TNZ literal 1954 zcmV;T2VM9XKFmJs58Gd|y47~{?yCE7?m4Hr{pX;s zIUV{x{z*XvO~)-?aHTVF6e?#R;}lAC!;HrUe3ho;z48XFFF5!={>(BA`0DDvE;s(t zkiX_P&C4r|Mq{nfXuP}HXnfcF`fBs*=lyq-hGz5hwjIL3uy}Y{`J<%B2LTH7EmY2*&J&-eOAa=^}?gnH>DY z2P$E}F0Kv*LBCQulY4dFzyp|Yf4d&;0cfgLl1B{&$X zkQM@!EE-_Rl?QY|rJ4ZyTm`8|>JnHMO3TPw73$cj<|^yLcI84ru4G4Tm`^4Kj-{g3 z9c4Oah616$07D|{=iwzEIbSGy9t6M!+<84uF{Mn!0n@Q1r^uw#O_BwsfL?$Pt<@^6 zH&>PoZ0>Iz`a11JNql83yU>-9O$(N|dW;hfKCq*#&Y>qTE|exHD;<>81co#6;HX4p zz)ws{nSg(qspi79Yw)OMU_?hKRrWR202w+CB0b-RfPUykdi5%QJY~01&#<1 ztgiCdS`?R7LxNupV3RY7N=w-Ur}|Z3=S2!><)w<>s0&8~V>Cgo##o+93Wd}aL6DuO zj6W6YfG|{!JdCF_m)YkW`9a_VSs2ue>_#a_@u#g$=@1zDQuUxR8H$uqpJ~i$3Mls%i2 zZ1DG!@vVJ+NSKG^jkm zh)l*#q&4g)(~iB$?K8BA3}YSXJ`F_PlL-p7fS`RhdgulOSCJq&!g_MkAu4f^iCig@ zYxMBgDlrh0%6l;}sV*l&H4o!iC+I|4IUjwfbwpD>#zRoBi{Yel!hz6jexqJk`G5cZ zk1lM0<{%-hVngJ7JsV}bRpO3SZNpL9BKt`jsGE21C&>+XQ7H>cvqLh{z8+&PILufl z!CN#UH>o`$8eN=VE4cxOjwGsg#?P9NoP4mf)_Nw(S5 z>dYvh(!LE=6M7Ow2)#KD;LSc`wuiRnroZ3oi3xRaFi?iN)^YsaOW9FpCfc53%u2M& zQ49y92k?Y_NRs%mU>~O9ot{U#;uHe(w&OB6IXRRE!jnVHPm+^Pr*rzBoNUCjutg)% zY{mV~yY#?YG!flSU1x`F9C*AmLS4~%YvcHUI(Jr&-@RT`9*?^du0qwPZpvlv z@AV$*csWTPs4`Cgyi3*vw#y|taulRF1HPuDsd>F(P3xo-ckx|nPA?(GY6z21Ln z{UhF7TYLX_uO#*I>C5+%@nGYY+Z`S8F5YqWadGeX-t%`i&&{>=! Snapshot 1 { + knownUnsupportedBlocks: [ + 'RX4CG2zsBk:affine:note', + 'S1mkc8zUoU:affine:note', + 'yGlBdshAqN:affine:note', + '6lDiuDqZGL:affine:note', + 'cauvaHOQmh:affine:note', + '2jwCeO8Yot:affine:note', + 'c9MF_JiRgx:affine:note', + '6x7ALjUDjj:affine:surface', + ], markdown: `AFFiNE is an open source all in one workspace, an operating system for all the building blocks of your team wiki, knowledge management and digital assets and a better alternative to Notion and Miro.␊ ␊ ␊ @@ -70,33 +80,7 @@ Generated by [AVA](https://avajs.dev). ␊ ␊ ␊ - ␊ - [](Bookmark,https://affine.pro/)␊ - ␊ - ␊ - [](Bookmark,https://www.youtube.com/@affinepro)␊ - ␊ - ␊ - ␊ - ␊ - ␊ - ␊ - ␊ `, title: 'Write, Draw, Plan all at Once.', + unknownBlocks: [], } diff --git a/packages/backend/server/src/core/doc/__tests__/__snapshots__/reader-from-rpc.spec.ts.snap b/packages/backend/server/src/core/doc/__tests__/__snapshots__/reader-from-rpc.spec.ts.snap index c2e0c3a45cc3ef780217b604a687a1e8f80ab407..dc0fdb39233381a75df2eecadb2f09452d3a1e18 100644 GIT binary patch literal 1968 zcmV;h2T%AxRzVq=`fKwk zQn*7Bk{^o*00000000BMSj}!6#}S^9ELj=E78wTyatR7cfUtz!W#TxBO;|!G(v%cS zV$7e&vXTh3Gqp3#?dckI_v~^79^y;h!UyL8a?dGmkq5{j&k zS66-Y)z>}y{cxZ;9l3A+MqYVMCmmOCtuwIXYil6m6iT$Cj3)+Mou=fRat13`uy@~n zpJnLr@lWE5(YulS#aH4>n^6?~Hj1J`-dCdN=1LTO5MN%6FYgVGDfMwYe_UBviQ@O7 z_?;+zKaT&n5=B3aFGlgD)%e4a;fXmiuC6M!p0e8^&M3YbeGp%mSzVdM)(%{lT;1B; zRS&-a~ZG#O+nR~|K>bL3qZLKu+uB>p{FgIC7yqsH1DN~`qbW)L3WK!xU$r4jQFTsaZrB&K2t}8aMy}xtlnr&aUNlVkd zl&lR{{OSo#9XQXHvStrGg-M|_L0N0BDotQGBL|jBR0iDCq?8Hx^PL(ltgS)Q0S!7r zsdAvH0m#samsWLM@aRSkMra1YY%8028Zg={Ne&CL)aHveC*Uzzjg^qv7o;qp=TfE6 zd4csEVeNz{P{NTQv`LV;rfgi6{f z7YeT0)`AobxB{!?LQA6zHBTBw)-5B%vcyqqsMV0>QX{&foivOwlzL!6Kq6uhntMUf z=0IygYd|`-ZFb43vYgeamljvK5xz8NbsM7F=xytkz+60EN z4s>rFk#l6cLMV*qNRF|oTw6pXDl(OkGD)61kY`a8IVP&g{pq8^`)YGz z!vqoUJH7^~MpaTaXVa3;7E3i&e%glvWm1Bt0^S2|x@ZVnTEh;N?7B~3hwEIUgD*@@ zdVtn<#hs<*=#YFX%elo8MGj_1&O%HJbTE+CMW|})YSl0g4Tu1h=m|>YoQRl2my@A} zhvBRfbONoc3p&(!L@gh~At=}dcM>__Q0TUpXbfxj@4tN4hb_?T#bu?~5LwsMMj2Nr zQOCNG;n3UC_R}s`(2{bu3 zYx5;7z7G3T8uqg1HddG_ugEPg=5Rsk4NwkekF(23vdgyV&Wt=N?YdBDLPydOLT?TQ zc(Tu!J;18rrrX@u5E1I*aHtIRE6d@1BV|jyS<`M<#;nBZT4;s?(nGk*&L>HDEZF(B zdE4{oz9@wNy=%ElPEQZz0(p9f`Dt?6>-FaU$>~7}gM-t3Ige$<;S_n7x8Wk%OJx0=8|-;gbC zohGM(V%F>}R16yAJZjxiKd)6r3v6>1U|Z?h;q;Byzf!KhI4(ZRA5K1btY0lxwrFy? z*15Gak8F|g8~4!v!NV(MeK)`BzT7*y_wbun%X*+|$$4MsFE_hI$}emAmruOq0AE{p z-eBchPVx2h@pYHI<>X#d!(V&dX>(2afpjLx89jPYj?P!3Co?%(`1v2FMYgcqcvh=) z)N>xKza|%h7e34{g_Zm&OFQbP&`4kHu|KBZr{J&S5@cBRc z=D!mE=pQRl^v{(j`p-%fy}ueoH&&zQ&#O`N*VQQc`)U;ZYqkB6;nJ41IE^pNo~~ZK z8pT)6%w0S)m&kPdV~Kw8>%;!npKRax__@11+H6qRl!CQpaXhy>_+>dtKYf10N9Wni z;rMWRN8ip|vGwfXd3G1)b~p5ns&}4!bLakfcJIvXQmn^#ckipRIM43V-0tJ!$u{kM z`k4KBc9$3aK6|kH^ml4MKRM6t-I?9bZk+sP>;Cc4&hhcg&ee8^Dg75OHze~(5&!_t Cs>TNZ literal 1954 zcmV;T2VM9XKFmJs58Gd|y47~{?yCE7?m4Hr{pX;s zIUV{x{z*XvO~)-?aHTVF6e?#R;}lAC!;HrUe3ho;z48XFFF5!={>(BA`0DDvE;s(t zkiX_P&C4r|Mq{nfXuP}HXnfcF`fBs*=lyq-hGz5hwjIL3uy}Y{`J<%B2LTH7EmY2*&J&-eOAa=^}?gnH>DY z2P$E}F0Kv*LBCQulY4dFzyp|Yf4d&;0cfgLl1B{&$X zkQM@!EE-_Rl?QY|rJ4ZyTm`8|>JnHMO3TPw73$cj<|^yLcI84ru4G4Tm`^4Kj-{g3 z9c4Oah616$07D|{=iwzEIbSGy9t6M!+<84uF{Mn!0n@Q1r^uw#O_BwsfL?$Pt<@^6 zH&>PoZ0>Iz`a11JNql83yU>-9O$(N|dW;hfKCq*#&Y>qTE|exHD;<>81co#6;HX4p zz)ws{nSg(qspi79Yw)OMU_?hKRrWR202w+CB0b-RfPUykdi5%QJY~01&#<1 ztgiCdS`?R7LxNupV3RY7N=w-Ur}|Z3=S2!><)w<>s0&8~V>Cgo##o+93Wd}aL6DuO zj6W6YfG|{!JdCF_m)YkW`9a_VSs2ue>_#a_@u#g$=@1zDQuUxR8H$uqpJ~i$3Mls%i2 zZ1DG!@vVJ+NSKG^jkm zh)l*#q&4g)(~iB$?K8BA3}YSXJ`F_PlL-p7fS`RhdgulOSCJq&!g_MkAu4f^iCig@ zYxMBgDlrh0%6l;}sV*l&H4o!iC+I|4IUjwfbwpD>#zRoBi{Yel!hz6jexqJk`G5cZ zk1lM0<{%-hVngJ7JsV}bRpO3SZNpL9BKt`jsGE21C&>+XQ7H>cvqLh{z8+&PILufl z!CN#UH>o`$8eN=VE4cxOjwGsg#?P9NoP4mf)_Nw(S5 z>dYvh(!LE=6M7Ow2)#KD;LSc`wuiRnroZ3oi3xRaFi?iN)^YsaOW9FpCfc53%u2M& zQ49y92k?Y_NRs%mU>~O9ot{U#;uHe(w&OB6IXRRE!jnVHPm+^Pr*rzBoNUCjutg)% zY{mV~yY#?YG!flSU1x`F9C*AmLS4~%YvcHUI(Jr&-@RT`9*?^du0qwPZpvlv z@AV$*csWTPs4`Cgyi3*vw#y|taulRF1HPuDsd>F(P3xo-ckx|nPA?(GY6z21Ln z{UhF7TYLX_uO#*I>C5+%@nGYY+Z`S8F5YqWadGeX-t%`i&&{>=! 0) { + this.logger.warn( + `Unknown blocks found when parsing markdown for ${workspaceId}/${docId}.`, + { unknownBlocks } + ); + } + + return markdown; + } catch (error) { + this.logger.error(`Failed to parse ${workspaceId}/${docId}.`, error); + throw error; + } } async getDocDiff( diff --git a/packages/backend/server/src/core/utils/__tests__/__snapshots__/blocksute.spec.ts.md b/packages/backend/server/src/core/utils/__tests__/__snapshots__/blocksute.spec.ts.md index 998a1ed4e2..e1288ed3e7 100644 --- a/packages/backend/server/src/core/utils/__tests__/__snapshots__/blocksute.spec.ts.md +++ b/packages/backend/server/src/core/utils/__tests__/__snapshots__/blocksute.spec.ts.md @@ -1379,6 +1379,16 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 { + knownUnsupportedBlocks: [ + 'RX4CG2zsBk:affine:note', + 'S1mkc8zUoU:affine:note', + 'yGlBdshAqN:affine:note', + '6lDiuDqZGL:affine:note', + 'cauvaHOQmh:affine:note', + '2jwCeO8Yot:affine:note', + 'c9MF_JiRgx:affine:note', + '6x7ALjUDjj:affine:surface', + ], markdown: `AFFiNE is an open source all in one workspace, an operating system for all the building blocks of your team wiki, knowledge management and digital assets and a better alternative to Notion and Miro.␊ ␊ ␊ @@ -1440,35 +1450,9 @@ Generated by [AVA](https://avajs.dev). ␊ ␊ ␊ - ␊ - [](Bookmark,https://affine.pro/)␊ - ␊ - ␊ - [](Bookmark,https://www.youtube.com/@affinepro)␊ - ␊ - ␊ - ␊ - ␊ - ␊ - ␊ - ␊ `, title: 'Write, Draw, Plan all at Once.', + unknownBlocks: [], } ## can parse doc to markdown from doc snapshot with ai editable @@ -1476,6 +1460,16 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 { + knownUnsupportedBlocks: [ + 'RX4CG2zsBk:affine:note', + 'S1mkc8zUoU:affine:note', + 'yGlBdshAqN:affine:note', + '6lDiuDqZGL:affine:note', + 'cauvaHOQmh:affine:note', + '2jwCeO8Yot:affine:note', + 'c9MF_JiRgx:affine:note', + '6x7ALjUDjj:affine:surface', + ], markdown: `␊ AFFiNE is an open source all in one workspace, an operating system for all the building blocks of your team wiki, knowledge management and digital assets and a better alternative to Notion and Miro.␊ ␊ @@ -1565,38 +1559,7 @@ Generated by [AVA](https://avajs.dev). ␊ ␊ ␊ - ␊ - ␊ - [](Bookmark,https://affine.pro/)␊ - ␊ - ␊ - ␊ - [](Bookmark,https://www.youtube.com/@affinepro)␊ - ␊ - ␊ - ␊ - ␊ - ␊ - ␊ - ␊ - ␊ - ␊ - ␊ `, title: 'Write, Draw, Plan all at Once.', + unknownBlocks: [], } diff --git a/packages/backend/server/src/core/utils/__tests__/__snapshots__/blocksute.spec.ts.snap b/packages/backend/server/src/core/utils/__tests__/__snapshots__/blocksute.spec.ts.snap index 9833dcdc06e60db19683426ba1976d92bd4eb514..cdac9bc904862fc5338620144c7479ab22e5e93b 100644 GIT binary patch literal 7961 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 aPk<{#ayG)4sI3Y<{c{mYqc_1u0{9;nz-M#- literal 7948 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, + pub unknown_blocks: Vec, +} + +fn is_known_unsupported_markdown_flavour(flavour: &str) -> bool { + KNOWN_UNSUPPORTED_MARKDOWN_FLAVOURS.contains(&flavour) || flavour.starts_with("affine:edgeless-") +} + +fn is_edgeless_markdown_flavour(flavour: &str) -> bool { + matches!(flavour, "affine:surface" | "affine:frame" | "affine:surface-ref") || flavour.starts_with("affine:edgeless-") +} + +fn has_skipped_markdown_ancestor( + block_id: &str, + parent_lookup: &HashMap, + skipped_subtrees: &HashSet, +) -> bool { + let mut cursor = parent_lookup.get(block_id).cloned(); + while let Some(parent_id) = cursor { + if skipped_subtrees.contains(&parent_id) { + return true; + } + cursor = parent_lookup.get(&parent_id).cloned(); + } + false } pub fn parse_workspace_doc(doc_bin: Vec) -> Result, ParseError> { @@ -235,6 +272,8 @@ pub fn parse_doc_to_markdown( return Ok(MarkdownResult { title: "".into(), markdown: "".into(), + known_unsupported_blocks: vec![], + unknown_blocks: vec![], }); } @@ -244,6 +283,9 @@ pub fn parse_doc_to_markdown( let mut walker = context.walker(); let mut doc_title = String::from(DEFAULT_PAGE_TITLE); let mut markdown = String::new(); + let mut known_unsupported_blocks = Vec::new(); + let mut unknown_blocks = Vec::new(); + let mut skipped_subtrees = HashSet::new(); let md_options = DeltaToMdOptions::new(doc_url_prefix); let renderer = MarkdownRenderer::new(&md_options); @@ -258,6 +300,14 @@ pub fn parse_doc_to_markdown( None => continue, }; + if flavour == PAGE_FLAVOUR { + // enqueue children first to keep traversal order similar to JS implementation + walker.enqueue_children(&block_id, block); + let title = get_string(block, "prop:title").unwrap_or_default(); + doc_title = title.clone(); + continue; + } + let parent_id = context.parent_lookup.get(&block_id); let parent_flavour = parent_id .and_then(|id| context.block_pool.get(id)) @@ -270,9 +320,21 @@ pub fn parse_doc_to_markdown( // enqueue children first to keep traversal order similar to JS implementation walker.enqueue_children(&block_id, block); - if flavour == PAGE_FLAVOUR { - let title = get_string(block, "prop:title").unwrap_or_default(); - doc_title = title.clone(); + if is_known_unsupported_markdown_flavour(flavour.as_str()) { + known_unsupported_blocks.push(format!("{block_id}:{flavour}")); + if is_edgeless_markdown_flavour(flavour.as_str()) { + skipped_subtrees.insert(block_id.clone()); + } + continue; + } + + if BlockFlavour::from_str(flavour.as_str()).is_none() && flavour.as_str() != "affine:database" { + unknown_blocks.push(format!("{block_id}:{flavour}")); + skipped_subtrees.insert(block_id.clone()); + continue; + } + + if has_skipped_markdown_ancestor(&block_id, &context.parent_lookup, &skipped_subtrees) { continue; } @@ -297,19 +359,17 @@ pub fn parse_doc_to_markdown( writer.push_table(&table_md); } } - "affine:note" | "affine:surface" | "affine:frame" => {} _ => { - if let Some(block_flavour) = BlockFlavour::from_str(flavour.as_str()) { - let spec = BlockSpec::from_block_map_with_flavour(block, block_flavour); - let list_depth = if block_flavour == BlockFlavour::List { - get_list_depth(&block_id, &context.parent_lookup, &context.block_pool) - } else { - 0 - }; - renderer.write_block(&mut block_markdown, &spec, list_depth); + let Some(block_flavour) = BlockFlavour::from_str(flavour.as_str()) else { + continue; + }; + let spec = BlockSpec::from_block_map_with_flavour(block, block_flavour); + let list_depth = if block_flavour == BlockFlavour::List { + get_list_depth(&block_id, &context.parent_lookup, &context.block_pool) } else { - return Err(ParseError::ParserError(format!("unsupported_block_flavour:{flavour}"))); - } + 0 + }; + renderer.write_block(&mut block_markdown, &spec, list_depth); } } @@ -322,6 +382,8 @@ pub fn parse_doc_to_markdown( Ok(MarkdownResult { title: doc_title, markdown, + known_unsupported_blocks, + unknown_blocks, }) } @@ -791,5 +853,133 @@ mod tests { assert!(md.contains("blob://image-id")); assert!(md.contains("|A|B|")); assert!(md.contains("|---|---|")); + assert!( + result + .known_unsupported_blocks + .iter() + .any(|block| block.ends_with(":affine:note")) + ); + assert!(result.unknown_blocks.is_empty()); + } + + #[test] + fn test_parse_doc_to_markdown_with_edgeless_text_container() { + let doc_id = "edgeless-text-doc".to_string(); + let doc = DocOptions::new().with_guid(doc_id.clone()).build(); + let mut blocks = doc.get_or_create_map("blocks").unwrap(); + + let mut page = doc.create_map().unwrap(); + page.insert("sys:id".into(), "page").unwrap(); + page.insert("sys:flavour".into(), "affine:page").unwrap(); + let mut page_children = doc.create_array().unwrap(); + page_children.push("surface").unwrap(); + page.insert("sys:children".into(), Value::Array(page_children)).unwrap(); + let mut page_title = doc.create_text().unwrap(); + page_title.insert(0, "Page").unwrap(); + page.insert("prop:title".into(), Value::Text(page_title)).unwrap(); + blocks.insert("page".into(), Value::Map(page)).unwrap(); + + let mut surface = doc.create_map().unwrap(); + surface.insert("sys:id".into(), "surface").unwrap(); + surface.insert("sys:flavour".into(), "affine:surface").unwrap(); + let mut surface_children = doc.create_array().unwrap(); + surface_children.push("edgeless-text").unwrap(); + surface + .insert("sys:children".into(), Value::Array(surface_children)) + .unwrap(); + blocks.insert("surface".into(), Value::Map(surface)).unwrap(); + + let mut edgeless_text = doc.create_map().unwrap(); + edgeless_text.insert("sys:id".into(), "edgeless-text").unwrap(); + edgeless_text + .insert("sys:flavour".into(), "affine:edgeless-text") + .unwrap(); + let mut edgeless_text_children = doc.create_array().unwrap(); + edgeless_text_children.push("paragraph").unwrap(); + edgeless_text + .insert("sys:children".into(), Value::Array(edgeless_text_children)) + .unwrap(); + blocks + .insert("edgeless-text".into(), Value::Map(edgeless_text)) + .unwrap(); + + let mut paragraph = doc.create_map().unwrap(); + paragraph.insert("sys:id".into(), "paragraph").unwrap(); + paragraph.insert("sys:flavour".into(), "affine:paragraph").unwrap(); + paragraph + .insert("sys:children".into(), Value::Array(doc.create_array().unwrap())) + .unwrap(); + paragraph.insert("prop:type".into(), "text").unwrap(); + let mut paragraph_text = doc.create_text().unwrap(); + paragraph_text.insert(0, "hello from edgeless").unwrap(); + paragraph + .insert("prop:text".into(), Value::Text(paragraph_text)) + .unwrap(); + blocks.insert("paragraph".into(), Value::Map(paragraph)).unwrap(); + + let doc_bin = doc.encode_update_v1().unwrap(); + let result = parse_doc_to_markdown(doc_bin, doc_id, false, None).expect("parse doc"); + + assert!(result.markdown.is_empty()); + assert!( + result + .known_unsupported_blocks + .contains(&"surface:affine:surface".to_string()) + ); + assert!( + result + .known_unsupported_blocks + .contains(&"edgeless-text:affine:edgeless-text".to_string()) + ); + assert!(result.unknown_blocks.is_empty()); + } + + #[test] + fn test_parse_doc_to_markdown_collects_unknown_blocks() { + let doc_id = "unknown-block-doc".to_string(); + let doc = DocOptions::new().with_guid(doc_id.clone()).build(); + let mut blocks = doc.get_or_create_map("blocks").unwrap(); + + let mut page = doc.create_map().unwrap(); + page.insert("sys:id".into(), "page").unwrap(); + page.insert("sys:flavour".into(), "affine:page").unwrap(); + let mut page_children = doc.create_array().unwrap(); + page_children.push("mystery").unwrap(); + page.insert("sys:children".into(), Value::Array(page_children)).unwrap(); + let mut page_title = doc.create_text().unwrap(); + page_title.insert(0, "Page").unwrap(); + page.insert("prop:title".into(), Value::Text(page_title)).unwrap(); + blocks.insert("page".into(), Value::Map(page)).unwrap(); + + let mut mystery = doc.create_map().unwrap(); + mystery.insert("sys:id".into(), "mystery").unwrap(); + mystery.insert("sys:flavour".into(), "affine:custom-unknown").unwrap(); + let mut mystery_children = doc.create_array().unwrap(); + mystery_children.push("paragraph").unwrap(); + mystery + .insert("sys:children".into(), Value::Array(mystery_children)) + .unwrap(); + blocks.insert("mystery".into(), Value::Map(mystery)).unwrap(); + + let mut paragraph = doc.create_map().unwrap(); + paragraph.insert("sys:id".into(), "paragraph").unwrap(); + paragraph.insert("sys:flavour".into(), "affine:paragraph").unwrap(); + paragraph + .insert("sys:children".into(), Value::Array(doc.create_array().unwrap())) + .unwrap(); + paragraph.insert("prop:type".into(), "text").unwrap(); + let mut paragraph_text = doc.create_text().unwrap(); + paragraph_text.insert(0, "child of unknown").unwrap(); + paragraph + .insert("prop:text".into(), Value::Text(paragraph_text)) + .unwrap(); + blocks.insert("paragraph".into(), Value::Map(paragraph)).unwrap(); + + let doc_bin = doc.encode_update_v1().unwrap(); + let result = parse_doc_to_markdown(doc_bin, doc_id, false, None).expect("parse doc"); + + assert!(result.markdown.is_empty()); + assert!(result.known_unsupported_blocks.is_empty()); + assert_eq!(result.unknown_blocks, vec!["mystery:affine:custom-unknown".to_string()]); } }