@jackuait/blok 0.4.1-beta.0 → 0.4.1-beta.11

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 (402) hide show
  1. package/README.md +138 -17
  2. package/codemod/README.md +45 -7
  3. package/codemod/migrate-editorjs-to-blok.js +960 -92
  4. package/codemod/test.js +780 -77
  5. package/dist/blok.mjs +5 -2
  6. package/dist/chunks/blok-oNSQ3HA6.mjs +13217 -0
  7. package/dist/chunks/i18next-CugVlwWp.mjs +1292 -0
  8. package/dist/chunks/i18next-loader-BdNRw4n4.mjs +43 -0
  9. package/dist/{index-OwEtDFlk.mjs → chunks/index-DHgXmfki.mjs} +2 -2
  10. package/dist/chunks/inline-tool-convert-CRqgjRim.mjs +1989 -0
  11. package/dist/chunks/messages-0tDXLuyH.mjs +48 -0
  12. package/dist/chunks/messages-2_xedlYw.mjs +48 -0
  13. package/dist/chunks/messages-AHESHJm_.mjs +48 -0
  14. package/dist/chunks/messages-B5hdXZwA.mjs +48 -0
  15. package/dist/chunks/messages-B5jGUnOy.mjs +48 -0
  16. package/dist/chunks/messages-B5puUm7R.mjs +48 -0
  17. package/dist/chunks/messages-B66ZSDCJ.mjs +48 -0
  18. package/dist/chunks/messages-B9Oba7sq.mjs +48 -0
  19. package/dist/chunks/messages-BA0rcTCY.mjs +48 -0
  20. package/dist/chunks/messages-BBJgd5jG.mjs +48 -0
  21. package/dist/chunks/messages-BPqWKx5Z.mjs +48 -0
  22. package/dist/chunks/messages-Bdv-IkfG.mjs +48 -0
  23. package/dist/chunks/messages-BeUhMpsr.mjs +48 -0
  24. package/dist/chunks/messages-Bf6Y3_GI.mjs +48 -0
  25. package/dist/chunks/messages-BiExzWJv.mjs +48 -0
  26. package/dist/chunks/messages-BlpqL8vG.mjs +48 -0
  27. package/dist/chunks/messages-BmKCChWZ.mjs +48 -0
  28. package/dist/chunks/messages-Bn253WWC.mjs +48 -0
  29. package/dist/chunks/messages-BrJHUxQL.mjs +48 -0
  30. package/dist/chunks/messages-C5b7hr_E.mjs +48 -0
  31. package/dist/chunks/messages-C7I_AVH2.mjs +48 -0
  32. package/dist/chunks/messages-CJoBtXU6.mjs +48 -0
  33. package/dist/chunks/messages-CQj2JU2j.mjs +48 -0
  34. package/dist/chunks/messages-CUZ1x1QD.mjs +48 -0
  35. package/dist/chunks/messages-CUy1vn-b.mjs +48 -0
  36. package/dist/chunks/messages-CVeWVKsV.mjs +48 -0
  37. package/dist/chunks/messages-CXHd9SUK.mjs +48 -0
  38. package/dist/chunks/messages-CbMyJSzS.mjs +48 -0
  39. package/dist/chunks/messages-CbhuIWRJ.mjs +48 -0
  40. package/dist/chunks/messages-CeCjVKMW.mjs +48 -0
  41. package/dist/chunks/messages-Cj-t1bdy.mjs +48 -0
  42. package/dist/chunks/messages-CkFT2gle.mjs +48 -0
  43. package/dist/chunks/messages-Cm9aLHeX.mjs +48 -0
  44. package/dist/chunks/messages-CnvW8Slp.mjs +48 -0
  45. package/dist/chunks/messages-Cr-RJ7YB.mjs +48 -0
  46. package/dist/chunks/messages-CrsJ1TEJ.mjs +48 -0
  47. package/dist/chunks/messages-Cu08aLS3.mjs +48 -0
  48. package/dist/chunks/messages-CvaqJFN-.mjs +48 -0
  49. package/dist/chunks/messages-CyDU5lz9.mjs +48 -0
  50. package/dist/chunks/messages-CySyfkMU.mjs +48 -0
  51. package/dist/chunks/messages-Cyi2AMmz.mjs +48 -0
  52. package/dist/chunks/messages-D00OjS2n.mjs +48 -0
  53. package/dist/chunks/messages-DDLgIPDF.mjs +48 -0
  54. package/dist/chunks/messages-DMQIHGRj.mjs +48 -0
  55. package/dist/chunks/messages-DOlC_Tty.mjs +48 -0
  56. package/dist/chunks/messages-DV6shA9b.mjs +48 -0
  57. package/dist/chunks/messages-DY94ykcE.mjs +48 -0
  58. package/dist/chunks/messages-DbVquYKN.mjs +48 -0
  59. package/dist/chunks/messages-DcKOuncK.mjs +48 -0
  60. package/dist/chunks/messages-Dg92dXZ5.mjs +48 -0
  61. package/dist/chunks/messages-DnbbyJT3.mjs +48 -0
  62. package/dist/chunks/messages-DteYq0rv.mjs +48 -0
  63. package/dist/chunks/messages-GC2PhgV3.mjs +48 -0
  64. package/dist/chunks/messages-JGsXAReJ.mjs +48 -0
  65. package/dist/chunks/messages-JZUhXTuV.mjs +48 -0
  66. package/dist/chunks/messages-LvFKBBPa.mjs +48 -0
  67. package/dist/chunks/messages-NP1myMGI.mjs +48 -0
  68. package/dist/chunks/messages-Q4kc_ZtL.mjs +48 -0
  69. package/dist/chunks/messages-RvMHb2Ht.mjs +48 -0
  70. package/dist/chunks/messages-ftMcCEuO.mjs +48 -0
  71. package/dist/chunks/messages-o24dK6CU.mjs +48 -0
  72. package/dist/chunks/messages-pA5TvcAj.mjs +48 -0
  73. package/dist/chunks/messages-rRSHQDCX.mjs +48 -0
  74. package/dist/chunks/messages-srxrv8Yh.mjs +48 -0
  75. package/dist/chunks/messages-wdqp4610.mjs +48 -0
  76. package/dist/chunks/messages-zS1AXZ0y.mjs +48 -0
  77. package/dist/chunks/messages-zSzDzXej.mjs +48 -0
  78. package/dist/full.mjs +50 -0
  79. package/dist/locales.mjs +228 -0
  80. package/dist/messages-0tDXLuyH.mjs +48 -0
  81. package/dist/messages-2_xedlYw.mjs +48 -0
  82. package/dist/messages-AHESHJm_.mjs +48 -0
  83. package/dist/messages-B5hdXZwA.mjs +48 -0
  84. package/dist/messages-B5jGUnOy.mjs +48 -0
  85. package/dist/messages-B5puUm7R.mjs +48 -0
  86. package/dist/messages-B66ZSDCJ.mjs +48 -0
  87. package/dist/messages-B9Oba7sq.mjs +48 -0
  88. package/dist/messages-BA0rcTCY.mjs +48 -0
  89. package/dist/messages-BBJgd5jG.mjs +48 -0
  90. package/dist/messages-BPqWKx5Z.mjs +48 -0
  91. package/dist/messages-Bdv-IkfG.mjs +48 -0
  92. package/dist/messages-BeUhMpsr.mjs +48 -0
  93. package/dist/messages-Bf6Y3_GI.mjs +48 -0
  94. package/dist/messages-BiExzWJv.mjs +48 -0
  95. package/dist/messages-BlpqL8vG.mjs +48 -0
  96. package/dist/messages-BmKCChWZ.mjs +48 -0
  97. package/dist/messages-Bn253WWC.mjs +48 -0
  98. package/dist/messages-BrJHUxQL.mjs +48 -0
  99. package/dist/messages-C5b7hr_E.mjs +48 -0
  100. package/dist/messages-C7I_AVH2.mjs +48 -0
  101. package/dist/messages-CJoBtXU6.mjs +48 -0
  102. package/dist/messages-CQj2JU2j.mjs +48 -0
  103. package/dist/messages-CUZ1x1QD.mjs +48 -0
  104. package/dist/messages-CUy1vn-b.mjs +48 -0
  105. package/dist/messages-CVeWVKsV.mjs +48 -0
  106. package/dist/messages-CXHd9SUK.mjs +48 -0
  107. package/dist/messages-CbMyJSzS.mjs +48 -0
  108. package/dist/messages-CbhuIWRJ.mjs +48 -0
  109. package/dist/messages-CeCjVKMW.mjs +48 -0
  110. package/dist/messages-Cj-t1bdy.mjs +48 -0
  111. package/dist/messages-CkFT2gle.mjs +48 -0
  112. package/dist/messages-Cm9aLHeX.mjs +48 -0
  113. package/dist/messages-CnvW8Slp.mjs +48 -0
  114. package/dist/messages-Cr-RJ7YB.mjs +48 -0
  115. package/dist/messages-CrsJ1TEJ.mjs +48 -0
  116. package/dist/messages-Cu08aLS3.mjs +48 -0
  117. package/dist/messages-CvaqJFN-.mjs +48 -0
  118. package/dist/messages-CyDU5lz9.mjs +48 -0
  119. package/dist/messages-CySyfkMU.mjs +48 -0
  120. package/dist/messages-Cyi2AMmz.mjs +48 -0
  121. package/dist/messages-D00OjS2n.mjs +48 -0
  122. package/dist/messages-DDLgIPDF.mjs +48 -0
  123. package/dist/messages-DMQIHGRj.mjs +48 -0
  124. package/dist/messages-DOlC_Tty.mjs +48 -0
  125. package/dist/messages-DV6shA9b.mjs +48 -0
  126. package/dist/messages-DY94ykcE.mjs +48 -0
  127. package/dist/messages-DbVquYKN.mjs +48 -0
  128. package/dist/messages-DcKOuncK.mjs +48 -0
  129. package/dist/messages-Dg92dXZ5.mjs +48 -0
  130. package/dist/messages-DnbbyJT3.mjs +48 -0
  131. package/dist/messages-DteYq0rv.mjs +48 -0
  132. package/dist/messages-GC2PhgV3.mjs +48 -0
  133. package/dist/messages-JGsXAReJ.mjs +48 -0
  134. package/dist/messages-JZUhXTuV.mjs +48 -0
  135. package/dist/messages-LvFKBBPa.mjs +48 -0
  136. package/dist/messages-NP1myMGI.mjs +48 -0
  137. package/dist/messages-Q4kc_ZtL.mjs +48 -0
  138. package/dist/messages-RvMHb2Ht.mjs +48 -0
  139. package/dist/messages-ftMcCEuO.mjs +48 -0
  140. package/dist/messages-o24dK6CU.mjs +48 -0
  141. package/dist/messages-pA5TvcAj.mjs +48 -0
  142. package/dist/messages-rRSHQDCX.mjs +48 -0
  143. package/dist/messages-srxrv8Yh.mjs +48 -0
  144. package/dist/messages-wdqp4610.mjs +48 -0
  145. package/dist/messages-zS1AXZ0y.mjs +48 -0
  146. package/dist/messages-zSzDzXej.mjs +48 -0
  147. package/dist/tools.mjs +3117 -0
  148. package/dist/vendor.LICENSE.txt +26 -225
  149. package/package.json +63 -24
  150. package/src/blok.ts +267 -0
  151. package/src/components/__module.ts +139 -0
  152. package/src/components/block/api.ts +155 -0
  153. package/src/components/block/index.ts +1428 -0
  154. package/src/components/block-tunes/block-tune-delete.ts +51 -0
  155. package/src/components/blocks.ts +352 -0
  156. package/src/components/constants/data-attributes.ts +344 -0
  157. package/src/components/constants.ts +76 -0
  158. package/src/components/core.ts +392 -0
  159. package/src/components/dom.ts +773 -0
  160. package/src/components/domIterator.ts +189 -0
  161. package/src/components/errors/critical.ts +5 -0
  162. package/src/components/events/BlockChanged.ts +16 -0
  163. package/src/components/events/BlockHovered.ts +21 -0
  164. package/src/components/events/BlockSettingsClosed.ts +12 -0
  165. package/src/components/events/BlockSettingsOpened.ts +12 -0
  166. package/src/components/events/BlokMobileLayoutToggled.ts +15 -0
  167. package/src/components/events/FakeCursorAboutToBeToggled.ts +17 -0
  168. package/src/components/events/FakeCursorHaveBeenSet.ts +17 -0
  169. package/src/components/events/HistoryStateChanged.ts +19 -0
  170. package/src/components/events/RedactorDomChanged.ts +14 -0
  171. package/src/components/events/index.ts +46 -0
  172. package/src/components/flipper.ts +497 -0
  173. package/src/components/i18n/i18next-loader.ts +84 -0
  174. package/src/components/i18n/lightweight-i18n.ts +86 -0
  175. package/src/components/i18n/locales/TRANSLATION_GUIDELINES.md +113 -0
  176. package/src/components/i18n/locales/am/messages.json +45 -0
  177. package/src/components/i18n/locales/ar/messages.json +45 -0
  178. package/src/components/i18n/locales/az/messages.json +45 -0
  179. package/src/components/i18n/locales/bg/messages.json +45 -0
  180. package/src/components/i18n/locales/bn/messages.json +45 -0
  181. package/src/components/i18n/locales/bs/messages.json +45 -0
  182. package/src/components/i18n/locales/cs/messages.json +45 -0
  183. package/src/components/i18n/locales/da/messages.json +45 -0
  184. package/src/components/i18n/locales/de/messages.json +45 -0
  185. package/src/components/i18n/locales/dv/messages.json +45 -0
  186. package/src/components/i18n/locales/el/messages.json +45 -0
  187. package/src/components/i18n/locales/en/messages.json +45 -0
  188. package/src/components/i18n/locales/es/messages.json +45 -0
  189. package/src/components/i18n/locales/et/messages.json +45 -0
  190. package/src/components/i18n/locales/fa/messages.json +45 -0
  191. package/src/components/i18n/locales/fi/messages.json +45 -0
  192. package/src/components/i18n/locales/fil/messages.json +45 -0
  193. package/src/components/i18n/locales/fr/messages.json +45 -0
  194. package/src/components/i18n/locales/gu/messages.json +45 -0
  195. package/src/components/i18n/locales/he/messages.json +45 -0
  196. package/src/components/i18n/locales/hi/messages.json +45 -0
  197. package/src/components/i18n/locales/hr/messages.json +45 -0
  198. package/src/components/i18n/locales/hu/messages.json +45 -0
  199. package/src/components/i18n/locales/hy/messages.json +45 -0
  200. package/src/components/i18n/locales/id/messages.json +45 -0
  201. package/src/components/i18n/locales/index.ts +231 -0
  202. package/src/components/i18n/locales/it/messages.json +45 -0
  203. package/src/components/i18n/locales/ja/messages.json +45 -0
  204. package/src/components/i18n/locales/ka/messages.json +45 -0
  205. package/src/components/i18n/locales/km/messages.json +45 -0
  206. package/src/components/i18n/locales/kn/messages.json +45 -0
  207. package/src/components/i18n/locales/ko/messages.json +45 -0
  208. package/src/components/i18n/locales/ku/messages.json +45 -0
  209. package/src/components/i18n/locales/lo/messages.json +45 -0
  210. package/src/components/i18n/locales/lt/messages.json +45 -0
  211. package/src/components/i18n/locales/lv/messages.json +45 -0
  212. package/src/components/i18n/locales/mk/messages.json +45 -0
  213. package/src/components/i18n/locales/ml/messages.json +45 -0
  214. package/src/components/i18n/locales/mn/messages.json +45 -0
  215. package/src/components/i18n/locales/mr/messages.json +45 -0
  216. package/src/components/i18n/locales/ms/messages.json +45 -0
  217. package/src/components/i18n/locales/my/messages.json +45 -0
  218. package/src/components/i18n/locales/ne/messages.json +45 -0
  219. package/src/components/i18n/locales/nl/messages.json +45 -0
  220. package/src/components/i18n/locales/no/messages.json +45 -0
  221. package/src/components/i18n/locales/pa/messages.json +45 -0
  222. package/src/components/i18n/locales/pl/messages.json +45 -0
  223. package/src/components/i18n/locales/ps/messages.json +45 -0
  224. package/src/components/i18n/locales/pt/messages.json +45 -0
  225. package/src/components/i18n/locales/ro/messages.json +45 -0
  226. package/src/components/i18n/locales/ru/messages.json +45 -0
  227. package/src/components/i18n/locales/sd/messages.json +45 -0
  228. package/src/components/i18n/locales/si/messages.json +45 -0
  229. package/src/components/i18n/locales/sk/messages.json +45 -0
  230. package/src/components/i18n/locales/sl/messages.json +45 -0
  231. package/src/components/i18n/locales/sq/messages.json +45 -0
  232. package/src/components/i18n/locales/sr/messages.json +45 -0
  233. package/src/components/i18n/locales/sv/messages.json +45 -0
  234. package/src/components/i18n/locales/sw/messages.json +45 -0
  235. package/src/components/i18n/locales/ta/messages.json +45 -0
  236. package/src/components/i18n/locales/te/messages.json +45 -0
  237. package/src/components/i18n/locales/th/messages.json +45 -0
  238. package/src/components/i18n/locales/tr/messages.json +45 -0
  239. package/src/components/i18n/locales/ug/messages.json +45 -0
  240. package/src/components/i18n/locales/uk/messages.json +45 -0
  241. package/src/components/i18n/locales/ur/messages.json +45 -0
  242. package/src/components/i18n/locales/vi/messages.json +45 -0
  243. package/src/components/i18n/locales/yi/messages.json +45 -0
  244. package/src/components/i18n/locales/zh/messages.json +45 -0
  245. package/src/components/icons/index.ts +242 -0
  246. package/src/components/inline-tools/inline-tool-bold.ts +2213 -0
  247. package/src/components/inline-tools/inline-tool-convert.ts +141 -0
  248. package/src/components/inline-tools/inline-tool-italic.ts +500 -0
  249. package/src/components/inline-tools/inline-tool-link.ts +539 -0
  250. package/src/components/modules/api/blocks.ts +377 -0
  251. package/src/components/modules/api/caret.ts +125 -0
  252. package/src/components/modules/api/events.ts +51 -0
  253. package/src/components/modules/api/history.ts +73 -0
  254. package/src/components/modules/api/i18n.ts +35 -0
  255. package/src/components/modules/api/index.ts +39 -0
  256. package/src/components/modules/api/inlineToolbar.ts +33 -0
  257. package/src/components/modules/api/listeners.ts +56 -0
  258. package/src/components/modules/api/notifier.ts +46 -0
  259. package/src/components/modules/api/readonly.ts +39 -0
  260. package/src/components/modules/api/sanitizer.ts +30 -0
  261. package/src/components/modules/api/saver.ts +52 -0
  262. package/src/components/modules/api/selection.ts +48 -0
  263. package/src/components/modules/api/styles.ts +72 -0
  264. package/src/components/modules/api/toolbar.ts +79 -0
  265. package/src/components/modules/api/tools.ts +16 -0
  266. package/src/components/modules/api/tooltip.ts +67 -0
  267. package/src/components/modules/api/ui.ts +36 -0
  268. package/src/components/modules/blockEvents.ts +1591 -0
  269. package/src/components/modules/blockManager.ts +1356 -0
  270. package/src/components/modules/blockSelection.ts +708 -0
  271. package/src/components/modules/caret.ts +853 -0
  272. package/src/components/modules/crossBlockSelection.ts +329 -0
  273. package/src/components/modules/dragManager.ts +1204 -0
  274. package/src/components/modules/history.ts +1098 -0
  275. package/src/components/modules/i18n.ts +332 -0
  276. package/src/components/modules/index.ts +139 -0
  277. package/src/components/modules/modificationsObserver.ts +147 -0
  278. package/src/components/modules/paste.ts +1092 -0
  279. package/src/components/modules/readonly.ts +136 -0
  280. package/src/components/modules/rectangleSelection.ts +711 -0
  281. package/src/components/modules/renderer.ts +155 -0
  282. package/src/components/modules/saver.ts +283 -0
  283. package/src/components/modules/toolbar/blockSettings.ts +781 -0
  284. package/src/components/modules/toolbar/index.ts +1315 -0
  285. package/src/components/modules/toolbar/inline.ts +956 -0
  286. package/src/components/modules/tools.ts +625 -0
  287. package/src/components/modules/ui.ts +1283 -0
  288. package/src/components/polyfills.ts +113 -0
  289. package/src/components/selection.ts +1179 -0
  290. package/src/components/tools/base.ts +301 -0
  291. package/src/components/tools/block.ts +339 -0
  292. package/src/components/tools/collection.ts +67 -0
  293. package/src/components/tools/factory.ts +138 -0
  294. package/src/components/tools/inline.ts +71 -0
  295. package/src/components/tools/tune.ts +33 -0
  296. package/src/components/ui/toolbox.ts +601 -0
  297. package/src/components/utils/announcer.ts +205 -0
  298. package/src/components/utils/api.ts +20 -0
  299. package/src/components/utils/bem.ts +26 -0
  300. package/src/components/utils/blocks.ts +284 -0
  301. package/src/components/utils/caret.ts +1067 -0
  302. package/src/components/utils/data-model-transform.ts +382 -0
  303. package/src/components/utils/events.ts +117 -0
  304. package/src/components/utils/keyboard.ts +60 -0
  305. package/src/components/utils/listeners.ts +296 -0
  306. package/src/components/utils/mutations.ts +39 -0
  307. package/src/components/utils/notifier/draw.ts +190 -0
  308. package/src/components/utils/notifier/index.ts +66 -0
  309. package/src/components/utils/notifier/types.ts +1 -0
  310. package/src/components/utils/notifier.ts +77 -0
  311. package/src/components/utils/placeholder.ts +140 -0
  312. package/src/components/utils/popover/components/hint/hint.const.ts +10 -0
  313. package/src/components/utils/popover/components/hint/hint.ts +46 -0
  314. package/src/components/utils/popover/components/hint/index.ts +6 -0
  315. package/src/components/utils/popover/components/popover-header/index.ts +2 -0
  316. package/src/components/utils/popover/components/popover-header/popover-header.const.ts +8 -0
  317. package/src/components/utils/popover/components/popover-header/popover-header.ts +80 -0
  318. package/src/components/utils/popover/components/popover-header/popover-header.types.ts +14 -0
  319. package/src/components/utils/popover/components/popover-item/index.ts +13 -0
  320. package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.const.ts +50 -0
  321. package/src/components/utils/popover/components/popover-item/popover-item-default/popover-item-default.ts +680 -0
  322. package/src/components/utils/popover/components/popover-item/popover-item-html/popover-item-html.const.ts +14 -0
  323. package/src/components/utils/popover/components/popover-item/popover-item-html/popover-item-html.ts +136 -0
  324. package/src/components/utils/popover/components/popover-item/popover-item-separator/popover-item-separator.const.ts +20 -0
  325. package/src/components/utils/popover/components/popover-item/popover-item-separator/popover-item-separator.ts +117 -0
  326. package/src/components/utils/popover/components/popover-item/popover-item.ts +186 -0
  327. package/src/components/utils/popover/components/search-input/index.ts +2 -0
  328. package/src/components/utils/popover/components/search-input/search-input.const.ts +8 -0
  329. package/src/components/utils/popover/components/search-input/search-input.ts +178 -0
  330. package/src/components/utils/popover/components/search-input/search-input.types.ts +59 -0
  331. package/src/components/utils/popover/index.ts +13 -0
  332. package/src/components/utils/popover/popover-abstract.ts +457 -0
  333. package/src/components/utils/popover/popover-desktop.ts +676 -0
  334. package/src/components/utils/popover/popover-inline.ts +338 -0
  335. package/src/components/utils/popover/popover-mobile.ts +201 -0
  336. package/src/components/utils/popover/popover.const.ts +81 -0
  337. package/src/components/utils/popover/utils/popover-states-history.ts +72 -0
  338. package/src/components/utils/promise-queue.ts +43 -0
  339. package/src/components/utils/sanitizer.ts +537 -0
  340. package/src/components/utils/scroll-locker.ts +87 -0
  341. package/src/components/utils/shortcut.ts +231 -0
  342. package/src/components/utils/shortcuts.ts +113 -0
  343. package/src/components/utils/tools.ts +110 -0
  344. package/src/components/utils/tooltip.ts +591 -0
  345. package/src/components/utils/tw.ts +241 -0
  346. package/src/components/utils.ts +1081 -0
  347. package/src/env.d.ts +13 -0
  348. package/src/full.ts +69 -0
  349. package/src/locales.ts +51 -0
  350. package/src/stories/Block.stories.ts +498 -0
  351. package/src/stories/EditorModes.stories.ts +505 -0
  352. package/src/stories/Header.stories.ts +137 -0
  353. package/src/stories/InlineToolbar.stories.ts +498 -0
  354. package/src/stories/List.stories.ts +259 -0
  355. package/src/stories/Notifier.stories.ts +340 -0
  356. package/src/stories/Paragraph.stories.ts +112 -0
  357. package/src/stories/Placeholder.stories.ts +319 -0
  358. package/src/stories/Popover.stories.ts +844 -0
  359. package/src/stories/Selection.stories.ts +250 -0
  360. package/src/stories/StubBlock.stories.ts +156 -0
  361. package/src/stories/Toolbar.stories.ts +223 -0
  362. package/src/stories/Toolbox.stories.ts +166 -0
  363. package/src/stories/Tooltip.stories.ts +198 -0
  364. package/src/stories/helpers.ts +463 -0
  365. package/src/styles/main.css +123 -0
  366. package/src/tools/header/index.ts +646 -0
  367. package/src/tools/index.ts +45 -0
  368. package/src/tools/list/index.ts +1819 -0
  369. package/src/tools/paragraph/index.ts +412 -0
  370. package/src/tools/stub/index.ts +107 -0
  371. package/src/types-internal/blok-modules.d.ts +87 -0
  372. package/src/types-internal/html-janitor.d.ts +28 -0
  373. package/src/types-internal/module-config.d.ts +11 -0
  374. package/src/variants/all-locales.ts +155 -0
  375. package/src/variants/blok-maximum.ts +20 -0
  376. package/src/variants/blok-minimum.ts +243 -0
  377. package/types/api/blocks.d.ts +9 -1
  378. package/types/api/history.d.ts +7 -0
  379. package/types/api/i18n.d.ts +22 -3
  380. package/types/api/selection.d.ts +6 -0
  381. package/types/api/styles.d.ts +23 -10
  382. package/types/configs/blok-config.d.ts +29 -0
  383. package/types/configs/i18n-config.d.ts +52 -2
  384. package/types/configs/i18n-dictionary.d.ts +16 -90
  385. package/types/data-attributes.d.ts +170 -0
  386. package/types/data-formats/output-data.d.ts +15 -0
  387. package/types/full.d.ts +80 -0
  388. package/types/index.d.ts +30 -13
  389. package/types/locales.d.ts +59 -0
  390. package/types/tools/adapters/inline-tool-adapter.d.ts +10 -0
  391. package/types/tools/block-tool.d.ts +9 -0
  392. package/types/tools/header.d.ts +18 -0
  393. package/types/tools/index.d.ts +1 -0
  394. package/types/tools/list.d.ts +91 -0
  395. package/types/tools/paragraph.d.ts +71 -0
  396. package/types/tools/tool-settings.d.ts +92 -6
  397. package/types/tools/tool.d.ts +6 -0
  398. package/types/tools-entry.d.ts +49 -0
  399. package/types/utils/popover/popover-item.d.ts +18 -5
  400. package/types/utils/popover/popover.d.ts +7 -0
  401. package/dist/blok-D_baBvTG.mjs +0 -25795
  402. package/dist/blok.umd.js +0 -181
