@jackuait/blok 0.10.3 → 0.10.4

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.
Files changed (284) hide show
  1. package/dist/blok.mjs +2 -2
  2. package/dist/chunks/{blok-3wc3aInM.mjs → blok-NcdNQ0I6.mjs} +2364 -2178
  3. package/dist/chunks/{constants-Bp622jic.mjs → constants-DtfShkXT.mjs} +318 -227
  4. package/dist/chunks/{i18next-loader-CDnSPae_.mjs → i18next-loader-D32EUWLr.mjs} +1 -1
  5. package/dist/chunks/{lightweight-i18n-DZmo8dAI.mjs → lightweight-i18n-DpkvRXEd.mjs} +19 -0
  6. package/dist/{messages-Ddq3Ce3E2.mjs → chunks/messages-AD17iDBx.mjs} +18 -0
  7. package/dist/{messages-neGD3WGq.mjs → chunks/messages-B03yUEra2.mjs} +18 -0
  8. package/dist/chunks/{messages-CIfUm1Oa.mjs → messages-B2dU00Z3.mjs} +18 -0
  9. package/dist/chunks/{messages-BKN3YVIj.mjs → messages-B8hICx3L.mjs} +18 -0
  10. package/dist/{messages-Dnd5YSWv.mjs → chunks/messages-BBe45sPH.mjs} +18 -0
  11. package/dist/{messages-C7lJg8fy2.mjs → chunks/messages-BCifzMVO2.mjs} +18 -0
  12. package/dist/chunks/{messages-D7dx_6k8.mjs → messages-BGmvvtg_.mjs} +18 -0
  13. package/dist/{messages-Q5sQeVap2.mjs → chunks/messages-BJNFCDv42.mjs} +18 -0
  14. package/dist/chunks/{messages-BlxwW7M6.mjs → messages-BNy4e7Xl.mjs} +18 -0
  15. package/dist/chunks/{messages-C15z2r5U.mjs → messages-BcHZf9o-.mjs} +18 -0
  16. package/dist/{messages-A96tMxeU.mjs → chunks/messages-BjyYZeBm2.mjs} +18 -0
  17. package/dist/{messages-BbJ7ZXY8.mjs → chunks/messages-Bop7vrhU.mjs} +18 -0
  18. package/dist/{messages-BiTMwiKH.mjs → chunks/messages-BouFtpfO.mjs} +18 -0
  19. package/dist/chunks/{messages-ElIGUi0O2.mjs → messages-Br6bE1FD2.mjs} +18 -0
  20. package/dist/chunks/{messages-BHMiK51R.mjs → messages-C-EBhOHE.mjs} +18 -0
  21. package/dist/chunks/{messages-kGmxkeFH.mjs → messages-C3X7dv3f.mjs} +18 -0
  22. package/dist/chunks/{messages-4Ck88DYZ2.mjs → messages-C7Pjof0d2.mjs} +18 -0
  23. package/dist/{messages-D0lLw9KM.mjs → chunks/messages-C7sBaZOO2.mjs} +18 -0
  24. package/dist/chunks/{messages-QMOmwcZb.mjs → messages-C85zv_7x.mjs} +18 -0
  25. package/dist/chunks/{messages-DSrdy9Nw2.mjs → messages-CCEgR9GN2.mjs} +18 -0
  26. package/dist/chunks/{messages-DUr9WAkD.mjs → messages-CDSyoUft.mjs} +18 -0
  27. package/dist/{messages-bkGniiaz.mjs → chunks/messages-CGFlOwst.mjs} +18 -0
  28. package/dist/chunks/{messages-DBMaLL8b2.mjs → messages-CGLTjtRv2.mjs} +18 -0
  29. package/dist/{messages-2ZWBTerL.mjs → chunks/messages-CGPxUESo.mjs} +18 -0
  30. package/dist/chunks/{messages-BfAcUavP.mjs → messages-CNaaqQVz.mjs} +18 -0
  31. package/dist/{messages-DBhvm8NK.mjs → chunks/messages-CPFB2_m-2.mjs} +18 -0
  32. package/dist/chunks/{messages-zt6zdYWh.mjs → messages-CTdSIOAc.mjs} +18 -0
  33. package/dist/chunks/{messages-1_6UkKLS.mjs → messages-CXVWb9js.mjs} +18 -0
  34. package/dist/{messages-CdEASHDp2.mjs → chunks/messages-Cfbmwdep2.mjs} +18 -0
  35. package/dist/chunks/{messages-DxHh0O8j2.mjs → messages-ChayV9WY2.mjs} +18 -0
  36. package/dist/chunks/{messages-BgM91Lxm2.mjs → messages-Ci7UXRVI2.mjs} +18 -0
  37. package/dist/chunks/{messages-Clku7Cf-2.mjs → messages-CpXvyGWv2.mjs} +18 -0
  38. package/dist/chunks/{messages-DjvaiALg2.mjs → messages-Cql2ozf_2.mjs} +18 -0
  39. package/dist/{messages-DODrhcop.mjs → chunks/messages-Cxy_E2IS.mjs} +18 -0
  40. package/dist/chunks/{messages-CZSlfnkO2.mjs → messages-D9syZVGi2.mjs} +18 -0
  41. package/dist/chunks/{messages-BRAoJpOu.mjs → messages-D9uWAWjW.mjs} +18 -0
  42. package/dist/chunks/{messages-BK8Cp2d0.mjs → messages-DRJxSTqs.mjs} +18 -0
  43. package/dist/{messages-C_Qn9SbQ.mjs → chunks/messages-DSbI0vJf.mjs} +18 -0
  44. package/dist/chunks/{messages-CD_MnBln.mjs → messages-DVvrZRyZ.mjs} +18 -0
  45. package/dist/{messages-BE_z-zrb.mjs → chunks/messages-DY8zPIZW.mjs} +18 -0
  46. package/dist/chunks/{messages-Bz0-KNEB.mjs → messages-D_kZN9rB.mjs} +18 -0
  47. package/dist/{messages-C1vc5584.mjs → chunks/messages-DjSuq0-y2.mjs} +18 -0
  48. package/dist/chunks/{messages-DPzHD51Y.mjs → messages-DkP3Jf4F.mjs} +18 -0
  49. package/dist/{messages-_PLyRfVw.mjs → chunks/messages-DoPdy75l.mjs} +18 -0
  50. package/dist/chunks/{messages-JSQjKQ8I.mjs → messages-DpydMd36.mjs} +18 -0
  51. package/dist/{messages-BckDk9aq2.mjs → chunks/messages-DtZ9U9g72.mjs} +18 -0
  52. package/dist/{messages-JNrYldAa2.mjs → chunks/messages-H6vLy8wJ.mjs} +18 -0
  53. package/dist/chunks/{messages-DTN1XGll.mjs → messages-HzH9_QH8.mjs} +18 -0
  54. package/dist/chunks/{messages-C0IFfhnp.mjs → messages-O6FOfUgF.mjs} +18 -0
  55. package/dist/{messages-Be_2RHZD.mjs → chunks/messages-OSP4Hj5o.mjs} +18 -0
  56. package/dist/chunks/{messages-DMoERagV2.mjs → messages-RiqdVwuN2.mjs} +18 -0
  57. package/dist/chunks/{messages-BJ-vT1SU2.mjs → messages-SP659Sal2.mjs} +18 -0
  58. package/dist/{messages-Che99vKP.mjs → chunks/messages-THR8q8bJ.mjs} +18 -0
  59. package/dist/chunks/{messages-CvANwuht2.mjs → messages-VlEyFUxF2.mjs} +18 -0
  60. package/dist/{messages-apA6BStA.mjs → chunks/messages-VtfKWZ2S.mjs} +18 -0
  61. package/dist/{messages-DpJGbx3q.mjs → chunks/messages-YbckahVx2.mjs} +18 -0
  62. package/dist/{messages-DYuD5-rO.mjs → chunks/messages-ZhHLC6dk.mjs} +18 -0
  63. package/dist/{messages-C0GSBBCo2.mjs → chunks/messages-bFEdH3lv.mjs} +18 -0
  64. package/dist/chunks/{messages-euM2m3wQ.mjs → messages-dpXwA3Sz.mjs} +18 -0
  65. package/dist/chunks/{messages-CQBo3lmL2.mjs → messages-fbL5y58u2.mjs} +18 -0
  66. package/dist/chunks/{messages-CxiURE2X.mjs → messages-oPV2oMxM.mjs} +18 -0
  67. package/dist/{messages-DM4Gjc9h.mjs → chunks/messages-oXBbHW9A.mjs} +18 -0
  68. package/dist/chunks/{messages-QilfinOn2.mjs → messages-vDgsEqQW2.mjs} +18 -0
  69. package/dist/{messages-ClGvlFcH2.mjs → chunks/messages-wYQksm10.mjs} +18 -0
  70. package/dist/{messages-CnuH-BZK2.mjs → chunks/messages-yGedmr61.mjs} +18 -0
  71. package/dist/chunks/{messages-sDdNf8O9.mjs → messages-zQOpKjl3.mjs} +18 -0
  72. package/dist/chunks/{messages-eFd4YYzt.mjs → messages-zWqsggJh.mjs} +18 -0
  73. package/dist/chunks/{tools-BC1jRfoS.mjs → tools-DMSi-3RW.mjs} +3445 -1302
  74. package/dist/full.mjs +10 -10
  75. package/dist/locales.mjs +86 -67
  76. package/dist/{messages-BK_LsgY4.mjs → messages-0lOPMv8u.mjs} +18 -0
  77. package/dist/{messages-LYJbLq_F.mjs → messages-5wuR90qS.mjs} +18 -0
  78. package/dist/{messages-98nQiC7t2.mjs → messages-6eX0fWGR2.mjs} +18 -0
  79. package/dist/{chunks/messages-DUeiPraX.mjs → messages-9L4qqCKh2.mjs} +18 -0
  80. package/dist/{chunks/messages-Q7-4ZJLB2.mjs → messages-B4zPxKl62.mjs} +18 -0
  81. package/dist/{messages-D0005ti32.mjs → messages-BCMFYqKc2.mjs} +18 -0
  82. package/dist/{chunks/messages-CC_noR8y.mjs → messages-BKXjO3NH.mjs} +18 -0
  83. package/dist/{messages-CRNogopy2.mjs → messages-BLW2GX7J2.mjs} +18 -0
  84. package/dist/{messages-D81w6AmW.mjs → messages-BLfK27kX.mjs} +18 -0
  85. package/dist/{chunks/messages-CPBN4zWc.mjs → messages-BM2kx9Td.mjs} +18 -0
  86. package/dist/{messages-E8NjqzWq2.mjs → messages-BORkMoil2.mjs} +18 -0
  87. package/dist/{messages-Dqu4aX9s.mjs → messages-BPw_x-6H.mjs} +18 -0
  88. package/dist/{messages-DSmxJWju2.mjs → messages-BRY51SEw2.mjs} +18 -0
  89. package/dist/{chunks/messages-BONyZroH.mjs → messages-BSNsrZVN.mjs} +18 -0
  90. package/dist/{chunks/messages-BAlZjPcl.mjs → messages-B_UKuqrH.mjs} +18 -0
  91. package/dist/{chunks/messages-DB_-5Xln.mjs → messages-BrYeJsSE2.mjs} +18 -0
  92. package/dist/{messages-Brd5R-da2.mjs → messages-BwttyHDI2.mjs} +18 -0
  93. package/dist/{messages-qfvXgPpu2.mjs → messages-C-8qb9sf2.mjs} +18 -0
  94. package/dist/{chunks/messages-BbEW9bQz.mjs → messages-C34dTwF72.mjs} +18 -0
  95. package/dist/{messages-BmH2cQHQ.mjs → messages-C67YUZ9-.mjs} +18 -0
  96. package/dist/{messages-DpwMKDV0.mjs → messages-C6yKu_PJ.mjs} +18 -0
  97. package/dist/{messages-Do7Xjy0n.mjs → messages-CA6J_QoC.mjs} +18 -0
  98. package/dist/{messages-DVL0KZE5.mjs → messages-CFUBJfnf.mjs} +18 -0
  99. package/dist/{chunks/messages-DVr1sqfI2.mjs → messages-CLUBh7O_.mjs} +18 -0
  100. package/dist/{chunks/messages-wl8YrvGG.mjs → messages-CLZoy5fQ.mjs} +18 -0
  101. package/dist/{messages-CisR4PNV.mjs → messages-CNGwdIEz.mjs} +18 -0
  102. package/dist/{messages-DopaMHC42.mjs → messages-CR4gHjd82.mjs} +18 -0
  103. package/dist/{messages-DK6dA0O2.mjs → messages-CVMngZNA.mjs} +18 -0
  104. package/dist/{messages-Xc0KUbYl.mjs → messages-Cd5CW5Tt.mjs} +18 -0
  105. package/dist/{chunks/messages-ChK7v1PV.mjs → messages-CrjQ2Op0.mjs} +18 -0
  106. package/dist/{chunks/messages-CRF7nNrO.mjs → messages-Cv1PSaNk.mjs} +18 -0
  107. package/dist/{messages-DOGbHYv-2.mjs → messages-CxZarWTm2.mjs} +18 -0
  108. package/dist/{messages-D3rwCtKn.mjs → messages-D0eT_eWA.mjs} +18 -0
  109. package/dist/{messages-C6ONf71u2.mjs → messages-D6RYu9JW2.mjs} +18 -0
  110. package/dist/{messages-DQORja0D.mjs → messages-D8U5D391.mjs} +18 -0
  111. package/dist/{chunks/messages-EDMC5ukV.mjs → messages-D8dO6OMN.mjs} +18 -0
  112. package/dist/{messages-DfFZ6Yj5.mjs → messages-DA4T9WBe.mjs} +18 -0
  113. package/dist/{chunks/messages-D22e9h7V2.mjs → messages-DB4UKN8D.mjs} +18 -0
  114. package/dist/{chunks/messages-DEBy3nuJ2.mjs → messages-DCdP2ujL.mjs} +18 -0
  115. package/dist/{messages-D05jqBIa2.mjs → messages-DPFuzIdF2.mjs} +18 -0
  116. package/dist/{chunks/messages-DrfRYiM32.mjs → messages-DQ1icG7L.mjs} +18 -0
  117. package/dist/{chunks/messages-a07QVz8U.mjs → messages-DT7dwzEe.mjs} +18 -0
  118. package/dist/{chunks/messages-CszmHAvQ.mjs → messages-DUYxMxrQ2.mjs} +18 -0
  119. package/dist/{chunks/messages-DtoId_bw2.mjs → messages-D_V0kHD7.mjs} +18 -0
  120. package/dist/{messages-BesJaI6A.mjs → messages-DfqM_XvD.mjs} +18 -0
  121. package/dist/{messages-CT-Kdas6.mjs → messages-Di3-WVzq.mjs} +18 -0
  122. package/dist/{messages-BcVB3osF.mjs → messages-Dl0bfeA-.mjs} +18 -0
  123. package/dist/{chunks/messages-C1S9ztpF.mjs → messages-Do3mHd9U.mjs} +18 -0
  124. package/dist/{chunks/messages-BeGZqQwz.mjs → messages-DqDlcEPn.mjs} +18 -0
  125. package/dist/{chunks/messages-CTCe595D2.mjs → messages-DwiykEgr2.mjs} +18 -0
  126. package/dist/{chunks/messages-8Ld7P_9j2.mjs → messages-Dx5n6MLQ2.mjs} +18 -0
  127. package/dist/{messages-LMaR2_bE.mjs → messages-DxEiqa-B.mjs} +18 -0
  128. package/dist/{chunks/messages-CxxyR4vY.mjs → messages-Dxr1BBvo.mjs} +18 -0
  129. package/dist/{messages-D6VIFnSW.mjs → messages-DzknMM7W.mjs} +18 -0
  130. package/dist/{chunks/messages-oMc7qugU2.mjs → messages-ELvF3qMl2.mjs} +18 -0
  131. package/dist/{messages-53w0fPZS2.mjs → messages-JVJdC0Er2.mjs} +18 -0
  132. package/dist/{chunks/messages-BMD37y3q2.mjs → messages-KVerxvZC.mjs} +18 -0
  133. package/dist/{chunks/messages-Du2BffA7.mjs → messages-OOiDDmVw.mjs} +18 -0
  134. package/dist/{messages-uwK7ktqk.mjs → messages-PyOr_YgV.mjs} +18 -0
  135. package/dist/{messages-qbKjjvgd2.mjs → messages-VrQw3tQ62.mjs} +18 -0
  136. package/dist/{messages-CTTmWn4Y2.mjs → messages-WsUHzXMu2.mjs} +18 -0
  137. package/dist/{messages-CZbcxlZt2.mjs → messages-ZHgPRUj02.mjs} +18 -0
  138. package/dist/{messages-DKHbt-7l2.mjs → messages-aoO_TtoE2.mjs} +18 -0
  139. package/dist/{chunks/messages-CW35K1pq.mjs → messages-bh8BiOee2.mjs} +18 -0
  140. package/dist/{messages-BrOWqNCu2.mjs → messages-gZEhkRrR2.mjs} +18 -0
  141. package/dist/{chunks/messages-BRoa9tGl.mjs → messages-hya8YLMj.mjs} +18 -0
  142. package/dist/{messages-CdduYw-q.mjs → messages-tb1FD_ge.mjs} +18 -0
  143. package/dist/react.mjs +2 -2
  144. package/dist/tools.mjs +3 -3
  145. package/dist/vendor.LICENSE.txt +135 -0
  146. package/package.json +2 -1
  147. package/src/blok.ts +48 -0
  148. package/src/components/block/index.ts +21 -2
  149. package/src/components/block-tunes/block-tune-copy-link.ts +82 -0
  150. package/src/components/i18n/locales/am/messages.json +18 -0
  151. package/src/components/i18n/locales/ar/messages.json +18 -0
  152. package/src/components/i18n/locales/az/messages.json +18 -0
  153. package/src/components/i18n/locales/bg/messages.json +18 -0
  154. package/src/components/i18n/locales/bn/messages.json +18 -0
  155. package/src/components/i18n/locales/bs/messages.json +18 -0
  156. package/src/components/i18n/locales/cs/messages.json +18 -0
  157. package/src/components/i18n/locales/da/messages.json +18 -0
  158. package/src/components/i18n/locales/de/messages.json +18 -0
  159. package/src/components/i18n/locales/dv/messages.json +18 -0
  160. package/src/components/i18n/locales/el/messages.json +18 -0
  161. package/src/components/i18n/locales/en/messages.json +19 -0
  162. package/src/components/i18n/locales/es/messages.json +18 -0
  163. package/src/components/i18n/locales/et/messages.json +18 -0
  164. package/src/components/i18n/locales/fa/messages.json +18 -0
  165. package/src/components/i18n/locales/fi/messages.json +18 -0
  166. package/src/components/i18n/locales/fil/messages.json +18 -0
  167. package/src/components/i18n/locales/fr/messages.json +18 -0
  168. package/src/components/i18n/locales/gu/messages.json +18 -0
  169. package/src/components/i18n/locales/he/messages.json +18 -0
  170. package/src/components/i18n/locales/hi/messages.json +18 -0
  171. package/src/components/i18n/locales/hr/messages.json +18 -0
  172. package/src/components/i18n/locales/hu/messages.json +18 -0
  173. package/src/components/i18n/locales/hy/messages.json +18 -0
  174. package/src/components/i18n/locales/id/messages.json +18 -0
  175. package/src/components/i18n/locales/it/messages.json +18 -0
  176. package/src/components/i18n/locales/ja/messages.json +18 -0
  177. package/src/components/i18n/locales/ka/messages.json +18 -0
  178. package/src/components/i18n/locales/km/messages.json +18 -0
  179. package/src/components/i18n/locales/kn/messages.json +18 -0
  180. package/src/components/i18n/locales/ko/messages.json +18 -0
  181. package/src/components/i18n/locales/ku/messages.json +18 -0
  182. package/src/components/i18n/locales/lo/messages.json +18 -0
  183. package/src/components/i18n/locales/lt/messages.json +18 -0
  184. package/src/components/i18n/locales/lv/messages.json +18 -0
  185. package/src/components/i18n/locales/mk/messages.json +18 -0
  186. package/src/components/i18n/locales/ml/messages.json +18 -0
  187. package/src/components/i18n/locales/mn/messages.json +18 -0
  188. package/src/components/i18n/locales/mr/messages.json +18 -0
  189. package/src/components/i18n/locales/ms/messages.json +18 -0
  190. package/src/components/i18n/locales/my/messages.json +18 -0
  191. package/src/components/i18n/locales/ne/messages.json +18 -0
  192. package/src/components/i18n/locales/nl/messages.json +18 -0
  193. package/src/components/i18n/locales/no/messages.json +18 -0
  194. package/src/components/i18n/locales/pa/messages.json +18 -0
  195. package/src/components/i18n/locales/pl/messages.json +18 -0
  196. package/src/components/i18n/locales/ps/messages.json +18 -0
  197. package/src/components/i18n/locales/pt/messages.json +18 -0
  198. package/src/components/i18n/locales/ro/messages.json +18 -0
  199. package/src/components/i18n/locales/ru/messages.json +18 -0
  200. package/src/components/i18n/locales/sd/messages.json +18 -0
  201. package/src/components/i18n/locales/si/messages.json +18 -0
  202. package/src/components/i18n/locales/sk/messages.json +18 -0
  203. package/src/components/i18n/locales/sl/messages.json +18 -0
  204. package/src/components/i18n/locales/sq/messages.json +18 -0
  205. package/src/components/i18n/locales/sr/messages.json +18 -0
  206. package/src/components/i18n/locales/sv/messages.json +18 -0
  207. package/src/components/i18n/locales/sw/messages.json +18 -0
  208. package/src/components/i18n/locales/ta/messages.json +18 -0
  209. package/src/components/i18n/locales/te/messages.json +18 -0
  210. package/src/components/i18n/locales/th/messages.json +18 -0
  211. package/src/components/i18n/locales/tr/messages.json +18 -0
  212. package/src/components/i18n/locales/ug/messages.json +18 -0
  213. package/src/components/i18n/locales/uk/messages.json +18 -0
  214. package/src/components/i18n/locales/ur/messages.json +18 -0
  215. package/src/components/i18n/locales/vi/messages.json +18 -0
  216. package/src/components/i18n/locales/yi/messages.json +18 -0
  217. package/src/components/i18n/locales/zh/messages.json +18 -0
  218. package/src/components/icons/index.ts +65 -0
  219. package/src/components/inline-tools/inline-tool-bold.ts +10 -0
  220. package/src/components/inline-tools/inline-tool-code.ts +54 -1
  221. package/src/components/inline-tools/inline-tool-italic.ts +54 -1
  222. package/src/components/inline-tools/inline-tool-strikethrough.ts +54 -1
  223. package/src/components/inline-tools/inline-tool-underline.ts +54 -1
  224. package/src/components/inline-tools/services/bold-normalization-pass.ts +29 -3
  225. package/src/components/inline-tools/utils/formatting-range-utils.ts +83 -0
  226. package/src/components/modules/api/tools.ts +19 -0
  227. package/src/components/modules/blockEvents/composers/keyboardNavigation.ts +5 -5
  228. package/src/components/modules/blockManager/blockManager.ts +22 -0
  229. package/src/components/modules/blockManager/event-binder.ts +12 -1
  230. package/src/components/modules/blockManager/factory.ts +4 -0
  231. package/src/components/modules/blockManager/types.ts +4 -0
  232. package/src/components/modules/blockManager/yjs-sync.ts +16 -2
  233. package/src/components/modules/paste/google-docs-preprocessor.ts +49 -3
  234. package/src/components/modules/paste/handlers/table-cells-handler.ts +12 -2
  235. package/src/components/modules/paste/index.ts +8 -4
  236. package/src/components/modules/paste/types.ts +2 -0
  237. package/src/components/modules/renderer.ts +22 -2
  238. package/src/components/modules/saver.ts +19 -1
  239. package/src/components/modules/themeManager.ts +3 -1
  240. package/src/components/modules/toolbar/blockSettings.ts +51 -1
  241. package/src/components/modules/toolbar/inline/index.ts +0 -3
  242. package/src/components/modules/tools.ts +5 -0
  243. package/src/components/modules/uiControllers/controllers/keyboard.ts +29 -0
  244. package/src/components/modules/uiControllers/controllers/selection.ts +14 -2
  245. package/src/components/modules/yjs/document-store.ts +22 -0
  246. package/src/components/modules/yjs/index.ts +10 -0
  247. package/src/components/modules/yjs/serializer.ts +20 -0
  248. package/src/components/ui/toolbox.ts +0 -1
  249. package/src/components/utils/id-generator.ts +11 -0
  250. package/src/components/utils/key-icon.ts +187 -0
  251. package/src/components/utils/popover/components/hint/hint.ts +3 -1
  252. package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts +18 -5
  253. package/src/components/utils/popover/popover-abstract.ts +45 -0
  254. package/src/components/utils/popover/popover-desktop.ts +1 -0
  255. package/src/components/utils/popover/popover-position.ts +4 -2
  256. package/src/components/utils/popover/popover.const.ts +2 -0
  257. package/src/components/utils.ts +1 -0
  258. package/src/styles/main.css +1269 -0
  259. package/src/tools/code/index.ts +4 -0
  260. package/src/tools/database/database-backend-sync.ts +132 -0
  261. package/src/tools/database/database-board-view.ts +410 -0
  262. package/src/tools/database/database-card-drag.ts +306 -0
  263. package/src/tools/database/database-card-drawer.ts +546 -0
  264. package/src/tools/database/database-column-controls.ts +141 -0
  265. package/src/tools/database/database-column-drag.ts +262 -0
  266. package/src/tools/database/database-keyboard.ts +35 -0
  267. package/src/tools/database/database-list-row-drag.ts +245 -0
  268. package/src/tools/database/database-list-view.ts +333 -0
  269. package/src/tools/database/database-model.ts +214 -0
  270. package/src/tools/database/database-property-type-popover.ts +108 -0
  271. package/src/tools/database/database-tab-bar.ts +558 -0
  272. package/src/tools/database/database-view-popover.ts +129 -0
  273. package/src/tools/database/database-view-renderer.ts +25 -0
  274. package/src/tools/database/index.ts +1223 -0
  275. package/src/tools/database/types.ts +152 -0
  276. package/src/tools/database-row/index.ts +74 -0
  277. package/src/tools/index.ts +4 -0
  278. package/types/api/tools.d.ts +18 -0
  279. package/types/configs/blok-config.d.ts +27 -0
  280. package/types/data-formats/output-data.d.ts +13 -0
  281. package/types/index.d.ts +17 -0
  282. package/types/tools/database.d.ts +152 -0
  283. package/types/tools-entry.d.ts +7 -4
  284. package/types/utils/popover/popover.d.ts +6 -0
