@jackuait/blok 0.4.1 → 0.4.3-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 (397) hide show
  1. package/README.md +136 -17
  2. package/codemod/README.md +45 -7
  3. package/codemod/migrate-editorjs-to-blok.js +951 -92
  4. package/codemod/test.js +780 -77
  5. package/dist/blok.mjs +5 -2
  6. package/dist/chunks/blok-8ptWuVZC.mjs +12838 -0
  7. package/dist/chunks/i18next-CugVlwWp.mjs +1292 -0
  8. package/dist/chunks/i18next-loader-bLawSYRV.mjs +43 -0
  9. package/dist/{index-CBkApZKo.mjs → chunks/index-5nYtWZD2.mjs} +2 -2
  10. package/dist/chunks/inline-tool-convert-CvMDAIzb.mjs +1988 -0
  11. package/dist/chunks/messages-2434tVOK.mjs +47 -0
  12. package/dist/chunks/messages-3DcCwXMF.mjs +47 -0
  13. package/dist/chunks/messages-4kMwVAKY.mjs +47 -0
  14. package/dist/chunks/messages-57uL5htT.mjs +47 -0
  15. package/dist/chunks/messages-76-iJV9Q.mjs +47 -0
  16. package/dist/chunks/messages-8p86Eyf2.mjs +47 -0
  17. package/dist/chunks/messages-BBX0p0Pi.mjs +47 -0
  18. package/dist/chunks/messages-BCm2eudQ.mjs +47 -0
  19. package/dist/chunks/messages-BFiUomgG.mjs +47 -0
  20. package/dist/chunks/messages-BIPNHHAV.mjs +47 -0
  21. package/dist/chunks/messages-BUlwu9mo.mjs +47 -0
  22. package/dist/chunks/messages-BX-DPa-z.mjs +47 -0
  23. package/dist/chunks/messages-BextV3Qh.mjs +47 -0
  24. package/dist/chunks/messages-BiPSFlUG.mjs +47 -0
  25. package/dist/chunks/messages-BiXe9G-O.mjs +47 -0
  26. package/dist/chunks/messages-Bl5z_Igo.mjs +47 -0
  27. package/dist/chunks/messages-BnsE97ku.mjs +47 -0
  28. package/dist/chunks/messages-BoO8gsVD.mjs +47 -0
  29. package/dist/chunks/messages-BqWaOGMn.mjs +47 -0
  30. package/dist/chunks/messages-BqkL2_Ro.mjs +47 -0
  31. package/dist/chunks/messages-BvCkXKX-.mjs +47 -0
  32. package/dist/chunks/messages-C6tbPLoj.mjs +47 -0
  33. package/dist/chunks/messages-CA6T3-gQ.mjs +47 -0
  34. package/dist/chunks/messages-CFFPFdWP.mjs +47 -0
  35. package/dist/chunks/messages-CFrKE-TN.mjs +47 -0
  36. package/dist/chunks/messages-CHz8VlG-.mjs +47 -0
  37. package/dist/chunks/messages-CLixzySl.mjs +47 -0
  38. package/dist/chunks/messages-CV7OM_qk.mjs +47 -0
  39. package/dist/chunks/messages-CXHt3eCC.mjs +47 -0
  40. package/dist/chunks/messages-CbmsBrB0.mjs +47 -0
  41. package/dist/chunks/messages-Ceo1KtFx.mjs +47 -0
  42. package/dist/chunks/messages-Cm0LJLtB.mjs +47 -0
  43. package/dist/chunks/messages-CmymP_Ar.mjs +47 -0
  44. package/dist/chunks/messages-D0ohMB5H.mjs +47 -0
  45. package/dist/chunks/messages-D3GrDwXh.mjs +47 -0
  46. package/dist/chunks/messages-D3vTzIpL.mjs +47 -0
  47. package/dist/chunks/messages-D5WeksbV.mjs +47 -0
  48. package/dist/chunks/messages-DGaab4EP.mjs +47 -0
  49. package/dist/chunks/messages-DKha57ZU.mjs +47 -0
  50. package/dist/chunks/messages-DOaujgMW.mjs +47 -0
  51. package/dist/chunks/messages-DVbPLd_0.mjs +47 -0
  52. package/dist/chunks/messages-D_FCyfW6.mjs +47 -0
  53. package/dist/chunks/messages-Dd5iZN3c.mjs +47 -0
  54. package/dist/chunks/messages-DehM7135.mjs +47 -0
  55. package/dist/chunks/messages-Dg1OHftD.mjs +47 -0
  56. package/dist/chunks/messages-Di6Flq-b.mjs +47 -0
  57. package/dist/chunks/messages-Dqhhex6e.mjs +47 -0
  58. package/dist/chunks/messages-DueVe0F1.mjs +47 -0
  59. package/dist/chunks/messages-Dx3eFwI0.mjs +47 -0
  60. package/dist/chunks/messages-FOtiUoKl.mjs +47 -0
  61. package/dist/chunks/messages-FTOZNhRD.mjs +47 -0
  62. package/dist/chunks/messages-IQxGfQIV.mjs +47 -0
  63. package/dist/chunks/messages-JF2fzCkK.mjs +47 -0
  64. package/dist/chunks/messages-MOGl7I5v.mjs +47 -0
  65. package/dist/chunks/messages-QgYhPL-3.mjs +47 -0
  66. package/dist/chunks/messages-WYWIbQwo.mjs +47 -0
  67. package/dist/chunks/messages-a6A_LgDv.mjs +47 -0
  68. package/dist/chunks/messages-bSf31LJi.mjs +47 -0
  69. package/dist/chunks/messages-diGozhTn.mjs +47 -0
  70. package/dist/chunks/messages-er-kd-VO.mjs +47 -0
  71. package/dist/chunks/messages-ez3w5NBn.mjs +47 -0
  72. package/dist/chunks/messages-f3uXjegd.mjs +47 -0
  73. package/dist/chunks/messages-ohwI1UGv.mjs +47 -0
  74. package/dist/chunks/messages-p9BZJaFV.mjs +47 -0
  75. package/dist/chunks/messages-qIQ4L4rw.mjs +47 -0
  76. package/dist/chunks/messages-qWkXPggi.mjs +47 -0
  77. package/dist/chunks/messages-w5foGze_.mjs +47 -0
  78. package/dist/full.mjs +50 -0
  79. package/dist/locales.mjs +227 -0
  80. package/dist/messages-2434tVOK.mjs +47 -0
  81. package/dist/messages-3DcCwXMF.mjs +47 -0
  82. package/dist/messages-4kMwVAKY.mjs +47 -0
  83. package/dist/messages-57uL5htT.mjs +47 -0
  84. package/dist/messages-76-iJV9Q.mjs +47 -0
  85. package/dist/messages-8p86Eyf2.mjs +47 -0
  86. package/dist/messages-BBX0p0Pi.mjs +47 -0
  87. package/dist/messages-BCm2eudQ.mjs +47 -0
  88. package/dist/messages-BFiUomgG.mjs +47 -0
  89. package/dist/messages-BIPNHHAV.mjs +47 -0
  90. package/dist/messages-BUlwu9mo.mjs +47 -0
  91. package/dist/messages-BX-DPa-z.mjs +47 -0
  92. package/dist/messages-BextV3Qh.mjs +47 -0
  93. package/dist/messages-BiPSFlUG.mjs +47 -0
  94. package/dist/messages-BiXe9G-O.mjs +47 -0
  95. package/dist/messages-Bl5z_Igo.mjs +47 -0
  96. package/dist/messages-BnsE97ku.mjs +47 -0
  97. package/dist/messages-BoO8gsVD.mjs +47 -0
  98. package/dist/messages-BqWaOGMn.mjs +47 -0
  99. package/dist/messages-BqkL2_Ro.mjs +47 -0
  100. package/dist/messages-BvCkXKX-.mjs +47 -0
  101. package/dist/messages-C6tbPLoj.mjs +47 -0
  102. package/dist/messages-CA6T3-gQ.mjs +47 -0
  103. package/dist/messages-CFFPFdWP.mjs +47 -0
  104. package/dist/messages-CFrKE-TN.mjs +47 -0
  105. package/dist/messages-CHz8VlG-.mjs +47 -0
  106. package/dist/messages-CLixzySl.mjs +47 -0
  107. package/dist/messages-CV7OM_qk.mjs +47 -0
  108. package/dist/messages-CXHt3eCC.mjs +47 -0
  109. package/dist/messages-CbmsBrB0.mjs +47 -0
  110. package/dist/messages-Ceo1KtFx.mjs +47 -0
  111. package/dist/messages-Cm0LJLtB.mjs +47 -0
  112. package/dist/messages-CmymP_Ar.mjs +47 -0
  113. package/dist/messages-D0ohMB5H.mjs +47 -0
  114. package/dist/messages-D3GrDwXh.mjs +47 -0
  115. package/dist/messages-D3vTzIpL.mjs +47 -0
  116. package/dist/messages-D5WeksbV.mjs +47 -0
  117. package/dist/messages-DGaab4EP.mjs +47 -0
  118. package/dist/messages-DKha57ZU.mjs +47 -0
  119. package/dist/messages-DOaujgMW.mjs +47 -0
  120. package/dist/messages-DVbPLd_0.mjs +47 -0
  121. package/dist/messages-D_FCyfW6.mjs +47 -0
  122. package/dist/messages-Dd5iZN3c.mjs +47 -0
  123. package/dist/messages-DehM7135.mjs +47 -0
  124. package/dist/messages-Dg1OHftD.mjs +47 -0
  125. package/dist/messages-Di6Flq-b.mjs +47 -0
  126. package/dist/messages-Dqhhex6e.mjs +47 -0
  127. package/dist/messages-DueVe0F1.mjs +47 -0
  128. package/dist/messages-Dx3eFwI0.mjs +47 -0
  129. package/dist/messages-FOtiUoKl.mjs +47 -0
  130. package/dist/messages-FTOZNhRD.mjs +47 -0
  131. package/dist/messages-IQxGfQIV.mjs +47 -0
  132. package/dist/messages-JF2fzCkK.mjs +47 -0
  133. package/dist/messages-MOGl7I5v.mjs +47 -0
  134. package/dist/messages-QgYhPL-3.mjs +47 -0
  135. package/dist/messages-WYWIbQwo.mjs +47 -0
  136. package/dist/messages-a6A_LgDv.mjs +47 -0
  137. package/dist/messages-bSf31LJi.mjs +47 -0
  138. package/dist/messages-diGozhTn.mjs +47 -0
  139. package/dist/messages-er-kd-VO.mjs +47 -0
  140. package/dist/messages-ez3w5NBn.mjs +47 -0
  141. package/dist/messages-f3uXjegd.mjs +47 -0
  142. package/dist/messages-ohwI1UGv.mjs +47 -0
  143. package/dist/messages-p9BZJaFV.mjs +47 -0
  144. package/dist/messages-qIQ4L4rw.mjs +47 -0
  145. package/dist/messages-qWkXPggi.mjs +47 -0
  146. package/dist/messages-w5foGze_.mjs +47 -0
  147. package/dist/tools.mjs +3073 -0
  148. package/dist/vendor.LICENSE.txt +59 -156
  149. package/package.json +60 -15
  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 +1427 -0
  154. package/src/components/block-tunes/block-tune-delete.ts +51 -0
  155. package/src/components/blocks.ts +338 -0
  156. package/src/components/constants/data-attributes.ts +342 -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 +481 -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 +44 -0
  177. package/src/components/i18n/locales/ar/messages.json +44 -0
  178. package/src/components/i18n/locales/az/messages.json +44 -0
  179. package/src/components/i18n/locales/bg/messages.json +44 -0
  180. package/src/components/i18n/locales/bn/messages.json +44 -0
  181. package/src/components/i18n/locales/bs/messages.json +44 -0
  182. package/src/components/i18n/locales/cs/messages.json +44 -0
  183. package/src/components/i18n/locales/da/messages.json +44 -0
  184. package/src/components/i18n/locales/de/messages.json +44 -0
  185. package/src/components/i18n/locales/dv/messages.json +44 -0
  186. package/src/components/i18n/locales/el/messages.json +44 -0
  187. package/src/components/i18n/locales/en/messages.json +44 -0
  188. package/src/components/i18n/locales/es/messages.json +44 -0
  189. package/src/components/i18n/locales/et/messages.json +44 -0
  190. package/src/components/i18n/locales/fa/messages.json +44 -0
  191. package/src/components/i18n/locales/fi/messages.json +44 -0
  192. package/src/components/i18n/locales/fil/messages.json +44 -0
  193. package/src/components/i18n/locales/fr/messages.json +44 -0
  194. package/src/components/i18n/locales/gu/messages.json +44 -0
  195. package/src/components/i18n/locales/he/messages.json +44 -0
  196. package/src/components/i18n/locales/hi/messages.json +44 -0
  197. package/src/components/i18n/locales/hr/messages.json +44 -0
  198. package/src/components/i18n/locales/hu/messages.json +44 -0
  199. package/src/components/i18n/locales/hy/messages.json +44 -0
  200. package/src/components/i18n/locales/id/messages.json +44 -0
  201. package/src/components/i18n/locales/index.ts +225 -0
  202. package/src/components/i18n/locales/it/messages.json +44 -0
  203. package/src/components/i18n/locales/ja/messages.json +44 -0
  204. package/src/components/i18n/locales/ka/messages.json +44 -0
  205. package/src/components/i18n/locales/km/messages.json +44 -0
  206. package/src/components/i18n/locales/kn/messages.json +44 -0
  207. package/src/components/i18n/locales/ko/messages.json +44 -0
  208. package/src/components/i18n/locales/ku/messages.json +44 -0
  209. package/src/components/i18n/locales/lo/messages.json +44 -0
  210. package/src/components/i18n/locales/lt/messages.json +44 -0
  211. package/src/components/i18n/locales/lv/messages.json +44 -0
  212. package/src/components/i18n/locales/mk/messages.json +44 -0
  213. package/src/components/i18n/locales/ml/messages.json +44 -0
  214. package/src/components/i18n/locales/mn/messages.json +44 -0
  215. package/src/components/i18n/locales/mr/messages.json +44 -0
  216. package/src/components/i18n/locales/ms/messages.json +44 -0
  217. package/src/components/i18n/locales/my/messages.json +44 -0
  218. package/src/components/i18n/locales/ne/messages.json +44 -0
  219. package/src/components/i18n/locales/nl/messages.json +44 -0
  220. package/src/components/i18n/locales/no/messages.json +44 -0
  221. package/src/components/i18n/locales/pa/messages.json +44 -0
  222. package/src/components/i18n/locales/pl/messages.json +44 -0
  223. package/src/components/i18n/locales/ps/messages.json +44 -0
  224. package/src/components/i18n/locales/pt/messages.json +44 -0
  225. package/src/components/i18n/locales/ro/messages.json +44 -0
  226. package/src/components/i18n/locales/ru/messages.json +44 -0
  227. package/src/components/i18n/locales/sd/messages.json +44 -0
  228. package/src/components/i18n/locales/si/messages.json +44 -0
  229. package/src/components/i18n/locales/sk/messages.json +44 -0
  230. package/src/components/i18n/locales/sl/messages.json +44 -0
  231. package/src/components/i18n/locales/sq/messages.json +44 -0
  232. package/src/components/i18n/locales/sr/messages.json +44 -0
  233. package/src/components/i18n/locales/sv/messages.json +44 -0
  234. package/src/components/i18n/locales/sw/messages.json +44 -0
  235. package/src/components/i18n/locales/ta/messages.json +44 -0
  236. package/src/components/i18n/locales/te/messages.json +44 -0
  237. package/src/components/i18n/locales/th/messages.json +44 -0
  238. package/src/components/i18n/locales/tr/messages.json +44 -0
  239. package/src/components/i18n/locales/ug/messages.json +44 -0
  240. package/src/components/i18n/locales/uk/messages.json +44 -0
  241. package/src/components/i18n/locales/ur/messages.json +44 -0
  242. package/src/components/i18n/locales/vi/messages.json +44 -0
  243. package/src/components/i18n/locales/yi/messages.json +44 -0
  244. package/src/components/i18n/locales/zh/messages.json +44 -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 +363 -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 +33 -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 +1375 -0
  269. package/src/components/modules/blockManager.ts +1348 -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 +1141 -0
  274. package/src/components/modules/history.ts +1098 -0
  275. package/src/components/modules/i18n.ts +325 -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 +668 -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 +776 -0
  284. package/src/components/modules/toolbar/index.ts +1311 -0
  285. package/src/components/modules/toolbar/inline.ts +956 -0
  286. package/src/components/modules/tools.ts +589 -0
  287. package/src/components/modules/ui.ts +1179 -0
  288. package/src/components/polyfills.ts +113 -0
  289. package/src/components/selection.ts +1189 -0
  290. package/src/components/tools/base.ts +274 -0
  291. package/src/components/tools/block.ts +291 -0
  292. package/src/components/tools/collection.ts +67 -0
  293. package/src/components/tools/factory.ts +85 -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 +497 -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 +666 -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 +187 -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 +181 -0
  330. package/src/components/utils/popover/components/search-input/search-input.types.ts +30 -0
  331. package/src/components/utils/popover/index.ts +13 -0
  332. package/src/components/utils/popover/popover-abstract.ts +448 -0
  333. package/src/components/utils/popover/popover-desktop.ts +643 -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 +105 -0
  344. package/src/components/utils/tooltip.ts +642 -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 +570 -0
  367. package/src/tools/index.ts +38 -0
  368. package/src/tools/list/index.ts +1803 -0
  369. package/src/tools/paragraph/index.ts +411 -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 +1 -1
  378. package/types/api/i18n.d.ts +5 -3
  379. package/types/api/selection.d.ts +6 -0
  380. package/types/api/styles.d.ts +0 -5
  381. package/types/configs/blok-config.d.ts +21 -0
  382. package/types/configs/i18n-config.d.ts +52 -2
  383. package/types/configs/i18n-dictionary.d.ts +16 -90
  384. package/types/data-attributes.d.ts +169 -0
  385. package/types/data-formats/output-data.d.ts +15 -0
  386. package/types/full.d.ts +80 -0
  387. package/types/index.d.ts +9 -24
  388. package/types/locales.d.ts +59 -0
  389. package/types/tools/adapters/inline-tool-adapter.d.ts +10 -0
  390. package/types/tools/block-tool.d.ts +9 -0
  391. package/types/tools/list.d.ts +25 -18
  392. package/types/tools/tool-settings.d.ts +8 -1
  393. package/types/tools/tool.d.ts +6 -0
  394. package/types/tools-entry.d.ts +49 -0
  395. package/types/utils/popover/popover-item.d.ts +0 -5
  396. package/dist/blok-BwPfU8ro.mjs +0 -21510
  397. package/dist/blok.umd.js +0 -198
