@jackuait/blok 0.4.1-beta.1 → 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-CEXLTV6f.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 +29 -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-C8XbyLHh.mjs +0 -25795
  402. package/dist/blok.umd.js +0 -181
@@ -0,0 +1,1092 @@
1
+ import { Module } from '../__module';
2
+ import { Dom as $ } from '../dom';
3
+ import { getFileExtension, isEmpty, isObject, isString, isValidMimeType, log } from '../utils';
4
+ import type {
5
+ PasteEvent,
6
+ PasteEventDetail,
7
+ SanitizerConfig,
8
+ SanitizerRule
9
+ } from '../../../types';
10
+ import type { SavedData } from '../../../types/data-formats';
11
+ import { clean, composeSanitizerConfig, sanitizeBlocks } from '../utils/sanitizer';
12
+ import type { BlockToolAdapter } from '../tools/block';
13
+
14
+ /**
15
+ * Tag substitute object.
16
+ */
17
+ interface TagSubstitute {
18
+ /**
19
+ * Name of related Tool
20
+ *
21
+ */
22
+ tool: BlockToolAdapter;
23
+
24
+ /**
25
+ * If a Tool specifies just a tag name, all the attributes will be sanitized.
26
+ * But Tool can explicitly specify sanitizer configuration for supported tags
27
+ */
28
+ sanitizationConfig?: SanitizerRule;
29
+ }
30
+
31
+ const SAFE_STRUCTURAL_TAGS = new Set([
32
+ 'table',
33
+ 'thead',
34
+ 'tbody',
35
+ 'tfoot',
36
+ 'tr',
37
+ 'th',
38
+ 'td',
39
+ 'caption',
40
+ 'colgroup',
41
+ 'col',
42
+ 'ul',
43
+ 'ol',
44
+ 'li',
45
+ 'dl',
46
+ 'dt',
47
+ 'dd',
48
+ ]);
49
+
50
+ /**
51
+ * Pattern substitute object.
52
+ */
53
+ interface PatternSubstitute {
54
+ /**
55
+ * Pattern`s key
56
+ */
57
+ key: string;
58
+
59
+ /**
60
+ * Pattern regexp
61
+ */
62
+ pattern: RegExp;
63
+
64
+ /**
65
+ * Name of related Tool
66
+ */
67
+ tool: BlockToolAdapter;
68
+ }
69
+
70
+ /**
71
+ * Files` types substitutions object.
72
+ */
73
+ interface FilesSubstitution {
74
+ /**
75
+ * Array of file extensions Tool can handle
76
+ * @type {string[]}
77
+ */
78
+ extensions: string[];
79
+
80
+ /**
81
+ * Array of MIME types Tool can handle
82
+ * @type {string[]}
83
+ */
84
+ mimeTypes: string[];
85
+ }
86
+
87
+ /**
88
+ * Processed paste data object.
89
+ * @interface PasteData
90
+ */
91
+ interface PasteData {
92
+ /**
93
+ * Name of related Tool
94
+ * @type {string}
95
+ */
96
+ tool: string;
97
+
98
+ /**
99
+ * Pasted data. Processed and wrapped to HTML element
100
+ * @type {HTMLElement}
101
+ */
102
+ content: HTMLElement;
103
+
104
+ /**
105
+ * Pasted data
106
+ */
107
+ event: PasteEvent;
108
+
109
+ /**
110
+ * True if content should be inserted as new Block
111
+ * @type {boolean}
112
+ */
113
+ isBlock: boolean;
114
+ }
115
+
116
+ /**
117
+ * @class Paste
118
+ * @classdesc Contains methods to handle paste on blok
119
+ * @module Paste
120
+ * @version 2.0.0
121
+ */
122
+ export class Paste extends Module {
123
+ /** If string`s length is greater than this number we don't check paste patterns */
124
+ public static readonly PATTERN_PROCESSING_MAX_LENGTH = 450;
125
+
126
+ /** Custom Blok mime-type to handle in-blok copy/paste actions */
127
+ public readonly MIME_TYPE = 'application/x-blok';
128
+
129
+ /**
130
+ * Tags` substitutions parameters
131
+ */
132
+ private toolsTags: { [tag: string]: TagSubstitute } = {};
133
+
134
+ /**
135
+ * Store tags to substitute by tool name
136
+ */
137
+ private tagsByTool: { [tools: string]: string[] } = {};
138
+
139
+ /** Patterns` substitutions parameters */
140
+ private toolsPatterns: PatternSubstitute[] = [];
141
+
142
+ /** Files` substitutions parameters */
143
+ private toolsFiles: {
144
+ [tool: string]: FilesSubstitution;
145
+ } = {};
146
+
147
+ /**
148
+ * List of tools which do not need a paste handling
149
+ */
150
+ private exceptionList: string[] = [];
151
+
152
+ /**
153
+ * Set onPaste callback and collect tools` paste configurations
154
+ */
155
+ public async prepare(): Promise<void> {
156
+ this.processTools();
157
+ }
158
+
159
+ /**
160
+ * Determines whether current block should be replaced by the pasted file tool.
161
+ * @param toolName - tool that is going to handle the file
162
+ */
163
+ private shouldReplaceCurrentBlockForFile(toolName?: string): boolean {
164
+ const { BlockManager } = this.Blok;
165
+ const currentBlock = BlockManager.currentBlock;
166
+
167
+ if (!currentBlock) {
168
+ return false;
169
+ }
170
+
171
+ if (toolName && currentBlock.name === toolName) {
172
+ return true;
173
+ }
174
+
175
+ const isCurrentBlockDefault = Boolean(currentBlock.tool.isDefault);
176
+
177
+ return isCurrentBlockDefault && currentBlock.isEmpty;
178
+ }
179
+
180
+ /**
181
+ * Builds sanitize config that keeps structural tags such as tables and lists intact.
182
+ * @param node - root node to inspect
183
+ */
184
+ private getStructuralTagsSanitizeConfig(node: HTMLElement): SanitizerConfig {
185
+ const config: SanitizerConfig = {} as SanitizerConfig;
186
+ const nodesToProcess: Element[] = [ node ];
187
+
188
+ while (nodesToProcess.length > 0) {
189
+ const current = nodesToProcess.pop();
190
+
191
+ if (!current) {
192
+ continue;
193
+ }
194
+
195
+ const tagName = current.tagName.toLowerCase();
196
+
197
+ if (SAFE_STRUCTURAL_TAGS.has(tagName)) {
198
+ config[tagName] = config[tagName] ?? {};
199
+ }
200
+
201
+ nodesToProcess.push(...Array.from(current.children));
202
+ }
203
+
204
+ return config;
205
+ }
206
+
207
+ /**
208
+ * Set read-only state
209
+ * @param {boolean} readOnlyEnabled - read only flag value
210
+ */
211
+ public toggleReadOnly(readOnlyEnabled: boolean): void {
212
+ if (!readOnlyEnabled) {
213
+ this.setCallback();
214
+ } else {
215
+ this.unsetCallback();
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Determines whether provided DataTransfer contains file-like entries
221
+ * @param dataTransfer - data transfer payload to inspect
222
+ */
223
+ private containsFiles(dataTransfer: DataTransfer): boolean {
224
+ const types = Array.from(dataTransfer.types);
225
+
226
+ /**
227
+ * Common case: browser exposes explicit "Files" entry
228
+ */
229
+ if (types.includes('Files')) {
230
+ return true;
231
+ }
232
+
233
+ /**
234
+ * File uploads sometimes omit `types` and set files directly
235
+ */
236
+ if (dataTransfer.files?.length) {
237
+ return true;
238
+ }
239
+
240
+ try {
241
+ const legacyList = dataTransfer.types as unknown as DOMStringList;
242
+
243
+ if (typeof legacyList?.contains === 'function' && legacyList.contains('Files')) {
244
+ return true;
245
+ }
246
+ } catch {
247
+ // ignore and fallthrough
248
+ }
249
+
250
+ return false;
251
+ }
252
+
253
+ /**
254
+ * Handle pasted data transfer object
255
+ * @param {DataTransfer} dataTransfer - pasted data transfer object
256
+ */
257
+ public async processDataTransfer(dataTransfer: DataTransfer): Promise<void> {
258
+ const { Tools } = this.Blok;
259
+ const includesFiles = this.containsFiles(dataTransfer);
260
+
261
+ if (includesFiles && !isEmpty(this.toolsFiles)) {
262
+ await this.processFiles(dataTransfer.files);
263
+
264
+ return;
265
+ }
266
+
267
+ const blokData = dataTransfer.getData(this.MIME_TYPE);
268
+ const plainData = dataTransfer.getData('text/plain');
269
+ const rawHtmlData = dataTransfer.getData('text/html');
270
+ const normalizedHtmlData = rawHtmlData;
271
+
272
+ /**
273
+ * If Blok json is passed, insert it
274
+ */
275
+ if (blokData) {
276
+ try {
277
+ this.insertBlokData(JSON.parse(blokData));
278
+
279
+ return;
280
+ } catch (_e) { } // Do nothing and continue execution as usual if error appears
281
+ }
282
+
283
+ /** Add all tags that can be substituted to sanitizer configuration */
284
+ const toolsTags = Object.fromEntries(
285
+ Object.keys(this.toolsTags).map((tag) => [
286
+ tag.toLowerCase(),
287
+ this.toolsTags[tag].sanitizationConfig ?? {},
288
+ ])
289
+ ) as SanitizerConfig;
290
+
291
+ const inlineSanitizeConfig = Tools.getAllInlineToolsSanitizeConfig();
292
+ const customConfig = composeSanitizerConfig(
293
+ this.config.sanitizer as SanitizerConfig,
294
+ toolsTags,
295
+ inlineSanitizeConfig,
296
+ { br: {} }
297
+ );
298
+ const cleanData = clean(normalizedHtmlData, customConfig);
299
+ const cleanDataIsHtml = $.isHTMLString(cleanData);
300
+ const shouldProcessAsPlain = !cleanData.trim() || (cleanData.trim() === plainData || !cleanDataIsHtml);
301
+
302
+ /** If there is no HTML or HTML string is equal to plain one, process it as plain text */
303
+ if (shouldProcessAsPlain) {
304
+ await this.processText(plainData);
305
+ } else {
306
+ await this.processText(cleanData, true);
307
+ }
308
+ }
309
+
310
+ /**
311
+ * Process pasted text and divide them into Blocks
312
+ * @param {string} data - text to process. Can be HTML or plain.
313
+ * @param {boolean} isHTML - if passed string is HTML, this parameter should be true
314
+ */
315
+ public async processText(data: string, isHTML = false): Promise<void> {
316
+ const { Caret, BlockManager } = this.Blok;
317
+ const dataToInsert = isHTML ? this.processHTML(data) : this.processPlain(data);
318
+
319
+ if (!dataToInsert.length) {
320
+ return;
321
+ }
322
+
323
+ if (dataToInsert.length > 1) {
324
+ const isCurrentBlockDefault = Boolean(BlockManager.currentBlock?.tool.isDefault);
325
+ const needToReplaceCurrentBlock = isCurrentBlockDefault && Boolean(BlockManager.currentBlock?.isEmpty);
326
+
327
+ for (const [index, content] of dataToInsert.entries()) {
328
+ await this.insertBlock(content, index === 0 && needToReplaceCurrentBlock);
329
+ }
330
+
331
+ BlockManager.currentBlock &&
332
+ Caret.setToBlock(BlockManager.currentBlock, Caret.positions.END);
333
+
334
+ return;
335
+ }
336
+
337
+ const [ singleItem ] = dataToInsert;
338
+
339
+ if (singleItem.isBlock) {
340
+ await this.processSingleBlock(singleItem);
341
+
342
+ return;
343
+ }
344
+
345
+ await this.processInlinePaste(singleItem);
346
+ }
347
+
348
+ /**
349
+ * Wrapper handler for paste event that matches listeners.on signature
350
+ * @param {Event} event - paste event
351
+ */
352
+ private handlePasteEventWrapper = (event: Event): void => {
353
+ void this.handlePasteEvent(event as ClipboardEvent);
354
+ };
355
+
356
+ /**
357
+ * Set onPaste callback handler
358
+ */
359
+ private setCallback(): void {
360
+ this.listeners.on(this.Blok.UI.nodes.holder, 'paste', this.handlePasteEventWrapper);
361
+ }
362
+
363
+ /**
364
+ * Unset onPaste callback handler
365
+ */
366
+ private unsetCallback(): void {
367
+ this.listeners.off(this.Blok.UI.nodes.holder, 'paste', this.handlePasteEventWrapper);
368
+ }
369
+
370
+ /**
371
+ * Get and process tool`s paste configs
372
+ */
373
+ private processTools(): void {
374
+ const tools = this.Blok.Tools.blockTools;
375
+
376
+ Array
377
+ .from(tools.values())
378
+ .forEach(this.processTool);
379
+ }
380
+
381
+ /**
382
+ * Process paste config for each tool
383
+ * @param tool - BlockTool object
384
+ */
385
+ private processTool = (tool: BlockToolAdapter): void => {
386
+ try {
387
+ if (tool.pasteConfig === false) {
388
+ this.exceptionList.push(tool.name);
389
+
390
+ return;
391
+ }
392
+
393
+ if (!tool.hasOnPasteHandler) {
394
+ return;
395
+ }
396
+
397
+ this.getTagsConfig(tool);
398
+ this.getFilesConfig(tool);
399
+ this.getPatternsConfig(tool);
400
+ } catch (e) {
401
+ log(
402
+ `Paste handling for «${tool.name}» Tool hasn't been set up because of the error`,
403
+ 'warn',
404
+ e
405
+ );
406
+ }
407
+ };
408
+
409
+ /**
410
+ * Get tags name list from either tag name or sanitization config.
411
+ * @param {string | object} tagOrSanitizeConfig - tag name or sanitize config object.
412
+ * @returns {string[]} array of tags.
413
+ */
414
+ private collectTagNames(tagOrSanitizeConfig: string | SanitizerConfig): string[] {
415
+ /**
416
+ * If string, then it is a tag name.
417
+ */
418
+ if (isString(tagOrSanitizeConfig)) {
419
+ return [ tagOrSanitizeConfig ];
420
+ }
421
+ /**
422
+ * If object, then its keys are tags.
423
+ */
424
+ if (isObject(tagOrSanitizeConfig)) {
425
+ return Object.keys(tagOrSanitizeConfig);
426
+ }
427
+
428
+ /** Return empty tag list */
429
+ return [];
430
+ }
431
+
432
+ /**
433
+ * Get tags to substitute by Tool
434
+ * @param tool - BlockTool object
435
+ */
436
+ private getTagsConfig(tool: BlockToolAdapter): void {
437
+ if (tool.pasteConfig === false) {
438
+ return;
439
+ }
440
+
441
+ const tagsOrSanitizeConfigs = tool.pasteConfig.tags || [];
442
+ const toolTags: string[] = [];
443
+
444
+ tagsOrSanitizeConfigs.forEach((tagOrSanitizeConfig) => {
445
+ const tags = this.collectTagNames(tagOrSanitizeConfig);
446
+
447
+ /**
448
+ * Add tags to toolTags array
449
+ */
450
+ toolTags.push(...tags);
451
+ tags.forEach((tag) => {
452
+ if (Object.prototype.hasOwnProperty.call(this.toolsTags, tag)) {
453
+ log(
454
+ `Paste handler for «${tool.name}» Tool on «${tag}» tag is skipped ` +
455
+ `because it is already used by «${this.toolsTags[tag].tool.name}» Tool.`,
456
+ 'warn'
457
+ );
458
+
459
+ return;
460
+ }
461
+ /**
462
+ * Get sanitize config for tag.
463
+ */
464
+ const sanitizationConfig = isObject(tagOrSanitizeConfig) ? tagOrSanitizeConfig[tag] : undefined;
465
+
466
+ this.toolsTags[tag.toUpperCase()] = {
467
+ tool,
468
+ sanitizationConfig,
469
+ };
470
+ });
471
+ });
472
+
473
+ this.tagsByTool[tool.name] = toolTags.map((t) => t.toUpperCase());
474
+ }
475
+
476
+ /**
477
+ * Get files` types and extensions to substitute by Tool
478
+ * @param tool - BlockTool object
479
+ */
480
+ private getFilesConfig(tool: BlockToolAdapter): void {
481
+ if (tool.pasteConfig === false) {
482
+ return;
483
+ }
484
+
485
+ const { files = {} } = tool.pasteConfig;
486
+ const { extensions: rawExtensions, mimeTypes: rawMimeTypes } = files;
487
+
488
+ if (!rawExtensions && !rawMimeTypes) {
489
+ return;
490
+ }
491
+
492
+ const normalizedExtensions = (() => {
493
+ if (rawExtensions == null) {
494
+ return [];
495
+ }
496
+
497
+ if (Array.isArray(rawExtensions)) {
498
+ return rawExtensions;
499
+ }
500
+
501
+ log(`«extensions» property of the paste config for «${tool.name}» Tool should be an array`);
502
+
503
+ return [];
504
+ })();
505
+
506
+ const normalizedMimeTypes = (() => {
507
+ if (rawMimeTypes == null) {
508
+ return [];
509
+ }
510
+
511
+ if (!Array.isArray(rawMimeTypes)) {
512
+ log(`«mimeTypes» property of the paste config for «${tool.name}» Tool should be an array`);
513
+
514
+ return [];
515
+ }
516
+
517
+ return rawMimeTypes.filter((type) => {
518
+ if (!isValidMimeType(type)) {
519
+ log(`MIME type value «${type}» for the «${tool.name}» Tool is not a valid MIME type`, 'warn');
520
+
521
+ return false;
522
+ }
523
+
524
+ return true;
525
+ });
526
+ })();
527
+
528
+ this.toolsFiles[tool.name] = {
529
+ extensions: normalizedExtensions,
530
+ mimeTypes: normalizedMimeTypes,
531
+ };
532
+ }
533
+
534
+ /**
535
+ * Get RegExp patterns to substitute by Tool
536
+ * @param tool - BlockTool object
537
+ */
538
+ private getPatternsConfig(tool: BlockToolAdapter): void {
539
+ if (
540
+ tool.pasteConfig === false ||
541
+ !tool.pasteConfig.patterns ||
542
+ isEmpty(tool.pasteConfig.patterns)
543
+ ) {
544
+ return;
545
+ }
546
+
547
+ Object.entries(tool.pasteConfig.patterns).forEach(([key, pattern]: [string, RegExp]) => {
548
+ /** Still need to validate pattern as it provided by user */
549
+ if (!(pattern instanceof RegExp)) {
550
+ log(
551
+ `Pattern ${pattern} for «${tool.name}» Tool is skipped because it should be a Regexp instance.`,
552
+ 'warn'
553
+ );
554
+ }
555
+
556
+ this.toolsPatterns.push({
557
+ key,
558
+ pattern,
559
+ tool,
560
+ });
561
+ });
562
+ }
563
+
564
+ /**
565
+ * Check if browser behavior suits better
566
+ * @param {EventTarget} element - element where content has been pasted
567
+ * @returns {boolean}
568
+ */
569
+ private isNativeBehaviour(element: EventTarget): boolean {
570
+ return $.isNativeInput(element);
571
+ }
572
+
573
+ /**
574
+ * Check if Blok should process pasted data and pass data transfer object to handler
575
+ * @param {ClipboardEvent} event - clipboard event
576
+ */
577
+ private handlePasteEvent = async (event: ClipboardEvent): Promise<void> => {
578
+ const { BlockManager, Toolbar } = this.Blok;
579
+
580
+ /**
581
+ * When someone pasting into a block, its more stable to set current block by event target, instead of relying on current block set before
582
+ */
583
+ const currentBlock = BlockManager.setCurrentBlockByChildNode(event.target as HTMLElement);
584
+
585
+ /** If target is native input or is not Block, use browser behaviour */
586
+ if (
587
+ !currentBlock || (event.target && this.isNativeBehaviour(event.target) && event.clipboardData && !event.clipboardData.types.includes('Files'))
588
+ ) {
589
+ return;
590
+ }
591
+
592
+ /**
593
+ * If Tools is in list of errors, skip processing of paste event
594
+ */
595
+ if (this.exceptionList.includes(currentBlock.name)) {
596
+ return;
597
+ }
598
+
599
+ event.preventDefault();
600
+ if (event.clipboardData) {
601
+ await this.processDataTransfer(event.clipboardData);
602
+ }
603
+
604
+ Toolbar.close();
605
+ };
606
+
607
+ /**
608
+ * Get files from data transfer object and insert related Tools
609
+ * @param {FileList} items - pasted items
610
+ */
611
+ private async processFiles(items: FileList): Promise<void> {
612
+ const { BlockManager } = this.Blok;
613
+
614
+ const processedFiles = await Promise.all(
615
+ Array
616
+ .from(items)
617
+ .map((item) => this.processFile(item))
618
+ );
619
+ const dataToInsert = processedFiles.filter((data): data is { type: string; event: PasteEvent } => data != null);
620
+
621
+ if (dataToInsert.length === 0) {
622
+ return;
623
+ }
624
+
625
+ const shouldReplaceCurrentBlock = this.shouldReplaceCurrentBlockForFile(dataToInsert[0]?.type);
626
+
627
+ for (const [index, data] of dataToInsert.entries()) {
628
+ await BlockManager.paste(data.type, data.event, index === 0 && shouldReplaceCurrentBlock);
629
+ }
630
+ }
631
+
632
+ /**
633
+ * Get information about file and find Tool to handle it
634
+ * @param {File} file - file to process
635
+ */
636
+ private async processFile(file: File): Promise<{ event: PasteEvent; type: string } | undefined> {
637
+ const extension = getFileExtension(file);
638
+
639
+ const foundConfig = Object
640
+ .entries(this.toolsFiles)
641
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
642
+ .find(([toolName, { mimeTypes, extensions } ]) => {
643
+ const [fileType, fileSubtype] = file.type.split('/');
644
+
645
+ const foundExt = extensions.find((ext) => ext.toLowerCase() === extension.toLowerCase());
646
+ const foundMimeType = mimeTypes.find((mime) => {
647
+ const [type, subtype] = mime.split('/');
648
+
649
+ return type === fileType && (subtype === fileSubtype || subtype === '*');
650
+ });
651
+
652
+ return foundExt !== undefined || foundMimeType !== undefined;
653
+ });
654
+
655
+ if (!foundConfig) {
656
+ return;
657
+ }
658
+
659
+ const [ tool ] = foundConfig;
660
+ const pasteEvent = this.composePasteEvent('file', {
661
+ file,
662
+ });
663
+
664
+ return {
665
+ event: pasteEvent,
666
+ type: tool,
667
+ };
668
+ }
669
+
670
+ /**
671
+ * Split HTML string to blocks and return it as array of Block data
672
+ * @param {string} innerHTML - html string to process
673
+ * @returns {PasteData[]}
674
+ */
675
+ private processHTML(innerHTML: string): PasteData[] {
676
+ const { Tools } = this.Blok;
677
+
678
+ /**
679
+ * @todo Research, do we really need to always wrap innerHTML to a div:
680
+ * - <img> tag could be processed separately, but for now it becomes div-wrapped
681
+ * and then .getNodes() returns strange: [document-fragment, img]
682
+ * (description of the method says that it should should return only block tags or fragments,
683
+ * but there are inline-block element along with redundant empty fragment)
684
+ * - probably this is a reason of bugs with unexpected new block creation instead of inline pasting:
685
+ * - https://github.com/codex-team/editor.js/issues/1427
686
+ * - https://github.com/codex-team/editor.js/issues/1244
687
+ * - https://github.com/codex-team/editor.js/issues/740
688
+ */
689
+ const wrapper = $.make('DIV');
690
+
691
+ wrapper.innerHTML = innerHTML;
692
+
693
+ const nodes = this.getNodes(wrapper);
694
+
695
+ return nodes
696
+ .map((node) => {
697
+ const nodeData = (() => {
698
+ switch (node.nodeType) {
699
+ case Node.DOCUMENT_FRAGMENT_NODE: {
700
+ const fragmentWrapper = $.make('div');
701
+
702
+ fragmentWrapper.appendChild(node);
703
+
704
+ return {
705
+ content: fragmentWrapper,
706
+ tool: Tools.defaultTool,
707
+ isBlock: false,
708
+ };
709
+ }
710
+
711
+ case Node.ELEMENT_NODE: {
712
+ const elementContent = node as HTMLElement;
713
+ const tagSubstitute = this.toolsTags[elementContent.tagName];
714
+
715
+ return {
716
+ content: elementContent,
717
+ tool: tagSubstitute?.tool ?? Tools.defaultTool,
718
+ isBlock: true,
719
+ };
720
+ }
721
+
722
+ default:
723
+ return null;
724
+ }
725
+ })();
726
+
727
+ if (!nodeData) {
728
+ return null;
729
+ }
730
+
731
+ const { content, tool, isBlock } = nodeData;
732
+
733
+ const tagsOrSanitizeConfigs = tool.pasteConfig === false
734
+ ? []
735
+ : (tool.pasteConfig?.tags || []);
736
+
737
+ const toolTags = tagsOrSanitizeConfigs.reduce<SanitizerConfig>((result, tagOrSanitizeConfig) => {
738
+ const tags = this.collectTagNames(tagOrSanitizeConfig);
739
+ const nextResult: SanitizerConfig = { ...result };
740
+
741
+ tags.forEach((tag) => {
742
+ const sanitizationConfig = isObject(tagOrSanitizeConfig)
743
+ ? (tagOrSanitizeConfig as SanitizerConfig)[tag]
744
+ : null;
745
+
746
+ nextResult[tag.toLowerCase()] = sanitizationConfig ?? {};
747
+ });
748
+
749
+ return nextResult;
750
+ }, {} as SanitizerConfig);
751
+
752
+ const structuralSanitizeConfig = this.getStructuralTagsSanitizeConfig(content);
753
+ const customConfig = Object.assign({}, structuralSanitizeConfig, toolTags, tool.baseSanitizeConfig);
754
+ const sanitizedContent = (() => {
755
+ if (content.tagName.toLowerCase() !== 'table') {
756
+ content.innerHTML = clean(content.innerHTML, customConfig);
757
+
758
+ return content;
759
+ }
760
+
761
+ const cleanTableHTML = clean(content.outerHTML, customConfig);
762
+ const tmpWrapper = $.make('div', undefined, {
763
+ innerHTML: cleanTableHTML,
764
+ });
765
+ const firstChild = tmpWrapper.firstChild;
766
+
767
+ if (!firstChild || !(firstChild instanceof HTMLElement)) {
768
+ return null;
769
+ }
770
+
771
+ return firstChild;
772
+ })();
773
+
774
+ if (!sanitizedContent) {
775
+ return null;
776
+ }
777
+
778
+ const event = this.composePasteEvent('tag', {
779
+ data: sanitizedContent,
780
+ });
781
+
782
+ return {
783
+ content: sanitizedContent,
784
+ isBlock,
785
+ tool: tool.name,
786
+ event,
787
+ };
788
+ })
789
+ .filter((data): data is PasteData => {
790
+ if (!data) {
791
+ return false;
792
+ }
793
+ const isContentEmpty = $.isEmpty(data.content);
794
+ const isSingleTag = $.isSingleTag(data.content);
795
+
796
+ return !isContentEmpty || isSingleTag;
797
+ });
798
+ }
799
+
800
+ /**
801
+ * Split plain text by new line symbols and return it as array of Block data
802
+ * @param {string} plain - string to process
803
+ * @returns {PasteData[]}
804
+ */
805
+ private processPlain(plain: string): PasteData[] {
806
+ const { defaultBlock } = this.config as { defaultBlock: string };
807
+
808
+ if (!plain) {
809
+ return [];
810
+ }
811
+
812
+ const tool = defaultBlock;
813
+
814
+ return plain
815
+ .split(/\r?\n/)
816
+ .filter((text) => text.trim())
817
+ .map((text) => {
818
+ const content = $.make('div');
819
+
820
+ content.textContent = text;
821
+
822
+ const event = this.composePasteEvent('tag', {
823
+ data: content,
824
+ });
825
+
826
+ return {
827
+ content,
828
+ tool,
829
+ isBlock: false,
830
+ event,
831
+ };
832
+ });
833
+ }
834
+
835
+ /**
836
+ * Process paste of single Block tool content
837
+ * @param {PasteData} dataToInsert - data of Block to insert
838
+ */
839
+ private async processSingleBlock(dataToInsert: PasteData): Promise<void> {
840
+ const { Caret, BlockManager } = this.Blok;
841
+ const { currentBlock } = BlockManager;
842
+
843
+ /**
844
+ * If pasted tool isn`t equal current Block or if pasted content contains block elements, insert it as new Block
845
+ */
846
+ if (
847
+ !currentBlock ||
848
+ dataToInsert.tool !== currentBlock.name ||
849
+ !$.containsOnlyInlineElements(dataToInsert.content.innerHTML)
850
+ ) {
851
+ await this.insertBlock(dataToInsert, currentBlock ? (currentBlock.tool.isDefault && currentBlock.isEmpty) : false);
852
+
853
+ return;
854
+ }
855
+
856
+ Caret.insertContentAtCaretPosition(dataToInsert.content.innerHTML);
857
+ }
858
+
859
+ /**
860
+ * Process paste to single Block:
861
+ * 1. Find patterns` matches
862
+ * 2. Insert new block if it is not the same type as current one
863
+ * 3. Just insert text if there is no substitutions
864
+ * @param {PasteData} dataToInsert - data of Block to insert
865
+ */
866
+ private async processInlinePaste(dataToInsert: PasteData): Promise<void> {
867
+ const { BlockManager, Caret } = this.Blok;
868
+ const { content } = dataToInsert;
869
+
870
+ const currentBlockIsDefault = BlockManager.currentBlock?.tool.isDefault ?? false;
871
+ const textContent = content.textContent;
872
+
873
+ const canProcessPattern = currentBlockIsDefault &&
874
+ textContent !== null &&
875
+ textContent.length < Paste.PATTERN_PROCESSING_MAX_LENGTH;
876
+
877
+ const blockData = canProcessPattern && textContent !== null
878
+ ? await this.processPattern(textContent)
879
+ : undefined;
880
+
881
+ if (blockData) {
882
+ const needToReplaceCurrentBlock = BlockManager.currentBlock &&
883
+ BlockManager.currentBlock.tool.isDefault &&
884
+ BlockManager.currentBlock.isEmpty;
885
+
886
+ const insertedBlock = await BlockManager.paste(blockData.tool, blockData.event, needToReplaceCurrentBlock);
887
+
888
+ Caret.setToBlock(insertedBlock, Caret.positions.END);
889
+
890
+ return;
891
+ }
892
+
893
+ /** If there is no pattern substitute - insert string as it is */
894
+ if (BlockManager.currentBlock && BlockManager.currentBlock.currentInput) {
895
+ const currentToolSanitizeConfig = BlockManager.currentBlock.tool.baseSanitizeConfig;
896
+
897
+ Caret.insertContentAtCaretPosition(
898
+ clean(content.innerHTML, currentToolSanitizeConfig)
899
+ );
900
+ } else {
901
+ await this.insertBlock(dataToInsert);
902
+ }
903
+ }
904
+
905
+ /**
906
+ * Get patterns` matches
907
+ * @param {string} text - text to process
908
+ * @returns {Promise<{event: PasteEvent, tool: string}>}
909
+ */
910
+ private async processPattern(text: string): Promise<{ event: PasteEvent; tool: string } | undefined> {
911
+ const pattern = this.toolsPatterns.find((substitute) => {
912
+ const execResult = substitute.pattern.exec(text);
913
+
914
+ if (!execResult) {
915
+ return false;
916
+ }
917
+
918
+ return text === execResult.shift();
919
+ });
920
+
921
+ if (!pattern) {
922
+ return;
923
+ }
924
+
925
+ const event = this.composePasteEvent('pattern', {
926
+ key: pattern.key,
927
+ data: text,
928
+ });
929
+
930
+ return {
931
+ event,
932
+ tool: pattern.tool.name,
933
+ };
934
+ }
935
+
936
+ /**
937
+ * Insert pasted Block content to Blok
938
+ * @param {PasteData} data - data to insert
939
+ * @param {boolean} canReplaceCurrentBlock - if true and is current Block is empty, will replace current Block
940
+ * @returns {void}
941
+ */
942
+ private async insertBlock(data: PasteData, canReplaceCurrentBlock = false): Promise<void> {
943
+ const { BlockManager, Caret } = this.Blok;
944
+ const { currentBlock } = BlockManager;
945
+
946
+ if (canReplaceCurrentBlock && currentBlock && currentBlock.isEmpty) {
947
+ const replacedBlock = await BlockManager.paste(data.tool, data.event, true);
948
+
949
+ Caret.setToBlock(replacedBlock, Caret.positions.END);
950
+
951
+ return;
952
+ }
953
+
954
+ const block = await BlockManager.paste(data.tool, data.event);
955
+
956
+ Caret.setToBlock(block, Caret.positions.END);
957
+ }
958
+
959
+ /**
960
+ * Insert data passed as application/x-blok JSON
961
+ * @param {Array} blocks — Blocks' data to insert
962
+ * @returns {void}
963
+ */
964
+ private insertBlokData(blocks: Pick<SavedData, 'id' | 'data' | 'tool'>[]): void {
965
+ const { BlockManager, Caret, Tools } = this.Blok;
966
+ const sanitizedBlocks = sanitizeBlocks(
967
+ blocks,
968
+ (name) => Tools.blockTools.get(name)?.sanitizeConfig ?? {},
969
+ this.config.sanitizer as SanitizerConfig
970
+ );
971
+
972
+ sanitizedBlocks.forEach(({ tool, data }, i) => {
973
+ const needToReplaceCurrentBlock = i === 0 &&
974
+ Boolean(BlockManager.currentBlock?.tool.isDefault) &&
975
+ Boolean(BlockManager.currentBlock?.isEmpty);
976
+
977
+ const block = BlockManager.insert({
978
+ tool,
979
+ data,
980
+ replace: needToReplaceCurrentBlock,
981
+ });
982
+
983
+ Caret.setToBlock(block, Caret.positions.END);
984
+ });
985
+ }
986
+
987
+ /**
988
+ * Fetch nodes from Element node
989
+ * @param {Node} node - current node
990
+ * @param {Node[]} nodes - processed nodes
991
+ * @param {Node} destNode - destination node
992
+ */
993
+ private processElementNode(node: Node, nodes: Node[], destNode: Node): Node[] | void {
994
+ const tags = Object.keys(this.toolsTags);
995
+
996
+ const element = node as HTMLElement;
997
+
998
+ const tagSubstitute = this.toolsTags[element.tagName];
999
+ const tool = tagSubstitute?.tool;
1000
+ const toolTags = this.tagsByTool[tool?.name ?? ''] ?? [];
1001
+
1002
+ const isSubstitutable = tags.includes(element.tagName);
1003
+ const isBlockElement = $.blockElements.includes(element.tagName.toLowerCase());
1004
+ const isStructuralElement = SAFE_STRUCTURAL_TAGS.has(element.tagName.toLowerCase());
1005
+ const containsAnotherToolTags = Array
1006
+ .from(element.children)
1007
+ .some(
1008
+ ({ tagName }) => tags.includes(tagName) && !toolTags.includes(tagName)
1009
+ );
1010
+
1011
+ const containsBlockElements = Array.from(element.children).some(
1012
+ ({ tagName }) => $.blockElements.includes(tagName.toLowerCase())
1013
+ );
1014
+
1015
+ /** Append inline elements to previous fragment */
1016
+ if (!isBlockElement && !isSubstitutable && !containsAnotherToolTags) {
1017
+ destNode.appendChild(element);
1018
+
1019
+ return [...nodes, destNode];
1020
+ }
1021
+
1022
+ if (
1023
+ (isSubstitutable && !containsAnotherToolTags) ||
1024
+ (isBlockElement && !containsBlockElements && !containsAnotherToolTags) ||
1025
+ (isStructuralElement && !containsAnotherToolTags)
1026
+ ) {
1027
+ return [...nodes, destNode, element];
1028
+ }
1029
+ }
1030
+
1031
+ /**
1032
+ * Recursively divide HTML string to two types of nodes:
1033
+ * 1. Block element
1034
+ * 2. Document Fragments contained text and markup tags like a, b, i etc.
1035
+ * @param {Node} wrapper - wrapper of paster HTML content
1036
+ * @returns {Node[]}
1037
+ */
1038
+ private getNodes(wrapper: Node): Node[] {
1039
+ const children = Array.from(wrapper.childNodes);
1040
+
1041
+ const reducer = (nodes: Node[], node: Node): Node[] => {
1042
+ if ($.isEmpty(node) && !$.isSingleTag(node as HTMLElement)) {
1043
+ return nodes;
1044
+ }
1045
+
1046
+ const lastNode = nodes[nodes.length - 1];
1047
+ const isLastNodeFragment = lastNode !== undefined && $.isFragment(lastNode);
1048
+ const { destNode, remainingNodes } = isLastNodeFragment
1049
+ ? {
1050
+ destNode: lastNode,
1051
+ remainingNodes: nodes.slice(0, -1),
1052
+ }
1053
+ : {
1054
+ destNode: new DocumentFragment(),
1055
+ remainingNodes: nodes,
1056
+ };
1057
+
1058
+ if (node.nodeType === Node.TEXT_NODE) {
1059
+ destNode.appendChild(node);
1060
+
1061
+ return [...remainingNodes, destNode];
1062
+ }
1063
+
1064
+ if (node.nodeType !== Node.ELEMENT_NODE) {
1065
+ return [...remainingNodes, destNode];
1066
+ }
1067
+
1068
+ const elementNodeProcessingResult = this.processElementNode(node, remainingNodes, destNode);
1069
+
1070
+ if (elementNodeProcessingResult) {
1071
+ return elementNodeProcessingResult;
1072
+ }
1073
+
1074
+ const processedChildNodes = Array.from(node.childNodes).reduce(reducer, []);
1075
+
1076
+ return [...remainingNodes, ...processedChildNodes];
1077
+ };
1078
+
1079
+ return children.reduce(reducer, []);
1080
+ }
1081
+
1082
+ /**
1083
+ * Compose paste event with passed type and detail
1084
+ * @param {string} type - event type
1085
+ * @param {PasteEventDetail} detail - event detail
1086
+ */
1087
+ private composePasteEvent(type: string, detail: PasteEventDetail): PasteEvent {
1088
+ return new CustomEvent(type, {
1089
+ detail,
1090
+ }) as PasteEvent;
1091
+ }
1092
+ }