@@ -18,6 +18,8 @@
18
18
  "toolNames.numberedList": "נומעריקע ליסטע",
19
19
  "toolNames.todoList": "צו-טאָן ליסטע",
20
20
  "toolNames.toggleList": "אויפֿקלאַפּ ליסטע",
21
+ "toolNames.board": "טאָוול",
22
+ "toolNames.database": "דאַטאַבאַנק",
21
23
  "tools.link.addLink": "צולייגן לינק",
22
24
  "tools.link.invalidLink": "אומגילטיקער לינק",
23
25
  "tools.marker.textColor": "טעקסט",
@@ -143,6 +145,22 @@
143
145
  "tools.callout.emojiCategoryObjects": "זאַכן",
144
146
  "tools.callout.emojiCategorySymbols": "סימבאָלן",
145
147
  "tools.callout.emojiCategoryFlags": "פֿאָנען",
148
+ "tools.database.addCard": "צוגעבן קאַרטל",
149
+ "tools.database.newPage": "נייע בלאט",
150
+ "tools.database.addColumn": "צוגעבן שפּאַלט",
151
+ "tools.database.cardTitlePlaceholder": "ליידיק בלאַט",
152
+ "tools.database.columnTitlePlaceholder": "שפּאַלט",
153
+ "tools.database.deleteCard": "אויסמעקן קאַרטל",
154
+ "tools.database.editCardTitle": "Edit title",
155
+ "tools.database.cardMenuLabel": "Card options",
156
+ "tools.database.deleteColumn": "אויסמעקן שפּאַלט",
157
+ "tools.database.emptyColumn": "קיין קארטן",
158
+ "tools.database.rowTitlePlaceholder": "ליידיק בלאַט",
159
+ "tools.database.addRow": "צוגעבן שורה",
160
+ "tools.database.newRow": "נייע שורה",
161
+ "tools.database.openRow": "עפֿענען שורה",
162
+ "tools.database.deleteRow": "אויסמעקן שורה",
163
+ "tools.database.renameColumn": "באַנענען שפּאַלט",
146
164
  "tools.callout.color": "קאָליר",
