@ckeditor/ckeditor5-emoji 0.0.0-nightly-20250217.0 → 0.0.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 (406) hide show
  1. package/LICENSE.md +5 -16
  2. package/README.md +3 -30
  3. package/package.json +5 -58
  4. package/CHANGELOG.md +0 -4
  5. package/build/emoji.js +0 -5
  6. package/build/translations/af.js +0 -1
  7. package/build/translations/ar.js +0 -1
  8. package/build/translations/ast.js +0 -1
  9. package/build/translations/az.js +0 -1
  10. package/build/translations/bg.js +0 -1
  11. package/build/translations/bn.js +0 -1
  12. package/build/translations/bs.js +0 -1
  13. package/build/translations/ca.js +0 -1
  14. package/build/translations/cs.js +0 -1
  15. package/build/translations/da.js +0 -1
  16. package/build/translations/de-ch.js +0 -1
  17. package/build/translations/de.js +0 -1
  18. package/build/translations/el.js +0 -1
  19. package/build/translations/en-au.js +0 -1
  20. package/build/translations/en-gb.js +0 -1
  21. package/build/translations/eo.js +0 -1
  22. package/build/translations/es-co.js +0 -1
  23. package/build/translations/es.js +0 -1
  24. package/build/translations/et.js +0 -1
  25. package/build/translations/eu.js +0 -1
  26. package/build/translations/fa.js +0 -1
  27. package/build/translations/fi.js +0 -1
  28. package/build/translations/fr.js +0 -1
  29. package/build/translations/gl.js +0 -1
  30. package/build/translations/gu.js +0 -1
  31. package/build/translations/he.js +0 -1
  32. package/build/translations/hi.js +0 -1
  33. package/build/translations/hr.js +0 -1
  34. package/build/translations/hu.js +0 -1
  35. package/build/translations/hy.js +0 -1
  36. package/build/translations/id.js +0 -1
  37. package/build/translations/it.js +0 -1
  38. package/build/translations/ja.js +0 -1
  39. package/build/translations/jv.js +0 -1
  40. package/build/translations/kk.js +0 -1
  41. package/build/translations/km.js +0 -1
  42. package/build/translations/kn.js +0 -1
  43. package/build/translations/ko.js +0 -1
  44. package/build/translations/ku.js +0 -1
  45. package/build/translations/lt.js +0 -1
  46. package/build/translations/lv.js +0 -1
  47. package/build/translations/ms.js +0 -1
  48. package/build/translations/nb.js +0 -1
  49. package/build/translations/ne.js +0 -1
  50. package/build/translations/nl.js +0 -1
  51. package/build/translations/no.js +0 -1
  52. package/build/translations/oc.js +0 -1
  53. package/build/translations/pl.js +0 -1
  54. package/build/translations/pt-br.js +0 -1
  55. package/build/translations/pt.js +0 -1
  56. package/build/translations/ro.js +0 -1
  57. package/build/translations/ru.js +0 -1
  58. package/build/translations/si.js +0 -1
  59. package/build/translations/sk.js +0 -1
  60. package/build/translations/sl.js +0 -1
  61. package/build/translations/sq.js +0 -1
  62. package/build/translations/sr-latn.js +0 -1
  63. package/build/translations/sr.js +0 -1
  64. package/build/translations/sv.js +0 -1
  65. package/build/translations/th.js +0 -1
  66. package/build/translations/ti.js +0 -1
  67. package/build/translations/tk.js +0 -1
  68. package/build/translations/tr.js +0 -1
  69. package/build/translations/tt.js +0 -1
  70. package/build/translations/ug.js +0 -1
  71. package/build/translations/uk.js +0 -1
  72. package/build/translations/ur.js +0 -1
  73. package/build/translations/uz.js +0 -1
  74. package/build/translations/vi.js +0 -1
  75. package/build/translations/zh-cn.js +0 -1
  76. package/build/translations/zh.js +0 -1
  77. package/ckeditor5-metadata.json +0 -45
  78. package/dist/index-content.css +0 -4
  79. package/dist/index-editor.css +0 -111
  80. package/dist/index.css +0 -143
  81. package/dist/index.css.map +0 -1
  82. package/dist/index.js +0 -1698
  83. package/dist/index.js.map +0 -1
  84. package/dist/translations/af.d.ts +0 -8
  85. package/dist/translations/af.js +0 -5
  86. package/dist/translations/af.umd.js +0 -11
  87. package/dist/translations/ar.d.ts +0 -8
  88. package/dist/translations/ar.js +0 -5
  89. package/dist/translations/ar.umd.js +0 -11
  90. package/dist/translations/ast.d.ts +0 -8
  91. package/dist/translations/ast.js +0 -5
  92. package/dist/translations/ast.umd.js +0 -11
  93. package/dist/translations/az.d.ts +0 -8
  94. package/dist/translations/az.js +0 -5
  95. package/dist/translations/az.umd.js +0 -11
  96. package/dist/translations/bg.d.ts +0 -8
  97. package/dist/translations/bg.js +0 -5
  98. package/dist/translations/bg.umd.js +0 -11
  99. package/dist/translations/bn.d.ts +0 -8
  100. package/dist/translations/bn.js +0 -5
  101. package/dist/translations/bn.umd.js +0 -11
  102. package/dist/translations/bs.d.ts +0 -8
  103. package/dist/translations/bs.js +0 -5
  104. package/dist/translations/bs.umd.js +0 -11
  105. package/dist/translations/ca.d.ts +0 -8
  106. package/dist/translations/ca.js +0 -5
  107. package/dist/translations/ca.umd.js +0 -11
  108. package/dist/translations/cs.d.ts +0 -8
  109. package/dist/translations/cs.js +0 -5
  110. package/dist/translations/cs.umd.js +0 -11
  111. package/dist/translations/da.d.ts +0 -8
  112. package/dist/translations/da.js +0 -5
  113. package/dist/translations/da.umd.js +0 -11
  114. package/dist/translations/de-ch.d.ts +0 -8
  115. package/dist/translations/de-ch.js +0 -5
  116. package/dist/translations/de-ch.umd.js +0 -11
  117. package/dist/translations/de.d.ts +0 -8
  118. package/dist/translations/de.js +0 -5
  119. package/dist/translations/de.umd.js +0 -11
  120. package/dist/translations/el.d.ts +0 -8
  121. package/dist/translations/el.js +0 -5
  122. package/dist/translations/el.umd.js +0 -11
  123. package/dist/translations/en-au.d.ts +0 -8
  124. package/dist/translations/en-au.js +0 -5
  125. package/dist/translations/en-au.umd.js +0 -11
  126. package/dist/translations/en-gb.d.ts +0 -8
  127. package/dist/translations/en-gb.js +0 -5
  128. package/dist/translations/en-gb.umd.js +0 -11
  129. package/dist/translations/en.d.ts +0 -8
  130. package/dist/translations/en.js +0 -5
  131. package/dist/translations/en.umd.js +0 -11
  132. package/dist/translations/eo.d.ts +0 -8
  133. package/dist/translations/eo.js +0 -5
  134. package/dist/translations/eo.umd.js +0 -11
  135. package/dist/translations/es-co.d.ts +0 -8
  136. package/dist/translations/es-co.js +0 -5
  137. package/dist/translations/es-co.umd.js +0 -11
  138. package/dist/translations/es.d.ts +0 -8
  139. package/dist/translations/es.js +0 -5
  140. package/dist/translations/es.umd.js +0 -11
  141. package/dist/translations/et.d.ts +0 -8
  142. package/dist/translations/et.js +0 -5
  143. package/dist/translations/et.umd.js +0 -11
  144. package/dist/translations/eu.d.ts +0 -8
  145. package/dist/translations/eu.js +0 -5
  146. package/dist/translations/eu.umd.js +0 -11
  147. package/dist/translations/fa.d.ts +0 -8
  148. package/dist/translations/fa.js +0 -5
  149. package/dist/translations/fa.umd.js +0 -11
  150. package/dist/translations/fi.d.ts +0 -8
  151. package/dist/translations/fi.js +0 -5
  152. package/dist/translations/fi.umd.js +0 -11
  153. package/dist/translations/fr.d.ts +0 -8
  154. package/dist/translations/fr.js +0 -5
  155. package/dist/translations/fr.umd.js +0 -11
  156. package/dist/translations/gl.d.ts +0 -8
  157. package/dist/translations/gl.js +0 -5
  158. package/dist/translations/gl.umd.js +0 -11
  159. package/dist/translations/gu.d.ts +0 -8
  160. package/dist/translations/gu.js +0 -5
  161. package/dist/translations/gu.umd.js +0 -11
  162. package/dist/translations/he.d.ts +0 -8
  163. package/dist/translations/he.js +0 -5
  164. package/dist/translations/he.umd.js +0 -11
  165. package/dist/translations/hi.d.ts +0 -8
  166. package/dist/translations/hi.js +0 -5
  167. package/dist/translations/hi.umd.js +0 -11
  168. package/dist/translations/hr.d.ts +0 -8
  169. package/dist/translations/hr.js +0 -5
  170. package/dist/translations/hr.umd.js +0 -11
  171. package/dist/translations/hu.d.ts +0 -8
  172. package/dist/translations/hu.js +0 -5
  173. package/dist/translations/hu.umd.js +0 -11
  174. package/dist/translations/hy.d.ts +0 -8
  175. package/dist/translations/hy.js +0 -5
  176. package/dist/translations/hy.umd.js +0 -11
  177. package/dist/translations/id.d.ts +0 -8
  178. package/dist/translations/id.js +0 -5
  179. package/dist/translations/id.umd.js +0 -11
  180. package/dist/translations/it.d.ts +0 -8
  181. package/dist/translations/it.js +0 -5
  182. package/dist/translations/it.umd.js +0 -11
  183. package/dist/translations/ja.d.ts +0 -8
  184. package/dist/translations/ja.js +0 -5
  185. package/dist/translations/ja.umd.js +0 -11
  186. package/dist/translations/jv.d.ts +0 -8
  187. package/dist/translations/jv.js +0 -5
  188. package/dist/translations/jv.umd.js +0 -11
  189. package/dist/translations/kk.d.ts +0 -8
  190. package/dist/translations/kk.js +0 -5
  191. package/dist/translations/kk.umd.js +0 -11
  192. package/dist/translations/km.d.ts +0 -8
  193. package/dist/translations/km.js +0 -5
  194. package/dist/translations/km.umd.js +0 -11
  195. package/dist/translations/kn.d.ts +0 -8
  196. package/dist/translations/kn.js +0 -5
  197. package/dist/translations/kn.umd.js +0 -11
  198. package/dist/translations/ko.d.ts +0 -8
  199. package/dist/translations/ko.js +0 -5
  200. package/dist/translations/ko.umd.js +0 -11
  201. package/dist/translations/ku.d.ts +0 -8
  202. package/dist/translations/ku.js +0 -5
  203. package/dist/translations/ku.umd.js +0 -11
  204. package/dist/translations/lt.d.ts +0 -8
  205. package/dist/translations/lt.js +0 -5
  206. package/dist/translations/lt.umd.js +0 -11
  207. package/dist/translations/lv.d.ts +0 -8
  208. package/dist/translations/lv.js +0 -5
  209. package/dist/translations/lv.umd.js +0 -11
  210. package/dist/translations/ms.d.ts +0 -8
  211. package/dist/translations/ms.js +0 -5
  212. package/dist/translations/ms.umd.js +0 -11
  213. package/dist/translations/nb.d.ts +0 -8
  214. package/dist/translations/nb.js +0 -5
  215. package/dist/translations/nb.umd.js +0 -11
  216. package/dist/translations/ne.d.ts +0 -8
  217. package/dist/translations/ne.js +0 -5
  218. package/dist/translations/ne.umd.js +0 -11
  219. package/dist/translations/nl.d.ts +0 -8
  220. package/dist/translations/nl.js +0 -5
  221. package/dist/translations/nl.umd.js +0 -11
  222. package/dist/translations/no.d.ts +0 -8
  223. package/dist/translations/no.js +0 -5
  224. package/dist/translations/no.umd.js +0 -11
  225. package/dist/translations/oc.d.ts +0 -8
  226. package/dist/translations/oc.js +0 -5
  227. package/dist/translations/oc.umd.js +0 -11
  228. package/dist/translations/pl.d.ts +0 -8
  229. package/dist/translations/pl.js +0 -5
  230. package/dist/translations/pl.umd.js +0 -11
  231. package/dist/translations/pt-br.d.ts +0 -8
  232. package/dist/translations/pt-br.js +0 -5
  233. package/dist/translations/pt-br.umd.js +0 -11
  234. package/dist/translations/pt.d.ts +0 -8
  235. package/dist/translations/pt.js +0 -5
  236. package/dist/translations/pt.umd.js +0 -11
  237. package/dist/translations/ro.d.ts +0 -8
  238. package/dist/translations/ro.js +0 -5
  239. package/dist/translations/ro.umd.js +0 -11
  240. package/dist/translations/ru.d.ts +0 -8
  241. package/dist/translations/ru.js +0 -5
  242. package/dist/translations/ru.umd.js +0 -11
  243. package/dist/translations/si.d.ts +0 -8
  244. package/dist/translations/si.js +0 -5
  245. package/dist/translations/si.umd.js +0 -11
  246. package/dist/translations/sk.d.ts +0 -8
  247. package/dist/translations/sk.js +0 -5
  248. package/dist/translations/sk.umd.js +0 -11
  249. package/dist/translations/sl.d.ts +0 -8
  250. package/dist/translations/sl.js +0 -5
  251. package/dist/translations/sl.umd.js +0 -11
  252. package/dist/translations/sq.d.ts +0 -8
  253. package/dist/translations/sq.js +0 -5
  254. package/dist/translations/sq.umd.js +0 -11
  255. package/dist/translations/sr-latn.d.ts +0 -8
  256. package/dist/translations/sr-latn.js +0 -5
  257. package/dist/translations/sr-latn.umd.js +0 -11
  258. package/dist/translations/sr.d.ts +0 -8
  259. package/dist/translations/sr.js +0 -5
  260. package/dist/translations/sr.umd.js +0 -11
  261. package/dist/translations/sv.d.ts +0 -8
  262. package/dist/translations/sv.js +0 -5
  263. package/dist/translations/sv.umd.js +0 -11
  264. package/dist/translations/th.d.ts +0 -8
  265. package/dist/translations/th.js +0 -5
  266. package/dist/translations/th.umd.js +0 -11
  267. package/dist/translations/ti.d.ts +0 -8
  268. package/dist/translations/ti.js +0 -5
  269. package/dist/translations/ti.umd.js +0 -11
  270. package/dist/translations/tk.d.ts +0 -8
  271. package/dist/translations/tk.js +0 -5
  272. package/dist/translations/tk.umd.js +0 -11
  273. package/dist/translations/tr.d.ts +0 -8
  274. package/dist/translations/tr.js +0 -5
  275. package/dist/translations/tr.umd.js +0 -11
  276. package/dist/translations/tt.d.ts +0 -8
  277. package/dist/translations/tt.js +0 -5
  278. package/dist/translations/tt.umd.js +0 -11
  279. package/dist/translations/ug.d.ts +0 -8
  280. package/dist/translations/ug.js +0 -5
  281. package/dist/translations/ug.umd.js +0 -11
  282. package/dist/translations/uk.d.ts +0 -8
  283. package/dist/translations/uk.js +0 -5
  284. package/dist/translations/uk.umd.js +0 -11
  285. package/dist/translations/ur.d.ts +0 -8
  286. package/dist/translations/ur.js +0 -5
  287. package/dist/translations/ur.umd.js +0 -11
  288. package/dist/translations/uz.d.ts +0 -8
  289. package/dist/translations/uz.js +0 -5
  290. package/dist/translations/uz.umd.js +0 -11
  291. package/dist/translations/vi.d.ts +0 -8
  292. package/dist/translations/vi.js +0 -5
  293. package/dist/translations/vi.umd.js +0 -11
  294. package/dist/translations/zh-cn.d.ts +0 -8
  295. package/dist/translations/zh-cn.js +0 -5
  296. package/dist/translations/zh-cn.umd.js +0 -11
  297. package/dist/translations/zh.d.ts +0 -8
  298. package/dist/translations/zh.js +0 -5
  299. package/dist/translations/zh.umd.js +0 -11
  300. package/lang/contexts.json +0 -24
  301. package/lang/translations/af.po +0 -100
  302. package/lang/translations/ar.po +0 -100
  303. package/lang/translations/ast.po +0 -100
  304. package/lang/translations/az.po +0 -100
  305. package/lang/translations/bg.po +0 -100
  306. package/lang/translations/bn.po +0 -100
  307. package/lang/translations/bs.po +0 -100
  308. package/lang/translations/ca.po +0 -100
  309. package/lang/translations/cs.po +0 -100
  310. package/lang/translations/da.po +0 -100
  311. package/lang/translations/de-ch.po +0 -100
  312. package/lang/translations/de.po +0 -100
  313. package/lang/translations/el.po +0 -100
  314. package/lang/translations/en-au.po +0 -100
  315. package/lang/translations/en-gb.po +0 -100
  316. package/lang/translations/en.po +0 -100
  317. package/lang/translations/eo.po +0 -100
  318. package/lang/translations/es-co.po +0 -100
  319. package/lang/translations/es.po +0 -100
  320. package/lang/translations/et.po +0 -100
  321. package/lang/translations/eu.po +0 -100
  322. package/lang/translations/fa.po +0 -100
  323. package/lang/translations/fi.po +0 -100
  324. package/lang/translations/fr.po +0 -100
  325. package/lang/translations/gl.po +0 -100
  326. package/lang/translations/gu.po +0 -100
  327. package/lang/translations/he.po +0 -100
  328. package/lang/translations/hi.po +0 -100
  329. package/lang/translations/hr.po +0 -100
  330. package/lang/translations/hu.po +0 -100
  331. package/lang/translations/hy.po +0 -100
  332. package/lang/translations/id.po +0 -100
  333. package/lang/translations/it.po +0 -100
  334. package/lang/translations/ja.po +0 -100
  335. package/lang/translations/jv.po +0 -100
  336. package/lang/translations/kk.po +0 -100
  337. package/lang/translations/km.po +0 -100
  338. package/lang/translations/kn.po +0 -100
  339. package/lang/translations/ko.po +0 -100
  340. package/lang/translations/ku.po +0 -100
  341. package/lang/translations/lt.po +0 -100
  342. package/lang/translations/lv.po +0 -100
  343. package/lang/translations/ms.po +0 -100
  344. package/lang/translations/nb.po +0 -100
  345. package/lang/translations/ne.po +0 -100
  346. package/lang/translations/nl.po +0 -100
  347. package/lang/translations/no.po +0 -100
  348. package/lang/translations/oc.po +0 -100
  349. package/lang/translations/pl.po +0 -100
  350. package/lang/translations/pt-br.po +0 -100
  351. package/lang/translations/pt.po +0 -100
  352. package/lang/translations/ro.po +0 -100
  353. package/lang/translations/ru.po +0 -100
  354. package/lang/translations/si.po +0 -100
  355. package/lang/translations/sk.po +0 -100
  356. package/lang/translations/sl.po +0 -100
  357. package/lang/translations/sq.po +0 -100
  358. package/lang/translations/sr-latn.po +0 -100
  359. package/lang/translations/sr.po +0 -100
  360. package/lang/translations/sv.po +0 -100
  361. package/lang/translations/th.po +0 -100
  362. package/lang/translations/ti.po +0 -100
  363. package/lang/translations/tk.po +0 -100
  364. package/lang/translations/tr.po +0 -100
  365. package/lang/translations/tt.po +0 -100
  366. package/lang/translations/ug.po +0 -100
  367. package/lang/translations/uk.po +0 -100
  368. package/lang/translations/ur.po +0 -100
  369. package/lang/translations/uz.po +0 -100
  370. package/lang/translations/vi.po +0 -100
  371. package/lang/translations/zh-cn.po +0 -100
  372. package/lang/translations/zh.po +0 -100
  373. package/src/augmentation.d.ts +0 -25
  374. package/src/augmentation.js +0 -5
  375. package/src/emoji.d.ts +0 -32
  376. package/src/emoji.js +0 -38
  377. package/src/emojicommand.d.ts +0 -24
  378. package/src/emojicommand.js +0 -33
  379. package/src/emojiconfig.d.ts +0 -98
  380. package/src/emojiconfig.js +0 -5
  381. package/src/emojimention.d.ts +0 -75
  382. package/src/emojimention.js +0 -210
  383. package/src/emojipicker.d.ts +0 -97
  384. package/src/emojipicker.js +0 -255
  385. package/src/emojirepository.d.ts +0 -169
  386. package/src/emojirepository.js +0 -290
  387. package/src/emojiutils.d.ts +0 -58
  388. package/src/emojiutils.js +0 -141
  389. package/src/index.d.ts +0 -15
  390. package/src/index.js +0 -14
  391. package/src/ui/emojicategoriesview.d.ts +0 -68
  392. package/src/ui/emojicategoriesview.js +0 -131
  393. package/src/ui/emojigridview.d.ts +0 -140
  394. package/src/ui/emojigridview.js +0 -183
  395. package/src/ui/emojipickerview.d.ts +0 -91
  396. package/src/ui/emojipickerview.js +0 -173
  397. package/src/ui/emojisearchview.d.ts +0 -51
  398. package/src/ui/emojisearchview.js +0 -89
  399. package/src/ui/emojitoneview.d.ts +0 -46
  400. package/src/ui/emojitoneview.js +0 -89
  401. package/src/utils/isemojisupported.d.ts +0 -11
  402. package/src/utils/isemojisupported.js +0 -68
  403. package/theme/emojicategories.css +0 -29
  404. package/theme/emojigrid.css +0 -55
  405. package/theme/emojipicker.css +0 -32
  406. package/theme/emojitone.css +0 -21