@@ -0,0 +1,781 @@
1
+ import { Module } from '../../__module';
2
+ import { Dom as $ } from '../../dom';
3
+ import { SelectionUtils } from '../../selection';
4
+ import type { Block } from '../../block';
5
+ import { Flipper } from '../../flipper';
6
+ import type { MenuConfigItem } from '../../../../types/tools';
7
+ import type { PopoverItemParams } from '../../utils/popover';
8
+ import { type Popover, PopoverDesktop, PopoverMobile, PopoverItemType } from '../../utils/popover';
9
+ import type { PopoverParams } from '@/types/utils/popover/popover';
10
+ import { PopoverEvent } from '@/types/utils/popover/popover-event';
11
+ import { isMobileScreen, keyCodes } from '../../utils';
12
+ import { css as popoverItemCls } from '../../utils/popover/components/popover-item';
13
+ import { BlockSettingsClosed, BlockSettingsOpened, BlokMobileLayoutToggled } from '../../events';
14
+ import { IconReplace, IconCross } from '../../icons';
15
+ import { getConvertibleToolsForBlock, getConvertibleToolsForBlocks } from '../../utils/blocks';
16
+ import { translateToolTitle } from '../../utils/tools';
17
+ import { BlockAPI } from '../../block/api';
18
+ import type { BlockToolAdapter } from '../../tools/block';
19
+
20
+ /**
21
+ * HTML Elements that used for BlockSettings
22
+ */
23
+ interface BlockSettingsNodes {
24
+ /**
25
+ * Block Settings wrapper. Undefined when before "make" method called
26
+ */
27
+ wrapper: HTMLElement | undefined;
28
+ }
29
+
30
+ /**
31
+ * Block Settings
32
+ * @todo Make Block Settings no-module but a standalone class, like Toolbox
33
+ */
34
+ export class BlockSettings extends Module<BlockSettingsNodes> {
35
+ /**
36
+ * Module Events
37
+ */
38
+ public get events(): { opened: typeof BlockSettingsOpened; closed: typeof BlockSettingsClosed } {
39
+ return {
40
+ opened: BlockSettingsOpened,
41
+ closed: BlockSettingsClosed,
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Block Settings CSS
47
+ * @deprecated Use data attributes for identification instead
48
+ */
49
+ public get CSS(): { [name: string]: string } {
50
+ return {
51
+ settings: '',
52
+ };
53
+ }
54
+
55
+ /**
56
+ * Opened state
57
+ */
58
+ public opened = false;
59
+
60
+ /**
61
+ * Getter for inner popover's flipper instance
62
+ * @todo remove once BlockSettings becomes standalone non-module class
63
+ */
64
+ public get flipper(): Flipper {
65
+ return this.flipperInstance;
66
+ }
67
+
68
+ /**
69
+ * Page selection utils
70
+ */
71
+ private selection: SelectionUtils = new SelectionUtils();
72
+
73
+ /**
74
+ * Popover instance. There is a util for vertical lists.
75
+ * Null until popover is not initialized
76
+ */
77
+ private popover: Popover | null = null;
78
+
79
+ /**
80
+ * Shared flipper instance used for keyboard navigation in block settings popover
81
+ */
82
+ private readonly flipperInstance: Flipper = new Flipper({
83
+ focusedItemClass: popoverItemCls.focused,
84
+ allowedKeys: [
85
+ keyCodes.TAB,
86
+ keyCodes.UP,
87
+ keyCodes.DOWN,
88
+ keyCodes.ENTER,
89
+ keyCodes.RIGHT,
90
+ keyCodes.LEFT,
91
+ ],
92
+ });
93
+
94
+ /**
95
+ * Stored keydown handler reference to detach when block tunes are closed
96
+ */
97
+ private flipperKeydownHandler: ((event: KeyboardEvent) => void) | null = null;
98
+
99
+ /**
100
+ * Element that listens for keydown events while block tunes are opened
101
+ */
102
+ private flipperKeydownSource: HTMLElement | null = null;
103
+
104
+ /**
105
+ * Panel with block settings with 2 sections:
106
+ * - Tool's Settings
107
+ * - Default Settings [Move, Remove, etc]
108
+ */
109
+ public make(): void {
110
+ this.nodes.wrapper = $.make('div');
111
+ this.nodes.wrapper.setAttribute('data-blok-testid', 'block-tunes-wrapper');
112
+
113
+ this.eventsDispatcher.on(BlokMobileLayoutToggled, this.close);
114
+ }
115
+
116
+ /**
117
+ * Destroys module
118
+ */
119
+ public destroy(): void {
120
+ this.detachFlipperKeydownListener();
121
+ this.removeAllNodes();
122
+ this.listeners.destroy();
123
+ this.eventsDispatcher.off(BlokMobileLayoutToggled, this.close);
124
+ }
125
+
126
+ /**
127
+ * Open Block Settings pane
128
+ * @param targetBlock - near which Block we should open BlockSettings
129
+ * @param trigger - element to position the popover relative to
130
+ */
131
+ public async open(targetBlock?: Block, trigger?: HTMLElement): Promise<void> {
132
+ const selectedBlocks = this.Blok.BlockSelection.selectedBlocks;
133
+ const hasMultipleBlocksSelected = selectedBlocks.length > 1;
134
+
135
+ /**
136
+ * When multiple blocks are selected, use the first selected block as the anchor
137
+ * Otherwise, use the target block or current block
138
+ */
139
+ const block = hasMultipleBlocksSelected
140
+ ? selectedBlocks[0]
141
+ : (targetBlock ?? this.Blok.BlockManager.currentBlock);
142
+
143
+ if (block === undefined) {
144
+ return;
145
+ }
146
+
147
+ /**
148
+ * If block settings contains any inputs, focus will be set there,
149
+ * so we need to save current selection to restore it after block settings is closed
150
+ */
151
+ this.selection.save();
152
+
153
+ /**
154
+ * Highlight content of a Block we are working with
155
+ * For multiple blocks, they should already be selected
156
+ */
157
+ if (!hasMultipleBlocksSelected) {
158
+ this.Blok.BlockSelection.selectBlock(block);
159
+ this.Blok.BlockSelection.clearCache();
160
+ }
161
+
162
+ /** Get tool's settings data - only relevant for single block selection */
163
+ const { toolTunes, commonTunes } = block.getTunes();
164
+
165
+ const PopoverClass = isMobileScreen() ? PopoverMobile : PopoverDesktop;
166
+ const popoverParams: PopoverParams & { flipper?: Flipper } = {
167
+ searchable: true,
168
+ trigger: trigger || this.nodes.wrapper,
169
+ items: await this.getTunesItems(block, commonTunes, toolTunes),
170
+ scopeElement: this.Blok.API.methods.ui.nodes.redactor,
171
+ messages: {
172
+ nothingFound: this.Blok.I18n.t('popover.nothingFound'),
173
+ search: this.Blok.I18n.t('popover.search'),
174
+ },
175
+ };
176
+
177
+ if (PopoverClass === PopoverDesktop) {
178
+ popoverParams.flipper = this.flipperInstance;
179
+ }
180
+
181
+ this.popover = new PopoverClass(popoverParams);
182
+ this.popover.getElement().setAttribute('data-blok-testid', 'block-tunes-popover');
183
+
184
+ this.popover.on(PopoverEvent.Closed, this.onPopoverClose);
185
+
186
+ /**
187
+ * Set opened flag AFTER popover is created to prevent race conditions
188
+ * where close() is called during the async getTunesItems() call
189
+ * when opened=true but popover is still null
190
+ */
191
+ this.opened = true;
192
+
193
+ /** Tell to subscribers that block settings is opened */
194
+ this.eventsDispatcher.emit(this.events.opened);
195
+
196
+ this.popover.show();
197
+ this.attachFlipperKeydownListener(block);
198
+ }
199
+
200
+ /**
201
+ * Returns root block settings element
202
+ */
203
+ public getElement(): HTMLElement | undefined {
204
+ return this.nodes.wrapper;
205
+ }
206
+
207
+ /**
208
+ * Checks if the element is contained in the BlockSettings or its Popover
209
+ * @param element - element to check
210
+ */
211
+ public contains(element: HTMLElement): boolean {
212
+ if (this.nodes.wrapper?.contains(element)) {
213
+ return true;
214
+ }
215
+
216
+ if (this.popover?.hasNode(element)) {
217
+ return true;
218
+ }
219
+
220
+ return false;
221
+ }
222
+
223
+ /**
224
+ * Close Block Settings pane
225
+ */
226
+ public close = (): void => {
227
+ if (!this.opened) {
228
+ return;
229
+ }
230
+
231
+ this.opened = false;
232
+
233
+ /**
234
+ * If selection is at blok on Block Settings closing,
235
+ * it means that caret placed at some editable element inside the Block Settings.
236
+ * Previously we have saved the selection, then open the Block Settings and set caret to the input
237
+ *
238
+ * So, we need to restore selection back to Block after closing the Block Settings
239
+ */
240
+ if (!SelectionUtils.isAtBlok) {
241
+ this.selection.restore();
242
+ }
243
+
244
+ this.selection.clearSaved();
245
+ this.detachFlipperKeydownListener();
246
+
247
+ /**
248
+ * Remove highlighted content of Blocks we are working with
249
+ * Handle both single and multiple block selection
250
+ */
251
+ this.clearBlockSelectionOnClose();
252
+
253
+ /** Tell to subscribers that block settings is closed */
254
+ this.eventsDispatcher.emit(this.events.closed);
255
+
256
+ if (this.popover) {
257
+ this.popover.off(PopoverEvent.Closed, this.onPopoverClose);
258
+ this.popover.destroy();
259
+ this.popover.getElement().remove();
260
+ this.popover = null;
261
+ }
262
+ };
263
+
264
+ /**
265
+ * Returns list of items to be displayed in block tunes menu.
266
+ * Merges tool specific tunes, conversion menu and common tunes in one list in predefined order
267
+ * @param currentBlock – block we are about to open block tunes for
268
+ * @param commonTunes – common tunes
269
+ * @param toolTunes - tool specific tunes
270
+ */
271
+ private async getTunesItems(currentBlock: Block, commonTunes: MenuConfigItem[], toolTunes?: MenuConfigItem[]): Promise<PopoverItemParams[]> {
272
+ const items = [] as MenuConfigItem[];
273
+ const selectedBlocks = this.Blok.BlockSelection.selectedBlocks;
274
+ const hasMultipleBlocksSelected = selectedBlocks.length > 1;
275
+
276
+ /**
277
+ * Only show tool-specific tunes when a single block is selected
278
+ */
279
+ if (!hasMultipleBlocksSelected && toolTunes !== undefined && toolTunes.length > 0) {
280
+ items.push(...toolTunes);
281
+ items.push({
282
+ type: PopoverItemType.Separator,
283
+ });
284
+ }
285
+
286
+ const allBlockTools = Array.from(this.Blok.Tools.blockTools.values());
287
+
288
+ /**
289
+ * Get convertible tools based on selection:
290
+ * - For single block: use existing single-block conversion logic
291
+ * - For multiple blocks: find tools that ALL selected blocks can convert to
292
+ */
293
+ const convertibleTools = hasMultipleBlocksSelected
294
+ ? await getConvertibleToolsForBlocks(
295
+ selectedBlocks.map((block) => new BlockAPI(block)),
296
+ allBlockTools
297
+ )
298
+ : await getConvertibleToolsForBlock(currentBlock, allBlockTools);
299
+
300
+ const convertToItems = convertibleTools.reduce<PopoverItemParams[]>((result, tool) => {
301
+ if (tool.toolbox === undefined) {
302
+ return result;
303
+ }
304
+
305
+ tool.toolbox.forEach((toolboxItem) => {
306
+ result.push({
307
+ icon: toolboxItem.icon,
308
+ title: translateToolTitle(this.Blok.I18n, toolboxItem, tool.name),
309
+ name: toolboxItem.name ?? tool.name,
310
+ closeOnActivate: true,
311
+ onActivate: async () => {
312
+ const { Caret, Toolbar } = this.Blok;
313
+
314
+ const newBlock = await this.convertBlock(
315
+ currentBlock,
316
+ selectedBlocks,
317
+ hasMultipleBlocksSelected,
318
+ tool,
319
+ toolboxItem.data
320
+ );
321
+
322
+ Toolbar.close();
323
+
324
+ if (newBlock) {
325
+ Caret.setToBlock(newBlock, Caret.positions.END);
326
+ }
327
+ },
328
+ });
329
+ });
330
+
331
+ return result;
332
+ }, []);
333
+
334
+ if (convertToItems.length > 0) {
335
+ items.push({
336
+ icon: IconReplace,
337
+ name: 'convert-to',
338
+ title: this.Blok.I18n.t('popover.convertTo'),
339
+ children: {
340
+ items: convertToItems,
341
+ },
342
+ });
343
+ items.push({
344
+ type: PopoverItemType.Separator,
345
+ });
346
+ }
347
+
348
+ /**
349
+ * For single block selection, show all common tunes (delete, move, etc.)
350
+ * For multiple blocks, only show delete option with multi-block delete behavior
351
+ */
352
+ if (!hasMultipleBlocksSelected) {
353
+ items.push(...commonTunes);
354
+ } else {
355
+ items.push({
356
+ icon: IconCross,
357
+ title: this.Blok.I18n.t('blockSettings.delete'),
358
+ name: 'delete',
359
+ closeOnActivate: true,
360
+ onActivate: () => {
361
+ const { BlockManager, Caret, Toolbar } = this.Blok;
362
+ const indexToInsert = BlockManager.removeSelectedBlocks();
363
+
364
+ if (indexToInsert !== undefined && BlockManager.blocks.length === 0) {
365
+ BlockManager.insert();
366
+ }
367
+
368
+ const currentBlock = BlockManager.currentBlock;
369
+
370
+ if (currentBlock) {
371
+ Caret.setToBlock(currentBlock, Caret.positions.END);
372
+ }
373
+
374
+ Toolbar.close();
375
+ },
376
+ });
377
+ }
378
+
379
+ return items;
380
+ }
381
+
382
+ /**
383
+ * Handles popover close event
384
+ */
385
+ private onPopoverClose = (): void => {
386
+ this.close();
387
+ };
388
+
389
+ /**
390
+ * Clears block selection when block settings is closed
391
+ * Handles both single and multiple block selection scenarios
392
+ */
393
+ private clearBlockSelectionOnClose(): void {
394
+ if (this.Blok.CrossBlockSelection.isCrossBlockSelectionStarted) {
395
+ return;
396
+ }
397
+
398
+ const selectedBlocks = this.Blok.BlockSelection.selectedBlocks;
399
+ const hasMultipleBlocksSelected = selectedBlocks.length > 1;
400
+
401
+ if (hasMultipleBlocksSelected) {
402
+ this.Blok.BlockSelection.allBlocksSelected = false;
403
+
404
+ return;
405
+ }
406
+
407
+ const currentBlock = this.Blok.BlockManager.currentBlock;
408
+
409
+ if (currentBlock) {
410
+ this.Blok.BlockSelection.unselectBlock(currentBlock);
411
+ }
412
+ }
413
+
414
+ /**
415
+ * Converts multiple selected blocks to a target tool type.
416
+ * For tools that support multi-item data (like lists), all blocks are combined into a single block.
417
+ * Otherwise, each block is converted individually and remains as a separate block.
418
+ * @param blocks - array of blocks to convert
419
+ * @param targetToolName - name of the tool to convert to
420
+ * @param toolboxData - optional data overrides for the new blocks
421
+ * @returns the resulting block (merged or last converted) or null if all conversions failed
422
+ */
423
+ private async convertBlock(
424
+ currentBlock: Block,
425
+ selectedBlocks: Block[],
426
+ hasMultipleBlocksSelected: boolean,
427
+ tool: BlockToolAdapter,
428
+ toolboxData?: Record<string, unknown>
429
+ ): Promise<Block | null> {
430
+ const { BlockManager } = this.Blok;
431
+
432
+ if (hasMultipleBlocksSelected) {
433
+ return this.convertMultipleBlocks(selectedBlocks, tool.name, toolboxData);
434
+ }
435
+
436
+ /**
437
+ * Check if we should explode a multi-item block (like List) into separate blocks
438
+ * This happens when converting to a tool that doesn't support multiple items
439
+ */
440
+ const explodableItems = await this.getExplodableItems(currentBlock);
441
+ const shouldExplode = !this.canToolMergeMultipleItems(tool) && explodableItems !== null;
442
+
443
+ if (shouldExplode) {
444
+ return this.convertMultiItemBlockToSeparateBlocks(currentBlock, tool.name, toolboxData);
445
+ }
446
+
447
+ return BlockManager.convert(currentBlock, tool.name, toolboxData);
448
+ }
449
+
450
+ /**
451
+ * Converts multiple selected blocks to a target tool type.
452
+ * For tools that support multi-item data (like lists), all blocks are combined into a single block.
453
+ * Otherwise, each block is converted individually and remains as a separate block.
454
+ * @param blocks - array of blocks to convert
455
+ * @param targetToolName - name of the tool to convert to
456
+ * @param toolboxData - optional data overrides for the new blocks
457
+ * @returns the resulting block (merged or last converted) or null if all conversions failed
458
+ */
459
+ private async convertMultipleBlocks(
460
+ blocks: Block[],
461
+ targetToolName: string,
462
+ toolboxData?: Record<string, unknown>
463
+ ): Promise<Block | null> {
464
+ const { Tools } = this.Blok;
465
+
466
+ if (blocks.length === 0) {
467
+ return null;
468
+ }
469
+
470
+ /**
471
+ * Check if the target tool's conversion config import function can handle
472
+ * newline-separated content to create multiple items (like lists do).
473
+ * We detect this by checking if the import function returns data with an 'items' array.
474
+ */
475
+ const targetTool = Tools.blockTools.get(targetToolName);
476
+ const shouldMergeIntoSingleBlock = targetTool && this.canToolMergeMultipleItems(targetTool);
477
+
478
+ if (shouldMergeIntoSingleBlock) {
479
+ return this.convertBlocksToSingleMergedBlock(blocks, targetToolName, toolboxData);
480
+ }
481
+
482
+ /**
483
+ * Convert each block individually, maintaining them as separate blocks
484
+ */
485
+ return this.convertBlocksIndividually(blocks, targetToolName, toolboxData);
486
+ }
487
+
488
+ /**
489
+ * Checks if a tool can merge multiple items into a single block.
490
+ * This is determined by testing if the tool's import function creates an 'items' array.
491
+ * @param tool - the target tool adapter
492
+ * @returns true if the tool supports merging multiple items
493
+ */
494
+ private canToolMergeMultipleItems(tool: BlockToolAdapter): boolean {
495
+ const conversionConfig = tool.conversionConfig;
496
+
497
+ if (!conversionConfig?.import) {
498
+ return false;
499
+ }
500
+
501
+ /**
502
+ * Test the import function with a sample multi-line string
503
+ * to see if it creates multiple items
504
+ */
505
+ try {
506
+ const testResult = typeof conversionConfig.import === 'function'
507
+ ? conversionConfig.import('line1\nline2', tool.settings)
508
+ : { [conversionConfig.import]: 'line1\nline2' };
509
+
510
+ return Array.isArray(testResult?.items) && testResult.items.length > 1;
511
+ } catch {
512
+ return false;
513
+ }
514
+ }
515
+
516
+ /**
517
+ * Converts multiple blocks into a single merged block by combining their exported content.
518
+ * Used for tools like lists that can hold multiple items.
519
+ * @param blocks - blocks to convert and merge
520
+ * @param targetToolName - name of the tool to convert to
521
+ * @param toolboxData - optional data overrides
522
+ * @returns the merged block or null if conversion failed
523
+ */
524
+ private async convertBlocksToSingleMergedBlock(
525
+ blocks: Block[],
526
+ targetToolName: string,
527
+ toolboxData?: Record<string, unknown>
528
+ ): Promise<Block | null> {
529
+ const { BlockManager } = this.Blok;
530
+
531
+ /**
532
+ * Export all blocks' content and combine with newlines
533
+ */
534
+ const exportedContents: string[] = [];
535
+
536
+ for (const block of blocks) {
537
+ try {
538
+ const content = await block.exportDataAsString();
539
+
540
+ exportedContents.push(content);
541
+ } catch {
542
+ // Skip blocks that fail to export
543
+ }
544
+ }
545
+
546
+ if (exportedContents.length === 0) {
547
+ return null;
548
+ }
549
+
550
+ /**
551
+ * Convert the first block with combined content
552
+ */
553
+ const firstBlock = blocks[0];
554
+ const combinedContent = exportedContents.join('\n');
555
+
556
+ /**
557
+ * Get the target tool to use its conversion config
558
+ */
559
+ const targetTool = this.Blok.Tools.blockTools.get(targetToolName);
560
+
561
+ if (!targetTool) {
562
+ return null;
563
+ }
564
+
565
+ /**
566
+ * Import the combined content using the target tool's conversion config
567
+ */
568
+ const importedData = typeof targetTool.conversionConfig?.import === 'function'
569
+ ? targetTool.conversionConfig.import(combinedContent, targetTool.settings)
570
+ : { [targetTool.conversionConfig?.import as string]: combinedContent };
571
+
572
+ const newBlockData = toolboxData
573
+ ? Object.assign(importedData, toolboxData)
574
+ : importedData;
575
+
576
+ /**
577
+ * Replace the first block with the new merged block
578
+ */
579
+ const newBlock = BlockManager.replace(firstBlock, targetToolName, newBlockData);
580
+
581
+ /**
582
+ * Remove the remaining blocks (they've been merged into the first one)
583
+ */
584
+ const remainingBlocks = blocks.slice(1);
585
+
586
+ for (const block of remainingBlocks) {
587
+ await BlockManager.removeBlock(block, false);
588
+ }
589
+
590
+ return newBlock;
591
+ }
592
+
593
+ /**
594
+ * Converts blocks individually, keeping them as separate blocks.
595
+ * @param blocks - blocks to convert
596
+ * @param targetToolName - name of the tool to convert to
597
+ * @param toolboxData - optional data overrides
598
+ * @returns the last converted block or null if all conversions failed
599
+ */
600
+ private async convertBlocksIndividually(
601
+ blocks: Block[],
602
+ targetToolName: string,
603
+ toolboxData?: Record<string, unknown>
604
+ ): Promise<Block | null> {
605
+ const { BlockManager } = this.Blok;
606
+ const convertedBlocks: Block[] = [];
607
+
608
+ for (const block of blocks) {
609
+ const convertedBlock = await this.convertBlockSafely(BlockManager, block, targetToolName, toolboxData);
610
+
611
+ if (convertedBlock) {
612
+ convertedBlocks.push(convertedBlock);
613
+ }
614
+ }
615
+
616
+ return convertedBlocks.length > 0
617
+ ? convertedBlocks[convertedBlocks.length - 1]
618
+ : null;
619
+ }
620
+
621
+ /**
622
+ * Safely converts a single block, catching any errors
623
+ * @param blockManager - the block manager instance
624
+ * @param block - block to convert
625
+ * @param targetToolName - name of the tool to convert to
626
+ * @param toolboxData - optional data overrides
627
+ * @returns the converted block or null if conversion failed
628
+ */
629
+ private async convertBlockSafely(
630
+ blockManager: typeof this.Blok.BlockManager,
631
+ block: Block,
632
+ targetToolName: string,
633
+ toolboxData?: Record<string, unknown>
634
+ ): Promise<Block | null> {
635
+ try {
636
+ return await blockManager.convert(block, targetToolName, toolboxData);
637
+ } catch (e) {
638
+ console.warn(`Failed to convert block ${block.id}:`, e);
639
+
640
+ return null;
641
+ }
642
+ }
643
+
644
+ /**
645
+ * Checks if a block contains multiple items that should be exploded into separate blocks
646
+ * when converting to a single-item tool.
647
+ * @param block - block to check
648
+ * @returns array of content strings if block should be exploded, null otherwise
649
+ */
650
+ private async getExplodableItems(block: Block): Promise<string[] | null> {
651
+ try {
652
+ const blockData = await block.data;
653
+
654
+ /**
655
+ * Check if block has an 'items' array with multiple items (like List tool)
656
+ */
657
+ if (!Array.isArray(blockData?.items) || blockData.items.length <= 1) {
658
+ return null;
659
+ }
660
+
661
+ /**
662
+ * Extract content from each item, handling nested items recursively
663
+ */
664
+ const extractContent = (items: Array<{ content?: string; items?: unknown[] }>): string[] => {
665
+ const contents: string[] = [];
666
+
667
+ for (const item of items) {
668
+ if (item.content !== undefined && item.content !== '') {
669
+ contents.push(item.content);
670
+ }
671
+ if (Array.isArray(item.items) && item.items.length > 0) {
672
+ contents.push(...extractContent(item.items as Array<{ content?: string; items?: unknown[] }>));
673
+ }
674
+ }
675
+
676
+ return contents;
677
+ };
678
+
679
+ return extractContent(blockData.items);
680
+ } catch {
681
+ return null;
682
+ }
683
+ }
684
+
685
+ /**
686
+ * Converts a multi-item block (like List) into multiple single-item blocks.
687
+ * Each item becomes a separate block of the target type.
688
+ * @param block - block to convert
689
+ * @param targetToolName - name of the tool to convert to
690
+ * @param toolboxData - optional data overrides
691
+ * @returns the last created block or null if conversion failed
692
+ */
693
+ private async convertMultiItemBlockToSeparateBlocks(
694
+ block: Block,
695
+ targetToolName: string,
696
+ toolboxData?: Record<string, unknown>
697
+ ): Promise<Block | null> {
698
+ const { BlockManager, Tools } = this.Blok;
699
+ const items = await this.getExplodableItems(block);
700
+
701
+ if (!items || items.length === 0) {
702
+ return null;
703
+ }
704
+
705
+ const targetTool = Tools.blockTools.get(targetToolName);
706
+ const conversionImport = targetTool?.conversionConfig?.import;
707
+
708
+ if (!conversionImport) {
709
+ return null;
710
+ }
711
+
712
+ const blockIndex = BlockManager.getBlockIndex(block);
713
+
714
+ /**
715
+ * Remove the original block first
716
+ */
717
+ await BlockManager.removeBlock(block, false);
718
+
719
+ /**
720
+ * Create a new block for each item
721
+ */
722
+ const createdBlocks = items.map((content, index) => {
723
+ /**
724
+ * Import the content using the target tool's conversion config
725
+ */
726
+ const importedData = typeof conversionImport === 'function'
727
+ ? conversionImport(content, targetTool?.settings)
728
+ : { [conversionImport as string]: content };
729
+
730
+ const newBlockData = toolboxData
731
+ ? Object.assign(importedData, toolboxData)
732
+ : importedData;
733
+
734
+ return BlockManager.insert({
735
+ tool: targetToolName,
736
+ data: newBlockData,
737
+ index: blockIndex + index,
738
+ needToFocus: false,
739
+ });
740
+ });
741
+
742
+ return createdBlocks.length > 0 ? createdBlocks[createdBlocks.length - 1] : null;
743
+ }
744
+
745
+ /**
746
+ * Attaches keydown listener to delegate navigation events to the shared flipper
747
+ * @param block - block that owns the currently focused content
748
+ */
749
+ private attachFlipperKeydownListener(block: Block): void {
750
+ this.detachFlipperKeydownListener();
751
+
752
+ const pluginsContent = block?.pluginsContent;
753
+
754
+ if (!(pluginsContent instanceof HTMLElement)) {
755
+ return;
756
+ }
757
+
758
+ this.flipperInstance.setHandleContentEditableTargets(true);
759
+
760
+ this.flipperKeydownHandler = (event: KeyboardEvent) => {
761
+ this.flipperInstance.handleExternalKeydown(event);
762
+ };
763
+
764
+ pluginsContent.addEventListener('keydown', this.flipperKeydownHandler, true);
765
+ this.flipperKeydownSource = pluginsContent;
766
+ }
767
+
768
+ /**
769
+ * Removes keydown listener from the previously active block
770
+ */
771
+ private detachFlipperKeydownListener(): void {
772
+ if (this.flipperKeydownSource !== null && this.flipperKeydownHandler !== null) {
773
+ this.flipperKeydownSource.removeEventListener('keydown', this.flipperKeydownHandler, true);
774
+ }
775
+
776
+ this.flipperInstance.setHandleContentEditableTargets(false);
777
+
778
+ this.flipperKeydownSource = null;
779
+ this.flipperKeydownHandler = null;
780
+ }
781
+ }