147
165
  "tools.callout.colorDefault": "שטאַנדאַרד",
148
166
  "tools.callout.colorGray": "גרוי",
@@ -18,6 +18,8 @@
18
18
  "toolNames.numberedList": "有序列表",
19
19
  "toolNames.todoList": "清单",
20
20
  "toolNames.toggleList": "折叠列表",
21
+ "toolNames.board": "看板",
22
+ "toolNames.database": "数据库",
21
23
  "tools.link.addLink": "添加链接",
22
24
  "tools.link.invalidLink": "链接无效",
23
25
  "tools.marker.textColor": "文字",
@@ -143,6 +145,22 @@
143
145
  "tools.callout.emojiCategoryObjects": "物品",
144
146
  "tools.callout.emojiCategorySymbols": "符号",
145
147
  "tools.callout.emojiCategoryFlags": "旗帜",
148
+ "tools.database.addCard": "添加卡片",
149
+ "tools.database.newPage": "新页面",
150
+ "tools.database.addColumn": "添加列",
151
+ "tools.database.cardTitlePlaceholder": "空白页面",
152
+ "tools.database.columnTitlePlaceholder": "列",
153
+ "tools.database.deleteCard": "删除卡片",
154
+ "tools.database.editCardTitle": "Edit title",
155
+ "tools.database.cardMenuLabel": "Card options",
156
+ "tools.database.deleteColumn": "删除列",
157
+ "tools.database.emptyColumn": "没有卡片",
158
+ "tools.database.rowTitlePlaceholder": "空白页面",
159
+ "tools.database.addRow": "添加行",
160
+ "tools.database.newRow": "新行",
161
+ "tools.database.openRow": "打开行",
162
+ "tools.database.deleteRow": "删除行",
163
+ "tools.database.renameColumn": "重命名列",
146
164
  "tools.callout.color": "颜色",