@@ -0,0 +1,776 @@
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
+ this.opened = true;
148
+
149
+ /**
150
+ * If block settings contains any inputs, focus will be set there,
151
+ * so we need to save current selection to restore it after block settings is closed
152
+ */
153
+ this.selection.save();
154
+
155
+ /**
156
+ * Highlight content of a Block we are working with
157
+ * For multiple blocks, they should already be selected
158
+ */
159
+ if (!hasMultipleBlocksSelected) {
160
+ this.Blok.BlockSelection.selectBlock(block);
161
+ this.Blok.BlockSelection.clearCache();
162
+ }
163
+
164
+ /** Get tool's settings data - only relevant for single block selection */
165
+ const { toolTunes, commonTunes } = block.getTunes();
166
+
167
+ /** Tell to subscribers that block settings is opened */
168
+ this.eventsDispatcher.emit(this.events.opened);
169
+
170
+ const PopoverClass = isMobileScreen() ? PopoverMobile : PopoverDesktop;
171
+ const popoverParams: PopoverParams & { flipper?: Flipper } = {
172
+ searchable: true,
173
+ trigger: trigger || this.nodes.wrapper,
174
+ items: await this.getTunesItems(block, commonTunes, toolTunes),
175
+ scopeElement: this.Blok.API.methods.ui.nodes.redactor,
176
+ messages: {
177
+ nothingFound: this.Blok.I18n.t('popover.nothingFound'),
178
+ search: this.Blok.I18n.t('popover.search'),
179
+ },
180
+ };
181
+
182
+ if (PopoverClass === PopoverDesktop) {
183
+ popoverParams.flipper = this.flipperInstance;
184
+ }
185
+
186
+ this.popover = new PopoverClass(popoverParams);
187
+ this.popover.getElement().setAttribute('data-blok-testid', 'block-tunes-popover');
188
+
189
+ this.popover.on(PopoverEvent.Closed, this.onPopoverClose);
190
+
191
+ this.popover.show();
192
+ this.attachFlipperKeydownListener(block);
193
+ }
194
+
195
+ /**
196
+ * Returns root block settings element
197
+ */
198
+ public getElement(): HTMLElement | undefined {
199
+ return this.nodes.wrapper;
200
+ }
201
+
202
+ /**
203
+ * Checks if the element is contained in the BlockSettings or its Popover
204
+ * @param element - element to check
205
+ */
206
+ public contains(element: HTMLElement): boolean {
207
+ if (this.nodes.wrapper?.contains(element)) {
208
+ return true;
209
+ }
210
+
211
+ if (this.popover?.hasNode(element)) {
212
+ return true;
213
+ }
214
+
215
+ return false;
216
+ }
217
+
218
+ /**
219
+ * Close Block Settings pane
220
+ */
221
+ public close = (): void => {
222
+ if (!this.opened) {
223
+ return;
224
+ }
225
+
226
+ this.opened = false;
227
+
228
+ /**
229
+ * If selection is at blok on Block Settings closing,
230
+ * it means that caret placed at some editable element inside the Block Settings.
231
+ * Previously we have saved the selection, then open the Block Settings and set caret to the input
232
+ *
233
+ * So, we need to restore selection back to Block after closing the Block Settings
234
+ */
235
+ if (!SelectionUtils.isAtBlok) {
236
+ this.selection.restore();
237
+ }
238
+
239
+ this.selection.clearSaved();
240
+ this.detachFlipperKeydownListener();
241
+
242
+ /**
243
+ * Remove highlighted content of Blocks we are working with
244
+ * Handle both single and multiple block selection
245
+ */
246
+ this.clearBlockSelectionOnClose();
247
+
248
+ /** Tell to subscribers that block settings is closed */
249
+ this.eventsDispatcher.emit(this.events.closed);
250
+
251
+ if (this.popover) {
252
+ this.popover.off(PopoverEvent.Closed, this.onPopoverClose);
253
+ this.popover.destroy();
254
+ this.popover.getElement().remove();
255
+ this.popover = null;
256
+ }
257
+ };
258
+
259
+ /**
260
+ * Returns list of items to be displayed in block tunes menu.
261
+ * Merges tool specific tunes, conversion menu and common tunes in one list in predefined order
262
+ * @param currentBlock – block we are about to open block tunes for
263
+ * @param commonTunes – common tunes
264
+ * @param toolTunes - tool specific tunes
265
+ */
266
+ private async getTunesItems(currentBlock: Block, commonTunes: MenuConfigItem[], toolTunes?: MenuConfigItem[]): Promise<PopoverItemParams[]> {
267
+ const items = [] as MenuConfigItem[];
268
+ const selectedBlocks = this.Blok.BlockSelection.selectedBlocks;
269
+ const hasMultipleBlocksSelected = selectedBlocks.length > 1;
270
+
271
+ /**
272
+ * Only show tool-specific tunes when a single block is selected
273
+ */
274
+ if (!hasMultipleBlocksSelected && toolTunes !== undefined && toolTunes.length > 0) {
275
+ items.push(...toolTunes);
276
+ items.push({
277
+ type: PopoverItemType.Separator,
278
+ });
279
+ }
280
+
281
+ const allBlockTools = Array.from(this.Blok.Tools.blockTools.values());
282
+
283
+ /**
284
+ * Get convertible tools based on selection:
285
+ * - For single block: use existing single-block conversion logic
286
+ * - For multiple blocks: find tools that ALL selected blocks can convert to
287
+ */
288
+ const convertibleTools = hasMultipleBlocksSelected
289
+ ? await getConvertibleToolsForBlocks(
290
+ selectedBlocks.map((block) => new BlockAPI(block)),
291
+ allBlockTools
292
+ )
293
+ : await getConvertibleToolsForBlock(currentBlock, allBlockTools);
294
+
295
+ const convertToItems = convertibleTools.reduce<PopoverItemParams[]>((result, tool) => {
296
+ if (tool.toolbox === undefined) {
297
+ return result;
298
+ }
299
+
300
+ tool.toolbox.forEach((toolboxItem) => {
301
+ result.push({
302
+ icon: toolboxItem.icon,
303
+ title: translateToolTitle(this.Blok.I18n, toolboxItem, tool.name),
304
+ name: toolboxItem.name ?? tool.name,
305
+ closeOnActivate: true,
306
+ onActivate: async () => {
307
+ const { Caret, Toolbar } = this.Blok;
308
+
309
+ const newBlock = await this.convertBlock(
310
+ currentBlock,
311
+ selectedBlocks,
312
+ hasMultipleBlocksSelected,
313
+ tool,
314
+ toolboxItem.data
315
+ );
316
+
317
+ Toolbar.close();
318
+
319
+ if (newBlock) {
320
+ Caret.setToBlock(newBlock, Caret.positions.END);
321
+ }
322
+ },
323
+ });
324
+ });
325
+
326
+ return result;
327
+ }, []);
328
+
329
+ if (convertToItems.length > 0) {
330
+ items.push({
331
+ icon: IconReplace,
332
+ name: 'convert-to',
333
+ title: this.Blok.I18n.t('popover.convertTo'),
334
+ children: {
335
+ items: convertToItems,
336
+ },
337
+ });
338
+ items.push({
339
+ type: PopoverItemType.Separator,
340
+ });
341
+ }
342
+
343
+ /**
344
+ * For single block selection, show all common tunes (delete, move, etc.)
345
+ * For multiple blocks, only show delete option with multi-block delete behavior
346
+ */
347
+ if (!hasMultipleBlocksSelected) {
348
+ items.push(...commonTunes);
349
+ } else {
350
+ items.push({
351
+ icon: IconCross,
352
+ title: this.Blok.I18n.t('blockSettings.delete'),
353
+ name: 'delete',
354
+ closeOnActivate: true,
355
+ onActivate: () => {
356
+ const { BlockManager, Caret, Toolbar } = this.Blok;
357
+ const indexToInsert = BlockManager.removeSelectedBlocks();
358
+
359
+ if (indexToInsert !== undefined && BlockManager.blocks.length === 0) {
360
+ BlockManager.insert();
361
+ }
362
+
363
+ const currentBlock = BlockManager.currentBlock;
364
+
365
+ if (currentBlock) {
366
+ Caret.setToBlock(currentBlock, Caret.positions.END);
367
+ }
368
+
369
+ Toolbar.close();
370
+ },
371
+ });
372
+ }
373
+
374
+ return items;
375
+ }
376
+
377
+ /**
378
+ * Handles popover close event
379
+ */
380
+ private onPopoverClose = (): void => {
381
+ this.close();
382
+ };
383
+
384
+ /**
385
+ * Clears block selection when block settings is closed
386
+ * Handles both single and multiple block selection scenarios
387
+ */
388
+ private clearBlockSelectionOnClose(): void {
389
+ if (this.Blok.CrossBlockSelection.isCrossBlockSelectionStarted) {
390
+ return;
391
+ }
392
+
393
+ const selectedBlocks = this.Blok.BlockSelection.selectedBlocks;
394
+ const hasMultipleBlocksSelected = selectedBlocks.length > 1;
395
+
396
+ if (hasMultipleBlocksSelected) {
397
+ this.Blok.BlockSelection.allBlocksSelected = false;
398
+
399
+ return;
400
+ }
401
+
402
+ const currentBlock = this.Blok.BlockManager.currentBlock;
403
+
404
+ if (currentBlock) {
405
+ this.Blok.BlockSelection.unselectBlock(currentBlock);
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Converts multiple selected blocks to a target tool type.
411
+ * For tools that support multi-item data (like lists), all blocks are combined into a single block.
412
+ * Otherwise, each block is converted individually and remains as a separate block.
413
+ * @param blocks - array of blocks to convert
414
+ * @param targetToolName - name of the tool to convert to
415
+ * @param toolboxData - optional data overrides for the new blocks
416
+ * @returns the resulting block (merged or last converted) or null if all conversions failed
417
+ */
418
+ private async convertBlock(
419
+ currentBlock: Block,
420
+ selectedBlocks: Block[],
421
+ hasMultipleBlocksSelected: boolean,
422
+ tool: BlockToolAdapter,
423
+ toolboxData?: Record<string, unknown>
424
+ ): Promise<Block | null> {
425
+ const { BlockManager } = this.Blok;
426
+
427
+ if (hasMultipleBlocksSelected) {
428
+ return this.convertMultipleBlocks(selectedBlocks, tool.name, toolboxData);
429
+ }
430
+
431
+ /**
432
+ * Check if we should explode a multi-item block (like List) into separate blocks
433
+ * This happens when converting to a tool that doesn't support multiple items
434
+ */
435
+ const explodableItems = await this.getExplodableItems(currentBlock);
436
+ const shouldExplode = !this.canToolMergeMultipleItems(tool) && explodableItems !== null;
437
+
438
+ if (shouldExplode) {
439
+ return this.convertMultiItemBlockToSeparateBlocks(currentBlock, tool.name, toolboxData);
440
+ }
441
+
442
+ return BlockManager.convert(currentBlock, tool.name, toolboxData);
443
+ }
444
+
445
+ /**
446
+ * Converts multiple selected blocks to a target tool type.
447
+ * For tools that support multi-item data (like lists), all blocks are combined into a single block.
448
+ * Otherwise, each block is converted individually and remains as a separate block.
449
+ * @param blocks - array of blocks to convert
450
+ * @param targetToolName - name of the tool to convert to
451
+ * @param toolboxData - optional data overrides for the new blocks
452
+ * @returns the resulting block (merged or last converted) or null if all conversions failed
453
+ */
454
+ private async convertMultipleBlocks(
455
+ blocks: Block[],
456
+ targetToolName: string,
457
+ toolboxData?: Record<string, unknown>
458
+ ): Promise<Block | null> {
459
+ const { Tools } = this.Blok;
460
+
461
+ if (blocks.length === 0) {
462
+ return null;
463
+ }
464
+
465
+ /**
466
+ * Check if the target tool's conversion config import function can handle
467
+ * newline-separated content to create multiple items (like lists do).
468
+ * We detect this by checking if the import function returns data with an 'items' array.
469
+ */
470
+ const targetTool = Tools.blockTools.get(targetToolName);
471
+ const shouldMergeIntoSingleBlock = targetTool && this.canToolMergeMultipleItems(targetTool);
472
+
473
+ if (shouldMergeIntoSingleBlock) {
474
+ return this.convertBlocksToSingleMergedBlock(blocks, targetToolName, toolboxData);
475
+ }
476
+
477
+ /**
478
+ * Convert each block individually, maintaining them as separate blocks
479
+ */
480
+ return this.convertBlocksIndividually(blocks, targetToolName, toolboxData);
481
+ }
482
+
483
+ /**
484
+ * Checks if a tool can merge multiple items into a single block.
485
+ * This is determined by testing if the tool's import function creates an 'items' array.
486
+ * @param tool - the target tool adapter
487
+ * @returns true if the tool supports merging multiple items
488
+ */
489
+ private canToolMergeMultipleItems(tool: BlockToolAdapter): boolean {
490
+ const conversionConfig = tool.conversionConfig;
491
+
492
+ if (!conversionConfig?.import) {
493
+ return false;
494
+ }
495
+
496
+ /**
497
+ * Test the import function with a sample multi-line string
498
+ * to see if it creates multiple items
499
+ */
500
+ try {
501
+ const testResult = typeof conversionConfig.import === 'function'
502
+ ? conversionConfig.import('line1\nline2', tool.settings)
503
+ : { [conversionConfig.import]: 'line1\nline2' };
504
+
505
+ return Array.isArray(testResult?.items) && testResult.items.length > 1;
506
+ } catch {
507
+ return false;
508
+ }
509
+ }
510
+
511
+ /**
512
+ * Converts multiple blocks into a single merged block by combining their exported content.
513
+ * Used for tools like lists that can hold multiple items.
514
+ * @param blocks - blocks to convert and merge
515
+ * @param targetToolName - name of the tool to convert to
516
+ * @param toolboxData - optional data overrides
517
+ * @returns the merged block or null if conversion failed
518
+ */
519
+ private async convertBlocksToSingleMergedBlock(
520
+ blocks: Block[],
521
+ targetToolName: string,
522
+ toolboxData?: Record<string, unknown>
523
+ ): Promise<Block | null> {
524
+ const { BlockManager } = this.Blok;
525
+
526
+ /**
527
+ * Export all blocks' content and combine with newlines
528
+ */
529
+ const exportedContents: string[] = [];
530
+
531
+ for (const block of blocks) {
532
+ try {
533
+ const content = await block.exportDataAsString();
534
+
535
+ exportedContents.push(content);
536
+ } catch {
537
+ // Skip blocks that fail to export
538
+ }
539
+ }
540
+
541
+ if (exportedContents.length === 0) {
542
+ return null;
543
+ }
544
+
545
+ /**
546
+ * Convert the first block with combined content
547
+ */
548
+ const firstBlock = blocks[0];
549
+ const combinedContent = exportedContents.join('\n');
550
+
551
+ /**
552
+ * Get the target tool to use its conversion config
553
+ */
554
+ const targetTool = this.Blok.Tools.blockTools.get(targetToolName);
555
+
556
+ if (!targetTool) {
557
+ return null;
558
+ }
559
+
560
+ /**
561
+ * Import the combined content using the target tool's conversion config
562
+ */
563
+ const importedData = typeof targetTool.conversionConfig?.import === 'function'
564
+ ? targetTool.conversionConfig.import(combinedContent, targetTool.settings)
565
+ : { [targetTool.conversionConfig?.import as string]: combinedContent };
566
+
567
+ const newBlockData = toolboxData
568
+ ? Object.assign(importedData, toolboxData)
569
+ : importedData;
570
+
571
+ /**
572
+ * Replace the first block with the new merged block
573
+ */
574
+ const newBlock = BlockManager.replace(firstBlock, targetToolName, newBlockData);
575
+
576
+ /**
577
+ * Remove the remaining blocks (they've been merged into the first one)
578
+ */
579
+ const remainingBlocks = blocks.slice(1);
580
+
581
+ for (const block of remainingBlocks) {
582
+ await BlockManager.removeBlock(block, false);
583
+ }
584
+
585
+ return newBlock;
586
+ }
587
+
588
+ /**
589
+ * Converts blocks individually, keeping them as separate blocks.
590
+ * @param blocks - blocks to convert
591
+ * @param targetToolName - name of the tool to convert to
592
+ * @param toolboxData - optional data overrides
593
+ * @returns the last converted block or null if all conversions failed
594
+ */
595
+ private async convertBlocksIndividually(
596
+ blocks: Block[],
597
+ targetToolName: string,
598
+ toolboxData?: Record<string, unknown>
599
+ ): Promise<Block | null> {
600
+ const { BlockManager } = this.Blok;
601
+ const convertedBlocks: Block[] = [];
602
+
603
+ for (const block of blocks) {
604
+ const convertedBlock = await this.convertBlockSafely(BlockManager, block, targetToolName, toolboxData);
605
+
606
+ if (convertedBlock) {
607
+ convertedBlocks.push(convertedBlock);
608
+ }
609
+ }
610
+
611
+ return convertedBlocks.length > 0
612
+ ? convertedBlocks[convertedBlocks.length - 1]
613
+ : null;
614
+ }
615
+
616
+ /**
617
+ * Safely converts a single block, catching any errors
618
+ * @param blockManager - the block manager instance
619
+ * @param block - block to convert
620
+ * @param targetToolName - name of the tool to convert to
621
+ * @param toolboxData - optional data overrides
622
+ * @returns the converted block or null if conversion failed
623
+ */
624
+ private async convertBlockSafely(
625
+ blockManager: typeof this.Blok.BlockManager,
626
+ block: Block,
627
+ targetToolName: string,
628
+ toolboxData?: Record<string, unknown>
629
+ ): Promise<Block | null> {
630
+ try {
631
+ return await blockManager.convert(block, targetToolName, toolboxData);
632
+ } catch (e) {
633
+ console.warn(`Failed to convert block ${block.id}:`, e);
634
+
635
+ return null;
636
+ }
637
+ }
638
+
639
+ /**
640
+ * Checks if a block contains multiple items that should be exploded into separate blocks
641
+ * when converting to a single-item tool.
642
+ * @param block - block to check
643
+ * @returns array of content strings if block should be exploded, null otherwise
644
+ */
645
+ private async getExplodableItems(block: Block): Promise<string[] | null> {
646
+ try {
647
+ const blockData = await block.data;
648
+
649
+ /**
650
+ * Check if block has an 'items' array with multiple items (like List tool)
651
+ */
652
+ if (!Array.isArray(blockData?.items) || blockData.items.length <= 1) {
653
+ return null;
654
+ }
655
+
656
+ /**
657
+ * Extract content from each item, handling nested items recursively
658
+ */
659
+ const extractContent = (items: Array<{ content?: string; items?: unknown[] }>): string[] => {
660
+ const contents: string[] = [];
661
+
662
+ for (const item of items) {
663
+ if (item.content !== undefined && item.content !== '') {
664
+ contents.push(item.content);
665
+ }
666
+ if (Array.isArray(item.items) && item.items.length > 0) {
667
+ contents.push(...extractContent(item.items as Array<{ content?: string; items?: unknown[] }>));
668
+ }
669
+ }
670
+
671
+ return contents;
672
+ };
673
+
674
+ return extractContent(blockData.items);
675
+ } catch {
676
+ return null;
677
+ }
678
+ }
679
+
680
+ /**
681
+ * Converts a multi-item block (like List) into multiple single-item blocks.
682
+ * Each item becomes a separate block of the target type.
683
+ * @param block - block to convert
684
+ * @param targetToolName - name of the tool to convert to
685
+ * @param toolboxData - optional data overrides
686
+ * @returns the last created block or null if conversion failed
687
+ */
688
+ private async convertMultiItemBlockToSeparateBlocks(
689
+ block: Block,
690
+ targetToolName: string,
691
+ toolboxData?: Record<string, unknown>
692
+ ): Promise<Block | null> {
693
+ const { BlockManager, Tools } = this.Blok;
694
+ const items = await this.getExplodableItems(block);
695
+
696
+ if (!items || items.length === 0) {
697
+ return null;
698
+ }
699
+
700
+ const targetTool = Tools.blockTools.get(targetToolName);
701
+ const conversionImport = targetTool?.conversionConfig?.import;
702
+
703
+ if (!conversionImport) {
704
+ return null;
705
+ }
706
+
707
+ const blockIndex = BlockManager.getBlockIndex(block);
708
+
709
+ /**
710
+ * Remove the original block first
711
+ */
712
+ await BlockManager.removeBlock(block, false);
713
+
714
+ /**
715
+ * Create a new block for each item
716
+ */
717
+ const createdBlocks = items.map((content, index) => {
718
+ /**
719
+ * Import the content using the target tool's conversion config
720
+ */
721
+ const importedData = typeof conversionImport === 'function'
722
+ ? conversionImport(content, targetTool?.settings)
723
+ : { [conversionImport as string]: content };
724
+
725
+ const newBlockData = toolboxData
726
+ ? Object.assign(importedData, toolboxData)
727
+ : importedData;
728
+
729
+ return BlockManager.insert({
730
+ tool: targetToolName,
731
+ data: newBlockData,
732
+ index: blockIndex + index,
733
+ needToFocus: false,
734
+ });
735
+ });
736
+
737
+ return createdBlocks.length > 0 ? createdBlocks[createdBlocks.length - 1] : null;
738
+ }
739
+
740
+ /**
741
+ * Attaches keydown listener to delegate navigation events to the shared flipper
742
+ * @param block - block that owns the currently focused content
743
+ */
744
+ private attachFlipperKeydownListener(block: Block): void {
745
+ this.detachFlipperKeydownListener();
746
+
747
+ const pluginsContent = block?.pluginsContent;
748
+
749
+ if (!(pluginsContent instanceof HTMLElement)) {
750
+ return;
751
+ }
752
+
753
+ this.flipperInstance.setHandleContentEditableTargets(true);
754
+
755
+ this.flipperKeydownHandler = (event: KeyboardEvent) => {
756
+ this.flipperInstance.handleExternalKeydown(event);
757
+ };
758
+
759
+ pluginsContent.addEventListener('keydown', this.flipperKeydownHandler, true);
760
+ this.flipperKeydownSource = pluginsContent;
761
+ }
762
+
763
+ /**
764
+ * Removes keydown listener from the previously active block
765
+ */
766
+ private detachFlipperKeydownListener(): void {
767
+ if (this.flipperKeydownSource !== null && this.flipperKeydownHandler !== null) {
768
+ this.flipperKeydownSource.removeEventListener('keydown', this.flipperKeydownHandler, true);
769
+ }
770
+
771
+ this.flipperInstance.setHandleContentEditableTargets(false);
772
+
773
+ this.flipperKeydownSource = null;
774
+ this.flipperKeydownHandler = null;
775
+ }
776
+ }