@jackuait/blok 0.8.3-beta.4 → 0.10.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/codemod/migrate-editorjs-to-blok.js +50 -0
  2. package/codemod/test.js +39 -0
  3. package/dist/blok.mjs +2 -2
  4. package/dist/chunks/{blok-eDCl1y00.mjs → blok-wFPimG2f.mjs} +1590 -1566
  5. package/dist/chunks/{constants-Duj_CSIT.mjs → constants-B_TnFMSA.mjs} +56 -56
  6. package/dist/chunks/{i18next-loader-BCAGutUy.mjs → i18next-loader-BOlOKRt8.mjs} +1 -1
  7. package/dist/chunks/{lightweight-i18n-CNUuUkuo.mjs → lightweight-i18n-D1n0OClP.mjs} +42 -1
  8. package/dist/chunks/{messages-B847kJa02.mjs → messages-0Nh_GHU02.mjs} +42 -1
  9. package/dist/{messages--PYnorLu2.mjs → chunks/messages-16UmLAWZ2.mjs} +42 -1
  10. package/dist/chunks/{messages-CiSiPyKp.mjs → messages-2nj1xBDo.mjs} +42 -1
  11. package/dist/chunks/{messages-LiQ7ni4i2.mjs → messages-5WyxUYVR2.mjs} +42 -1
  12. package/dist/chunks/{messages-DYQ87v5m.mjs → messages-7PIvzufT.mjs} +42 -1
  13. package/dist/{messages-BD3lSUU_.mjs → chunks/messages-92ma9RJD2.mjs} +42 -1
  14. package/dist/chunks/{messages-uQOWdLDn2.mjs → messages-A3Z4jxwt2.mjs} +42 -1
  15. package/dist/{messages-CJ5pCncM.mjs → chunks/messages-B18MZnaY.mjs} +42 -1
  16. package/dist/{messages-Dl7LQ_fI.mjs → chunks/messages-B1uFbxNg.mjs} +42 -1
  17. package/dist/{messages-D7dJJ7dx.mjs → chunks/messages-B7Vlzmgw.mjs} +42 -1
  18. package/dist/{messages-Bw0kGTAV.mjs → chunks/messages-BF8c-lMm.mjs} +42 -1
  19. package/dist/{messages-Oqm_fCLp.mjs → chunks/messages-BLD1DC722.mjs} +42 -1
  20. package/dist/chunks/{messages-C35b_LVM.mjs → messages-BN_zp4oj.mjs} +42 -1
  21. package/dist/chunks/{messages-JQR7Z2f0.mjs → messages-BORQKKT9.mjs} +42 -1
  22. package/dist/{messages-DkaJV53s2.mjs → chunks/messages-BPQA3B862.mjs} +42 -1
  23. package/dist/chunks/{messages-CyrZJ9dG.mjs → messages-BQvBhQem.mjs} +42 -1
  24. package/dist/{messages-D9a5iSFC2.mjs → chunks/messages-BTyEo5Kb.mjs} +42 -1
  25. package/dist/{messages-D8SS2hOs2.mjs → chunks/messages-BVl-X2wo2.mjs} +42 -1
  26. package/dist/chunks/{messages-B2Xnfn7m2.mjs → messages-BivhofAQ2.mjs} +42 -1
  27. package/dist/chunks/{messages-D1eXOdyJ.mjs → messages-BtZ8oQXS.mjs} +42 -1
  28. package/dist/{messages-D2Xo8E0P.mjs → chunks/messages-By-gACFM2.mjs} +42 -1
  29. package/dist/chunks/{messages-B6n2OYKJ.mjs → messages-C1pm6RWX.mjs} +42 -1
  30. package/dist/chunks/{messages-ByeBRHpJ.mjs → messages-C7YpgZ9m.mjs} +42 -1
  31. package/dist/{messages-C9AvcT0K.mjs → chunks/messages-C9qPNbrJ2.mjs} +42 -1
  32. package/dist/{messages-DPECmAwT.mjs → chunks/messages-CDV8VcSZ2.mjs} +42 -1
  33. package/dist/{messages-tFDVfvWL.mjs → chunks/messages-CQgP-Fo22.mjs} +42 -1
  34. package/dist/{messages-C5MKR_WT2.mjs → chunks/messages-CR48wvl32.mjs} +42 -1
  35. package/dist/chunks/{messages-qt_Tsj1u2.mjs → messages-CYZL_rhV2.mjs} +42 -1
  36. package/dist/{messages-CGD9LR8l.mjs → chunks/messages-CYZgPXFK.mjs} +42 -1
  37. package/dist/chunks/{messages-B3VMWaO8.mjs → messages-CbXL5f99.mjs} +42 -1
  38. package/dist/{messages-BcrsCh5-2.mjs → chunks/messages-CkWidbwX2.mjs} +42 -1
  39. package/dist/{messages-BvIBkUCG2.mjs → chunks/messages-CmSsyItg.mjs} +42 -1
  40. package/dist/chunks/{messages-Dmxk7uOK.mjs → messages-CogKhvJL.mjs} +42 -1
  41. package/dist/chunks/{messages-B8nL5cPA2.mjs → messages-CqdYWY192.mjs} +42 -1
  42. package/dist/chunks/{messages-2i0z2hFq2.mjs → messages-Ct_H_5cB2.mjs} +42 -1
  43. package/dist/{messages-Z8dpjy_K2.mjs → chunks/messages-CuHxfDvL2.mjs} +42 -1
  44. package/dist/chunks/{messages-CDZrOiaR.mjs → messages-CuJLHCj5.mjs} +42 -1
  45. package/dist/chunks/{messages-CLcwMAAe.mjs → messages-CvA7Fbqf.mjs} +42 -1
  46. package/dist/chunks/{messages-K453lCht2.mjs → messages-D06U2QNl2.mjs} +42 -1
  47. package/dist/{messages-CY_Fj715.mjs → chunks/messages-D3hNTep_.mjs} +42 -1
  48. package/dist/{messages-CWIigMhw2.mjs → chunks/messages-D4QXMtW52.mjs} +42 -1
  49. package/dist/chunks/{messages-ClD_3Kmk.mjs → messages-D5iRrf9M.mjs} +42 -1
  50. package/dist/{messages-D5Uk_qYf.mjs → chunks/messages-D6ZvH6hX.mjs} +42 -1
  51. package/dist/chunks/{messages-LGqROvB5.mjs → messages-D9SfB6MI.mjs} +42 -1
  52. package/dist/chunks/{messages-Cqxe2gIN.mjs → messages-DBZ-uuAV.mjs} +42 -1
  53. package/dist/{messages-Bkv2sk7Y.mjs → chunks/messages-DDM4t-j8.mjs} +42 -1
  54. package/dist/{messages-DlEdd01l2.mjs → chunks/messages-DJ9yyqUO2.mjs} +42 -1
  55. package/dist/chunks/{messages-B38lWmza.mjs → messages-DQUUtL5v.mjs} +42 -1
  56. package/dist/{messages-DjWAR34D.mjs → chunks/messages-DcrkPXXn.mjs} +42 -1
  57. package/dist/chunks/{messages-CBa6-NHn.mjs → messages-DiaQNuPV.mjs} +42 -1
  58. package/dist/{messages-Cuw_6akN.mjs → chunks/messages-DjhgPQtb.mjs} +42 -1
  59. package/dist/chunks/{messages-DlSDBHih.mjs → messages-Dl16RBg1.mjs} +42 -1
  60. package/dist/{messages-BFbAM_pK2.mjs → chunks/messages-Dn1ZDZUy.mjs} +42 -1
  61. package/dist/chunks/{messages-CEVMB1TN2.mjs → messages-Du6j7HqD2.mjs} +42 -1
  62. package/dist/{messages-BR1Hlhdc2.mjs → chunks/messages-FEjIF48t.mjs} +42 -1
  63. package/dist/{messages-DffJ5W5G.mjs → chunks/messages-IKrYzwFq.mjs} +42 -1
  64. package/dist/{messages-C_YAmOyQ.mjs → chunks/messages-KihEeXdr.mjs} +42 -1
  65. package/dist/{messages-Dd4OTpH3.mjs → chunks/messages-LiSIeruD2.mjs} +42 -1
  66. package/dist/{messages-B_idm-Pp.mjs → chunks/messages-OFFQT8Fg.mjs} +42 -1
  67. package/dist/{messages-ItP2i78l.mjs → chunks/messages-cMwiuDZM.mjs} +42 -1
  68. package/dist/chunks/{messages-DAhzpf-j.mjs → messages-fitmpwb3.mjs} +42 -1
  69. package/dist/{messages-D0KBToO12.mjs → chunks/messages-i9ThpxZk2.mjs} +42 -1
  70. package/dist/chunks/{messages-4epWYTzK.mjs → messages-nZP1GShd.mjs} +42 -1
  71. package/dist/{messages-UpoT1AS6.mjs → chunks/messages-naWwXCx3.mjs} +42 -1
  72. package/dist/{messages-B2X5gO-s.mjs → chunks/messages-rnd6qiJ12.mjs} +42 -1
  73. package/dist/chunks/{messages-BHMUmX3N.mjs → messages-rxlf-Ule.mjs} +42 -1
  74. package/dist/chunks/{messages-DvUNk-e7.mjs → messages-x6GyZWWT.mjs} +42 -1
  75. package/dist/chunks/{tools-BG9c26dK.mjs → tools-TuZCMYTS.mjs} +1137 -908
  76. package/dist/cli.mjs +1 -1
  77. package/dist/full.mjs +11 -11
  78. package/dist/locales.mjs +109 -68
  79. package/dist/{messages-Cr2hTJyi2.mjs → messages-4kMr3vfK2.mjs} +42 -1
  80. package/dist/{messages-BTFWPbHk2.mjs → messages-6Cq_jyNk2.mjs} +42 -1
  81. package/dist/{chunks/messages-DvZa_OtS2.mjs → messages-6edZhK922.mjs} +42 -1
  82. package/dist/{chunks/messages-B1mCwtgm2.mjs → messages-B-lXqB1z2.mjs} +42 -1
  83. package/dist/{messages-Bf0eqjDJ.mjs → messages-BBauvpFc.mjs} +42 -1
  84. package/dist/{chunks/messages-Kqu8QyYy2.mjs → messages-BCIuVjwb.mjs} +42 -1
  85. package/dist/{messages-DCjAu6M72.mjs → messages-BHIdzfAS2.mjs} +42 -1
  86. package/dist/{messages-ikSI7M732.mjs → messages-BLjz6V7y2.mjs} +42 -1
  87. package/dist/{chunks/messages-D2QcrPYF.mjs → messages-BQi_l2vs.mjs} +42 -1
  88. package/dist/{messages-syo0DKqf.mjs → messages-BRlbE8SE.mjs} +42 -1
  89. package/dist/{chunks/messages-D6EtVUzk2.mjs → messages-B_6S7hBj2.mjs} +42 -1
  90. package/dist/{messages-CIGa1j8-2.mjs → messages-BfbYJ8Wk2.mjs} +42 -1
  91. package/dist/{chunks/messages-CAH4L_Mq2.mjs → messages-BiK5fMYF2.mjs} +42 -1
  92. package/dist/{messages-CjCjr0wW.mjs → messages-BjEY7_jw.mjs} +42 -1
  93. package/dist/{chunks/messages-SOew8O6I.mjs → messages-BkZDaKu6.mjs} +42 -1
  94. package/dist/{messages-ierNgYE02.mjs → messages-BwyQiBdm2.mjs} +42 -1
  95. package/dist/{messages-DaZkRhoM.mjs → messages-BxcqzUx0.mjs} +42 -1
  96. package/dist/{chunks/messages-CxKX6wTt.mjs → messages-C-KPP7bC.mjs} +42 -1
  97. package/dist/{messages-BZg8xxIT.mjs → messages-C-v50b4r.mjs} +42 -1
  98. package/dist/{chunks/messages-BleThZm5.mjs → messages-C672uqt-.mjs} +42 -1
  99. package/dist/{chunks/messages-Dxa27UhV2.mjs → messages-CBWUNVHy.mjs} +42 -1
  100. package/dist/{chunks/messages-BN78Am3l2.mjs → messages-CCBgCmyq.mjs} +42 -1
  101. package/dist/{chunks/messages-CbilQw5o2.mjs → messages-CGqRnKaM.mjs} +42 -1
  102. package/dist/{messages-Bn77ieXA.mjs → messages-CHaVGY89.mjs} +42 -1
  103. package/dist/{messages-BRJBZB-H2.mjs → messages-CIpXgMRr2.mjs} +42 -1
  104. package/dist/{chunks/messages-DVDFAJaw.mjs → messages-CLB0caVL.mjs} +42 -1
  105. package/dist/{chunks/messages-D7qp-JvC.mjs → messages-CLN3oL77.mjs} +42 -1
  106. package/dist/{chunks/messages-Bo2PcQoc.mjs → messages-CXlAjnEQ.mjs} +42 -1
  107. package/dist/{chunks/messages-CqoOMgIb2.mjs → messages-CZkwcbV12.mjs} +42 -1
  108. package/dist/{chunks/messages-CTtFH-AE.mjs → messages-CcPrYMHE.mjs} +42 -1
  109. package/dist/{chunks/messages-BRa1Itk_.mjs → messages-CeyO7HXV.mjs} +42 -1
  110. package/dist/{chunks/messages-CF7WlKOY.mjs → messages-CfaiAQHW2.mjs} +42 -1
  111. package/dist/{chunks/messages-Dqoj0eKD2.mjs → messages-CsVZUVra.mjs} +42 -1
  112. package/dist/{messages-DBuHaabF2.mjs → messages-CyQQ8g9w2.mjs} +42 -1
  113. package/dist/{messages-DUPXmcZh.mjs → messages-CyZZ10br.mjs} +42 -1
  114. package/dist/{messages-CJd52qP7.mjs → messages-D-bnq2qy.mjs} +42 -1
  115. package/dist/{messages-CC596_yM.mjs → messages-D1-_eTfM.mjs} +42 -1
  116. package/dist/{chunks/messages-DEVxqfH0.mjs → messages-D2GHT83V2.mjs} +42 -1
  117. package/dist/{chunks/messages-CTI66ZU52.mjs → messages-D7Wofcg3.mjs} +42 -1
  118. package/dist/{chunks/messages-cjqgX4JV2.mjs → messages-DB1-0FXB.mjs} +42 -1
  119. package/dist/{chunks/messages-B-x11A7Z.mjs → messages-DEKkIgU6.mjs} +42 -1
  120. package/dist/{messages-DFlT_TM02.mjs → messages-DEfeDBuV2.mjs} +42 -1
  121. package/dist/{chunks/messages-CCywXuFz2.mjs → messages-DP9PhNTo.mjs} +42 -1
  122. package/dist/{messages-DR2Lgl002.mjs → messages-DVHWIOfr2.mjs} +42 -1
  123. package/dist/{chunks/messages-Dc2wd0BC2.mjs → messages-DVILiJw52.mjs} +42 -1
  124. package/dist/{chunks/messages-PDaWSrFT.mjs → messages-DaFDdCrr.mjs} +42 -1
  125. package/dist/{messages-DAjtVSb6.mjs → messages-Daza4lOM.mjs} +42 -1
  126. package/dist/{chunks/messages-fdO2V2XC.mjs → messages-DbPt9d2U.mjs} +42 -1
  127. package/dist/{chunks/messages-DMENc4jZ.mjs → messages-DflAKRLd.mjs} +42 -1
  128. package/dist/{messages-olK9oNtb.mjs → messages-Dn-khi3a.mjs} +42 -1
  129. package/dist/{chunks/messages-CTGjizuJ2.mjs → messages-DnNNd3RW2.mjs} +42 -1
  130. package/dist/{chunks/messages-BrsB1FRe.mjs → messages-Dtw27ih4.mjs} +42 -1
  131. package/dist/{messages-Bgu3IB_k.mjs → messages-Du0fWeyE.mjs} +42 -1
  132. package/dist/{chunks/messages-xG65ERBM2.mjs → messages-DwTwecgF2.mjs} +42 -1
  133. package/dist/{messages-Dc93MR86.mjs → messages-DwroI9YW.mjs} +42 -1
  134. package/dist/{messages-C6UkeZ0y.mjs → messages-DzlmoWqQ.mjs} +42 -1
  135. package/dist/{messages-BXvE3YB4.mjs → messages-Dzqb5lg6.mjs} +42 -1
  136. package/dist/{messages-rgXeOQMh2.mjs → messages-QkGAxuVC2.mjs} +42 -1
  137. package/dist/{messages-CH8tCpAX.mjs → messages-YlWV9cSl.mjs} +42 -1
  138. package/dist/{messages-DVReQ5EC.mjs → messages-ZMa-zmIc.mjs} +42 -1
  139. package/dist/{messages-Buf_f_-32.mjs → messages-axsznSTn2.mjs} +42 -1
  140. package/dist/{chunks/messages-DJ_gZym52.mjs → messages-h_VN1Kyo2.mjs} +42 -1
  141. package/dist/{chunks/messages-lu4RI1A3.mjs → messages-ke15helG2.mjs} +42 -1
  142. package/dist/{messages-CqbeAhjH.mjs → messages-kuLrhtV2.mjs} +42 -1
  143. package/dist/{chunks/messages-DHJIlD2m.mjs → messages-nosa-xnx2.mjs} +42 -1
  144. package/dist/{chunks/messages-Bqk7cuZY.mjs → messages-uP6wmMOs.mjs} +42 -1
  145. package/dist/{messages-BMUQ7kA62.mjs → messages-us2JotS-2.mjs} +42 -1
  146. package/dist/react.mjs +2 -2
  147. package/dist/tools.mjs +3 -3
  148. package/package.json +1 -1
  149. package/src/components/i18n/locales/am/messages.json +42 -1
  150. package/src/components/i18n/locales/ar/messages.json +42 -1
  151. package/src/components/i18n/locales/az/messages.json +42 -1
  152. package/src/components/i18n/locales/bg/messages.json +42 -1
  153. package/src/components/i18n/locales/bn/messages.json +42 -1
  154. package/src/components/i18n/locales/bs/messages.json +42 -1
  155. package/src/components/i18n/locales/cs/messages.json +42 -1
  156. package/src/components/i18n/locales/da/messages.json +42 -1
  157. package/src/components/i18n/locales/de/messages.json +42 -1
  158. package/src/components/i18n/locales/dv/messages.json +42 -1
  159. package/src/components/i18n/locales/el/messages.json +42 -1
  160. package/src/components/i18n/locales/en/messages.json +42 -1
  161. package/src/components/i18n/locales/es/messages.json +42 -1
  162. package/src/components/i18n/locales/et/messages.json +42 -1
  163. package/src/components/i18n/locales/fa/messages.json +42 -1
  164. package/src/components/i18n/locales/fi/messages.json +42 -1
  165. package/src/components/i18n/locales/fil/messages.json +42 -1
  166. package/src/components/i18n/locales/fr/messages.json +42 -1
  167. package/src/components/i18n/locales/gu/messages.json +42 -1
  168. package/src/components/i18n/locales/he/messages.json +42 -1
  169. package/src/components/i18n/locales/hi/messages.json +42 -1
  170. package/src/components/i18n/locales/hr/messages.json +42 -1
  171. package/src/components/i18n/locales/hu/messages.json +42 -1
  172. package/src/components/i18n/locales/hy/messages.json +42 -1
  173. package/src/components/i18n/locales/id/messages.json +42 -1
  174. package/src/components/i18n/locales/it/messages.json +42 -1
  175. package/src/components/i18n/locales/ja/messages.json +42 -1
  176. package/src/components/i18n/locales/ka/messages.json +42 -1
  177. package/src/components/i18n/locales/km/messages.json +42 -1
  178. package/src/components/i18n/locales/kn/messages.json +42 -1
  179. package/src/components/i18n/locales/ko/messages.json +42 -1
  180. package/src/components/i18n/locales/ku/messages.json +42 -1
  181. package/src/components/i18n/locales/lo/messages.json +42 -1
  182. package/src/components/i18n/locales/lt/messages.json +42 -1
  183. package/src/components/i18n/locales/lv/messages.json +42 -1
  184. package/src/components/i18n/locales/mk/messages.json +42 -1
  185. package/src/components/i18n/locales/ml/messages.json +42 -1
  186. package/src/components/i18n/locales/mn/messages.json +42 -1
  187. package/src/components/i18n/locales/mr/messages.json +42 -1
  188. package/src/components/i18n/locales/ms/messages.json +42 -1
  189. package/src/components/i18n/locales/my/messages.json +42 -1
  190. package/src/components/i18n/locales/ne/messages.json +42 -1
  191. package/src/components/i18n/locales/nl/messages.json +42 -1
  192. package/src/components/i18n/locales/no/messages.json +42 -1
  193. package/src/components/i18n/locales/pa/messages.json +42 -1
  194. package/src/components/i18n/locales/pl/messages.json +42 -1
  195. package/src/components/i18n/locales/ps/messages.json +42 -1
  196. package/src/components/i18n/locales/pt/messages.json +42 -1
  197. package/src/components/i18n/locales/ro/messages.json +42 -1
  198. package/src/components/i18n/locales/ru/messages.json +42 -1
  199. package/src/components/i18n/locales/sd/messages.json +42 -1
  200. package/src/components/i18n/locales/si/messages.json +42 -1
  201. package/src/components/i18n/locales/sk/messages.json +42 -1
  202. package/src/components/i18n/locales/sl/messages.json +42 -1
  203. package/src/components/i18n/locales/sq/messages.json +42 -1
  204. package/src/components/i18n/locales/sr/messages.json +42 -1
  205. package/src/components/i18n/locales/sv/messages.json +42 -1
  206. package/src/components/i18n/locales/sw/messages.json +42 -1
  207. package/src/components/i18n/locales/ta/messages.json +42 -1
  208. package/src/components/i18n/locales/te/messages.json +42 -1
  209. package/src/components/i18n/locales/th/messages.json +42 -1
  210. package/src/components/i18n/locales/tr/messages.json +42 -1
  211. package/src/components/i18n/locales/ug/messages.json +42 -1
  212. package/src/components/i18n/locales/uk/messages.json +42 -1
  213. package/src/components/i18n/locales/ur/messages.json +42 -1
  214. package/src/components/i18n/locales/vi/messages.json +42 -1
  215. package/src/components/i18n/locales/yi/messages.json +42 -1
  216. package/src/components/i18n/locales/zh/messages.json +42 -1
  217. package/src/components/icons/index.ts +17 -0
  218. package/src/components/modules/blockEvents/composers/markdownShortcuts.ts +65 -2
  219. package/src/components/modules/blockEvents/constants.ts +11 -0
  220. package/src/components/modules/renderer.ts +17 -0
  221. package/src/components/ui/toolbox.ts +9 -0
  222. package/src/components/utils/caret/boundaries.ts +15 -4
  223. package/src/components/utils/data-model-transform.ts +8 -6
  224. package/src/styles/main.css +6 -4
  225. package/src/tools/callout/constants.ts +1 -1
  226. package/src/tools/callout/emoji-picker/index.ts +38 -3
  227. package/src/tools/callout/index.ts +1 -0
  228. package/src/tools/divider/index.ts +97 -0
  229. package/src/tools/divider/types.ts +6 -0
  230. package/src/tools/header/index.ts +2 -0
  231. package/src/tools/index.ts +4 -0
  232. package/src/tools/list/style-config.ts +3 -0
  233. package/src/tools/paragraph/index.ts +1 -0
  234. package/src/tools/quote/index.ts +229 -0
  235. package/src/tools/table/index.ts +1 -0
  236. package/src/tools/toggle/index.ts +1 -0
  237. package/types/tools/divider.d.ts +11 -0
  238. package/types/tools/tool-settings.d.ts +7 -0
  239. package/types/tools-entry.d.ts +9 -3