147
165
  "tools.callout.colorDefault": "默认",
148
166
  "tools.callout.colorGray": "灰色",
@@ -399,6 +399,13 @@ export const IconCopy = `
399
399
  </svg>
400
400
  `;
401
401
 
402
+ // Pencil/Rename icon
403
+ export const IconPencil = `
404
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
405
+ <path d="M12.5 5.5l2 2M4.5 13.5l8-8 2 2-8 8H4.5v-2z" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
406
+ </svg>
407
+ `;
408
+
402
409
  // Trash/Delete icon
403
410
  export const IconTrash = `
404
411
  <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -409,6 +416,15 @@ export const IconTrash = `
409
416
  </svg>
410
417
  `;
411
418
 
419
+ // Horizontal dots / more options icon
420
+ export const IconDotsHorizontal = `
421
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
422
+ <circle cx="5" cy="10" r="1.25" fill="currentColor"/>
423
+ <circle cx="10" cy="10" r="1.25" fill="currentColor"/>
424
+ <circle cx="15" cy="10" r="1.25" fill="currentColor"/>
425
+ </svg>
426
+ `;
427
+
412
428
  // Toggle Heading 1 icon (H1 with toggle arrow)
413
429
  export const IconToggleH1 = `
414
430
  <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -592,6 +608,55 @@ export const IconEmojiFlag = `
592
608
  </svg>
593
609
  `;