package/dist/index.js DELETED
@@ -1,1698 +0,0 @@
1
- /**
2
- * @license Copyright (c) 2003-2025, CKSource Holding sp. z o.o. All rights reserved.
3
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
4
- */
5
- import { Plugin, Command, icons } from '@ckeditor/ckeditor5-core/dist/index.js';
6
- import { version, logWarning, FocusTracker, KeystrokeHandler, global, Collection } from '@ckeditor/ckeditor5-utils/dist/index.js';
7
- import { Typing } from '@ckeditor/ckeditor5-typing/dist/index.js';
8
- import Fuse from 'fuse.js';
9
- import { groupBy, escapeRegExp } from 'lodash-es';
10
- import { View, addKeyboardHandlingForGrid, ButtonView, FocusCycler, SearchTextView, createLabeledInputText, createDropdown, ViewModel, addListToDropdown, SearchInfoView, ContextualBalloon, Dialog, MenuBarMenuListItemButtonView, clickOutsideHandler } from '@ckeditor/ckeditor5-ui/dist/index.js';
11
-
12
- /**
13
- * @license Copyright (c) 2023, Koala Interactive SAS
14
- * For licensing, see https://github.com/koala-interactive/is-emoji-supported/blob/master/LICENSE.md
15
- */ /**
16
- * @module emoji/utils/isemojisupported
17
- */ /**
18
- * Checks if the two pixels parts are the same using canvas.
19
- */ function isEmojiSupported(unicode) {
20
- const ctx = getCanvas();
21
- /* istanbul ignore next -- @preserve */ if (!ctx) {
22
- return false;
23
- }
24
- const CANVAS_HEIGHT = 25;
25
- const CANVAS_WIDTH = 20;
26
- const textSize = Math.floor(CANVAS_HEIGHT / 2);
27
- // Initialize canvas context.
28
- ctx.font = textSize + 'px Arial, Sans-Serif';
29
- ctx.textBaseline = 'top';
30
- ctx.canvas.width = CANVAS_WIDTH * 2;
31
- ctx.canvas.height = CANVAS_HEIGHT;
32
- ctx.clearRect(0, 0, CANVAS_WIDTH * 2, CANVAS_HEIGHT);
33
- // Draw in red on the left.
34
- ctx.fillStyle = '#FF0000';
35
- ctx.fillText(unicode, 0, 22);
36
- // Draw in blue on right.
37
- ctx.fillStyle = '#0000FF';
38
- ctx.fillText(unicode, CANVAS_WIDTH, 22);
39
- const a = ctx.getImageData(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT).data;
40
- const count = a.length;
41
- let i = 0;
42
- // Search the first visible pixel.
43
- for(; i < count && !a[i + 3]; i += 4);
44
- // No visible pixel.
45
- /* istanbul ignore next -- @preserve */ if (i >= count) {
46
- return false;
47
- }
48
- // Emoji has immutable color, so we check the color of the emoji in two different colors.
49
- // the result show be the same.
50
- const x = CANVAS_WIDTH + i / 4 % CANVAS_WIDTH;
51
- const y = Math.floor(i / 4 / CANVAS_WIDTH);
52
- const b = ctx.getImageData(x, y, 1, 1).data;
53
- if (a[i] !== b[0] || a[i + 2] !== b[2]) {
54
- return false;
55
- }
56
- //Some emojis consist of different ones, so they will show multiple characters if they are not supported.
57
- /* istanbul ignore next -- @preserve */ if (ctx.measureText(unicode).width >= CANVAS_WIDTH) {
58
- return false;
59
- }
60
- // Supported.
61
- return true;
62
- }
63
- function getCanvas() {
64
- try {
65
- return document.createElement('canvas').getContext('2d', {
66
- willReadFrequently: true
67
- });
68
- } catch {
69
- /* istanbul ignore next -- @preserve */ return null;
70
- }
71
- }
72
-
73
- /**
74
- * @module emoji/emojiutils
75
- */ const SKIN_TONE_MAP = {
76
- 0: 'default',
77
- 1: 'light',
78
- 2: 'medium-light',
79
- 3: 'medium',
80
- 4: 'medium-dark',
81
- 5: 'dark'
82
- };
83
- /**
84
- * A map representing an emoji and its release version.
85
- * It's used to identify a user's minimal supported emoji level.
86
- */ const EMOJI_SUPPORT_LEVEL = {
87
- '🫩': 16,
88
- '🫨': 15.1 // Shaking head. Although the version of emoji is 15, it is used to detect versions 15 and 15.1.
89
- };
90
- const BASELINE_EMOJI_WIDTH = 24;
91
- /**
92
- * The Emoji utilities plugin.
93
- */ class EmojiUtils extends Plugin {
94
- /**
95
- * @inheritDoc
96
- */ static get pluginName() {
97
- return 'EmojiUtils';
98
- }
99
- /**
100
- * @inheritDoc
101
- */ static get isOfficialPlugin() {
102
- return true;
103
- }
104
- /**
105
- * Checks if the emoji is supported by verifying the emoji version supported by the system first.
106
- * Then checks if emoji contains a zero width joiner (ZWJ), and if yes, then checks if it is supported by the system.
107
- */ isEmojiSupported(item, emojiSupportedVersionByOs, container) {
108
- const isEmojiVersionSupported = item.version <= emojiSupportedVersionByOs;
109
- if (!isEmojiVersionSupported) {
110
- return false;
111
- }
112
- if (!this.hasZwj(item.emoji)) {
113
- return true;
114
- }
115
- return this.isEmojiZwjSupported(item, container);
116
- }
117
- /**
118
- * Checks the supported emoji version by the OS, by sampling some representatives from different emoji releases.
119
- */ getEmojiSupportedVersionByOs() {
120
- return Object.entries(EMOJI_SUPPORT_LEVEL).reduce((currentVersion, [emoji, newVersion])=>{
121
- if (newVersion > currentVersion && EmojiUtils._isEmojiSupported(emoji)) {
122
- return newVersion;
123
- }
124
- return currentVersion;
125
- }, 0);
126
- }
127
- /**
128
- * Check for ZWJ (zero width joiner) character.
129
- */ hasZwj(emoji) {
130
- return emoji.includes('\u200d');
131
- }
132
- /**
133
- * Checks whether the emoji is supported in the operating system.
134
- */ isEmojiZwjSupported(item, container) {
135
- const emojiWidth = this.getNodeWidth(container, item.emoji);
136
- // On Windows, some supported emoji are ~50% bigger than the baseline emoji, but what we really want to guard
137
- // against are the ones that are 2x the size, because those are truly broken (person with red hair = person with
138
- // floating red wig, black cat = cat with black square, polar bear = bear with snowflake, etc.)
139
- // So here we set the threshold at 1.8 times the size of the baseline emoji.
140
- return emojiWidth < BASELINE_EMOJI_WIDTH * 1.8;
141
- }
142
- /**
143
- * Returns the width of the provided node.
144
- */ getNodeWidth(container, node) {
145
- const span = document.createElement('span');
146
- span.textContent = node;
147
- container.appendChild(span);
148
- const nodeWidth = span.offsetWidth;
149
- container.removeChild(span);
150
- return nodeWidth;
151
- }
152
- /**
153
- * Creates a div for emoji width testing purposes.
154
- */ createEmojiWidthTestingContainer() {
155
- const container = document.createElement('div');
156
- container.setAttribute('aria-hidden', 'true');
157
- container.style.position = 'absolute';
158
- container.style.left = '-9999px';
159
- container.style.whiteSpace = 'nowrap';
160
- container.style.fontSize = BASELINE_EMOJI_WIDTH + 'px';
161
- return container;
162
- }
163
- /**
164
- * Adds default skin tone property to each emoji. If emoji defines other skin tones, they are added as well.
165
- */ normalizeEmojiSkinTone(item) {
166
- const entry = {
167
- ...item,
168
- skins: {
169
- default: item.emoji
170
- }
171
- };
172
- if (item.skins) {
173
- item.skins.forEach((skin)=>{
174
- const skinTone = SKIN_TONE_MAP[skin.tone];
175
- entry.skins[skinTone] = skin.emoji;
176
- });
177
- }
178
- return entry;
179
- }
180
- /**
181
- * Checks whether the emoji belongs to a group that is allowed.
182
- */ isEmojiCategoryAllowed(item) {
183
- // Category group=2 contains skin tones only, which we do not want to render.
184
- return item.group !== 2;
185
- }
186
- /**
187
- * A function used to determine if emoji is supported by detecting pixels.
188
- *
189
- * Referenced for unit testing purposes. Kept in a separate file because of licensing.
190
- */ static _isEmojiSupported = isEmojiSupported;
191
- }
192
-
193
- // An endpoint from which the emoji data will be downloaded during plugin initialization.
194
- // The `{version}` placeholder is replaced with the value from editor config.
195
- const DEFAULT_EMOJI_DATABASE_URL = 'https://cdn.ckeditor.com/ckeditor5/data/emoji/{version}/en.json';
196
- const DEFAULT_EMOJI_VERSION = 16;
197
- /**
198
- * The emoji repository plugin.
199
- *
200
- * Loads the emoji repository from URL during plugin initialization and provides utility methods to search it.
201
- */ class EmojiRepository extends Plugin {
202
- /**
203
- * An instance of the [Fuse.js](https://www.fusejs.io/) library.
204
- */ _fuseSearch;
205
- /**
206
- * The resolved URL from which the emoji repository is downloaded.
207
- */ _url;
208
- /**
209
- * A promise resolved after downloading the emoji collection.
210
- * The promise resolves with `true` when the repository is successfully downloaded or `false` otherwise.
211
- */ _repositoryPromise;
212
- /**
213
- * @inheritDoc
214
- */ static get requires() {
215
- return [
216
- EmojiUtils
217
- ];
218
- }
219
- /**
220
- * @inheritDoc
221
- */ static get pluginName() {
222
- return 'EmojiRepository';
223
- }
224
- /**
225
- * @inheritDoc
226
- */ static get isOfficialPlugin() {
227
- return true;
228
- }
229
- /**
230
- * @inheritDoc
231
- */ constructor(editor){
232
- super(editor);
233
- editor.config.define('emoji', {
234
- version: undefined,
235
- skinTone: 'default',
236
- definitionsUrl: undefined
237
- });
238
- this._url = this._getUrl();
239
- this._repositoryPromise = new Promise((resolve)=>{
240
- this._repositoryPromiseResolveCallback = resolve;
241
- });
242
- this._fuseSearch = null;
243
- }
244
- /**
245
- * @inheritDoc
246
- */ async init() {
247
- this._warnAboutCdnUse();
248
- await this._loadAndCacheEmoji();
249
- const items = this._getItems();
250
- // Skip plugin initialization if the emoji repository is not available.
251
- // The initialization of other dependent plugins, such as `EmojiMention` and `EmojiPicker`, is prevented as well.
252
- if (!items) {
253
- return this._repositoryPromiseResolveCallback(false);
254
- }
255
- // Create instance of the Fuse.js library with configured weighted search keys and disabled fuzzy search.
256
- this._fuseSearch = new Fuse(items, {
257
- keys: [
258
- {
259
- name: 'emoticon',
260
- weight: 5
261
- },
262
- {
263
- name: 'annotation',
264
- weight: 3
265
- },
266
- {
267
- name: 'tags',
268
- weight: 1
269
- }
270
- ],
271
- minMatchCharLength: 2,
272
- threshold: 0,
273
- ignoreLocation: true
274
- });
275
- return this._repositoryPromiseResolveCallback(true);
276
- }
277
- /**
278
- * Returns an array of emoji entries that match the search query.
279
- * If the emoji repository is not loaded, the [Fuse.js](https://www.fusejs.io/) instance is not created,
280
- * hence this method returns an empty array.
281
- *
282
- * @param searchQuery A search query to match emoji.
283
- * @returns An array of emoji entries that match the search query.
284
- */ getEmojiByQuery(searchQuery) {
285
- if (!this._fuseSearch) {
286
- return [];
287
- }
288
- const searchQueryTokens = searchQuery.split(/\s/).filter(Boolean);
289
- // Perform the search only if there is at least two non-white characters next to each other.
290
- const shouldSearch = searchQueryTokens.some((token)=>token.length >= 2);
291
- if (!shouldSearch) {
292
- return [];
293
- }
294
- return this._fuseSearch.search({
295
- '$or': [
296
- {
297
- emoticon: searchQuery
298
- },
299
- {
300
- '$and': searchQueryTokens.map((token)=>({
301
- annotation: token
302
- }))
303
- },
304
- {
305
- '$and': searchQueryTokens.map((token)=>({
306
- tags: token
307
- }))
308
- }
309
- ]
310
- }).map((result)=>result.item);
311
- }
312
- /**
313
- * Groups all emojis by categories.
314
- * If the emoji repository is not loaded, it returns an empty array.
315
- *
316
- * @returns An array of emoji entries grouped by categories.
317
- */ getEmojiCategories() {
318
- const repository = this._getItems();
319
- if (!repository) {
320
- return [];
321
- }
322
- const { t } = this.editor.locale;
323
- const categories = [
324
- {
325
- title: t('Smileys & Expressions'),
326
- icon: '😀',
327
- groupId: 0
328
- },
329
- {
330
- title: t('Gestures & People'),
331
- icon: '👋',
332
- groupId: 1
333
- },
334
- {
335
- title: t('Animals & Nature'),
336
- icon: '🐻',
337
- groupId: 3
338
- },
339
- {
340
- title: t('Food & Drinks'),
341
- icon: '🍎',
342
- groupId: 4
343
- },
344
- {
345
- title: t('Travel & Places'),
346
- icon: '🚘',
347
- groupId: 5
348
- },
349
- {
350
- title: t('Activities'),
351
- icon: '🏀',
352
- groupId: 6
353
- },
354
- {
355
- title: t('Objects'),
356
- icon: '💡',
357
- groupId: 7
358
- },
359
- {
360
- title: t('Symbols'),
361
- icon: '🟢',
362
- groupId: 8
363
- },
364
- {
365
- title: t('Flags'),
366
- icon: '🏁',
367
- groupId: 9
368
- }
369
- ];
370
- const groups = groupBy(repository, 'group');
371
- return categories.map((category)=>{
372
- return {
373
- ...category,
374
- items: groups[category.groupId]
375
- };
376
- });
377
- }
378
- /**
379
- * Returns an array of available skin tones.
380
- */ getSkinTones() {
381
- const { t } = this.editor.locale;
382
- return [
383
- {
384
- id: 'default',
385
- icon: '👋',
386
- tooltip: t('Default skin tone')
387
- },
388
- {
389
- id: 'light',
390
- icon: '👋🏻',
391
- tooltip: t('Light skin tone')
392
- },
393
- {
394
- id: 'medium-light',
395
- icon: '👋🏼',
396
- tooltip: t('Medium Light skin tone')
397
- },
398
- {
399
- id: 'medium',
400
- icon: '👋🏽',
401
- tooltip: t('Medium skin tone')
402
- },
403
- {
404
- id: 'medium-dark',
405
- icon: '👋🏾',
406
- tooltip: t('Medium Dark skin tone')
407
- },
408
- {
409
- id: 'dark',
410
- icon: '👋🏿',
411
- tooltip: t('Dark skin tone')
412
- }
413
- ];
414
- }
415
- /**
416
- * Indicates whether the emoji repository has been successfully downloaded and the plugin is operational.
417
- */ isReady() {
418
- return this._repositoryPromise;
419
- }
420
- /**
421
- * Returns the URL from which the emoji repository is downloaded. If the URL is not provided
422
- * in the configuration, the default URL is used with the version from the configuration.
423
- *
424
- * If both the URL and version are provided, a warning is logged.
425
- */ _getUrl() {
426
- const { definitionsUrl, version: version$1 } = this.editor.config.get('emoji');
427
- if (!definitionsUrl || definitionsUrl === 'cdn') {
428
- // URL was not provided or is set to 'cdn', so we use the default CDN URL.
429
- const urlVersion = version$1 || DEFAULT_EMOJI_VERSION;
430
- const url = new URL(DEFAULT_EMOJI_DATABASE_URL.replace('{version}', urlVersion.toString()));
431
- url.searchParams.set('editorVersion', version);
432
- return url;
433
- }
434
- if (version$1) {
435
- /**
436
- * Both {@link module:emoji/emojiconfig~EmojiConfig#definitionsUrl `emoji.definitionsUrl`} and
437
- * {@link module:emoji/emojiconfig~EmojiConfig#version `emoji.version`} configuration options
438
- * are set. Only the `emoji.definitionsUrl` option will be used.
439
- *
440
- * The `emoji.version` option will be ignored and should be removed from the configuration.
441
- *
442
- * @error emoji-repository-redundant-version
443
- */ logWarning('emoji-repository-redundant-version');
444
- }
445
- return new URL(definitionsUrl);
446
- }
447
- /**
448
- * Warn users on self-hosted installations that this plugin uses a CDN to fetch the emoji repository.
449
- */ _warnAboutCdnUse() {
450
- const editor = this.editor;
451
- const config = editor.config.get('emoji');
452
- const licenseKey = editor.config.get('licenseKey');
453
- const distributionChannel = window[Symbol.for('cke distribution')];
454
- if (licenseKey === 'GPL') {
455
- // Don't warn GPL users.
456
- return;
457
- }
458
- if (distributionChannel === 'cloud') {
459
- // Don't warn cloud users, because they already use our CDN.
460
- return;
461
- }
462
- if (config && config.definitionsUrl) {
463
- // Don't warn users who have configured their own definitions URL.
464
- return;
465
- }
466
- /**
467
- * By default, the Emoji plugin fetches the emoji repository from CKEditor 5 CDN. To avoid this,
468
- * you can use the {@link module:emoji/emojiconfig~EmojiConfig#definitionsUrl `emoji.definitionsUrl`}
469
- * configuration option to provide a URL to your own emoji repository.
470
- *
471
- * If you only want to suppress this warning, set this configuration option to `cdn`.
472
- *
473
- * @error emoji-repository-cdn-use
474
- */ logWarning('emoji-repository-cdn-use');
475
- }
476
- /**
477
- * Returns the emoji repository in a configured version if it is a non-empty array. Returns `null` otherwise.
478
- */ _getItems() {
479
- const repository = EmojiRepository._results[this._url.href];
480
- return repository && repository.length ? repository : null;
481
- }
482
- /**
483
- * Loads the emoji repository. If the repository is already loaded, it returns the cached result.
484
- * Otherwise, it fetches the repository from the URL and adds it to the cache.
485
- */ async _loadAndCacheEmoji() {
486
- if (EmojiRepository._results[this._url.href]) {
487
- // The repository has already been downloaded.
488
- return;
489
- }
490
- const result = await fetch(this._url, {
491
- cache: 'force-cache'
492
- }).then((response)=>{
493
- if (!response.ok) {
494
- return [];
495
- }
496
- return response.json();
497
- }).catch(()=>{
498
- return [];
499
- });
500
- if (!result.length) {
501
- /**
502
- * Unable to load the emoji repository from the URL.
503
- *
504
- * If the URL works properly and there is no disruption of communication, please check your
505
- * {@glink getting-started/setup/csp Content Security Policy (CSP)} setting and make sure
506
- * the URL connection is allowed by the editor.
507
- *
508
- * @error emoji-repository-load-failed
509
- */ logWarning('emoji-repository-load-failed');
510
- }
511
- EmojiRepository._results[this._url.href] = this._normalizeEmoji(result);
512
- }
513
- /**
514
- * Normalizes the raw data fetched from CDN. By normalization, we meant:
515
- *
516
- * * Filter out unsupported emoji (these that will not render correctly),
517
- * * Prepare skin tone variants if an emoji defines them.
518
- */ _normalizeEmoji(data) {
519
- const emojiUtils = this.editor.plugins.get('EmojiUtils');
520
- const emojiSupportedVersionByOs = emojiUtils.getEmojiSupportedVersionByOs();
521
- const container = emojiUtils.createEmojiWidthTestingContainer();
522
- document.body.appendChild(container);
523
- const results = data.filter((item)=>emojiUtils.isEmojiCategoryAllowed(item)).filter((item)=>emojiUtils.isEmojiSupported(item, emojiSupportedVersionByOs, container)).map((item)=>emojiUtils.normalizeEmojiSkinTone(item));
524
- container.remove();
525
- return results;
526
- }
527
- /**
528
- * Versioned emoji repository.
529
- */ static _results = {};
530
- }
531
-
532
- const EMOJI_MENTION_MARKER = ':';
533
- const EMOJI_SHOW_ALL_OPTION_ID = ':__EMOJI_SHOW_ALL:';
534
- const EMOJI_HINT_OPTION_ID = ':__EMOJI_HINT:';
535
- /**
536
- * The emoji mention plugin.
537
- *
538
- * Introduces the autocomplete of emojis while typing.
539
- */ class EmojiMention extends Plugin {
540
- /**
541
- * Defines a number of displayed items in the auto complete dropdown.
542
- *
543
- * It includes the "Show all emoji..." option if the `EmojiPicker` plugin is loaded.
544
- */ _emojiDropdownLimit;
545
- /**
546
- * Defines a skin tone that is set in the emoji config.
547
- */ _skinTone;
548
- /**
549
- * @inheritDoc
550
- */ static get requires() {
551
- return [
552
- EmojiRepository,
553
- Typing,
554
- 'Mention'
555
- ];
556
- }
557
- /**
558
- * @inheritDoc
559
- */ static get pluginName() {
560
- return 'EmojiMention';
561
- }
562
- /**
563
- * @inheritDoc
564
- */ static get isOfficialPlugin() {
565
- return true;
566
- }
567
- /**
568
- * @inheritDoc
569
- */ constructor(editor){
570
- super(editor);
571
- this.editor.config.define('emoji', {
572
- dropdownLimit: 6
573
- });
574
- this._emojiDropdownLimit = editor.config.get('emoji.dropdownLimit');
575
- this._skinTone = editor.config.get('emoji.skinTone');
576
- this._setupMentionConfiguration(editor);
577
- }
578
- /**
579
- * Initializes the configuration for emojis in the mention feature.
580
- * If the marker used by emoji mention is already registered, it displays a warning.
581
- * If emoji mention configuration is detected, it does not register it for a second time.
582
- */ _setupMentionConfiguration(editor) {
583
- const mergeFieldsPrefix = editor.config.get('mergeFields.prefix');
584
- const mentionFeedsConfigs = editor.config.get('mention.feeds');
585
- const isEmojiMarkerUsedByMergeFields = mergeFieldsPrefix ? mergeFieldsPrefix[0] === EMOJI_MENTION_MARKER : false;
586
- const isEmojiMarkerUsedByMention = mentionFeedsConfigs.filter((config)=>!config._isEmojiMarker).some((config)=>config.marker === EMOJI_MENTION_MARKER);
587
- if (isEmojiMarkerUsedByMention || isEmojiMarkerUsedByMergeFields) {
588
- /**
589
- * The `marker` in the `emoji` config is already used by other plugin configuration.
590
- *
591
- * @error emoji-config-marker-already-used
592
- * @param {string} marker Used marker.
593
- */ logWarning('emoji-config-marker-already-used', {
594
- marker: EMOJI_MENTION_MARKER
595
- });
596
- return;
597
- }
598
- const isEmojiConfigDefined = mentionFeedsConfigs.some((config)=>config._isEmojiMarker);
599
- if (isEmojiConfigDefined) {
600
- return;
601
- }
602
- const emojiMentionFeedConfig = {
603
- _isEmojiMarker: true,
604
- marker: EMOJI_MENTION_MARKER,
605
- dropdownLimit: this._emojiDropdownLimit,
606
- itemRenderer: this._customItemRendererFactory(this.editor.t),
607
- feed: this._queryEmojiCallbackFactory()
608
- };
609
- this.editor.config.set('mention.feeds', [
610
- ...mentionFeedsConfigs,
611
- emojiMentionFeedConfig
612
- ]);
613
- }
614
- /**
615
- * @inheritDoc
616
- */ async init() {
617
- const editor = this.editor;
618
- this.emojiPickerPlugin = editor.plugins.has('EmojiPicker') ? editor.plugins.get('EmojiPicker') : null;
619
- this.emojiRepositoryPlugin = editor.plugins.get('EmojiRepository');
620
- this._isEmojiRepositoryAvailable = await this.emojiRepositoryPlugin.isReady();
621
- // Override the `mention` command listener if the emoji repository is ready.
622
- if (this._isEmojiRepositoryAvailable) {
623
- editor.once('ready', this._overrideMentionExecuteListener.bind(this));
624
- }
625
- }
626
- /**
627
- * Returns the `itemRenderer()` callback for mention config.
628
- */ _customItemRendererFactory(t) {
629
- return (item)=>{
630
- const itemElement = document.createElement('button');
631
- itemElement.classList.add('ck');
632
- itemElement.classList.add('ck-button');
633
- itemElement.classList.add('ck-button_with-text');
634
- itemElement.id = `mention-list-item-id${item.id.slice(0, -1)}`;
635
- itemElement.type = 'button';
636
- itemElement.tabIndex = -1;
637
- const labelElement = document.createElement('span');
638
- labelElement.classList.add('ck');
639
- labelElement.classList.add('ck-button__label');
640
- itemElement.appendChild(labelElement);
641
- if (item.id === EMOJI_HINT_OPTION_ID) {
642
- itemElement.classList.add('ck-list-item-button');
643
- itemElement.classList.add('ck-disabled');
644
- labelElement.textContent = t('Keep on typing to see the emoji.');
645
- } else if (item.id === EMOJI_SHOW_ALL_OPTION_ID) {
646
- labelElement.textContent = t('Show all emoji...');
647
- } else {
648
- labelElement.textContent = `${item.text} ${item.id}`;
649
- }
650
- return itemElement;
651
- };
652
- }
653
- /**
654
- * Overrides the default mention execute listener to insert an emoji as plain text instead.
655
- */ _overrideMentionExecuteListener() {
656
- const editor = this.editor;
657
- editor.commands.get('mention').on('execute', (event, data)=>{
658
- const eventData = data[0];
659
- // Ignore non-emoji auto-complete actions.
660
- if (eventData.marker !== EMOJI_MENTION_MARKER) {
661
- return;
662
- }
663
- // Do not propagate the event.
664
- event.stop();
665
- // Do nothing when executing after selecting a hint message.
666
- if (eventData.mention.id === EMOJI_HINT_OPTION_ID) {
667
- return;
668
- }
669
- // Trigger the picker UI.
670
- if (eventData.mention.id === EMOJI_SHOW_ALL_OPTION_ID) {
671
- const text = [
672
- ...eventData.range.getItems()
673
- ].filter((item)=>item.is('$textProxy')).map((item)=>item.data).reduce((result, text)=>result + text, '');
674
- editor.model.change((writer)=>{
675
- editor.model.deleteContent(writer.createSelection(eventData.range));
676
- });
677
- const emojiPickerPlugin = this.emojiPickerPlugin;
678
- emojiPickerPlugin.showUI(text.slice(1));
679
- setTimeout(()=>{
680
- emojiPickerPlugin.emojiPickerView.focus();
681
- });
682
- } else {
683
- editor.execute('insertText', {
684
- text: eventData.mention.text,
685
- range: eventData.range
686
- });
687
- }
688
- }, {
689
- priority: 'high'
690
- });
691
- }
692
- /**
693
- * Returns the `feed()` callback for mention config.
694
- */ _queryEmojiCallbackFactory() {
695
- return (searchQuery)=>{
696
- // Do not show anything when a query starts with a space.
697
- if (searchQuery.startsWith(' ')) {
698
- return [];
699
- }
700
- // Do not show anything when a query starts with a marker character.
701
- if (searchQuery.startsWith(EMOJI_MENTION_MARKER)) {
702
- return [];
703
- }
704
- // If the repository plugin is not available, return an empty feed to avoid confusion. See: #17842.
705
- if (!this._isEmojiRepositoryAvailable) {
706
- return [];
707
- }
708
- const emojis = this.emojiRepositoryPlugin.getEmojiByQuery(searchQuery).map((emoji)=>{
709
- let text = emoji.skins[this._skinTone] || emoji.skins.default;
710
- if (this.emojiPickerPlugin) {
711
- text = emoji.skins[this.emojiPickerPlugin.skinTone] || emoji.skins.default;
712
- }
713
- return {
714
- id: `:${emoji.annotation}:`,
715
- text
716
- };
717
- });
718
- if (!this.emojiPickerPlugin) {
719
- return emojis.slice(0, this._emojiDropdownLimit);
720
- }
721
- const actionItem = {
722
- id: searchQuery.length > 1 ? EMOJI_SHOW_ALL_OPTION_ID : EMOJI_HINT_OPTION_ID
723
- };
724
- return [
725
- ...emojis.slice(0, this._emojiDropdownLimit - 1),
726
- actionItem
727
- ];
728
- };
729
- }
730
- }
731
-
732
- /**
733
- * Command that shows the emoji user interface.
734
- */ class EmojiCommand extends Command {
735
- /**
736
- * Updates the command's {@link #isEnabled} based on the current selection.
737
- */ refresh() {
738
- const editor = this.editor;
739
- const model = editor.model;
740
- const schema = model.schema;
741
- const selection = model.document.selection;
742
- this.isEnabled = schema.checkChild(selection.getFirstPosition(), '$text');
743
- }
744
- /**
745
- * Opens emoji user interface for the current document selection.
746
- *
747
- * @fires execute
748
- * @param [searchValue=''] A default query used to filer the grid when opening the UI.
749
- */ execute(searchValue = '') {
750
- const emojiPickerPlugin = this.editor.plugins.get('EmojiPicker');
751
- emojiPickerPlugin.showUI(searchValue);
752
- }
753
- }
754
-
755
- /**
756
- * A grid of emoji tiles. It allows browsing emojis and selecting them to be inserted into the content.
757
- */ class EmojiGridView extends View {
758
- /**
759
- * A collection of the child tile views. Each tile represents a particular emoji.
760
- */ tiles;
761
- /**
762
- * Tracks information about the DOM focus in the grid.
763
- */ focusTracker;
764
- /**
765
- * An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
766
- */ keystrokes;
767
- /**
768
- * An array containing all emojis grouped by their categories.
769
- */ emojiCategories;
770
- /**
771
- * A collection of all already created tile views. Each tile represents a particular emoji.
772
- * The cached tiles collection is used for efficiency purposes to avoid re-creating a particular
773
- * tile again when the grid view has changed.
774
- */ cachedTiles;
775
- /**
776
- * A callback used to filter grid items by a specified query.
777
- */ _getEmojiByQuery;
778
- /**
779
- * @inheritDoc
780
- */ constructor(locale, { categoryName, emojiCategories, getEmojiByQuery, skinTone }){
781
- super(locale);
782
- this.set('isEmpty', true);
783
- this.set('categoryName', categoryName);
784
- this.set('skinTone', skinTone);
785
- this.tiles = this.createCollection();
786
- this.cachedTiles = this.createCollection();
787
- this.focusTracker = new FocusTracker();
788
- this.keystrokes = new KeystrokeHandler();
789
- this._getEmojiByQuery = getEmojiByQuery;
790
- this.emojiCategories = emojiCategories;
791
- const bind = this.bindTemplate;
792
- this.setTemplate({
793
- tag: 'div',
794
- children: [
795
- {
796
- tag: 'div',
797
- attributes: {
798
- role: 'grid',
799
- class: [
800
- 'ck',
801
- 'ck-emoji__grid'
802
- ]
803
- },
804
- children: this.tiles
805
- }
806
- ],
807
- attributes: {
808
- role: 'tabpanel',
809
- class: [
810
- 'ck',
811
- 'ck-emoji__tiles',
812
- // To avoid issues with focus cycling, ignore a grid when it's empty.
813
- bind.if('isEmpty', 'ck-hidden', (value)=>value)
814
- ]
815
- }
816
- });
817
- addKeyboardHandlingForGrid({
818
- keystrokeHandler: this.keystrokes,
819
- focusTracker: this.focusTracker,
820
- gridItems: this.tiles,
821
- numberOfColumns: ()=>global.window.getComputedStyle(this.element.firstChild) // Responsive `.ck-emoji-grid__tiles`.
822
- .getPropertyValue('grid-template-columns').split(' ').length,
823
- uiLanguageDirection: this.locale && this.locale.uiLanguageDirection
824
- });
825
- }
826
- /**
827
- * @inheritDoc
828
- */ render() {
829
- super.render();
830
- this.keystrokes.listenTo(this.element);
831
- }
832
- /**
833
- * @inheritDoc
834
- */ destroy() {
835
- super.destroy();
836
- this.keystrokes.destroy();
837
- this.focusTracker.destroy();
838
- }
839
- /**
840
- * Focuses the first focusable in {@link ~EmojiGridView#tiles} if available.
841
- */ focus() {
842
- const firstTile = this.tiles.first;
843
- if (firstTile) {
844
- firstTile.focus();
845
- }
846
- }
847
- /**
848
- * Filters the grid view by the given regular expression.
849
- *
850
- * It filters either by the pattern or an emoji category, but never both.
851
- *
852
- * @param pattern Expression to search or `null` when filter by category name.
853
- */ filter(pattern) {
854
- const { matchingItems, allItems } = pattern ? this._getItemsByQuery(pattern.source) : this._getItemsByCategory();
855
- this._updateGrid(matchingItems);
856
- this.set('isEmpty', matchingItems.length === 0);
857
- return {
858
- resultsCount: matchingItems.length,
859
- totalItemsCount: allItems.length
860
- };
861
- }
862
- /**
863
- * Filters emojis to show based on the specified query phrase.
864
- *
865
- * @param query A query used to filter the grid.
866
- */ _getItemsByQuery(query) {
867
- return {
868
- matchingItems: this._getEmojiByQuery(query),
869
- allItems: this.emojiCategories.flatMap((group)=>group.items)
870
- };
871
- }
872
- /**
873
- * Returns emojis that belong to the specified category.
874
- */ _getItemsByCategory() {
875
- const emojiCategory = this.emojiCategories.find((item)=>item.title === this.categoryName);
876
- const { items } = emojiCategory;
877
- return {
878
- matchingItems: items,
879
- allItems: items
880
- };
881
- }
882
- /**
883
- * Updates the grid by removing the existing items and insert the new ones.
884
- *
885
- * @param items An array of items to insert.
886
- */ _updateGrid(items) {
887
- // Clean-up.
888
- [
889
- ...this.tiles
890
- ].forEach((item)=>{
891
- this.focusTracker.remove(item);
892
- this.tiles.remove(item);
893
- });
894
- items// Create tiles from matching results.
895
- .map((item)=>{
896
- const emoji = item.skins[this.skinTone] || item.skins.default;
897
- return this.cachedTiles.get(emoji) || this._createTile(emoji, item.annotation);
898
- })// Insert new elements.
899
- .forEach((item)=>{
900
- this.tiles.add(item);
901
- this.focusTracker.add(item);
902
- });
903
- }
904
- /**
905
- * Creates a new tile for the grid. Created tile is added to the {@link #cachedTiles} collection for further usage, if needed.
906
- *
907
- * @param emoji The emoji itself.
908
- * @param name The name of the emoji (e.g. "Smiling Face with Smiling Eyes").
909
- */ _createTile(emoji, name) {
910
- const tile = new ButtonView(this.locale);
911
- tile.viewUid = emoji;
912
- tile.extendTemplate({
913
- attributes: {
914
- class: [
915
- 'ck-emoji__tile'
916
- ]
917
- }
918
- });
919
- tile.set({
920
- label: emoji,
921
- tooltip: name,
922
- withText: true,
923
- ariaLabel: name,
924
- // To improve accessibility, disconnect a button and its label connection so that screen
925
- // readers can read the `[aria-label]` attribute directly from the more descriptive button.
926
- ariaLabelledBy: undefined
927
- });
928
- tile.on('execute', ()=>{
929
- this.fire('execute', {
930
- name,
931
- emoji
932
- });
933
- });
934
- this.cachedTiles.add(tile);
935
- return tile;
936
- }
937
- }
938
-
939
- /**
940
- * A class representing the navigation part of the emoji UI.
941
- * It is responsible allowing the user to select a particular emoji category.
942
- */ class EmojiCategoriesView extends View {
943
- /**
944
- * Tracks information about the DOM focus in the grid.
945
- */ focusTracker;
946
- /**
947
- * An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
948
- */ keystrokes;
949
- /**
950
- * Helps cycling over focusable children in the input view.
951
- */ focusCycler;
952
- /**
953
- * A collection of the categories buttons.
954
- */ buttonViews;
955
- /**
956
- * @inheritDoc
957
- */ constructor(locale, { emojiCategories, categoryName }){
958
- super(locale);
959
- this.buttonViews = this.createCollection(emojiCategories.map((emojiCategory)=>this._createCategoryButton(emojiCategory)));
960
- this.focusTracker = new FocusTracker();
961
- this.keystrokes = new KeystrokeHandler();
962
- this.focusCycler = new FocusCycler({
963
- focusables: this.buttonViews,
964
- focusTracker: this.focusTracker,
965
- keystrokeHandler: this.keystrokes,
966
- actions: {
967
- focusPrevious: 'arrowleft',
968
- focusNext: 'arrowright'
969
- }
970
- });
971
- this.setTemplate({
972
- tag: 'div',
973
- attributes: {
974
- class: [
975
- 'ck',
976
- 'ck-emoji__categories-list'
977
- ],
978
- role: 'tablist'
979
- },
980
- children: this.buttonViews
981
- });
982
- this.on('change:categoryName', (event, name, newValue, oldValue)=>{
983
- const oldCategoryButton = this.buttonViews.find((button)=>button.tooltip === oldValue);
984
- if (oldCategoryButton) {
985
- oldCategoryButton.isOn = false;
986
- }
987
- const newCategoryButton = this.buttonViews.find((button)=>button.tooltip === newValue);
988
- newCategoryButton.isOn = true;
989
- });
990
- this.set('categoryName', categoryName);
991
- }
992
- /**
993
- * @inheritDoc
994
- */ render() {
995
- super.render();
996
- this.buttonViews.forEach((buttonView)=>{
997
- this.focusTracker.add(buttonView);
998
- });
999
- this.keystrokes.listenTo(this.element);
1000
- }
1001
- /**
1002
- * @inheritDoc
1003
- */ destroy() {
1004
- super.destroy();
1005
- this.focusTracker.destroy();
1006
- this.keystrokes.destroy();
1007
- this.buttonViews.destroy();
1008
- }
1009
- /**
1010
- * @inheritDoc
1011
- */ focus() {
1012
- this.buttonViews.first.focus();
1013
- }
1014
- /**
1015
- * Marks all categories buttons as enabled (clickable).
1016
- */ enableCategories() {
1017
- this.buttonViews.forEach((buttonView)=>{
1018
- buttonView.isEnabled = true;
1019
- });
1020
- }
1021
- /**
1022
- * Marks all categories buttons as disabled (non-clickable).
1023
- */ disableCategories() {
1024
- this.buttonViews.forEach((buttonView)=>{
1025
- buttonView.set({
1026
- class: '',
1027
- isEnabled: false,
1028
- isOn: false
1029
- });
1030
- });
1031
- }
1032
- /**
1033
- * Creates a button representing a category item.
1034
- */ _createCategoryButton(emojiCategory) {
1035
- const buttonView = new ButtonView();
1036
- const bind = buttonView.bindTemplate;
1037
- // A `[role="tab"]` element requires also the `[aria-selected]` attribute with its state.
1038
- buttonView.extendTemplate({
1039
- attributes: {
1040
- 'aria-selected': bind.to('isOn', (value)=>value.toString()),
1041
- class: [
1042
- 'ck-emoji__category-item'
1043
- ]
1044
- }
1045
- });
1046
- buttonView.set({
1047
- ariaLabel: emojiCategory.title,
1048
- label: emojiCategory.icon,
1049
- role: 'tab',
1050
- tooltip: emojiCategory.title,
1051
- withText: true,
1052
- // To improve accessibility, disconnect a button and its label connection so that screen
1053
- // readers can read the `[aria-label]` attribute directly from the more descriptive button.
1054
- ariaLabelledBy: undefined
1055
- });
1056
- buttonView.on('execute', ()=>{
1057
- this.categoryName = emojiCategory.title;
1058
- });
1059
- buttonView.on('change:isEnabled', ()=>{
1060
- if (buttonView.isEnabled && buttonView.tooltip === this.categoryName) {
1061
- buttonView.isOn = true;
1062
- }
1063
- });
1064
- return buttonView;
1065
- }
1066
- }
1067
-
1068
- /**
1069
- * A view responsible for providing an input element that allows filtering emoji by the provided query.
1070
- */ class EmojiSearchView extends View {
1071
- /**
1072
- * The find in text input view that stores the searched string.
1073
- */ inputView;
1074
- /**
1075
- * An instance of the `EmojiGridView`.
1076
- */ gridView;
1077
- /**
1078
- * @inheritDoc
1079
- */ constructor(locale, { gridView, resultsView }){
1080
- super(locale);
1081
- this.gridView = gridView;
1082
- const t = locale.t;
1083
- this.inputView = new SearchTextView(this.locale, {
1084
- queryView: {
1085
- label: t('Find an emoji (min. 2 characters)'),
1086
- creator: createLabeledInputText
1087
- },
1088
- filteredView: this.gridView,
1089
- infoView: {
1090
- instance: resultsView
1091
- }
1092
- });
1093
- this.setTemplate({
1094
- tag: 'div',
1095
- attributes: {
1096
- class: [
1097
- 'ck',
1098
- 'ck-search'
1099
- ],
1100
- tabindex: '-1'
1101
- },
1102
- children: [
1103
- this.inputView.queryView
1104
- ]
1105
- });
1106
- // Pass through the `search` event to handle it by a parent view.
1107
- this.inputView.delegate('search').to(this);
1108
- }
1109
- /**
1110
- * @inheritDoc
1111
- */ destroy() {
1112
- super.destroy();
1113
- this.inputView.destroy();
1114
- }
1115
- /**
1116
- * Searches the {@link #gridView} for the given query.
1117
- *
1118
- * @param query The search query string.
1119
- */ search(query) {
1120
- const regExp = query ? new RegExp(escapeRegExp(query), 'ig') : null;
1121
- const filteringResults = this.gridView.filter(regExp);
1122
- this.inputView.fire('search', {
1123
- query,
1124
- ...filteringResults
1125
- });
1126
- }
1127
- /**
1128
- * Allows defining the default value in the search text field.
1129
- *
1130
- * @param value The new value.
1131
- */ setInputValue(value) {
1132
- if (!value) {
1133
- this.inputView.queryView.fieldView.reset();
1134
- } else {
1135
- this.inputView.queryView.fieldView.value = value;
1136
- }
1137
- }
1138
- /**
1139
- * Returns an input provided by a user in the search text field.
1140
- */ getInputValue() {
1141
- return this.inputView.queryView.fieldView.element.value;
1142
- }
1143
- /**
1144
- * @inheritDoc
1145
- */ focus() {
1146
- this.inputView.focus();
1147
- }
1148
- }
1149
-
1150
- /**
1151
- * A view responsible for selecting a skin tone for an emoji.
1152
- */ class EmojiToneView extends View {
1153
- /**
1154
- * A dropdown element for selecting an active skin tone.
1155
- */ dropdownView;
1156
- /**
1157
- * An array of available skin tones.
1158
- */ _skinTones;
1159
- /**
1160
- * @inheritDoc
1161
- */ constructor(locale, { skinTone, skinTones }){
1162
- super(locale);
1163
- this.set('skinTone', skinTone);
1164
- this._skinTones = skinTones;
1165
- const t = locale.t;
1166
- const accessibleLabel = t('Select skin tone');
1167
- const dropdownView = createDropdown(locale);
1168
- const itemDefinitions = new Collection();
1169
- for (const { id, icon, tooltip } of this._skinTones){
1170
- const def = {
1171
- type: 'button',
1172
- model: new ViewModel({
1173
- value: id,
1174
- label: icon,
1175
- ariaLabel: tooltip,
1176
- tooltip,
1177
- tooltipPosition: 'e',
1178
- role: 'menuitemradio',
1179
- withText: true,
1180
- // To improve accessibility, disconnect a button and its label connection so that screen
1181
- // readers can read the `[aria-label]` attribute directly from the more descriptive button.
1182
- ariaLabelledBy: undefined
1183
- })
1184
- };
1185
- def.model.bind('isOn').to(this, 'skinTone', (value)=>value === id);
1186
- itemDefinitions.add(def);
1187
- }
1188
- addListToDropdown(dropdownView, itemDefinitions, {
1189
- ariaLabel: accessibleLabel,
1190
- role: 'menu'
1191
- });
1192
- dropdownView.buttonView.set({
1193
- label: this._getSkinTone().icon,
1194
- ariaLabel: accessibleLabel,
1195
- ariaLabelledBy: undefined,
1196
- isOn: false,
1197
- withText: true,
1198
- tooltip: accessibleLabel
1199
- });
1200
- this.dropdownView = dropdownView;
1201
- // Execute command when an item from the dropdown is selected.
1202
- this.listenTo(dropdownView, 'execute', (evt)=>{
1203
- this.skinTone = evt.source.value;
1204
- });
1205
- dropdownView.buttonView.bind('label').to(this, 'skinTone', ()=>{
1206
- return this._getSkinTone().icon;
1207
- });
1208
- dropdownView.buttonView.bind('ariaLabel').to(this, 'skinTone', ()=>{
1209
- // Render a current state, but also what the dropdown does.
1210
- return `${this._getSkinTone().tooltip}, ${accessibleLabel}`;
1211
- });
1212
- this.setTemplate({
1213
- tag: 'div',
1214
- attributes: {
1215
- class: [
1216
- 'ck',
1217
- 'ck-emoji__skin-tone'
1218
- ]
1219
- },
1220
- children: [
1221
- dropdownView
1222
- ]
1223
- });
1224
- }
1225
- /**
1226
- * @inheritDoc
1227
- */ focus() {
1228
- this.dropdownView.buttonView.focus();
1229
- }
1230
- /**
1231
- * Helper method for receiving an object describing the active skin tone.
1232
- */ _getSkinTone() {
1233
- return this._skinTones.find((tone)=>tone.id === this.skinTone);
1234
- }
1235
- }
1236
-
1237
- /**
1238
- * A view that glues pieces of the emoji panel together.
1239
- */ class EmojiPickerView extends View {
1240
- /**
1241
- * A collection of the focusable children of the view.
1242
- */ items;
1243
- /**
1244
- * Tracks information about the DOM focus in the view.
1245
- */ focusTracker;
1246
- /**
1247
- * An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
1248
- */ keystrokes;
1249
- /**
1250
- * Helps cycling over focusable {@link #items} in the view.
1251
- */ focusCycler;
1252
- /**
1253
- * An instance of the `EmojiSearchView`.
1254
- */ searchView;
1255
- /**
1256
- * An instance of the `EmojiToneView`.
1257
- */ toneView;
1258
- /**
1259
- * An instance of the `EmojiCategoriesView`.
1260
- */ categoriesView;
1261
- /**
1262
- * An instance of the `EmojiGridView`.
1263
- */ gridView;
1264
- /**
1265
- * An instance of the `EmojiGridView`.
1266
- */ infoView;
1267
- /**
1268
- * @inheritDoc
1269
- */ constructor(locale, { emojiCategories, getEmojiByQuery, skinTone, skinTones }){
1270
- super(locale);
1271
- const categoryName = emojiCategories[0].title;
1272
- this.gridView = new EmojiGridView(locale, {
1273
- categoryName,
1274
- emojiCategories,
1275
- getEmojiByQuery,
1276
- skinTone
1277
- });
1278
- this.infoView = new SearchInfoView();
1279
- this.searchView = new EmojiSearchView(locale, {
1280
- gridView: this.gridView,
1281
- resultsView: this.infoView
1282
- });
1283
- this.categoriesView = new EmojiCategoriesView(locale, {
1284
- emojiCategories,
1285
- categoryName
1286
- });
1287
- this.toneView = new EmojiToneView(locale, {
1288
- skinTone,
1289
- skinTones
1290
- });
1291
- this.items = this.createCollection([
1292
- this.searchView,
1293
- this.toneView,
1294
- this.categoriesView,
1295
- this.gridView,
1296
- this.infoView
1297
- ]);
1298
- this.focusTracker = new FocusTracker();
1299
- this.keystrokes = new KeystrokeHandler();
1300
- this.focusCycler = new FocusCycler({
1301
- focusables: this.items,
1302
- focusTracker: this.focusTracker,
1303
- keystrokeHandler: this.keystrokes,
1304
- actions: {
1305
- focusPrevious: 'shift + tab',
1306
- focusNext: 'tab'
1307
- }
1308
- });
1309
- this.setTemplate({
1310
- tag: 'div',
1311
- children: [
1312
- {
1313
- tag: 'div',
1314
- children: [
1315
- this.searchView,
1316
- this.toneView
1317
- ],
1318
- attributes: {
1319
- class: [
1320
- 'ck',
1321
- 'ck-emoji__search'
1322
- ]
1323
- }
1324
- },
1325
- this.categoriesView,
1326
- this.gridView,
1327
- {
1328
- tag: 'div',
1329
- children: [
1330
- this.infoView
1331
- ],
1332
- attributes: {
1333
- class: [
1334
- 'ck',
1335
- 'ck-search__results'
1336
- ]
1337
- }
1338
- }
1339
- ],
1340
- attributes: {
1341
- tabindex: '-1',
1342
- class: [
1343
- 'ck',
1344
- 'ck-emoji',
1345
- 'ck-search'
1346
- ]
1347
- }
1348
- });
1349
- this._setupEventListeners();
1350
- }
1351
- /**
1352
- * @inheritDoc
1353
- */ render() {
1354
- super.render();
1355
- this.focusTracker.add(this.searchView.element);
1356
- this.focusTracker.add(this.toneView.element);
1357
- this.focusTracker.add(this.categoriesView.element);
1358
- this.focusTracker.add(this.gridView.element);
1359
- this.focusTracker.add(this.infoView.element);
1360
- // Start listening for the keystrokes coming from #element.
1361
- this.keystrokes.listenTo(this.element);
1362
- }
1363
- /**
1364
- * @inheritDoc
1365
- */ destroy() {
1366
- super.destroy();
1367
- this.focusTracker.destroy();
1368
- this.keystrokes.destroy();
1369
- }
1370
- /**
1371
- * Focuses the search input.
1372
- */ focus() {
1373
- this.searchView.focus();
1374
- }
1375
- /**
1376
- * Initializes interactions between sub-views.
1377
- */ _setupEventListeners() {
1378
- const t = this.locale.t;
1379
- // Disable the category switcher when filtering by a query.
1380
- this.searchView.on('search', (evt, data)=>{
1381
- if (data.query) {
1382
- this.categoriesView.disableCategories();
1383
- } else {
1384
- this.categoriesView.enableCategories();
1385
- }
1386
- });
1387
- // Show a user-friendly message depending on the search query.
1388
- this.searchView.on('search', (evt, data)=>{
1389
- if (data.query.length === 1) {
1390
- this.infoView.set({
1391
- primaryText: t('Keep on typing to see the emoji.'),
1392
- secondaryText: t('The query must contain at least two characters.'),
1393
- isVisible: true
1394
- });
1395
- } else if (!data.resultsCount) {
1396
- this.infoView.set({
1397
- primaryText: t('No emojis were found matching "%0".', data.query),
1398
- secondaryText: t('Please try a different phrase or check the spelling.'),
1399
- isVisible: true
1400
- });
1401
- } else {
1402
- this.infoView.set({
1403
- isVisible: false
1404
- });
1405
- }
1406
- });
1407
- // Emit an update event to react to balloon dimensions changes.
1408
- this.searchView.on('search', ()=>{
1409
- this.fire('update');
1410
- this.gridView.element.scrollTo(0, 0);
1411
- });
1412
- // Update the grid of emojis when the selected category is changed.
1413
- this.categoriesView.on('change:categoryName', (ev, args, categoryName)=>{
1414
- this.gridView.categoryName = categoryName;
1415
- this.searchView.search('');
1416
- });
1417
- // Update the grid of emojis when the selected skin tone is changed.
1418
- // In such a case, the displayed emoji should use an updated skin tone value.
1419
- this.toneView.on('change:skinTone', (evt, propertyName, newValue)=>{
1420
- this.gridView.skinTone = newValue;
1421
- this.searchView.search(this.searchView.getInputValue());
1422
- });
1423
- }
1424
- }
1425
-
1426
- const VISUAL_SELECTION_MARKER_NAME = 'emoji-picker';
1427
- /**
1428
- * The emoji picker plugin.
1429
- *
1430
- * Introduces the `'emoji'` dropdown.
1431
- */ class EmojiPicker extends Plugin {
1432
- /**
1433
- * @inheritDoc
1434
- */ static get requires() {
1435
- return [
1436
- EmojiRepository,
1437
- ContextualBalloon,
1438
- Dialog,
1439
- Typing
1440
- ];
1441
- }
1442
- /**
1443
- * @inheritDoc
1444
- */ static get pluginName() {
1445
- return 'EmojiPicker';
1446
- }
1447
- /**
1448
- * @inheritDoc
1449
- */ static get isOfficialPlugin() {
1450
- return true;
1451
- }
1452
- /**
1453
- * @inheritDoc
1454
- */ async init() {
1455
- const editor = this.editor;
1456
- this.balloonPlugin = editor.plugins.get('ContextualBalloon');
1457
- this.emojiRepositoryPlugin = editor.plugins.get('EmojiRepository');
1458
- // Skip registering a button in the toolbar and list item in the menu bar if the emoji repository is not ready.
1459
- if (!await this.emojiRepositoryPlugin.isReady()) {
1460
- return;
1461
- }
1462
- const command = new EmojiCommand(editor);
1463
- editor.commands.add('emoji', command);
1464
- editor.ui.componentFactory.add('emoji', ()=>{
1465
- const button = this._createButton(ButtonView, command);
1466
- button.set({
1467
- tooltip: true
1468
- });
1469
- return button;
1470
- });
1471
- editor.ui.componentFactory.add('menuBar:emoji', ()=>{
1472
- return this._createButton(MenuBarMenuListItemButtonView, command);
1473
- });
1474
- this._setupConversion();
1475
- }
1476
- /**
1477
- * @inheritDoc
1478
- */ destroy() {
1479
- super.destroy();
1480
- if (this.emojiPickerView) {
1481
- this.emojiPickerView.destroy();
1482
- }
1483
- }
1484
- /**
1485
- * Represents an active skin tone. Its value depends on the emoji UI plugin.
1486
- *
1487
- * Before opening the UI for the first time, the returned value is read from the editor configuration.
1488
- * Otherwise, it reflects the user's intention.
1489
- */ get skinTone() {
1490
- if (!this.emojiPickerView) {
1491
- return this.editor.config.get('emoji.skinTone');
1492
- }
1493
- return this.emojiPickerView.gridView.skinTone;
1494
- }
1495
- /**
1496
- * Displays the balloon with the emoji picker.
1497
- *
1498
- * @param [searchValue=''] A default query used to filer the grid when opening the UI.
1499
- */ showUI(searchValue = '') {
1500
- // Show visual selection on a text when the contextual balloon is displayed.
1501
- // See #17654.
1502
- this._showFakeVisualSelection();
1503
- if (!this.emojiPickerView) {
1504
- this.emojiPickerView = this._createEmojiPickerView();
1505
- }
1506
- if (searchValue) {
1507
- this.emojiPickerView.searchView.setInputValue(searchValue);
1508
- }
1509
- this.emojiPickerView.searchView.search(searchValue);
1510
- if (!this.balloonPlugin.hasView(this.emojiPickerView)) {
1511
- this.balloonPlugin.add({
1512
- view: this.emojiPickerView,
1513
- position: this._getBalloonPositionData()
1514
- });
1515
- }
1516
- this.emojiPickerView.focus();
1517
- }
1518
- /**
1519
- * Creates a button for toolbar and menu bar that will show the emoji dialog.
1520
- */ _createButton(ViewClass, command) {
1521
- const buttonView = new ViewClass(this.editor.locale);
1522
- const t = this.editor.locale.t;
1523
- buttonView.bind('isEnabled').to(command, 'isEnabled');
1524
- buttonView.set({
1525
- label: t('Emoji'),
1526
- icon: icons.emoji,
1527
- isToggleable: true
1528
- });
1529
- buttonView.on('execute', ()=>{
1530
- this.showUI();
1531
- });
1532
- return buttonView;
1533
- }
1534
- /**
1535
- * Creates an instance of the `EmojiPickerView` class that represents an emoji balloon.
1536
- */ _createEmojiPickerView() {
1537
- const emojiPickerView = new EmojiPickerView(this.editor.locale, {
1538
- emojiCategories: this.emojiRepositoryPlugin.getEmojiCategories(),
1539
- skinTone: this.editor.config.get('emoji.skinTone'),
1540
- skinTones: this.emojiRepositoryPlugin.getSkinTones(),
1541
- getEmojiByQuery: (query)=>{
1542
- return this.emojiRepositoryPlugin.getEmojiByQuery(query);
1543
- }
1544
- });
1545
- // Insert an emoji on a tile click.
1546
- this.listenTo(emojiPickerView.gridView, 'execute', (evt, data)=>{
1547
- const editor = this.editor;
1548
- const textToInsert = data.emoji;
1549
- this._hideUI();
1550
- editor.execute('insertText', {
1551
- text: textToInsert
1552
- });
1553
- });
1554
- // Update the balloon position when layout is changed.
1555
- this.listenTo(emojiPickerView, 'update', ()=>{
1556
- if (this.balloonPlugin.visibleView === emojiPickerView) {
1557
- this.balloonPlugin.updatePosition();
1558
- }
1559
- });
1560
- // Close the panel on `Esc` key press when the **actions have focus**.
1561
- emojiPickerView.keystrokes.set('Esc', (data, cancel)=>{
1562
- this._hideUI();
1563
- cancel();
1564
- });
1565
- // Close the dialog when clicking outside of it.
1566
- clickOutsideHandler({
1567
- emitter: emojiPickerView,
1568
- contextElements: [
1569
- this.balloonPlugin.view.element
1570
- ],
1571
- callback: ()=>this._hideUI(),
1572
- activator: ()=>this.balloonPlugin.visibleView === emojiPickerView
1573
- });
1574
- return emojiPickerView;
1575
- }
1576
- /**
1577
- * Hides the balloon with the emoji picker.
1578
- */ _hideUI() {
1579
- this.balloonPlugin.remove(this.emojiPickerView);
1580
- this.emojiPickerView.searchView.setInputValue('');
1581
- this.editor.editing.view.focus();
1582
- this._hideFakeVisualSelection();
1583
- }
1584
- /**
1585
- * Registers converters.
1586
- */ _setupConversion() {
1587
- const editor = this.editor;
1588
- // Renders a fake visual selection marker on an expanded selection.
1589
- editor.conversion.for('editingDowncast').markerToHighlight({
1590
- model: VISUAL_SELECTION_MARKER_NAME,
1591
- view: {
1592
- classes: [
1593
- 'ck-fake-emoji-selection'
1594
- ]
1595
- }
1596
- });
1597
- // Renders a fake visual selection marker on a collapsed selection.
1598
- editor.conversion.for('editingDowncast').markerToElement({
1599
- model: VISUAL_SELECTION_MARKER_NAME,
1600
- view: (data, { writer })=>{
1601
- if (!data.markerRange.isCollapsed) {
1602
- return null;
1603
- }
1604
- const markerElement = writer.createUIElement('span');
1605
- writer.addClass([
1606
- 'ck-fake-emoji-selection',
1607
- 'ck-fake-emoji-selection_collapsed'
1608
- ], markerElement);
1609
- return markerElement;
1610
- }
1611
- });
1612
- }
1613
- /**
1614
- * Returns positioning options for the {@link #balloonPlugin}. They control the way the balloon is attached
1615
- * to the target element or selection.
1616
- */ _getBalloonPositionData() {
1617
- const view = this.editor.editing.view;
1618
- const viewDocument = view.document;
1619
- // Set a target position by converting view selection range to DOM.
1620
- const target = ()=>view.domConverter.viewRangeToDom(viewDocument.selection.getFirstRange());
1621
- return {
1622
- target
1623
- };
1624
- }
1625
- /**
1626
- * Displays a fake visual selection when the contextual balloon is displayed.
1627
- *
1628
- * This adds an 'emoji-picker' marker into the document that is rendered as a highlight on selected text fragment.
1629
- */ _showFakeVisualSelection() {
1630
- const model = this.editor.model;
1631
- model.change((writer)=>{
1632
- const range = model.document.selection.getFirstRange();
1633
- if (model.markers.has(VISUAL_SELECTION_MARKER_NAME)) {
1634
- writer.updateMarker(VISUAL_SELECTION_MARKER_NAME, {
1635
- range
1636
- });
1637
- } else {
1638
- if (range.start.isAtEnd) {
1639
- const startPosition = range.start.getLastMatchingPosition(({ item })=>!model.schema.isContent(item), {
1640
- boundaries: range
1641
- });
1642
- writer.addMarker(VISUAL_SELECTION_MARKER_NAME, {
1643
- usingOperation: false,
1644
- affectsData: false,
1645
- range: writer.createRange(startPosition, range.end)
1646
- });
1647
- } else {
1648
- writer.addMarker(VISUAL_SELECTION_MARKER_NAME, {
1649
- usingOperation: false,
1650
- affectsData: false,
1651
- range
1652
- });
1653
- }
1654
- }
1655
- });
1656
- }
1657
- /**
1658
- * Hides the fake visual selection.
1659
- */ _hideFakeVisualSelection() {
1660
- const model = this.editor.model;
1661
- if (model.markers.has(VISUAL_SELECTION_MARKER_NAME)) {
1662
- model.change((writer)=>{
1663
- writer.removeMarker(VISUAL_SELECTION_MARKER_NAME);
1664
- });
1665
- }
1666
- }
1667
- }
1668
-
1669
- /**
1670
- * The emoji plugin.
1671
- *
1672
- * This is a "glue" plugin which loads the following plugins:
1673
- *
1674
- * * {@link module:emoji/emojimention~EmojiMention},
1675
- * * {@link module:emoji/emojipicker~EmojiPicker},
1676
- */ class Emoji extends Plugin {
1677
- /**
1678
- * @inheritDoc
1679
- */ static get requires() {
1680
- return [
1681
- EmojiMention,
1682
- EmojiPicker
1683
- ];
1684
- }
1685
- /**
1686
- * @inheritDoc
1687
- */ static get pluginName() {
1688
- return 'Emoji';
1689
- }
1690
- /**
1691
- * @inheritDoc
1692
- */ static get isOfficialPlugin() {
1693
- return true;
1694
- }
1695
- }
1696
-
1697
- export { Emoji, EmojiCommand, EmojiMention, EmojiPicker, EmojiRepository, EmojiUtils };
1698
- //# sourceMappingURL=index.js.map