@@ -12,6 +12,14 @@ import {
12
12
  } from '../utils/data-model-transform';
13
13
  import { migrateMarkColors } from '../utils/color-migration';
14
14
 
15
+ /**
16
+ * Map of legacy EditorJS tool names to their Blok equivalents.
17
+ * Used during rendering to transparently migrate old article data.
18
+ */
19
+ const TOOL_ALIASES: Readonly<Record<string, string>> = {
20
+ delimiter: 'divider',
21
+ };
22
+
15
23
  /**
16
24
  * Module that responsible for rendering Blocks on blok initialization
17
25
  */
@@ -111,6 +119,15 @@ export class Renderer extends Module {
111
119
  };
112
120
  }
113
121
 
122
+ const aliasTarget = TOOL_ALIASES[originalTool];
123
+
124
+ if (aliasTarget !== undefined && Tools.available.has(aliasTarget)) {
125
+ return {
126
+ tool: aliasTarget,
127
+ data: blockToolData,
128
+ };
129
+ }
130
+
114
131
  logLabeled(`Tool «${originalTool}» is not found. Check 'tools' property at the Blok config.`, 'warn');
115
132
 
116
133
  return {
@@ -535,6 +535,15 @@ export class Toolbox extends EventsDispatcher<ToolboxEventMap> {
535
535
  const userSearchTerms = tool.searchTerms ?? [];
536
536
  const mergedSearchTerms = [...new Set([...librarySearchTerms, ...userSearchTerms])];
537
537
 
538
+ // Resolve translated search aliases from searchTermKeys
539
+ for (const key of toolboxItem.searchTermKeys ?? []) {
540
+ const fullKey = `searchTerms.${key}`;
541
+
542
+ if (this.i18n.has(fullKey)) {
543
+ mergedSearchTerms.push(this.i18n.t(fullKey));
544
+ }
545
+ }
546
+
538
547
  // Use entry-level shortcut if available, otherwise fall back to tool-level shortcut (for first entry only)
539
548
  const shortcut = toolboxItem.shortcut ?? (displaySecondaryLabel ? tool.shortcut : undefined);
540
549
 
@@ -56,13 +56,24 @@ export const checkContenteditableSliceForEmptiness = (
56
56
  /**
57
57
  * Check if we have any tags in the slice
58
58
  * We should not ignore them to allow navigation inside (e.g. empty bold tag)
59
+ *
60
+ * When checking the right side, trailing <br> tags are browser artifacts
61
+ * in contenteditable elements (sentinels for cursor positioning after Shift+Enter).
62
+ * If the only significant tags are <br> and there's no text content,
63
+ * the slice is effectively empty — skip the early return.
59
64
  */
60
- const hasSignificantTags = tempDiv.querySelectorAll(
65
+ const significantTags = tempDiv.querySelectorAll(
61
66
  'img, br, hr, input, area, base, col, embed, link, meta, param, source, track, wbr'
62
- ).length > 0;
67
+ );
63
68
 
64
- if (hasSignificantTags) {
65
- return false;
69
+ if (significantTags.length > 0) {
70
+ const isOnlyTrailingBrs = direction === 'right'
71
+ && tempDiv.querySelectorAll('img, hr, input, area, base, col, embed, link, meta, param, source, track, wbr').length === 0
72
+ && textContent.trim() === '';
73
+
74
+ if (!isOnlyTrailingBrs) {
75
+ return false;
76
+ }
66
77
  }
67
78
 
68
79
  /**
@@ -260,10 +260,6 @@ const hasHierarchicalRefs = (block: OutputBlockData): boolean => {
260
260
  export const analyzeDataFormat = (blocks: OutputBlockData[]): DataFormatAnalysis => {
261
261
  const foundHierarchicalRefs = blocks.some(hasHierarchicalRefs);
262
262
 
263
- if (foundHierarchicalRefs) {
264
- return { format: 'hierarchical', hasHierarchy: true };
265
- }
266
-
267
263
  // Check if any block uses legacy list format (items[] array)
268
264
  const foundLegacyList = blocks.some(isLegacyListBlock);
269
265
 
@@ -273,9 +269,15 @@ export const analyzeDataFormat = (blocks: OutputBlockData[]): DataFormatAnalysis
273
269
  // Check if any block uses legacy callout format (has body field)
274
270
  const foundLegacyCallout = blocks.some(isLegacyCalloutBlock);
275
271
 
276
- if (foundLegacyList || foundLegacyToggle || foundLegacyCallout) {
272
+ const hasLegacyBlocks = foundLegacyList || foundLegacyToggle || foundLegacyCallout;
273
+
274
+ if (foundHierarchicalRefs && !hasLegacyBlocks) {
275
+ return { format: 'hierarchical', hasHierarchy: true };
276
+ }
277
+
278
+ if (hasLegacyBlocks) {
277
279
  // Check if there's actual nesting for the hasHierarchy flag
278
- const hasNesting = blocks.some(hasNestedItems) || blocks.some(block =>
280
+ const hasNesting = foundHierarchicalRefs || blocks.some(hasNestedItems) || blocks.some(block =>
279
281
  isLegacyToggleListBlock(block) && block.data.body?.blocks !== undefined && block.data.body.blocks.length > 0
280
282
  ) || blocks.some(block =>
281
283
  isLegacyCalloutBlock(block) && block.data.body?.blocks !== undefined && block.data.body.blocks.length > 0
@@ -1164,16 +1164,18 @@
1164
1164
  * Callout emoji vertical centering for heading first children.
1165
1165
  * Headings have taller line boxes than the emoji button, so the emoji
1166
1166
  * needs a top margin to shift its center down to match the heading's
1167
- * first line center. Values: (heading-line-height − emoji-content) / 2 + 1px mt-px.
1167
+ * first line center. Values: (heading-line-height − emoji-line-height) / 2 + 1px mt-px.
1168
+ * Emoji button: text-[1.5rem] leading-[1] → line-height 24px.
1169
+ * H1 text-3xl leading-[1.3] → 39px; H2 text-2xl → 31.2px; H3 text-xl → 26px.
1168
1170
  */
1169
1171
  [data-blok-component="callout"] button:has(+ [data-blok-toggle-children] > :first-child h1) {
1170
- margin-top: 7.5px;
1172
+ margin-top: 8.5px;
1171
1173
  }
1172
1174
  [data-blok-component="callout"] button:has(+ [data-blok-toggle-children] > :first-child h2) {
1173
- margin-top: 3.6px;
1175
+ margin-top: 4.6px;
1174
1176
  }
1175
1177
  [data-blok-component="callout"] button:has(+ [data-blok-toggle-children] > :first-child h3) {
1176
- margin-top: 1px;
1178
+ margin-top: 2px;
1177
1179
  }
1178
1180
 
1179
1181
  /**
@@ -26,5 +26,5 @@ export const DEFAULT_EMOJI = '💡';
26
26
 
27
27
  // CSS — Tailwind classes
28
28
  export const WRAPPER_STYLES = 'rounded-xl px-4 py-[5px] my-1 flex items-start gap-2';
29
- export const EMOJI_BUTTON_STYLES = 'text-[1.25rem] cursor-pointer bg-transparent border-0 px-0 py-[7px] flex-shrink-0 select-none';
29
+ export const EMOJI_BUTTON_STYLES = 'text-[1.5rem] leading-[1] cursor-pointer bg-transparent border-0 px-0 py-[7px] flex-shrink-0 select-none';
30
30
  export const CHILDREN_STYLES = 'flex-1 min-w-0';
@@ -63,6 +63,32 @@ const SKIN_TONE_HANDS: readonly string[] = [
63
63
  '✋', '✋🏻', '✋🏼', '✋🏽', '✋🏾', '✋🏿',
64
64
  ];
65
65
 
66
+ const SKIN_TONE_STORAGE_KEY = 'blok-emoji-skin-tone';
67
+
68
+ function loadSkinTone(): number {
69
+ try {
70
+ const raw = localStorage.getItem(SKIN_TONE_STORAGE_KEY);
71
+
72
+ if (raw === null) {
73
+ return 0;
74
+ }
75
+
76
+ const n = parseInt(raw, 10);
77
+
78
+ return n >= 0 && n <= 5 ? n : 0;
79
+ } catch {
80
+ return 0;
81
+ }
82
+ }
83
+
84
+ function saveSkinTone(index: number): void {
85
+ try {
86
+ localStorage.setItem(SKIN_TONE_STORAGE_KEY, String(index));
87
+ } catch {
88
+ // Silently ignore — storage quota or access denied
89
+ }
90
+ }
91
+
66
92
  /** Dice SVG for the random button. */
67
93
  const ICON_DICE = '<svg width="14" height="14" viewBox="0 0 14 14" fill="none"><rect x="1.5" y="1.5" width="11" height="11" rx="2" stroke="currentColor" stroke-width="1.2"/><circle cx="4.5" cy="4.5" r=".9" fill="currentColor"/><circle cx="7" cy="7" r=".9" fill="currentColor"/><circle cx="9.5" cy="9.5" r=".9" fill="currentColor"/></svg>';
68
94
 
@@ -138,6 +164,17 @@ export class EmojiPicker {
138
164
  this._filterInput.value = '';
139
165
  this._element.setAttribute('data-theme', this.resolveTheme());
140
166
 
167
+ const storedTone = loadSkinTone();
168
+
169
+ if (storedTone !== this._skinTone) {
170
+ this._skinTone = storedTone;
171
+ this._skinToneToggle.textContent = SKIN_TONE_HANDS[storedTone];
172
+
173
+ for (const [i, btn] of this._skinToneButtons.entries()) {
174
+ this.applySkinToneActiveStyle(btn, i === storedTone);
175
+ }
176
+ }
177
+
141
178
  if (this._allEmojis.length === 0) {
142
179
  this._allEmojis = await loadEmojiData();
143
180
  }
@@ -246,7 +283,6 @@ export class EmojiPicker {
246
283
  randomBtn.type = 'button';
247
284
  randomBtn.setAttribute('data-emoji-picker-random', '');
248
285
  randomBtn.setAttribute('aria-label', this.i18n.t(PICK_RANDOM_KEY));
249
- randomBtn.title = this.i18n.t(PICK_RANDOM_KEY);
250
286
  randomBtn.className = [
251
287
  'flex-shrink-0 w-[34px] h-[34px] flex items-center justify-center rounded-lg',
252
288
  'text-neutral-400 hover:bg-neutral-100 hover:text-neutral-600',
@@ -262,7 +298,6 @@ export class EmojiPicker {
262
298
  removeBtn.type = 'button';
263
299
  removeBtn.setAttribute('data-emoji-picker-remove', '');
264
300
  removeBtn.setAttribute('aria-label', this.i18n.t(REMOVE_EMOJI_KEY));
265
- removeBtn.title = this.i18n.t(REMOVE_EMOJI_KEY);
266
301
  removeBtn.className = [
267
302
  'flex-shrink-0 w-[34px] h-[34px] flex items-center justify-center rounded-lg',
268
303
  'text-neutral-400 hover:bg-neutral-100 hover:text-neutral-600',
@@ -401,6 +436,7 @@ export class EmojiPicker {
401
436
 
402
437
  private setSkinTone(index: number): void {
403
438
  this._skinTone = index;
439
+ saveSkinTone(index);
404
440
 
405
441
  // Update the hand toggle to reflect current skin tone
406
442
  this._skinToneToggle.textContent = SKIN_TONE_HANDS[index];
@@ -653,7 +689,6 @@ export class EmojiPicker {
653
689
  const btn = document.createElement('button');
654
690
  btn.type = 'button';
655
691
  btn.textContent = this.getSkinnedNative(emoji);
656
- btn.title = this.getDisplayName(emoji);
657
692
  btn.setAttribute('data-emoji-native', emoji.native);
658
693
  btn.className = [
659
694
  'aspect-square flex items-center justify-center',
@@ -360,6 +360,7 @@ export class CalloutTool implements BlockTool {
360
360
  titleKey: 'callout',
361
361
  name: TOOL_NAME,
362
362
  searchTerms: ['callout', 'note', 'info', 'warning', 'tip', 'alert'],
363
+ searchTermKeys: ['callout', 'note', 'info', 'warning', 'tip', 'alert'],
363
364
  };
364
365
  }
365
366
 
@@ -0,0 +1,97 @@
1
+ import type {
2
+ BlockTool,
3
+ BlockToolConstructorOptions,
4
+ PasteConfig,
5
+ SanitizerConfig,
6
+ ToolboxConfig,
7
+ } from '../../../types';
8
+ import type { DividerData } from './types';
9
+ import { IconDivider } from '../../components/icons';
10
+ import { twMerge } from '../../components/utils/tw';
11
+
12
+ /**
13
+ * Divider block tool — renders a thin horizontal line separator.
14
+ * Contentless void block with no editable content or settings.
15
+ */
16
+ export class DividerTool implements BlockTool {
17
+ /**
18
+ * Rendered wrapper element
19
+ */
20
+ private element: HTMLElement | null = null;
21
+
22
+ /**
23
+ * @param _options - block tool constructor options (unused for divider)
24
+ */
25
+ constructor(_options: BlockToolConstructorOptions<DividerData>) {}
26
+
27
+ /**
28
+ * Render a wrapper with a semantic <hr> element inside.
29
+ * Wrapper uses padding for spacing and minimal line-height so the
30
+ * toolbar positioning algorithm centers correctly on the divider line.
31
+ */
32
+ public render(): HTMLElement {
33
+ const wrapper = document.createElement('div');
34
+
35
+ wrapper.className = twMerge('py-3', 'leading-[1px]');
36
+
37
+ const hr = document.createElement('hr');
38
+
39
+ hr.className = twMerge('border-t', 'border-border-primary', 'border-b-0', 'border-l-0', 'border-r-0');
40
+ wrapper.appendChild(hr);
41
+ this.element = wrapper;
42
+
43
+ return wrapper;
44
+ }
45
+
46
+ /**
47
+ * Return empty data — divider has no content
48
+ */
49
+ public save(): DividerData {
50
+ return {} as DividerData;
51
+ }
52
+
53
+ /**
54
+ * Always valid — nothing to validate
55
+ */
56
+ public validate(_data: DividerData): boolean {
57
+ return true;
58
+ }
59
+
60
+ /**
61
+ * Toolbox appearance
62
+ */
63
+ public static get toolbox(): ToolboxConfig {
64
+ return {
65
+ icon: IconDivider,
66
+ titleKey: 'divider',
67
+ shortcut: '---',
68
+ searchTerms: ['hr', 'line', 'separator', 'rule', '---', 'divider', 'delimiter', 'splitter'],
69
+ searchTermKeys: ['divider', 'separator', 'delimiter', 'splitter'],
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Divider works in read-only mode
75
+ */
76
+ public static get isReadOnlySupported(): boolean {
77
+ return true;
78
+ }
79
+
80
+ /**
81
+ * Paste three-or-more hyphens to create a divider
82
+ */
83
+ public static get pasteConfig(): PasteConfig {
84
+ return {
85
+ patterns: {
86
+ divider: /^-{3,}$/,
87
+ },
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Nothing to sanitize — no HTML content
93
+ */
94
+ public static get sanitize(): SanitizerConfig {
95
+ return {};
96
+ }
97
+ }
@@ -0,0 +1,6 @@
1
+ import type { BlockToolData } from '../../../types';
2
+
3
+ /**
4
+ * Divider block data. Empty — dividers have no configurable properties.
5
+ */
6
+ export interface DividerData extends BlockToolData {}
@@ -1096,6 +1096,7 @@ export class Header implements BlockTool {
1096
1096
  name: `header-${level.number}`,
1097
1097
  data: { level: level.number },
1098
1098
  searchTerms: [`h${level.number}`, 'title', 'header', 'heading'],
1099
+ searchTermKeys: ['title', 'header', 'heading'],
1099
1100
  shortcut: '#'.repeat(level.number),
1100
1101
  }));
1101
1102
 
@@ -1114,6 +1115,7 @@ export class Header implements BlockTool {
1114
1115
  name: `toggle-header-${level.number}`,
1115
1116
  data: { level: level.number, isToggleable: true },
1116
1117
  searchTerms: ['toggle', 'heading', `h${level.number}`, 'collapsible'],
1118
+ searchTermKeys: ['toggle', 'heading', 'collapsible'],
1117
1119
  shortcut: '>' + '#'.repeat(level.number),
1118
1120
  }));
1119
1121
 
@@ -26,6 +26,8 @@ export { ListItem as List } from './list';
26
26
  export { Table } from './table';
27
27
  export { ToggleItem as Toggle } from './toggle';
28
28
  export { CalloutTool as Callout } from './callout';
29
+ export { DividerTool as Divider } from './divider';
30
+ export { Quote } from './quote';
29
31
 
30
32
  // Inline tools
31
33
  export { BoldInlineTool as Bold } from '../components/inline-tools/inline-tool-bold';
@@ -45,6 +47,8 @@ export const defaultBlockTools = {
45
47
  table: {},
46
48
  toggle: {},
47
49
  callout: {},
50
+ divider: {},
51
+ quote: {},
48
52
  } as const;
49
53
 
50
54
  export const defaultInlineTools = {
@@ -42,6 +42,7 @@ export const getToolboxConfig = (): ToolboxConfig => [
42
42
  data: { style: 'unordered' },
43
43
  name: 'bulleted-list',
44
44
  searchTerms: ['ul', 'bullet', 'unordered', 'list'],
45
+ searchTermKeys: ['bullet', 'unordered', 'list'],
45
46
  shortcut: '-',
46
47
  },
47
48
  {
@@ -51,6 +52,7 @@ export const getToolboxConfig = (): ToolboxConfig => [
51
52
  data: { style: 'ordered' },
52
53
  name: 'numbered-list',
53
54
  searchTerms: ['ol', 'ordered', 'number', 'list'],
55
+ searchTermKeys: ['ordered', 'number', 'list'],
54
56
  shortcut: '1.',
55
57
  },
56
58
  {
@@ -60,6 +62,7 @@ export const getToolboxConfig = (): ToolboxConfig => [
60
62
  data: { style: 'checklist' },
61
63
  name: 'check-list',
62
64
  searchTerms: ['checkbox', 'task', 'todo', 'check', 'list'],
65
+ searchTermKeys: ['checkbox', 'task', 'todo', 'check', 'list'],
63
66
  shortcut: '[]',
64
67
  },
65
68
  ];
@@ -415,6 +415,7 @@ export class Paragraph implements BlockTool {
415
415
  title: 'Text',
416
416
  titleKey: 'text',
417
417
  searchTerms: ['p', 'paragraph', 'plain'],
418
+ searchTermKeys: ['paragraph', 'plain'],
418
419
  };
419
420
  }
420
421
  }
@@ -0,0 +1,229 @@
1
+ import type {
2
+ API,
3
+ BlockTool,
4
+ BlockToolConstructorOptions,
5
+ BlockToolData,
6
+ PasteEvent,
7
+ ToolboxConfig,
8
+ ConversionConfig,
9
+ ToolSanitizerConfig,
10
+ PasteConfig,
11
+ } from '../../../types';
12
+ import type { MenuConfig } from '../../../types/tools/menu-config';
13
+ import { DATA_ATTR } from '../../components/constants';
14
+ import { IconQuote } from '../../components/icons';
15
+ import { stripFakeBackgroundElements } from '../../components/utils';
16
+ import { PLACEHOLDER_FOCUS_ONLY_CLASSES, setupPlaceholder } from '../../components/utils/placeholder';
17
+ import { twMerge } from '../../components/utils/tw';
18
+
19
+ export interface QuoteData extends BlockToolData {
20
+ text: string;
21
+ size: 'default' | 'large';
22
+ }
23
+
24
+ const DEFAULT_PLACEHOLDER = 'tools.quote.placeholder';
25
+
26
+ const BASE_CLASSES = [
27
+ 'border-l-[3px]',
28
+ 'border-current',
29
+ 'pl-[0.9em]',
30
+ 'pr-[0.9em]',
31
+ 'py-[0.2em]',
32
+ 'leading-[1.5]',
33
+ 'outline-hidden',
34
+ 'mt-[0.3em]',
35
+ 'mb-[0.3em]',
36
+ ];
37
+
38
+ const LARGE_CLASS = 'text-[1.2em]';
39
+
40
+ export class Quote implements BlockTool {
41
+ private api: API;
42
+ private readOnly: boolean;
43
+ private _data: QuoteData;
44
+ private _element: HTMLQuoteElement | null = null;
45
+
46
+ constructor({ data, api, readOnly }: BlockToolConstructorOptions<QuoteData>) {
47
+ this.api = api;
48
+ this.readOnly = readOnly;
49
+ this._data = {
50
+ text: data?.text ?? '',
51
+ size: data?.size ?? 'default',
52
+ };
53
+
54
+ if (!this.readOnly) {
55
+ this.onKeyUp = this.onKeyUp.bind(this);
56
+ }
57
+ }
58
+
59
+ public onKeyUp(e: KeyboardEvent): void {
60
+ if (e.code !== 'Backspace' && e.code !== 'Delete') {
61
+ return;
62
+ }
63
+
64
+ if (!this._element) {
65
+ return;
66
+ }
67
+
68
+ if (this._element.textContent === '') {
69
+ this._element.innerHTML = '';
70
+ }
71
+ }
72
+
73
+ public render(): HTMLQuoteElement {
74
+ const el = document.createElement('blockquote');
75
+
76
+ el.className = twMerge(
77
+ this.api.styles.block,
78
+ BASE_CLASSES,
79
+ PLACEHOLDER_FOCUS_ONLY_CLASSES,
80
+ this._data.size === 'large' ? LARGE_CLASS : ''
81
+ );
82
+ el.setAttribute(DATA_ATTR.tool, 'quote');
83
+ el.contentEditable = 'false';
84
+
85
+ if (this._data.text) {
86
+ el.innerHTML = this._data.text;
87
+ } else if (this.readOnly) {
88
+ el.innerHTML = '<br>';
89
+ }
90
+
91
+ if (!this.readOnly) {
92
+ el.contentEditable = 'true';
93
+ el.addEventListener('keyup', this.onKeyUp);
94
+ setupPlaceholder(el, this.api.i18n.t(DEFAULT_PLACEHOLDER), 'data-blok-placeholder-active');
95
+ }
96
+
97
+ this._element = el;
98
+
99
+ return el;
100
+ }
101
+
102
+ public save(blockContent: HTMLQuoteElement): QuoteData {
103
+ return {
104
+ text: stripFakeBackgroundElements(blockContent.innerHTML),
105
+ size: this._data.size,
106
+ };
107
+ }
108
+
109
+ public validate(savedData: QuoteData): boolean {
110
+ return savedData.text.trim() !== '';
111
+ }
112
+
113
+ public merge(data: QuoteData): void {
114
+ if (!this._element) {
115
+ return;
116
+ }
117
+
118
+ this._data.text += data.text;
119
+
120
+ const wrapper = document.createElement('div');
121
+ wrapper.innerHTML = data.text.trim();
122
+ const fragment = document.createDocumentFragment();
123
+ fragment.append(...Array.from(wrapper.childNodes));
124
+
125
+ this._element.appendChild(fragment);
126
+ this._element.normalize();
127
+ }
128
+
129
+ public renderSettings(): MenuConfig {
130
+ return [
131
+ {
132
+ icon: IconQuote,
133
+ title: this.api.i18n.t('tools.quote.size'),
134
+ name: 'quote-size',
135
+ children: {
136
+ items: [
137
+ {
138
+ icon: IconQuote,
139
+ title: this.api.i18n.t('tools.quote.defaultSize'),
140
+ onActivate: (): void => this.setSize('default'),
141
+ closeOnActivate: true,
142
+ isActive: this._data.size === 'default',
143
+ },
144
+ {
145
+ icon: IconQuote,
146
+ title: this.api.i18n.t('tools.quote.largeSize'),
147
+ onActivate: (): void => this.setSize('large'),
148
+ closeOnActivate: true,
149
+ isActive: this._data.size === 'large',
150
+ },
151
+ ],
152
+ },
153
+ },
154
+ ];
155
+ }
156
+
157
+ private setSize(size: 'default' | 'large'): void {
158
+ this._data.size = size;
159
+
160
+ if (this._element) {
161
+ this._element.className = twMerge(
162
+ this.api.styles.block,
163
+ BASE_CLASSES,
164
+ PLACEHOLDER_FOCUS_ONLY_CLASSES,
165
+ size === 'large' ? LARGE_CLASS : ''
166
+ );
167
+ }
168
+ }
169
+
170
+ public onPaste(event: PasteEvent): void {
171
+ const detail = event.detail;
172
+
173
+ if (!('data' in detail)) {
174
+ return;
175
+ }
176
+
177
+ const content = detail.data as HTMLElement;
178
+
179
+ this._data = {
180
+ text: content.innerHTML,
181
+ size: this._data.size,
182
+ };
183
+
184
+ if (this._element) {
185
+ this._element.innerHTML = this._data.text || '';
186
+ }
187
+ }
188
+
189
+ public static get toolbox(): ToolboxConfig {
190
+ return {
191
+ icon: IconQuote,
192
+ title: 'Quote',
193
+ titleKey: 'quote',
194
+ searchTerms: ['quote', 'blockquote', 'citation'],
195
+ searchTermKeys: ['quote', 'blockquote', 'citation'],
196
+ };
197
+ }
198
+
199
+ public static get conversionConfig(): ConversionConfig {
200
+ return {
201
+ export: 'text',
202
+ import: 'text',
203
+ };
204
+ }
205
+
206
+ public static get sanitize(): ToolSanitizerConfig {
207
+ return {
208
+ text: {
209
+ br: true,
210
+ b: true,
211
+ i: true,
212
+ a: true,
213
+ mark: {
214
+ style: true,
215
+ },
216
+ },
217
+ };
218
+ }
219
+
220
+ public static get isReadOnlySupported(): boolean {
221
+ return true;
222
+ }
223
+
224
+ public static get pasteConfig(): PasteConfig {
225
+ return {
226
+ tags: ['BLOCKQUOTE'],
227
+ };
228
+ }
229
+ }
@@ -220,6 +220,7 @@ export class Table implements BlockTool {
220
220
  title: 'Table',
221
221
  titleKey: 'tools.table.title',
222
222
  searchTerms: ['table', 'grid', 'spreadsheet'],
223
+ searchTermKeys: ['table', 'grid', 'spreadsheet'],
223
224
  };
224
225
  }
225
226
 
@@ -374,6 +374,7 @@ export class ToggleItem implements BlockTool {
374
374
  titleKey: 'toggleList',
375
375
  name: TOOL_NAME,
376
376
  searchTerms: ['toggle', 'collapse', 'expand', 'accordion'],
377
+ searchTermKeys: ['toggle', 'collapse', 'expand', 'accordion'],
377
378
  shortcut: '>',
378
379
  };
379
380
  }
@@ -0,0 +1,11 @@
1
+ import { BlockTool, BlockToolConstructable, BlockToolConstructorOptions, BlockToolData } from './block-tool';
2
+
3
+ /**
4
+ * Divider Tool's input and output data format.
5
+ * Empty — dividers have no configurable properties.
6
+ */
7
+ export interface DividerData extends BlockToolData {}
8
+
9
+ export interface DividerConstructable extends BlockToolConstructable {
10
+ new(options: BlockToolConstructorOptions<DividerData>): BlockTool;
11
+ }
@@ -53,6 +53,13 @@ export interface ToolboxConfigEntry {
53
53
  */
54
54
  searchTerms?: string[];
55
55
 
56
+ /**
57
+ * Translation keys for localized search aliases (e.g., ['divider', 'separator']).
58
+ * Each key is resolved via `searchTerms.{key}` in the i18n system.
59
+ * The translated strings are added to searchTerms at runtime for multilingual search.
60
+ */
61
+ searchTermKeys?: string[];
62
+
56
63
  /**
57
64
  * Shortcut hint to display in the toolbox (e.g., '#', '##', '-', '1.', '[]').
58
65
  * This is displayed as a secondary label next to the tool title.