594
610
 
611
+ export const IconDatabase = `
612
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
613
+ <ellipse cx="10" cy="5.5" rx="6" ry="2.5" stroke="currentColor" stroke-width="1.25"/>
614
+ <path d="M4 5.5v4c0 1.38 2.69 2.5 6 2.5s6-1.12 6-2.5v-4" stroke="currentColor" stroke-width="1.25"/>
615
+ <path d="M4 9.5v4c0 1.38 2.69 2.5 6 2.5s6-1.12 6-2.5v-4" stroke="currentColor" stroke-width="1.25"/>
616
+ </svg>
617
+ `;
618
+
619
+ export const IconBoard = `
620
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
621
+ <rect x="3" y="4" width="14" height="12" rx="1.5" stroke="currentColor" stroke-width="1.25"/>
622
+ <path d="M7.5 4v12M12.5 4v12" stroke="currentColor" stroke-width="1.25"/>
623
+ <rect x="4.5" y="6" width="1.5" height="2" rx="0.5" fill="currentColor"/>
624
+ <rect x="4.5" y="9" width="1.5" height="2" rx="0.5" fill="currentColor"/>
625
+ <rect x="9" y="6" width="1.5" height="2" rx="0.5" fill="currentColor"/>
626
+ <rect x="14" y="6" width="1.5" height="2" rx="0.5" fill="currentColor"/>
627
+ </svg>
628
+ `;
629
+
630
+ export const IconGallery = `
631
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
632
+ <rect x="3" y="4" width="6" height="5" rx="1" stroke="currentColor" stroke-width="1.25"/>
633
+ <rect x="11" y="4" width="6" height="5" rx="1" stroke="currentColor" stroke-width="1.25"/>
634
+ <rect x="3" y="11" width="6" height="5" rx="1" stroke="currentColor" stroke-width="1.25"/>
635
+ <rect x="11" y="11" width="6" height="5" rx="1" stroke="currentColor" stroke-width="1.25"/>
636
+ </svg>
637
+ `;
638
+
639
+ export const IconList = `
640
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
641
+ <circle cx="4.5" cy="6.5" r="1" fill="currentColor"/>
642
+ <circle cx="4.5" cy="10" r="1" fill="currentColor"/>
643
+ <circle cx="4.5" cy="13.5" r="1" fill="currentColor"/>
644
+ <path d="M7.5 6.5h9M7.5 10h9M7.5 13.5h9" stroke="currentColor" stroke-width="1.25" stroke-linecap="round"/>
645
+ </svg>
646
+ `;
647
+
648
+ // Calendar icon (date property type)
649
+ export const IconCalendar = `
650
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
651
+ <rect x="3" y="4.5" width="14" height="12" rx="1.5" stroke="currentColor" stroke-width="1.25"/>
652
+ <path d="M3 8.5h14" stroke="currentColor" stroke-width="1.25" stroke-linecap="round"/>
653
+ <path d="M7 3v3M13 3v3" stroke="currentColor" stroke-width="1.25" stroke-linecap="round"/>
654
+ <circle cx="7" cy="12" r="1" fill="currentColor"/>
655
+ <circle cx="10" cy="12" r="1" fill="currentColor"/>
656
+ <circle cx="13" cy="12" r="1" fill="currentColor"/>
657
+ </svg>
658
+ `;
659
+
595
660
  // Merge cells icon (two cells merging into one)
596
661
  export const IconMergeCells = `
597
662
  <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -16,6 +16,7 @@ import {
16
16
  import {
17
17
  isRangeFormatted,
18
18
  collectFormattingAncestors,
19
+ extendRangeToTrailingWhitespace,
19
20
  } from './utils/formatting-range-utils';
20
21
 
21
22
  /**
@@ -212,6 +213,7 @@ export class BoldInlineTool implements InlineTool {
212
213
  * @param range - The Range object containing the selection to wrap
213
214
  */
214
215
  private wrapWithBold(range: Range): void {
216
+ extendRangeToTrailingWhitespace(range);
215
217
  const html = this.getRangeHtmlWithoutBold(range);
216
218
  const insertedRange = this.replaceRangeWithHtml(range, `<strong>${html}</strong>`);
217
219
  const selection = window.getSelection();
@@ -308,8 +310,16 @@ export class BoldInlineTool implements InlineTool {
308
310
  BoldNormalizationPass.normalizeAroundSelection(selection);
309
311
 
310
312
  boldAncestors.forEach((element) => {
313
+ if (!element.isConnected) {
314
+ return;
315
+ }
316
+
311
317
  if (isElementEmpty(element)) {
312
318
  element.remove();
319
+ } else if (element.textContent.trim().length === 0) {
320
+ // Element contains only whitespace — unwrap it to preserve the whitespace
321
+ // as plain text while removing the bold formatting
322
+ this.unwrapElement(element);
313
323
  }
314
324
  });
315
325
 
@@ -6,6 +6,7 @@ import {
6
6
  isRangeFormatted,
7
7
  findFormattingAncestor,
8
8
  collectFormattingAncestors,
9
+ extendRangeToTrailingWhitespace,
9
10
  } from './utils/formatting-range-utils';
10
11
 
11
12
  /**
@@ -168,16 +169,68 @@ export class CodeInlineTool implements InlineTool {
168
169
  * @param range - The Range object containing the selection to wrap
169
170
  */
170
171
  private wrapWithCode(range: Range): void {
172
+ extendRangeToTrailingWhitespace(range);
171
173
  const html = this.getRangeHtmlWithoutCode(range);
172
174
  const insertedRange = this.replaceRangeWithHtml(range, `<code>${html}</code>`);
173
175
  const selection = window.getSelection();
174
176
 
175
177
  if (selection && insertedRange) {
178
+ const wrappedElement = insertedRange.startContainer.childNodes[insertedRange.startOffset] as HTMLElement | undefined;
179
+ const newRange = document.createRange();
180
+
181
+ if (wrappedElement) {
182
+ this.normalizeNbspInElement(wrappedElement);
183
+ newRange.selectNodeContents(wrappedElement);
184
+ } else {
185
+ newRange.setStart(insertedRange.startContainer, insertedRange.startOffset);
186
+ newRange.setEnd(insertedRange.endContainer, insertedRange.endOffset);
187
+ }
188
+
176
189
  selection.removeAllRanges();
177
- selection.addRange(insertedRange);
190
+ selection.addRange(newRange);
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Replace non-breaking spaces (\u00A0) with regular spaces in all text nodes of an element
196
+ * @param element - The element to normalize
197
+ */
198
+ private normalizeNbspInElement(element: HTMLElement): void {
199
+ const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
200
+
201
+ while (true) {
202
+ const node = walker.nextNode();
203
+
204
+ if (node === null) {
205
+ break;
206
+ }
207
+ if (!node.textContent?.includes('\u00A0')) {
208
+ continue;
209
+ }
210
+
211
+ const keepTrailing = node.textContent.endsWith('\u00A0') && this.isEffectivelyLastChild(node);
212
+
213
+ node.textContent = node.textContent.replace(/\u00A0/g, ' ');
214
+
215
+ if (keepTrailing) {
216
+ node.textContent = node.textContent.slice(0, -1) + '\u00A0';
217
+ }
178
218
  }
179
219
  }
180
220
 
221
+ /**
222
+ * Check whether all siblings after a node are empty
223
+ */
224
+ private isEffectivelyLastChild(node: Node): boolean {
225
+ const next = node.nextSibling;
226
+
227
+ if (!next) {
228
+ return true;
229
+ }
230
+
231
+ return (next.textContent ?? '').length === 0 && this.isEffectivelyLastChild(next);
232
+ }
233
+
181
234
  /**
182
235
  * Remove code tags (<code>) while preserving content
183
236
  * @param range - The Range object containing the selection to unwrap
@@ -7,6 +7,7 @@ import {
7
7
  findFormattingAncestor,
8
8
  hasFormattingAncestor,
9
9
  collectFormattingAncestors,
10
+ extendRangeToTrailingWhitespace,
10
11
  } from './utils/formatting-range-utils';
11
12
 
12
13
  /**
@@ -172,16 +173,68 @@ export class ItalicInlineTool implements InlineTool {
172
173
  * @param range - The Range object containing the selection to wrap
173
174
  */
174
175
  private wrapWithItalic(range: Range): void {
176
+ extendRangeToTrailingWhitespace(range);
175
177
  const html = this.getRangeHtmlWithoutItalic(range);
176
178
  const insertedRange = this.replaceRangeWithHtml(range, `<i>${html}</i>`);
177
179
  const selection = window.getSelection();
178
180
 
179
181
  if (selection && insertedRange) {
182
+ const wrappedElement = insertedRange.startContainer.childNodes[insertedRange.startOffset] as HTMLElement | undefined;
183
+ const newRange = document.createRange();
184
+
185
+ if (wrappedElement) {
186
+ this.normalizeNbspInElement(wrappedElement);
187
+ newRange.selectNodeContents(wrappedElement);
188
+ } else {
189
+ newRange.setStart(insertedRange.startContainer, insertedRange.startOffset);
190
+ newRange.setEnd(insertedRange.endContainer, insertedRange.endOffset);
191
+ }
192
+
180
193
  selection.removeAllRanges();
181
- selection.addRange(insertedRange);
194
+ selection.addRange(newRange);
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Replace non-breaking spaces (\u00A0) with regular spaces in all text nodes of an element
200
+ * @param element - The element to normalize
201
+ */
202
+ private normalizeNbspInElement(element: HTMLElement): void {
203
+ const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
204
+
205
+ while (true) {
206
+ const node = walker.nextNode();
207
+
208
+ if (node === null) {
209
+ break;
210
+ }
211
+ if (!node.textContent?.includes('\u00A0')) {
212
+ continue;
213
+ }
214
+
215
+ const keepTrailing = node.textContent.endsWith('\u00A0') && this.isEffectivelyLastChild(node);
216
+
217
+ node.textContent = node.textContent.replace(/\u00A0/g, ' ');
218
+
219
+ if (keepTrailing) {
220
+ node.textContent = node.textContent.slice(0, -1) + '\u00A0';
221
+ }
182
222
  }
183
223
  }
184
224
 
225
+ /**
226
+ * Check whether all siblings after a node are empty
227
+ */
228
+ private isEffectivelyLastChild(node: Node): boolean {
229
+ const next = node.nextSibling;
230
+
231
+ if (!next) {
232
+ return true;
233
+ }
234
+
235
+ return (next.textContent ?? '').length === 0 && this.isEffectivelyLastChild(next);
236
+ }
237
+
185
238
  /**
186
239
  * Remove italic tags (<i>/<em>) while preserving content
187
240
  * @param range - The Range object containing the selection to unwrap
@@ -7,6 +7,7 @@ import {
7
7
  findFormattingAncestor,
8
8
  hasFormattingAncestor,
9
9
  collectFormattingAncestors,
10
+ extendRangeToTrailingWhitespace,
10
11
  } from './utils/formatting-range-utils';
11
12
 
12
13
  /**
@@ -169,16 +170,68 @@ export class StrikethroughInlineTool implements InlineTool {
169
170
  * @param range - The Range object containing the selection to wrap
170
171
  */
171
172
  private wrapWithStrikethrough(range: Range): void {
173
+ extendRangeToTrailingWhitespace(range);
172
174
  const html = this.getRangeHtmlWithoutStrikethrough(range);
173
175
  const insertedRange = this.replaceRangeWithHtml(range, `<s>${html}</s>`);
174
176
  const selection = window.getSelection();
175
177
 
176
178
  if (selection && insertedRange) {
179
+ const wrappedElement = insertedRange.startContainer.childNodes[insertedRange.startOffset] as HTMLElement | undefined;
180
+ const newRange = document.createRange();
181
+
182
+ if (wrappedElement) {
183
+ this.normalizeNbspInElement(wrappedElement);
184
+ newRange.selectNodeContents(wrappedElement);
185
+ } else {
186
+ newRange.setStart(insertedRange.startContainer, insertedRange.startOffset);
187
+ newRange.setEnd(insertedRange.endContainer, insertedRange.endOffset);
188
+ }
189
+
177
190
  selection.removeAllRanges();
178
- selection.addRange(insertedRange);
191
+ selection.addRange(newRange);
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Replace non-breaking spaces (\u00A0) with regular spaces in all text nodes of an element
197
+ * @param element - The element to normalize
198
+ */
199
+ private normalizeNbspInElement(element: HTMLElement): void {
200
+ const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
201
+
202
+ while (true) {
203
+ const node = walker.nextNode();
204
+
205
+ if (node === null) {
206
+ break;
207
+ }
208
+ if (!node.textContent?.includes('\u00A0')) {
209
+ continue;
210
+ }
211
+
212
+ const keepTrailing = node.textContent.endsWith('\u00A0') && this.isEffectivelyLastChild(node);
213
+
214
+ node.textContent = node.textContent.replace(/\u00A0/g, ' ');
215
+
216
+ if (keepTrailing) {
217
+ node.textContent = node.textContent.slice(0, -1) + '\u00A0';
218
+ }
179
219
  }
180
220
  }
181
221
 
222
+ /**
223
+ * Check whether all siblings after a node are empty
224
+ */
225
+ private isEffectivelyLastChild(node: Node): boolean {
226
+ const next = node.nextSibling;
227
+
228
+ if (!next) {
229
+ return true;
230
+ }
231
+
232
+ return (next.textContent ?? '').length === 0 && this.isEffectivelyLastChild(next);
233
+ }
234
+
182
235
  /**
183
236
  * Remove strikethrough tags (<s>) while preserving content
184
237
  * @param range - The Range object containing the selection to unwrap
@@ -7,6 +7,7 @@ import {
7
7
  findFormattingAncestor,
8
8
  hasFormattingAncestor,
9
9
  collectFormattingAncestors,
10
+ extendRangeToTrailingWhitespace,
10
11
  } from './utils/formatting-range-utils';
11
12
 
12
13
  /**
@@ -169,16 +170,68 @@ export class UnderlineInlineTool implements InlineTool {
169
170
  * @param range - The Range object containing the selection to wrap
170
171
  */
171
172
  private wrapWithUnderline(range: Range): void {
173
+ extendRangeToTrailingWhitespace(range);
172
174
  const html = this.getRangeHtmlWithoutUnderline(range);
173
175
  const insertedRange = this.replaceRangeWithHtml(range, `<u>${html}</u>`);
174
176
  const selection = window.getSelection();
175
177
 
176
178
  if (selection && insertedRange) {
179
+ const wrappedElement = insertedRange.startContainer.childNodes[insertedRange.startOffset] as HTMLElement | undefined;
180
+ const newRange = document.createRange();
181
+
182
+ if (wrappedElement) {
183
+ this.normalizeNbspInElement(wrappedElement);
184
+ newRange.selectNodeContents(wrappedElement);
185
+ } else {
186
+ newRange.setStart(insertedRange.startContainer, insertedRange.startOffset);
187
+ newRange.setEnd(insertedRange.endContainer, insertedRange.endOffset);
188
+ }
189
+
177
190
  selection.removeAllRanges();
178
- selection.addRange(insertedRange);
191
+ selection.addRange(newRange);
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Replace non-breaking spaces (\u00A0) with regular spaces in all text nodes of an element
197
+ * @param element - The element to normalize
198
+ */
199
+ private normalizeNbspInElement(element: HTMLElement): void {
200
+ const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
201
+
202
+ while (true) {
203
+ const node = walker.nextNode();
204
+
205
+ if (node === null) {
206
+ break;
207
+ }
208
+ if (!node.textContent?.includes('\u00A0')) {
209
+ continue;
210
+ }
211
+
212
+ const keepTrailing = node.textContent.endsWith('\u00A0') && this.isEffectivelyLastChild(node);
213
+
214
+ node.textContent = node.textContent.replace(/\u00A0/g, ' ');
215
+
216
+ if (keepTrailing) {
217
+ node.textContent = node.textContent.slice(0, -1) + '\u00A0';
218
+ }
179
219
  }
180
220
  }
181
221
 
222
+ /**
223
+ * Check whether all siblings after a node are empty
224
+ */
225
+ private isEffectivelyLastChild(node: Node): boolean {
226
+ const next = node.nextSibling;
227
+
228
+ if (!next) {
229
+ return true;
230
+ }
231
+
232
+ return (next.textContent ?? '').length === 0 && this.isEffectivelyLastChild(next);
233
+ }
234
+
182
235
  /**
183
236
  * Remove underline tags (<u>) while preserving content
184
237
  * @param range - The Range object containing the selection to unwrap
@@ -201,7 +201,10 @@ export class BoldNormalizationPass {
201
201
  }
202
202
 
203
203
  /**
204
- * Replace non-breaking spaces with regular spaces in a text node
204
+ * Replace non-breaking spaces with regular spaces in a text node.
205
+ * Preserves a trailing \u00A0 when the text node is the last child of its
206
+ * parent, because converting it to a regular space (char 32) would make it
207
+ * invisible under CSS white-space:normal.
205
208
  * @param textNode - The text node to process
206
209
  */
207
210
  private replaceNbspInTextNode(textNode: Text): void {
@@ -211,10 +214,13 @@ export class BoldNormalizationPass {
211
214
  return;
212
215
  }
213
216
 
214
- const normalizedText = text.replace(/\u00A0/g, ' ');
217
+ const keepTrailing = text.endsWith('\u00A0') && BoldNormalizationPass.isEffectivelyLastChild(textNode);
218
+ const replaced = text.replace(/\u00A0/g, ' ');
215
219
 
216
220
  // eslint-disable-next-line no-param-reassign
217
- textNode.textContent = normalizedText;
221
+ textNode.textContent = keepTrailing
222
+ ? replaced.slice(0, -1) + '\u00A0'
223
+ : replaced;
218
224
  }
219
225
 
220
226
  /**
@@ -279,6 +285,26 @@ export class BoldNormalizationPass {
279
285
  rightStrong.remove();
280
286
  }
281
287
 
288
+ /**
289
+ * Check whether all siblings after a node are empty (zero-length text nodes
290
+ * or empty elements). The unbold process can leave behind empty text nodes
291
+ * which would cause `!node.nextSibling` to return false even though the node
292
+ * is effectively the last meaningful child.
293
+ */
294
+ private static isEffectivelyLastChild(node: Node): boolean {
295
+ const next = node.nextSibling;
296
+
297
+ if (!next) {
298
+ return true;
299
+ }
300
+
301
+ if ((next.textContent ?? '').length > 0) {
302
+ return false;
303
+ }
304
+
305
+ return BoldNormalizationPass.isEffectivelyLastChild(next);
306
+ }
307
+
282
308
  /**
283
309
  * Find the appropriate scope element from a selection
284
310
  * @param selection - The current selection
@@ -132,6 +132,89 @@ export const isRangeFormatted = (
132
132
  return textNodes.every((textNode) => hasFormattingAncestor(textNode, predicate));
133
133
  };
134
134
 
135
+ /**
136
+ * Walk down the `lastChild` chain of `node` and return the deepest last
137
+ * descendant that is a text node, or `null` if none exists.
138
+ */
139
+ const findDeepestLastTextNode = (node: Node): Text | null => {
140
+ const last = node.lastChild;
141
+
142
+ if (last === null) {
143
+ return node.nodeType === Node.TEXT_NODE ? (node as Text) : null;
144
+ }
145
+
146
+ return findDeepestLastTextNode(last);
147
+ };
148
+
149
+ /**
150
+ * Extend the range to include any trailing whitespace characters that browsers
151
+ * exclude from selections (e.g. Ctrl+A on loaded text stops before trailing spaces).
152
+ *
153
+ * When text is loaded via innerHTML (e.g. `"hello "`), browsers keep trailing
154
+ * regular spaces (char 32) as-is in the text node, but Ctrl+A / selectAll
155
+ * in Chromium and WebKit places the range end *before* those trailing spaces.
156
+ * This means `range.cloneContents()` omits them, so formatting operations
157
+ * (bold, italic, etc.) silently drop any trailing whitespace.
158
+ *
159
+ * By contrast, text typed by the user is stored internally with a non-breaking
160
+ * space (`\u00A0`) which is always included in the selection.
161
+ *
162
+ * This function mutates the range in place to cover those excluded trailing
163
+ * spaces, so downstream formatting can include them.
164
+ *
165
+ * Two browser behaviours are handled:
166
+ * 1. `endContainer` is a text node — the range ends mid-text, so check whether
167
+ * the remaining characters are all whitespace and extend if so.
168
+ * 2. `endContainer` is an element node — Ctrl+A / selectAll in Chromium and
169
+ * WebKit places the range end on the element itself with `endOffset` equal
170
+ * to the child count (i.e. after all children). In this case the last child
171
+ * text node may still have trailing whitespace that was excluded; walk to
172
+ * the deepest last text node and extend the range to its end if it ends with
173
+ * only whitespace after the current offset (or fully consists of whitespace).
174
+ *
175
+ * @param range - The range to extend (mutated in place)
176
+ */
177
+ export const extendRangeToTrailingWhitespace = (range: Range): void => {
178
+ const endContainer = range.endContainer;
179
+
180
+ if (endContainer.nodeType === Node.TEXT_NODE) {
181
+ const text = endContainer.textContent ?? '';
182
+ const endOffset = range.endOffset;
183
+
184
+ if (endOffset >= text.length) {
185
+ return;
186
+ }
187
+
188
+ // Check whether all characters between endOffset and end of text node are whitespace
189
+ const trailingSlice = text.slice(endOffset);
190
+
191
+ if (trailingSlice.length > 0 && trailingSlice.trim().length === 0) {
192
+ range.setEnd(endContainer, text.length);
193
+ }
194
+
195
+ return;
196
+ }
197
+
198
+ // endContainer is an element node (the common case for Ctrl+A on loaded content).
199
+ // Walk to the deepest last text node within it to find any trailing whitespace.
200
+ const lastTextNode = findDeepestLastTextNode(endContainer);
201
+
202
+ if (lastTextNode === null) {
203
+ return;
204
+ }
205
+
206
+ const text = lastTextNode.textContent ?? '';
207
+
208
+ if (text.length === 0) {
209
+ return;
210
+ }
211
+
212
+ // Extend range if the text ends with one or more whitespace characters.
213
+ if (/\s+$/.test(text)) {
214
+ range.setEnd(lastTextNode, text.length);
215
+ }
216
+ };
217
+
135
218
  /**
136
219
  * Collect all unique formatting ancestors within a range
137
220
  * @param range - The range to search within
@@ -11,6 +11,25 @@ export class ToolsAPI extends Module {
11
11
  public get methods(): ToolsAPIInterface {
12
12
  return {
13
13
  getBlockTools: () => Array.from(this.Blok.Tools.blockTools.values()),
14
+ getToolsConfig: () => {
15
+ const result: ReturnType<ToolsAPIInterface['getToolsConfig']> = {
16
+ tools: this.config.tools,
17
+ };
18
+
19
+ if (this.config.inlineToolbar !== undefined) {
20
+ result.inlineToolbar = this.config.inlineToolbar;
21
+ }
22
+
23
+ if (this.config.tunes !== undefined) {
24
+ result.tunes = this.config.tunes;
25
+ }
26
+
27
+ if (this.config.theme !== undefined) {
28
+ result.theme = this.config.theme;
29
+ }
30
+
31
+ return result;
32
+ },
14
33
  };
15
34
  }
16
35
  }