@ckeditor/ckeditor5-bookmark 0.0.1 → 44.0.0-alpha.0

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 (408) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/LICENSE.md +4 -5
  3. package/README.md +31 -3
  4. package/build/bookmark.js +5 -0
  5. package/build/translations/af.js +1 -0
  6. package/build/translations/ar.js +1 -0
  7. package/build/translations/ast.js +1 -0
  8. package/build/translations/az.js +1 -0
  9. package/build/translations/bg.js +1 -0
  10. package/build/translations/bn.js +1 -0
  11. package/build/translations/bs.js +1 -0
  12. package/build/translations/ca.js +1 -0
  13. package/build/translations/cs.js +1 -0
  14. package/build/translations/da.js +1 -0
  15. package/build/translations/de-ch.js +1 -0
  16. package/build/translations/de.js +1 -0
  17. package/build/translations/el.js +1 -0
  18. package/build/translations/en-au.js +1 -0
  19. package/build/translations/en-gb.js +1 -0
  20. package/build/translations/eo.js +1 -0
  21. package/build/translations/es-co.js +1 -0
  22. package/build/translations/es.js +1 -0
  23. package/build/translations/et.js +1 -0
  24. package/build/translations/eu.js +1 -0
  25. package/build/translations/fa.js +1 -0
  26. package/build/translations/fi.js +1 -0
  27. package/build/translations/fr.js +1 -0
  28. package/build/translations/gl.js +1 -0
  29. package/build/translations/gu.js +1 -0
  30. package/build/translations/he.js +1 -0
  31. package/build/translations/hi.js +1 -0
  32. package/build/translations/hr.js +1 -0
  33. package/build/translations/hu.js +1 -0
  34. package/build/translations/hy.js +1 -0
  35. package/build/translations/id.js +1 -0
  36. package/build/translations/it.js +1 -0
  37. package/build/translations/ja.js +1 -0
  38. package/build/translations/jv.js +1 -0
  39. package/build/translations/kk.js +1 -0
  40. package/build/translations/km.js +1 -0
  41. package/build/translations/kn.js +1 -0
  42. package/build/translations/ko.js +1 -0
  43. package/build/translations/ku.js +1 -0
  44. package/build/translations/lt.js +1 -0
  45. package/build/translations/lv.js +1 -0
  46. package/build/translations/ms.js +1 -0
  47. package/build/translations/nb.js +1 -0
  48. package/build/translations/ne.js +1 -0
  49. package/build/translations/nl.js +1 -0
  50. package/build/translations/no.js +1 -0
  51. package/build/translations/oc.js +1 -0
  52. package/build/translations/pl.js +1 -0
  53. package/build/translations/pt-br.js +1 -0
  54. package/build/translations/pt.js +1 -0
  55. package/build/translations/ro.js +1 -0
  56. package/build/translations/ru.js +1 -0
  57. package/build/translations/si.js +1 -0
  58. package/build/translations/sk.js +1 -0
  59. package/build/translations/sl.js +1 -0
  60. package/build/translations/sq.js +1 -0
  61. package/build/translations/sr-latn.js +1 -0
  62. package/build/translations/sr.js +1 -0
  63. package/build/translations/sv.js +1 -0
  64. package/build/translations/th.js +1 -0
  65. package/build/translations/ti.js +1 -0
  66. package/build/translations/tk.js +1 -0
  67. package/build/translations/tr.js +1 -0
  68. package/build/translations/tt.js +1 -0
  69. package/build/translations/ug.js +1 -0
  70. package/build/translations/uk.js +1 -0
  71. package/build/translations/ur.js +1 -0
  72. package/build/translations/uz.js +1 -0
  73. package/build/translations/vi.js +1 -0
  74. package/build/translations/zh-cn.js +1 -0
  75. package/build/translations/zh.js +1 -0
  76. package/ckeditor5-metadata.json +24 -0
  77. package/dist/augmentation.d.ts +28 -0
  78. package/dist/bookmark.d.ts +34 -0
  79. package/dist/bookmarkconfig.d.ts +52 -0
  80. package/dist/bookmarkediting.d.ts +55 -0
  81. package/dist/bookmarkui.d.ts +170 -0
  82. package/dist/index-content.css +4 -0
  83. package/dist/index-editor.css +150 -0
  84. package/dist/index.css +195 -0
  85. package/dist/index.css.map +1 -0
  86. package/dist/index.d.ts +18 -0
  87. package/dist/index.js +1320 -0
  88. package/dist/index.js.map +1 -0
  89. package/dist/insertbookmarkcommand.d.ts +42 -0
  90. package/dist/translations/af.d.ts +8 -0
  91. package/dist/translations/af.js +5 -0
  92. package/dist/translations/af.umd.js +11 -0
  93. package/dist/translations/ar.d.ts +8 -0
  94. package/dist/translations/ar.js +5 -0
  95. package/dist/translations/ar.umd.js +11 -0
  96. package/dist/translations/ast.d.ts +8 -0
  97. package/dist/translations/ast.js +5 -0
  98. package/dist/translations/ast.umd.js +11 -0
  99. package/dist/translations/az.d.ts +8 -0
  100. package/dist/translations/az.js +5 -0
  101. package/dist/translations/az.umd.js +11 -0
  102. package/dist/translations/bg.d.ts +8 -0
  103. package/dist/translations/bg.js +5 -0
  104. package/dist/translations/bg.umd.js +11 -0
  105. package/dist/translations/bn.d.ts +8 -0
  106. package/dist/translations/bn.js +5 -0
  107. package/dist/translations/bn.umd.js +11 -0
  108. package/dist/translations/bs.d.ts +8 -0
  109. package/dist/translations/bs.js +5 -0
  110. package/dist/translations/bs.umd.js +11 -0
  111. package/dist/translations/ca.d.ts +8 -0
  112. package/dist/translations/ca.js +5 -0
  113. package/dist/translations/ca.umd.js +11 -0
  114. package/dist/translations/cs.d.ts +8 -0
  115. package/dist/translations/cs.js +5 -0
  116. package/dist/translations/cs.umd.js +11 -0
  117. package/dist/translations/da.d.ts +8 -0
  118. package/dist/translations/da.js +5 -0
  119. package/dist/translations/da.umd.js +11 -0
  120. package/dist/translations/de-ch.d.ts +8 -0
  121. package/dist/translations/de-ch.js +5 -0
  122. package/dist/translations/de-ch.umd.js +11 -0
  123. package/dist/translations/de.d.ts +8 -0
  124. package/dist/translations/de.js +5 -0
  125. package/dist/translations/de.umd.js +11 -0
  126. package/dist/translations/el.d.ts +8 -0
  127. package/dist/translations/el.js +5 -0
  128. package/dist/translations/el.umd.js +11 -0
  129. package/dist/translations/en-au.d.ts +8 -0
  130. package/dist/translations/en-au.js +5 -0
  131. package/dist/translations/en-au.umd.js +11 -0
  132. package/dist/translations/en-gb.d.ts +8 -0
  133. package/dist/translations/en-gb.js +5 -0
  134. package/dist/translations/en-gb.umd.js +11 -0
  135. package/dist/translations/en.d.ts +8 -0
  136. package/dist/translations/en.js +5 -0
  137. package/dist/translations/en.umd.js +11 -0
  138. package/dist/translations/eo.d.ts +8 -0
  139. package/dist/translations/eo.js +5 -0
  140. package/dist/translations/eo.umd.js +11 -0
  141. package/dist/translations/es-co.d.ts +8 -0
  142. package/dist/translations/es-co.js +5 -0
  143. package/dist/translations/es-co.umd.js +11 -0
  144. package/dist/translations/es.d.ts +8 -0
  145. package/dist/translations/es.js +5 -0
  146. package/dist/translations/es.umd.js +11 -0
  147. package/dist/translations/et.d.ts +8 -0
  148. package/dist/translations/et.js +5 -0
  149. package/dist/translations/et.umd.js +11 -0
  150. package/dist/translations/eu.d.ts +8 -0
  151. package/dist/translations/eu.js +5 -0
  152. package/dist/translations/eu.umd.js +11 -0
  153. package/dist/translations/fa.d.ts +8 -0
  154. package/dist/translations/fa.js +5 -0
  155. package/dist/translations/fa.umd.js +11 -0
  156. package/dist/translations/fi.d.ts +8 -0
  157. package/dist/translations/fi.js +5 -0
  158. package/dist/translations/fi.umd.js +11 -0
  159. package/dist/translations/fr.d.ts +8 -0
  160. package/dist/translations/fr.js +5 -0
  161. package/dist/translations/fr.umd.js +11 -0
  162. package/dist/translations/gl.d.ts +8 -0
  163. package/dist/translations/gl.js +5 -0
  164. package/dist/translations/gl.umd.js +11 -0
  165. package/dist/translations/gu.d.ts +8 -0
  166. package/dist/translations/gu.js +5 -0
  167. package/dist/translations/gu.umd.js +11 -0
  168. package/dist/translations/he.d.ts +8 -0
  169. package/dist/translations/he.js +5 -0
  170. package/dist/translations/he.umd.js +11 -0
  171. package/dist/translations/hi.d.ts +8 -0
  172. package/dist/translations/hi.js +5 -0
  173. package/dist/translations/hi.umd.js +11 -0
  174. package/dist/translations/hr.d.ts +8 -0
  175. package/dist/translations/hr.js +5 -0
  176. package/dist/translations/hr.umd.js +11 -0
  177. package/dist/translations/hu.d.ts +8 -0
  178. package/dist/translations/hu.js +5 -0
  179. package/dist/translations/hu.umd.js +11 -0
  180. package/dist/translations/hy.d.ts +8 -0
  181. package/dist/translations/hy.js +5 -0
  182. package/dist/translations/hy.umd.js +11 -0
  183. package/dist/translations/id.d.ts +8 -0
  184. package/dist/translations/id.js +5 -0
  185. package/dist/translations/id.umd.js +11 -0
  186. package/dist/translations/it.d.ts +8 -0
  187. package/dist/translations/it.js +5 -0
  188. package/dist/translations/it.umd.js +11 -0
  189. package/dist/translations/ja.d.ts +8 -0
  190. package/dist/translations/ja.js +5 -0
  191. package/dist/translations/ja.umd.js +11 -0
  192. package/dist/translations/jv.d.ts +8 -0
  193. package/dist/translations/jv.js +5 -0
  194. package/dist/translations/jv.umd.js +11 -0
  195. package/dist/translations/kk.d.ts +8 -0
  196. package/dist/translations/kk.js +5 -0
  197. package/dist/translations/kk.umd.js +11 -0
  198. package/dist/translations/km.d.ts +8 -0
  199. package/dist/translations/km.js +5 -0
  200. package/dist/translations/km.umd.js +11 -0
  201. package/dist/translations/kn.d.ts +8 -0
  202. package/dist/translations/kn.js +5 -0
  203. package/dist/translations/kn.umd.js +11 -0
  204. package/dist/translations/ko.d.ts +8 -0
  205. package/dist/translations/ko.js +5 -0
  206. package/dist/translations/ko.umd.js +11 -0
  207. package/dist/translations/ku.d.ts +8 -0
  208. package/dist/translations/ku.js +5 -0
  209. package/dist/translations/ku.umd.js +11 -0
  210. package/dist/translations/lt.d.ts +8 -0
  211. package/dist/translations/lt.js +5 -0
  212. package/dist/translations/lt.umd.js +11 -0
  213. package/dist/translations/lv.d.ts +8 -0
  214. package/dist/translations/lv.js +5 -0
  215. package/dist/translations/lv.umd.js +11 -0
  216. package/dist/translations/ms.d.ts +8 -0
  217. package/dist/translations/ms.js +5 -0
  218. package/dist/translations/ms.umd.js +11 -0
  219. package/dist/translations/nb.d.ts +8 -0
  220. package/dist/translations/nb.js +5 -0
  221. package/dist/translations/nb.umd.js +11 -0
  222. package/dist/translations/ne.d.ts +8 -0
  223. package/dist/translations/ne.js +5 -0
  224. package/dist/translations/ne.umd.js +11 -0
  225. package/dist/translations/nl.d.ts +8 -0
  226. package/dist/translations/nl.js +5 -0
  227. package/dist/translations/nl.umd.js +11 -0
  228. package/dist/translations/no.d.ts +8 -0
  229. package/dist/translations/no.js +5 -0
  230. package/dist/translations/no.umd.js +11 -0
  231. package/dist/translations/oc.d.ts +8 -0
  232. package/dist/translations/oc.js +5 -0
  233. package/dist/translations/oc.umd.js +11 -0
  234. package/dist/translations/pl.d.ts +8 -0
  235. package/dist/translations/pl.js +5 -0
  236. package/dist/translations/pl.umd.js +11 -0
  237. package/dist/translations/pt-br.d.ts +8 -0
  238. package/dist/translations/pt-br.js +5 -0
  239. package/dist/translations/pt-br.umd.js +11 -0
  240. package/dist/translations/pt.d.ts +8 -0
  241. package/dist/translations/pt.js +5 -0
  242. package/dist/translations/pt.umd.js +11 -0
  243. package/dist/translations/ro.d.ts +8 -0
  244. package/dist/translations/ro.js +5 -0
  245. package/dist/translations/ro.umd.js +11 -0
  246. package/dist/translations/ru.d.ts +8 -0
  247. package/dist/translations/ru.js +5 -0
  248. package/dist/translations/ru.umd.js +11 -0
  249. package/dist/translations/si.d.ts +8 -0
  250. package/dist/translations/si.js +5 -0
  251. package/dist/translations/si.umd.js +11 -0
  252. package/dist/translations/sk.d.ts +8 -0
  253. package/dist/translations/sk.js +5 -0
  254. package/dist/translations/sk.umd.js +11 -0
  255. package/dist/translations/sl.d.ts +8 -0
  256. package/dist/translations/sl.js +5 -0
  257. package/dist/translations/sl.umd.js +11 -0
  258. package/dist/translations/sq.d.ts +8 -0
  259. package/dist/translations/sq.js +5 -0
  260. package/dist/translations/sq.umd.js +11 -0
  261. package/dist/translations/sr-latn.d.ts +8 -0
  262. package/dist/translations/sr-latn.js +5 -0
  263. package/dist/translations/sr-latn.umd.js +11 -0
  264. package/dist/translations/sr.d.ts +8 -0
  265. package/dist/translations/sr.js +5 -0
  266. package/dist/translations/sr.umd.js +11 -0
  267. package/dist/translations/sv.d.ts +8 -0
  268. package/dist/translations/sv.js +5 -0
  269. package/dist/translations/sv.umd.js +11 -0
  270. package/dist/translations/th.d.ts +8 -0
  271. package/dist/translations/th.js +5 -0
  272. package/dist/translations/th.umd.js +11 -0
  273. package/dist/translations/ti.d.ts +8 -0
  274. package/dist/translations/ti.js +5 -0
  275. package/dist/translations/ti.umd.js +11 -0
  276. package/dist/translations/tk.d.ts +8 -0
  277. package/dist/translations/tk.js +5 -0
  278. package/dist/translations/tk.umd.js +11 -0
  279. package/dist/translations/tr.d.ts +8 -0
  280. package/dist/translations/tr.js +5 -0
  281. package/dist/translations/tr.umd.js +11 -0
  282. package/dist/translations/tt.d.ts +8 -0
  283. package/dist/translations/tt.js +5 -0
  284. package/dist/translations/tt.umd.js +11 -0
  285. package/dist/translations/ug.d.ts +8 -0
  286. package/dist/translations/ug.js +5 -0
  287. package/dist/translations/ug.umd.js +11 -0
  288. package/dist/translations/uk.d.ts +8 -0
  289. package/dist/translations/uk.js +5 -0
  290. package/dist/translations/uk.umd.js +11 -0
  291. package/dist/translations/ur.d.ts +8 -0
  292. package/dist/translations/ur.js +5 -0
  293. package/dist/translations/ur.umd.js +11 -0
  294. package/dist/translations/uz.d.ts +8 -0
  295. package/dist/translations/uz.js +5 -0
  296. package/dist/translations/uz.umd.js +11 -0
  297. package/dist/translations/vi.d.ts +8 -0
  298. package/dist/translations/vi.js +5 -0
  299. package/dist/translations/vi.umd.js +11 -0
  300. package/dist/translations/zh-cn.d.ts +8 -0
  301. package/dist/translations/zh-cn.js +5 -0
  302. package/dist/translations/zh-cn.umd.js +11 -0
  303. package/dist/translations/zh.d.ts +8 -0
  304. package/dist/translations/zh.js +5 -0
  305. package/dist/translations/zh.umd.js +11 -0
  306. package/dist/ui/bookmarkactionsview.d.ts +106 -0
  307. package/dist/ui/bookmarkformview.d.ts +122 -0
  308. package/dist/updatebookmarkcommand.d.ts +46 -0
  309. package/dist/utils.d.ts +15 -0
  310. package/lang/contexts.json +13 -0
  311. package/lang/translations/af.po +56 -0
  312. package/lang/translations/ar.po +56 -0
  313. package/lang/translations/ast.po +56 -0
  314. package/lang/translations/az.po +56 -0
  315. package/lang/translations/bg.po +56 -0
  316. package/lang/translations/bn.po +56 -0
  317. package/lang/translations/bs.po +56 -0
  318. package/lang/translations/ca.po +56 -0
  319. package/lang/translations/cs.po +56 -0
  320. package/lang/translations/da.po +56 -0
  321. package/lang/translations/de-ch.po +56 -0
  322. package/lang/translations/de.po +56 -0
  323. package/lang/translations/el.po +56 -0
  324. package/lang/translations/en-au.po +56 -0
  325. package/lang/translations/en-gb.po +56 -0
  326. package/lang/translations/en.po +56 -0
  327. package/lang/translations/eo.po +56 -0
  328. package/lang/translations/es-co.po +56 -0
  329. package/lang/translations/es.po +56 -0
  330. package/lang/translations/et.po +56 -0
  331. package/lang/translations/eu.po +56 -0
  332. package/lang/translations/fa.po +56 -0
  333. package/lang/translations/fi.po +56 -0
  334. package/lang/translations/fr.po +56 -0
  335. package/lang/translations/gl.po +56 -0
  336. package/lang/translations/gu.po +56 -0
  337. package/lang/translations/he.po +56 -0
  338. package/lang/translations/hi.po +56 -0
  339. package/lang/translations/hr.po +56 -0
  340. package/lang/translations/hu.po +56 -0
  341. package/lang/translations/hy.po +56 -0
  342. package/lang/translations/id.po +56 -0
  343. package/lang/translations/it.po +56 -0
  344. package/lang/translations/ja.po +56 -0
  345. package/lang/translations/jv.po +56 -0
  346. package/lang/translations/kk.po +56 -0
  347. package/lang/translations/km.po +56 -0
  348. package/lang/translations/kn.po +56 -0
  349. package/lang/translations/ko.po +56 -0
  350. package/lang/translations/ku.po +56 -0
  351. package/lang/translations/lt.po +56 -0
  352. package/lang/translations/lv.po +56 -0
  353. package/lang/translations/ms.po +56 -0
  354. package/lang/translations/nb.po +56 -0
  355. package/lang/translations/ne.po +56 -0
  356. package/lang/translations/nl.po +56 -0
  357. package/lang/translations/no.po +56 -0
  358. package/lang/translations/oc.po +56 -0
  359. package/lang/translations/pl.po +56 -0
  360. package/lang/translations/pt-br.po +56 -0
  361. package/lang/translations/pt.po +56 -0
  362. package/lang/translations/ro.po +56 -0
  363. package/lang/translations/ru.po +56 -0
  364. package/lang/translations/si.po +56 -0
  365. package/lang/translations/sk.po +56 -0
  366. package/lang/translations/sl.po +56 -0
  367. package/lang/translations/sq.po +56 -0
  368. package/lang/translations/sr-latn.po +56 -0
  369. package/lang/translations/sr.po +56 -0
  370. package/lang/translations/sv.po +56 -0
  371. package/lang/translations/th.po +56 -0
  372. package/lang/translations/ti.po +56 -0
  373. package/lang/translations/tk.po +56 -0
  374. package/lang/translations/tr.po +56 -0
  375. package/lang/translations/tt.po +56 -0
  376. package/lang/translations/ug.po +56 -0
  377. package/lang/translations/uk.po +56 -0
  378. package/lang/translations/ur.po +56 -0
  379. package/lang/translations/uz.po +56 -0
  380. package/lang/translations/vi.po +56 -0
  381. package/lang/translations/zh-cn.po +56 -0
  382. package/lang/translations/zh.po +56 -0
  383. package/package.json +32 -3
  384. package/src/augmentation.d.ts +24 -0
  385. package/src/augmentation.js +5 -0
  386. package/src/bookmark.d.ts +30 -0
  387. package/src/bookmark.js +36 -0
  388. package/src/bookmarkconfig.d.ts +48 -0
  389. package/src/bookmarkconfig.js +5 -0
  390. package/src/bookmarkediting.d.ts +51 -0
  391. package/src/bookmarkediting.js +211 -0
  392. package/src/bookmarkui.d.ts +166 -0
  393. package/src/bookmarkui.js +582 -0
  394. package/src/index.d.ts +14 -0
  395. package/src/index.js +13 -0
  396. package/src/insertbookmarkcommand.d.ts +38 -0
  397. package/src/insertbookmarkcommand.js +113 -0
  398. package/src/ui/bookmarkactionsview.d.ts +102 -0
  399. package/src/ui/bookmarkactionsview.js +154 -0
  400. package/src/ui/bookmarkformview.d.ts +118 -0
  401. package/src/ui/bookmarkformview.js +203 -0
  402. package/src/updatebookmarkcommand.d.ts +42 -0
  403. package/src/updatebookmarkcommand.js +75 -0
  404. package/src/utils.d.ts +11 -0
  405. package/src/utils.js +19 -0
  406. package/theme/bookmark.css +50 -0
  407. package/theme/bookmarkactions.css +44 -0
  408. package/theme/bookmarkform.css +42 -0
package/dist/index.js ADDED
@@ -0,0 +1,1320 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ import { icons, Command, Plugin } from '@ckeditor/ckeditor5-core/dist/index.js';
6
+ import { toWidget, Widget } from '@ckeditor/ckeditor5-widget/dist/index.js';
7
+ import { View, ViewCollection, FocusCycler, submitHandler, FormHeaderView, LabeledFieldView, createLabeledInputText, ButtonView, LabelView, IconView, ContextualBalloon, CssTransitionDisablerMixin, MenuBarMenuListItemButtonView, clickOutsideHandler } from '@ckeditor/ckeditor5-ui/dist/index.js';
8
+ import { ClickObserver } from '@ckeditor/ckeditor5-engine/dist/index.js';
9
+ import { FocusTracker, KeystrokeHandler, logWarning } from '@ckeditor/ckeditor5-utils/dist/index.js';
10
+
11
+ /**
12
+ * The bookmark form view controller class.
13
+ *
14
+ * See {@link module:bookmark/ui/bookmarkformview~BookmarkFormView}.
15
+ */ class BookmarkFormView extends View {
16
+ /**
17
+ * Tracks information about DOM focus in the form.
18
+ */ focusTracker = new FocusTracker();
19
+ /**
20
+ * An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
21
+ */ keystrokes = new KeystrokeHandler();
22
+ /**
23
+ * The ID input view.
24
+ */ idInputView;
25
+ /**
26
+ * The Submit button view.
27
+ */ buttonView;
28
+ /**
29
+ * A collection of form child views in the form.
30
+ */ children;
31
+ /**
32
+ * An array of form validators used by {@link #isValid}.
33
+ */ _validators;
34
+ /**
35
+ * A collection of views that can be focused in the form.
36
+ */ _focusables = new ViewCollection();
37
+ /**
38
+ * Helps cycling over {@link #_focusables} in the form.
39
+ */ _focusCycler;
40
+ /**
41
+ * Creates an instance of the {@link module:bookmark/ui/bookmarkformview~BookmarkFormView} class.
42
+ *
43
+ * Also see {@link #render}.
44
+ *
45
+ * @param locale The localization services instance.
46
+ * @param validators Form validators used by {@link #isValid}.
47
+ */ constructor(locale, validators){
48
+ super(locale);
49
+ const t = locale.t;
50
+ this._validators = validators;
51
+ this.idInputView = this._createIdInput();
52
+ this.buttonView = this._createButton(t('Insert'), 'ck-button-action ck-button-bold');
53
+ this.buttonView.type = 'submit';
54
+ this.children = this._createViewChildren();
55
+ this._focusCycler = new FocusCycler({
56
+ focusables: this._focusables,
57
+ focusTracker: this.focusTracker,
58
+ keystrokeHandler: this.keystrokes,
59
+ actions: {
60
+ // Navigate form fields backwards using the Shift + Tab keystroke.
61
+ focusPrevious: 'shift + tab',
62
+ // Navigate form fields forwards using the Tab key.
63
+ focusNext: 'tab'
64
+ }
65
+ });
66
+ const classList = [
67
+ 'ck',
68
+ 'ck-bookmark-view'
69
+ ];
70
+ this.setTemplate({
71
+ tag: 'form',
72
+ attributes: {
73
+ class: classList,
74
+ // https://github.com/ckeditor/ckeditor5-link/issues/90
75
+ tabindex: '-1'
76
+ },
77
+ children: this.children
78
+ });
79
+ }
80
+ /**
81
+ * @inheritDoc
82
+ */ render() {
83
+ super.render();
84
+ submitHandler({
85
+ view: this
86
+ });
87
+ const childViews = [
88
+ this.idInputView,
89
+ this.buttonView
90
+ ];
91
+ childViews.forEach((v)=>{
92
+ // Register the view as focusable.
93
+ this._focusables.add(v);
94
+ // Register the view in the focus tracker.
95
+ this.focusTracker.add(v.element);
96
+ });
97
+ // Start listening for the keystrokes coming from #element.
98
+ this.keystrokes.listenTo(this.element);
99
+ }
100
+ /**
101
+ * @inheritDoc
102
+ */ destroy() {
103
+ super.destroy();
104
+ this.focusTracker.destroy();
105
+ this.keystrokes.destroy();
106
+ }
107
+ /**
108
+ * Focuses the fist {@link #_focusables} in the form.
109
+ */ focus() {
110
+ this._focusCycler.focusFirst();
111
+ }
112
+ /**
113
+ * Validates the form and returns `false` when some fields are invalid.
114
+ */ isValid() {
115
+ this.resetFormStatus();
116
+ for (const validator of this._validators){
117
+ const errorText = validator(this);
118
+ // One error per field is enough.
119
+ if (errorText) {
120
+ // Apply updated error.
121
+ this.idInputView.errorText = errorText;
122
+ return false;
123
+ }
124
+ }
125
+ return true;
126
+ }
127
+ /**
128
+ * Cleans up the supplementary error and information text of the {@link #idInputView}
129
+ * bringing them back to the state when the form has been displayed for the first time.
130
+ *
131
+ * See {@link #isValid}.
132
+ */ resetFormStatus() {
133
+ this.idInputView.errorText = null;
134
+ }
135
+ /**
136
+ * Creates header and form view.
137
+ */ _createViewChildren() {
138
+ const children = this.createCollection();
139
+ const t = this.t;
140
+ children.add(new FormHeaderView(this.locale, {
141
+ label: t('Bookmark')
142
+ }));
143
+ children.add(this._createFormContentView());
144
+ return children;
145
+ }
146
+ /**
147
+ * Creates form content view with input and button.
148
+ */ _createFormContentView() {
149
+ const view = new View(this.locale);
150
+ const children = this.createCollection();
151
+ const classList = [
152
+ 'ck',
153
+ 'ck-bookmark-form',
154
+ 'ck-responsive-form'
155
+ ];
156
+ children.add(this.idInputView);
157
+ children.add(this.buttonView);
158
+ view.setTemplate({
159
+ tag: 'div',
160
+ attributes: {
161
+ class: classList
162
+ },
163
+ children
164
+ });
165
+ return view;
166
+ }
167
+ /**
168
+ * Creates a labeled input view.
169
+ *
170
+ * @returns Labeled field view instance.
171
+ */ _createIdInput() {
172
+ const t = this.locale.t;
173
+ const labeledInput = new LabeledFieldView(this.locale, createLabeledInputText);
174
+ labeledInput.label = t('Bookmark name');
175
+ labeledInput.infoText = t('Enter the bookmark name without spaces.');
176
+ return labeledInput;
177
+ }
178
+ /**
179
+ * Creates a button view.
180
+ *
181
+ * @param label The button label.
182
+ * @param className The additional button CSS class name.
183
+ * @returns The button view instance.
184
+ */ _createButton(label, className) {
185
+ const button = new ButtonView(this.locale);
186
+ button.set({
187
+ label,
188
+ withText: true
189
+ });
190
+ button.extendTemplate({
191
+ attributes: {
192
+ class: className
193
+ }
194
+ });
195
+ return button;
196
+ }
197
+ /**
198
+ * The native DOM `value` of the {@link #idInputView} element.
199
+ *
200
+ * **Note**: Do not confuse it with the {@link module:ui/inputtext/inputtextview~InputTextView#value}
201
+ * which works one way only and may not represent the actual state of the component in the DOM.
202
+ */ get id() {
203
+ const { element } = this.idInputView.fieldView;
204
+ if (!element) {
205
+ return null;
206
+ }
207
+ return element.value.trim();
208
+ }
209
+ }
210
+
211
+ /**
212
+ * The bookmark actions view class. This view displays the bookmark preview, allows
213
+ * removing or editing the bookmark.
214
+ */ class BookmarkActionsView extends View {
215
+ /**
216
+ * Tracks information about DOM focus in the actions.
217
+ */ focusTracker = new FocusTracker();
218
+ /**
219
+ * An instance of the {@link module:utils/keystrokehandler~KeystrokeHandler}.
220
+ */ keystrokes = new KeystrokeHandler();
221
+ /**
222
+ * The bookmark preview view.
223
+ */ bookmarkPreviewView;
224
+ /**
225
+ * The remove button view.
226
+ */ removeButtonView;
227
+ /**
228
+ * The edit bookmark button view.
229
+ */ editButtonView;
230
+ /**
231
+ * A collection of views that can be focused in the view.
232
+ */ _focusables = new ViewCollection();
233
+ /**
234
+ * Helps cycling over {@link #_focusables} in the view.
235
+ */ _focusCycler;
236
+ /**
237
+ * @inheritDoc
238
+ */ constructor(locale){
239
+ super(locale);
240
+ const t = locale.t;
241
+ this.bookmarkPreviewView = this._createBookmarkPreviewView();
242
+ this.removeButtonView = this._createButton(t('Remove bookmark'), icons.remove, 'remove', this.bookmarkPreviewView);
243
+ this.editButtonView = this._createButton(t('Edit bookmark'), icons.pencil, 'edit', this.bookmarkPreviewView);
244
+ this.set('id', undefined);
245
+ this._focusCycler = new FocusCycler({
246
+ focusables: this._focusables,
247
+ focusTracker: this.focusTracker,
248
+ keystrokeHandler: this.keystrokes,
249
+ actions: {
250
+ // Navigate fields backwards using the Shift + Tab keystroke.
251
+ focusPrevious: 'shift + tab',
252
+ // Navigate fields forwards using the Tab key.
253
+ focusNext: 'tab'
254
+ }
255
+ });
256
+ this.setTemplate({
257
+ tag: 'div',
258
+ attributes: {
259
+ class: [
260
+ 'ck',
261
+ 'ck-bookmark-actions',
262
+ 'ck-responsive-form'
263
+ ],
264
+ // https://github.com/ckeditor/ckeditor5-link/issues/90
265
+ tabindex: '-1'
266
+ },
267
+ children: [
268
+ this.bookmarkPreviewView,
269
+ this.editButtonView,
270
+ this.removeButtonView
271
+ ]
272
+ });
273
+ }
274
+ /**
275
+ * @inheritDoc
276
+ */ render() {
277
+ super.render();
278
+ const childViews = [
279
+ this.editButtonView,
280
+ this.removeButtonView
281
+ ];
282
+ childViews.forEach((v)=>{
283
+ // Register the view as focusable.
284
+ this._focusables.add(v);
285
+ // Register the view in the focus tracker.
286
+ this.focusTracker.add(v.element);
287
+ });
288
+ // Start listening for the keystrokes coming from #element.
289
+ this.keystrokes.listenTo(this.element);
290
+ }
291
+ /**
292
+ * @inheritDoc
293
+ */ destroy() {
294
+ super.destroy();
295
+ this.focusTracker.destroy();
296
+ this.keystrokes.destroy();
297
+ }
298
+ /**
299
+ * Focuses the fist {@link #_focusables} in the actions.
300
+ */ focus() {
301
+ this._focusCycler.focusFirst();
302
+ }
303
+ /**
304
+ * Creates a button view.
305
+ *
306
+ * @param label The button label.
307
+ * @param icon The button icon.
308
+ * @param eventName An event name that the `ButtonView#execute` event will be delegated to.
309
+ * @param additionalLabel An additional label outside the button.
310
+ * @returns The button view instance.
311
+ */ _createButton(label, icon, eventName, additionalLabel) {
312
+ const button = new ButtonView(this.locale);
313
+ button.set({
314
+ label,
315
+ icon,
316
+ tooltip: true
317
+ });
318
+ button.delegate('execute').to(this, eventName);
319
+ // Since button label `id` is bound to the `ariaLabelledBy` property
320
+ // we need to modify this binding to include only the first ID token
321
+ // as this button will be labeled by multiple labels.
322
+ button.labelView.unbind('id');
323
+ button.labelView.bind('id').to(button, 'ariaLabelledBy', (ariaLabelledBy)=>{
324
+ return getFirstToken(ariaLabelledBy);
325
+ });
326
+ button.ariaLabelledBy = `${button.ariaLabelledBy} ${additionalLabel.id}`;
327
+ return button;
328
+ }
329
+ /**
330
+ * Creates a bookmark name preview label.
331
+ *
332
+ * @returns The label view instance.
333
+ */ _createBookmarkPreviewView() {
334
+ const label = new LabelView(this.locale);
335
+ label.extendTemplate({
336
+ attributes: {
337
+ class: [
338
+ 'ck',
339
+ 'ck-bookmark-actions__preview'
340
+ ]
341
+ }
342
+ });
343
+ // Bind label text with the bookmark ID.
344
+ label.bind('text').to(this, 'id');
345
+ return label;
346
+ }
347
+ }
348
+ /**
349
+ * Returns the first token from space separated token list.
350
+ */ function getFirstToken(tokenList) {
351
+ return tokenList.split(' ')[0];
352
+ }
353
+
354
+ /**
355
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
356
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
357
+ */ /**
358
+ * @module bookmark/utils
359
+ */ /**
360
+ * Returns `true` if the bookmark id is valid; otherwise, returns `false`.
361
+ */ function isBookmarkIdValid(id) {
362
+ if (!id || typeof id !== 'string') {
363
+ return false;
364
+ }
365
+ if (/\s/.test(id)) {
366
+ return false;
367
+ }
368
+ return true;
369
+ }
370
+
371
+ /**
372
+ * The insert bookmark command.
373
+ *
374
+ * The command is registered by {@link module:bookmark/bookmarkediting~BookmarkEditing} as `'insertBookmark'`.
375
+ *
376
+ * To insert a bookmark element at place where is the current collapsed selection or where is the beginning of document selection,
377
+ * execute the command passing the bookmark id as a parameter:
378
+ *
379
+ * ```ts
380
+ * editor.execute( 'insertBookmark', { bookmarkId: 'foo_bar' } );
381
+ * ```
382
+ */ class InsertBookmarkCommand extends Command {
383
+ /**
384
+ * @inheritDoc
385
+ */ refresh() {
386
+ const model = this.editor.model;
387
+ const selection = model.document.selection;
388
+ const position = this._getPositionToInsertBookmark(selection);
389
+ this.isEnabled = !!position;
390
+ }
391
+ /**
392
+ * Executes the command.
393
+ *
394
+ * @fires execute
395
+ * @param options Command options.
396
+ * @param options.bookmarkId The value of the `bookmarkId` attribute.
397
+ */ execute(options) {
398
+ if (!options) {
399
+ return;
400
+ }
401
+ const { bookmarkId } = options;
402
+ if (!isBookmarkIdValid(bookmarkId)) {
403
+ /**
404
+ * Insert bookmark command can be executed only with a valid name.
405
+ *
406
+ * A valid bookmark name must be a non-empty string and must not contain any spaces.
407
+ *
408
+ * @error insert-bookmark-command-executed-with-invalid-name
409
+ */ logWarning('insert-bookmark-command-executed-with-invalid-name');
410
+ return;
411
+ }
412
+ const editor = this.editor;
413
+ const model = editor.model;
414
+ const selection = model.document.selection;
415
+ model.change((writer)=>{
416
+ let position = this._getPositionToInsertBookmark(selection);
417
+ const isBookmarkAllowed = model.schema.checkChild(position, 'bookmark');
418
+ // If the position does not allow for `bookmark` but allows for a `paragraph`
419
+ // then insert a `paragraph` then we will insert a `bookmark` inside.
420
+ if (!isBookmarkAllowed) {
421
+ const newPosition = editor.execute('insertParagraph', {
422
+ position
423
+ });
424
+ if (!newPosition) {
425
+ return;
426
+ }
427
+ position = newPosition;
428
+ }
429
+ const bookmarkElement = writer.createElement('bookmark', {
430
+ ...Object.fromEntries(selection.getAttributes()),
431
+ bookmarkId
432
+ });
433
+ model.insertObject(bookmarkElement, position, null, {
434
+ setSelection: 'on'
435
+ });
436
+ });
437
+ }
438
+ /**
439
+ * Returns the position where the bookmark can be inserted. And if it is not possible to insert a bookmark,
440
+ * check if it is possible to insert a paragraph.
441
+ */ _getPositionToInsertBookmark(selection) {
442
+ const model = this.editor.model;
443
+ const schema = model.schema;
444
+ const firstRange = selection.getFirstRange();
445
+ const startPosition = firstRange.start;
446
+ // Return position if it is allowed to insert bookmark or if it is allowed to insert paragraph.
447
+ if (isBookmarkAllowed(startPosition, schema)) {
448
+ return startPosition;
449
+ }
450
+ for (const { previousPosition, item } of firstRange){
451
+ // When the table cell is selected (from the outside) we look for the first paragraph-like element inside.
452
+ if (item.is('element') && schema.checkChild(item, '$text') && isBookmarkAllowed(item, schema)) {
453
+ return model.createPositionAt(item, 0);
454
+ }
455
+ if (isBookmarkAllowed(previousPosition, schema)) {
456
+ return previousPosition;
457
+ }
458
+ }
459
+ return null;
460
+ }
461
+ }
462
+ /**
463
+ * Verify if the given position allows for bookmark insertion. Verify if auto-paragraphing could help.
464
+ */ function isBookmarkAllowed(position, schema) {
465
+ if (schema.checkChild(position, 'bookmark')) {
466
+ return true;
467
+ }
468
+ if (!schema.checkChild(position, 'paragraph')) {
469
+ return false;
470
+ }
471
+ return schema.checkChild('paragraph', 'bookmark');
472
+ }
473
+
474
+ /**
475
+ * The update bookmark command.
476
+ *
477
+ * The command is registered by {@link module:bookmark/bookmarkediting~BookmarkEditing} as `'updateBookmark'`.
478
+ *
479
+ * To update the `bookmarkId` of current selected bookmark element, execute the command passing the bookmark id as a parameter:
480
+ *
481
+ * ```ts
482
+ * editor.execute( 'updateBookmark', { bookmarkId: 'newId' } );
483
+ * ```
484
+ */ class UpdateBookmarkCommand extends Command {
485
+ /**
486
+ * @inheritDoc
487
+ */ refresh() {
488
+ const model = this.editor.model;
489
+ const selection = model.document.selection;
490
+ const selectedBookmark = getSelectedBookmark(selection);
491
+ this.isEnabled = !!selectedBookmark;
492
+ this.value = selectedBookmark ? selectedBookmark.getAttribute('bookmarkId') : undefined;
493
+ }
494
+ /**
495
+ * Executes the command.
496
+ *
497
+ * @fires execute
498
+ * @param options Command options.
499
+ * @param options.bookmarkId The new value of the `bookmarkId` attribute to set.
500
+ */ execute(options) {
501
+ if (!options) {
502
+ return;
503
+ }
504
+ const { bookmarkId } = options;
505
+ if (!isBookmarkIdValid(bookmarkId)) {
506
+ /**
507
+ * Update bookmark command can be executed only with a valid name.
508
+ *
509
+ * A valid bookmark name must be a non-empty string and must not contain any spaces.
510
+ *
511
+ * @error update-bookmark-command-executed-with-invalid-name
512
+ */ logWarning('update-bookmark-command-executed-with-invalid-name');
513
+ return;
514
+ }
515
+ const model = this.editor.model;
516
+ const selection = model.document.selection;
517
+ const selectedBookmark = getSelectedBookmark(selection);
518
+ if (selectedBookmark) {
519
+ model.change((writer)=>{
520
+ writer.setAttribute('bookmarkId', bookmarkId, selectedBookmark);
521
+ });
522
+ }
523
+ }
524
+ }
525
+ /**
526
+ * Returns the selected `bookmark` element in the model, if any.
527
+ */ function getSelectedBookmark(selection) {
528
+ const element = selection.getSelectedElement();
529
+ if (!!element && element.is('element', 'bookmark')) {
530
+ return element;
531
+ }
532
+ return null;
533
+ }
534
+
535
+ /**
536
+ * The bookmark editing plugin.
537
+ */ class BookmarkEditing extends Plugin {
538
+ /**
539
+ * A collection of bookmarks elements in the document.
540
+ */ _bookmarkElements = new Map();
541
+ /**
542
+ * @inheritDoc
543
+ */ static get pluginName() {
544
+ return 'BookmarkEditing';
545
+ }
546
+ /**
547
+ * @inheritDoc
548
+ */ static get isOfficialPlugin() {
549
+ return true;
550
+ }
551
+ /**
552
+ * @inheritDoc
553
+ */ init() {
554
+ const { editor } = this;
555
+ this._defineSchema();
556
+ this._defineConverters();
557
+ editor.commands.add('insertBookmark', new InsertBookmarkCommand(editor));
558
+ editor.commands.add('updateBookmark', new UpdateBookmarkCommand(editor));
559
+ this.listenTo(editor.model.document, 'change:data', ()=>{
560
+ this._trackBookmarkElements();
561
+ });
562
+ }
563
+ /**
564
+ * Returns the model element for the given bookmark ID if it exists.
565
+ */ getElementForBookmarkId(bookmarkId) {
566
+ for (const [element, id] of this._bookmarkElements){
567
+ if (id == bookmarkId) {
568
+ return element;
569
+ }
570
+ }
571
+ return null;
572
+ }
573
+ /**
574
+ * Defines the schema for the bookmark feature.
575
+ */ _defineSchema() {
576
+ const schema = this.editor.model.schema;
577
+ schema.register('bookmark', {
578
+ inheritAllFrom: '$inlineObject',
579
+ allowAttributes: 'bookmarkId',
580
+ disallowAttributes: [
581
+ 'linkHref',
582
+ 'htmlA'
583
+ ]
584
+ });
585
+ }
586
+ /**
587
+ * Defines the converters for the bookmark feature.
588
+ */ _defineConverters() {
589
+ const { editor } = this;
590
+ const { conversion, t } = editor;
591
+ editor.data.htmlProcessor.domConverter.registerInlineObjectMatcher((element)=>upcastMatcher(element));
592
+ // Register an inline object matcher so that bookmarks <a>s are correctly recognized as inline elements in editing pipeline.
593
+ // This prevents converting spaces around bookmarks to `&nbsp;`s.
594
+ editor.editing.view.domConverter.registerInlineObjectMatcher((element)=>upcastMatcher(element, false));
595
+ conversion.for('dataDowncast').elementToElement({
596
+ model: {
597
+ name: 'bookmark',
598
+ attributes: [
599
+ 'bookmarkId'
600
+ ]
601
+ },
602
+ view: (modelElement, { writer })=>{
603
+ const emptyElement = writer.createEmptyElement('a', {
604
+ 'id': modelElement.getAttribute('bookmarkId')
605
+ });
606
+ // `getFillerOffset` is not needed to set here, because `emptyElement` has already covered it.
607
+ return emptyElement;
608
+ }
609
+ });
610
+ conversion.for('editingDowncast').elementToElement({
611
+ model: {
612
+ name: 'bookmark',
613
+ attributes: [
614
+ 'bookmarkId'
615
+ ]
616
+ },
617
+ view: (modelElement, { writer })=>{
618
+ const id = modelElement.getAttribute('bookmarkId');
619
+ const containerElement = writer.createContainerElement('a', {
620
+ id,
621
+ class: 'ck-bookmark'
622
+ }, [
623
+ this._createBookmarkUIElement(writer)
624
+ ]);
625
+ this._bookmarkElements.set(modelElement, id);
626
+ // `getFillerOffset` is not needed to set here, because `toWidget` has already covered it.
627
+ const labelCreator = ()=>`${id} ${t('bookmark widget')}`;
628
+ return toWidget(containerElement, writer, {
629
+ label: labelCreator
630
+ });
631
+ }
632
+ });
633
+ conversion.for('upcast').add((dispatcher)=>dispatcher.on('element:a', dataViewModelAnchorInsertion(editor)));
634
+ }
635
+ /**
636
+ * Creates a UI element for the `bookmark` representation in editing view.
637
+ */ _createBookmarkUIElement(writer) {
638
+ return writer.createUIElement('span', {
639
+ class: 'ck-bookmark__icon'
640
+ }, function(domDocument) {
641
+ const domElement = this.toDomElement(domDocument);
642
+ const icon = new IconView();
643
+ icon.set({
644
+ content: icons.bookmarkInline,
645
+ isColorInherited: false
646
+ });
647
+ icon.render();
648
+ domElement.appendChild(icon.element);
649
+ return domElement;
650
+ });
651
+ }
652
+ /**
653
+ * Tracking the added or removed bookmark elements.
654
+ */ _trackBookmarkElements() {
655
+ this._bookmarkElements.forEach((id, element)=>{
656
+ if (element.root.rootName === '$graveyard') {
657
+ this._bookmarkElements.delete(element);
658
+ }
659
+ });
660
+ }
661
+ }
662
+ /**
663
+ * A helper function to match an `anchor` element which must contain `id` or `name` attribute but without `href` attribute,
664
+ * also when `expectEmpty` is set to `true` but the element is not empty matcher should not match any element.
665
+ *
666
+ * @param element The element to be checked.
667
+ * @param expectEmpty Default set to `true`, when set to `false` matcher expects that `anchor` is not empty;
668
+ * in editing pipeline it's not empty because it contains the `UIElement`.
669
+ */ function upcastMatcher(element, expectEmpty = true) {
670
+ const isAnchorElement = element.name === 'a';
671
+ if (!isAnchorElement) {
672
+ return null;
673
+ }
674
+ if (expectEmpty && !element.isEmpty) {
675
+ return null;
676
+ }
677
+ const hasIdAttribute = element.hasAttribute('id');
678
+ const hasNameAttribute = element.hasAttribute('name');
679
+ const hasHrefAttribute = element.hasAttribute('href');
680
+ if (hasIdAttribute && !hasHrefAttribute) {
681
+ return {
682
+ name: true,
683
+ attributes: [
684
+ 'id'
685
+ ]
686
+ };
687
+ }
688
+ if (hasNameAttribute && !hasHrefAttribute) {
689
+ return {
690
+ name: true,
691
+ attributes: [
692
+ 'name'
693
+ ]
694
+ };
695
+ }
696
+ return null;
697
+ }
698
+ /**
699
+ * A view-to-model converter that handles converting pointed or wrapped anchors with `id` and/or `name` attributes.
700
+ *
701
+ * @returns Returns a conversion callback.
702
+ */ function dataViewModelAnchorInsertion(editor) {
703
+ return (evt, data, conversionApi)=>{
704
+ const viewItem = data.viewItem;
705
+ const match = upcastMatcher(viewItem, false);
706
+ if (!match || !conversionApi.consumable.test(viewItem, match)) {
707
+ return;
708
+ }
709
+ const enableNonEmptyAnchorConversion = isEnabledNonEmptyAnchorConversion(editor);
710
+ if (!enableNonEmptyAnchorConversion && !viewItem.isEmpty) {
711
+ return;
712
+ }
713
+ const modelWriter = conversionApi.writer;
714
+ const anchorId = viewItem.getAttribute('id');
715
+ const anchorName = viewItem.getAttribute('name');
716
+ const bookmarkId = anchorId || anchorName;
717
+ const bookmark = modelWriter.createElement('bookmark', {
718
+ bookmarkId
719
+ });
720
+ if (!conversionApi.safeInsert(bookmark, data.modelCursor)) {
721
+ return;
722
+ }
723
+ conversionApi.consumable.consume(viewItem, match);
724
+ if (anchorId === anchorName) {
725
+ conversionApi.consumable.consume(viewItem, {
726
+ attributes: [
727
+ 'name'
728
+ ]
729
+ });
730
+ }
731
+ conversionApi.updateConversionResult(bookmark, data);
732
+ // Convert children uses the result of `bookmark` insertion to convert the `anchor` content
733
+ // after the bookmark element (not inside it).
734
+ const { modelCursor, modelRange } = conversionApi.convertChildren(viewItem, data.modelCursor);
735
+ data.modelCursor = modelCursor;
736
+ data.modelRange = modelWriter.createRange(data.modelRange.start, modelRange.end);
737
+ };
738
+ }
739
+ /**
740
+ * Normalize the bookmark configuration option `enableNonEmptyAnchorConversion`.
741
+ */ function isEnabledNonEmptyAnchorConversion(editor) {
742
+ const enableNonEmptyAnchorConversion = editor.config.get('bookmark.enableNonEmptyAnchorConversion');
743
+ // When not defined, option `enableNonEmptyAnchorConversion` by default is set to `true`.
744
+ return enableNonEmptyAnchorConversion !== undefined ? enableNonEmptyAnchorConversion : true;
745
+ }
746
+
747
+ const VISUAL_SELECTION_MARKER_NAME = 'bookmark-ui';
748
+ /**
749
+ * The UI plugin of the bookmark feature.
750
+ *
751
+ * It registers the `'bookmark'` UI button in the editor's {@link module:ui/componentfactory~ComponentFactory component factory}
752
+ * which inserts the `bookmark` element upon selection.
753
+ */ class BookmarkUI extends Plugin {
754
+ /**
755
+ * The actions view displayed inside of the balloon.
756
+ */ actionsView = null;
757
+ /**
758
+ * The form view displayed inside the balloon.
759
+ */ formView = null;
760
+ /**
761
+ * The contextual balloon plugin instance.
762
+ */ _balloon;
763
+ /**
764
+ * @inheritDoc
765
+ */ static get requires() {
766
+ return [
767
+ BookmarkEditing,
768
+ ContextualBalloon
769
+ ];
770
+ }
771
+ /**
772
+ * @inheritDoc
773
+ */ static get pluginName() {
774
+ return 'BookmarkUI';
775
+ }
776
+ /**
777
+ * @inheritDoc
778
+ */ static get isOfficialPlugin() {
779
+ return true;
780
+ }
781
+ /**
782
+ * @inheritDoc
783
+ */ init() {
784
+ const editor = this.editor;
785
+ editor.editing.view.addObserver(ClickObserver);
786
+ this._balloon = editor.plugins.get(ContextualBalloon);
787
+ // Create toolbar buttons.
788
+ this._createToolbarBookmarkButton();
789
+ this._enableBalloonActivators();
790
+ // Renders a fake visual selection marker on an expanded selection.
791
+ editor.conversion.for('editingDowncast').markerToHighlight({
792
+ model: VISUAL_SELECTION_MARKER_NAME,
793
+ view: {
794
+ classes: [
795
+ 'ck-fake-bookmark-selection'
796
+ ]
797
+ }
798
+ });
799
+ // Renders a fake visual selection marker on a collapsed selection.
800
+ editor.conversion.for('editingDowncast').markerToElement({
801
+ model: VISUAL_SELECTION_MARKER_NAME,
802
+ view: (data, { writer })=>{
803
+ if (!data.markerRange.isCollapsed) {
804
+ return null;
805
+ }
806
+ const markerElement = writer.createUIElement('span');
807
+ writer.addClass([
808
+ 'ck-fake-bookmark-selection',
809
+ 'ck-fake-bookmark-selection_collapsed'
810
+ ], markerElement);
811
+ return markerElement;
812
+ }
813
+ });
814
+ }
815
+ /**
816
+ * @inheritDoc
817
+ */ destroy() {
818
+ super.destroy();
819
+ // Destroy created UI components as they are not automatically destroyed (see ckeditor5#1341).
820
+ if (this.formView) {
821
+ this.formView.destroy();
822
+ }
823
+ if (this.actionsView) {
824
+ this.actionsView.destroy();
825
+ }
826
+ }
827
+ /**
828
+ * Creates views.
829
+ */ _createViews() {
830
+ this.actionsView = this._createActionsView();
831
+ this.formView = this._createFormView();
832
+ // Attach lifecycle actions to the the balloon.
833
+ this._enableUserBalloonInteractions();
834
+ }
835
+ /**
836
+ * Creates the {@link module:bookmark/ui/bookmarkactionsview~BookmarkActionsView} instance.
837
+ */ _createActionsView() {
838
+ const editor = this.editor;
839
+ const actionsView = new BookmarkActionsView(editor.locale);
840
+ const updateBookmarkCommand = editor.commands.get('updateBookmark');
841
+ const deleteCommand = editor.commands.get('delete');
842
+ actionsView.bind('id').to(updateBookmarkCommand, 'value');
843
+ actionsView.editButtonView.bind('isEnabled').to(updateBookmarkCommand);
844
+ actionsView.removeButtonView.bind('isEnabled').to(deleteCommand);
845
+ // Display edit form view after clicking on the "Edit" button.
846
+ this.listenTo(actionsView, 'edit', ()=>{
847
+ this._addFormView();
848
+ });
849
+ // Execute remove command after clicking on the "Remove" button.
850
+ this.listenTo(actionsView, 'remove', ()=>{
851
+ this._hideUI();
852
+ editor.execute('delete');
853
+ });
854
+ // Close the panel on esc key press when the **actions have focus**.
855
+ actionsView.keystrokes.set('Esc', (data, cancel)=>{
856
+ this._hideUI();
857
+ cancel();
858
+ });
859
+ return actionsView;
860
+ }
861
+ /**
862
+ * Creates the {@link module:bookmark/ui/bookmarkformview~BookmarkFormView} instance.
863
+ */ _createFormView() {
864
+ const editor = this.editor;
865
+ const locale = editor.locale;
866
+ const insertBookmarkCommand = editor.commands.get('insertBookmark');
867
+ const updateBookmarkCommand = editor.commands.get('updateBookmark');
868
+ const commands = [
869
+ insertBookmarkCommand,
870
+ updateBookmarkCommand
871
+ ];
872
+ const formView = new (CssTransitionDisablerMixin(BookmarkFormView))(locale, getFormValidators(editor));
873
+ formView.idInputView.fieldView.bind('value').to(updateBookmarkCommand, 'value');
874
+ // Form elements should be read-only when corresponding commands are disabled.
875
+ formView.idInputView.bind('isEnabled').toMany(commands, 'isEnabled', (...areEnabled)=>areEnabled.some((isEnabled)=>isEnabled));
876
+ // Disable the "save" button if the command is disabled.
877
+ formView.buttonView.bind('isEnabled').toMany(commands, 'isEnabled', (...areEnabled)=>areEnabled.some((isEnabled)=>isEnabled));
878
+ // Execute link command after clicking the "Save" button.
879
+ this.listenTo(formView, 'submit', ()=>{
880
+ if (formView.isValid()) {
881
+ const value = formView.id;
882
+ if (this._getSelectedBookmarkElement()) {
883
+ editor.execute('updateBookmark', {
884
+ bookmarkId: value
885
+ });
886
+ } else {
887
+ editor.execute('insertBookmark', {
888
+ bookmarkId: value
889
+ });
890
+ }
891
+ this._closeFormView();
892
+ }
893
+ });
894
+ // Update balloon position when form error changes.
895
+ this.listenTo(formView.idInputView, 'change:errorText', ()=>{
896
+ editor.ui.update();
897
+ });
898
+ // Close the panel on esc key press when the **form has focus**.
899
+ formView.keystrokes.set('Esc', (data, cancel)=>{
900
+ this._closeFormView();
901
+ cancel();
902
+ });
903
+ return formView;
904
+ }
905
+ /**
906
+ * Creates a toolbar Bookmark button. Clicking this button will show
907
+ * a {@link #_balloon} attached to the selection.
908
+ */ _createToolbarBookmarkButton() {
909
+ const editor = this.editor;
910
+ editor.ui.componentFactory.add('bookmark', ()=>{
911
+ const buttonView = this._createButton(ButtonView);
912
+ buttonView.set({
913
+ tooltip: true
914
+ });
915
+ return buttonView;
916
+ });
917
+ editor.ui.componentFactory.add('menuBar:bookmark', ()=>{
918
+ return this._createButton(MenuBarMenuListItemButtonView);
919
+ });
920
+ }
921
+ /**
922
+ * Creates a button for `bookmark` command to use either in toolbar or in menu bar.
923
+ */ _createButton(ButtonClass) {
924
+ const editor = this.editor;
925
+ const locale = editor.locale;
926
+ const view = new ButtonClass(locale);
927
+ const insertCommand = editor.commands.get('insertBookmark');
928
+ const updateCommand = editor.commands.get('updateBookmark');
929
+ const t = locale.t;
930
+ view.set({
931
+ label: t('Bookmark'),
932
+ icon: icons.bookmark
933
+ });
934
+ // Execute the command.
935
+ this.listenTo(view, 'execute', ()=>this._showUI(true));
936
+ view.bind('isEnabled').toMany([
937
+ insertCommand,
938
+ updateCommand
939
+ ], 'isEnabled', (...areEnabled)=>areEnabled.some((isEnabled)=>isEnabled));
940
+ view.bind('isOn').to(updateCommand, 'value', (value)=>!!value);
941
+ return view;
942
+ }
943
+ /**
944
+ * Attaches actions that control whether the balloon panel containing the
945
+ * {@link #formView} should be displayed.
946
+ */ _enableBalloonActivators() {
947
+ const editor = this.editor;
948
+ const viewDocument = editor.editing.view.document;
949
+ // Handle click on view document and show panel when selection is placed inside the bookmark element.
950
+ // Keep panel open until selection will be inside the same bookmark element.
951
+ this.listenTo(viewDocument, 'click', ()=>{
952
+ const bookmark = this._getSelectedBookmarkElement();
953
+ if (bookmark) {
954
+ // Then show panel but keep focus inside editor editable.
955
+ this._showUI();
956
+ }
957
+ });
958
+ }
959
+ /**
960
+ * Attaches actions that control whether the balloon panel containing the
961
+ * {@link #formView} is visible or not.
962
+ */ _enableUserBalloonInteractions() {
963
+ // Focus the form if the balloon is visible and the Tab key has been pressed.
964
+ this.editor.keystrokes.set('Tab', (data, cancel)=>{
965
+ if (this._areActionsVisible && !this.actionsView.focusTracker.isFocused) {
966
+ this.actionsView.focus();
967
+ cancel();
968
+ }
969
+ }, {
970
+ // Use the high priority because the bookmark UI navigation is more important
971
+ // than other feature's actions, e.g. list indentation.
972
+ priority: 'high'
973
+ });
974
+ // Close the panel on the Esc key press when the editable has focus and the balloon is visible.
975
+ this.editor.keystrokes.set('Esc', (data, cancel)=>{
976
+ if (this._isUIVisible) {
977
+ this._hideUI();
978
+ cancel();
979
+ }
980
+ });
981
+ // Close on click outside of balloon panel element.
982
+ clickOutsideHandler({
983
+ emitter: this.formView,
984
+ activator: ()=>this._isUIInPanel,
985
+ contextElements: ()=>[
986
+ this._balloon.view.element
987
+ ],
988
+ callback: ()=>this._hideUI()
989
+ });
990
+ }
991
+ /**
992
+ * Updates the button label. If bookmark is selected label is set to 'Update' otherwise
993
+ * it is 'Insert'.
994
+ */ _updateFormButtonLabel(isBookmarkSelected) {
995
+ const t = this.editor.locale.t;
996
+ this.formView.buttonView.label = isBookmarkSelected ? t('Update') : t('Insert');
997
+ }
998
+ /**
999
+ * Adds the {@link #actionsView} to the {@link #_balloon}.
1000
+ *
1001
+ * @internal
1002
+ */ _addActionsView() {
1003
+ if (!this.actionsView) {
1004
+ this._createViews();
1005
+ }
1006
+ if (this._areActionsInPanel) {
1007
+ return;
1008
+ }
1009
+ this._balloon.add({
1010
+ view: this.actionsView,
1011
+ position: this._getBalloonPositionData()
1012
+ });
1013
+ }
1014
+ /**
1015
+ * Adds the {@link #formView} to the {@link #_balloon}.
1016
+ */ _addFormView() {
1017
+ if (!this.formView) {
1018
+ this._createViews();
1019
+ }
1020
+ if (this._isFormInPanel) {
1021
+ return;
1022
+ }
1023
+ const editor = this.editor;
1024
+ const updateBookmarkCommand = editor.commands.get('updateBookmark');
1025
+ this.formView.disableCssTransitions();
1026
+ this.formView.resetFormStatus();
1027
+ this._balloon.add({
1028
+ view: this.formView,
1029
+ position: this._getBalloonPositionData()
1030
+ });
1031
+ this.formView.idInputView.fieldView.value = updateBookmarkCommand.value || '';
1032
+ // Select input when form view is currently visible.
1033
+ if (this._balloon.visibleView === this.formView) {
1034
+ this.formView.idInputView.fieldView.select();
1035
+ }
1036
+ this.formView.enableCssTransitions();
1037
+ }
1038
+ /**
1039
+ * Closes the form view. Decides whether the balloon should be hidden completely.
1040
+ */ _closeFormView() {
1041
+ const updateBookmarkCommand = this.editor.commands.get('updateBookmark');
1042
+ if (updateBookmarkCommand.value !== undefined) {
1043
+ this._removeFormView();
1044
+ } else {
1045
+ this._hideUI();
1046
+ }
1047
+ }
1048
+ /**
1049
+ * Removes the {@link #formView} from the {@link #_balloon}.
1050
+ */ _removeFormView() {
1051
+ if (this._isFormInPanel) {
1052
+ // Blur the input element before removing it from DOM to prevent issues in some browsers.
1053
+ // See https://github.com/ckeditor/ckeditor5/issues/1501.
1054
+ this.formView.buttonView.focus();
1055
+ // Reset the ID field to update the state of the submit button.
1056
+ this.formView.idInputView.fieldView.reset();
1057
+ this._balloon.remove(this.formView);
1058
+ // Because the form has an input which has focus, the focus must be brought back
1059
+ // to the editor. Otherwise, it would be lost.
1060
+ this.editor.editing.view.focus();
1061
+ this._hideFakeVisualSelection();
1062
+ }
1063
+ }
1064
+ /**
1065
+ * Shows the correct UI type. It is either {@link #formView} or {@link #actionsView}.
1066
+ */ _showUI(forceVisible = false) {
1067
+ if (!this.formView) {
1068
+ this._createViews();
1069
+ }
1070
+ // When there's no bookmark under the selection, go straight to the editing UI.
1071
+ if (!this._getSelectedBookmarkElement()) {
1072
+ // Show visual selection on a text without a bookmark when the contextual balloon is displayed.
1073
+ this._showFakeVisualSelection();
1074
+ this._addActionsView();
1075
+ // Be sure panel with bookmark is visible.
1076
+ if (forceVisible) {
1077
+ this._balloon.showStack('main');
1078
+ }
1079
+ this._addFormView();
1080
+ } else {
1081
+ // Go to the editing UI if actions are already visible.
1082
+ if (this._areActionsVisible) {
1083
+ this._addFormView();
1084
+ } else {
1085
+ this._addActionsView();
1086
+ }
1087
+ // Be sure panel with bookmark is visible.
1088
+ if (forceVisible) {
1089
+ this._balloon.showStack('main');
1090
+ }
1091
+ }
1092
+ // Begin responding to ui#update once the UI is added.
1093
+ this._startUpdatingUI();
1094
+ }
1095
+ /**
1096
+ * Removes the {@link #formView} from the {@link #_balloon}.
1097
+ *
1098
+ * See {@link #_addFormView}, {@link #_addActionsView}.
1099
+ */ _hideUI() {
1100
+ if (!this._isUIInPanel) {
1101
+ return;
1102
+ }
1103
+ const editor = this.editor;
1104
+ this.stopListening(editor.ui, 'update');
1105
+ this.stopListening(this._balloon, 'change:visibleView');
1106
+ // Make sure the focus always gets back to the editable _before_ removing the focused form view.
1107
+ // Doing otherwise causes issues in some browsers. See https://github.com/ckeditor/ckeditor5-link/issues/193.
1108
+ editor.editing.view.focus();
1109
+ // Remove form first because it's on top of the stack.
1110
+ this._removeFormView();
1111
+ // Then remove the actions view because it's beneath the form.
1112
+ this._balloon.remove(this.actionsView);
1113
+ this._hideFakeVisualSelection();
1114
+ }
1115
+ /**
1116
+ * Makes the UI react to the {@link module:ui/editorui/editorui~EditorUI#event:update} event to
1117
+ * reposition itself when the editor UI should be refreshed.
1118
+ *
1119
+ * See: {@link #_hideUI} to learn when the UI stops reacting to the `update` event.
1120
+ */ _startUpdatingUI() {
1121
+ const editor = this.editor;
1122
+ const viewDocument = editor.editing.view.document;
1123
+ let prevSelectedBookmark = this._getSelectedBookmarkElement();
1124
+ let prevSelectionParent = getSelectionParent();
1125
+ this._updateFormButtonLabel(!!prevSelectedBookmark);
1126
+ const update = ()=>{
1127
+ const selectedBookmark = this._getSelectedBookmarkElement();
1128
+ const selectionParent = getSelectionParent();
1129
+ // Hide the panel if:
1130
+ //
1131
+ // * the selection went out of the EXISTING bookmark element. E.g. user moved the caret out
1132
+ // of the bookmark,
1133
+ // * the selection went to a different parent when creating a NEW bookmark. E.g. someone
1134
+ // else modified the document.
1135
+ // * the selection has expanded (e.g. displaying bookmark actions then pressing SHIFT+Right arrow).
1136
+ //
1137
+ if (prevSelectedBookmark && !selectedBookmark || !prevSelectedBookmark && selectionParent !== prevSelectionParent) {
1138
+ this._hideUI();
1139
+ } else if (this._isUIVisible) {
1140
+ // If still in a bookmark element, simply update the position of the balloon.
1141
+ // If there was no bookmark (e.g. inserting one), the balloon must be moved
1142
+ // to the new position in the editing view (a new native DOM range).
1143
+ this._balloon.updatePosition(this._getBalloonPositionData());
1144
+ }
1145
+ this._updateFormButtonLabel(!!prevSelectedBookmark);
1146
+ prevSelectedBookmark = selectedBookmark;
1147
+ prevSelectionParent = selectionParent;
1148
+ };
1149
+ function getSelectionParent() {
1150
+ return viewDocument.selection.focus.getAncestors().reverse().find((node)=>node.is('element'));
1151
+ }
1152
+ this.listenTo(editor.ui, 'update', update);
1153
+ this.listenTo(this._balloon, 'change:visibleView', update);
1154
+ }
1155
+ /**
1156
+ * Returns `true` when {@link #formView} is in the {@link #_balloon}.
1157
+ */ get _isFormInPanel() {
1158
+ return !!this.formView && this._balloon.hasView(this.formView);
1159
+ }
1160
+ /**
1161
+ * Returns `true` when {@link #actionsView} is in the {@link #_balloon}.
1162
+ */ get _areActionsInPanel() {
1163
+ return !!this.actionsView && this._balloon.hasView(this.actionsView);
1164
+ }
1165
+ /**
1166
+ * Returns `true` when {@link #actionsView} is in the {@link #_balloon} and it is
1167
+ * currently visible.
1168
+ */ get _areActionsVisible() {
1169
+ return !!this.actionsView && this._balloon.visibleView === this.actionsView;
1170
+ }
1171
+ /**
1172
+ * Returns `true` when {@link #actionsView} or {@link #formView} is in the {@link #_balloon}.
1173
+ */ get _isUIInPanel() {
1174
+ return this._isFormInPanel || this._areActionsInPanel;
1175
+ }
1176
+ /**
1177
+ * Returns `true` when {@link #actionsView} or {@link #formView} is in the {@link #_balloon} and it is
1178
+ * currently visible.
1179
+ */ get _isUIVisible() {
1180
+ const visibleView = this._balloon.visibleView;
1181
+ return !!this.formView && visibleView == this.formView || this._areActionsVisible;
1182
+ }
1183
+ /**
1184
+ * Returns positioning options for the {@link #_balloon}. They control the way the balloon is attached
1185
+ * to the target element or selection.
1186
+ */ _getBalloonPositionData() {
1187
+ const view = this.editor.editing.view;
1188
+ const model = this.editor.model;
1189
+ let target;
1190
+ const bookmarkElement = this._getSelectedBookmarkElement();
1191
+ if (model.markers.has(VISUAL_SELECTION_MARKER_NAME)) {
1192
+ // There are cases when we highlight selection using a marker (#7705, #4721).
1193
+ const markerViewElements = Array.from(this.editor.editing.mapper.markerNameToElements(VISUAL_SELECTION_MARKER_NAME));
1194
+ const newRange = view.createRange(view.createPositionBefore(markerViewElements[0]), view.createPositionAfter(markerViewElements[markerViewElements.length - 1]));
1195
+ target = view.domConverter.viewRangeToDom(newRange);
1196
+ } else if (bookmarkElement) {
1197
+ target = ()=>{
1198
+ const mapper = this.editor.editing.mapper;
1199
+ const domConverter = view.domConverter;
1200
+ const viewElement = mapper.toViewElement(bookmarkElement);
1201
+ return domConverter.mapViewToDom(viewElement);
1202
+ };
1203
+ }
1204
+ return target && {
1205
+ target
1206
+ };
1207
+ }
1208
+ /**
1209
+ * Returns the bookmark {@link module:engine/view/attributeelement~AttributeElement} under
1210
+ * the {@link module:engine/view/document~Document editing view's} selection or `null`
1211
+ * if there is none.
1212
+ */ _getSelectedBookmarkElement() {
1213
+ const selection = this.editor.model.document.selection;
1214
+ const element = selection.getSelectedElement();
1215
+ if (element && element.is('element', 'bookmark')) {
1216
+ return element;
1217
+ }
1218
+ return null;
1219
+ }
1220
+ /**
1221
+ * Displays a fake visual selection when the contextual balloon is displayed.
1222
+ *
1223
+ * This adds a 'bookmark-ui' marker into the document that is rendered as a highlight on selected text fragment.
1224
+ */ _showFakeVisualSelection() {
1225
+ const model = this.editor.model;
1226
+ model.change((writer)=>{
1227
+ const range = model.document.selection.getFirstRange();
1228
+ if (model.markers.has(VISUAL_SELECTION_MARKER_NAME)) {
1229
+ writer.updateMarker(VISUAL_SELECTION_MARKER_NAME, {
1230
+ range
1231
+ });
1232
+ } else {
1233
+ if (range.start.isAtEnd) {
1234
+ const startPosition = range.start.getLastMatchingPosition(({ item })=>!model.schema.isContent(item), {
1235
+ boundaries: range
1236
+ });
1237
+ writer.addMarker(VISUAL_SELECTION_MARKER_NAME, {
1238
+ usingOperation: false,
1239
+ affectsData: false,
1240
+ range: writer.createRange(startPosition, range.end)
1241
+ });
1242
+ } else {
1243
+ writer.addMarker(VISUAL_SELECTION_MARKER_NAME, {
1244
+ usingOperation: false,
1245
+ affectsData: false,
1246
+ range
1247
+ });
1248
+ }
1249
+ }
1250
+ });
1251
+ }
1252
+ /**
1253
+ * Hides the fake visual selection created in {@link #_showFakeVisualSelection}.
1254
+ */ _hideFakeVisualSelection() {
1255
+ const model = this.editor.model;
1256
+ if (model.markers.has(VISUAL_SELECTION_MARKER_NAME)) {
1257
+ model.change((writer)=>{
1258
+ writer.removeMarker(VISUAL_SELECTION_MARKER_NAME);
1259
+ });
1260
+ }
1261
+ }
1262
+ }
1263
+ /**
1264
+ * Returns bookmark form validation callbacks.
1265
+ */ function getFormValidators(editor) {
1266
+ const { t } = editor;
1267
+ const bookmarkEditing = editor.plugins.get(BookmarkEditing);
1268
+ return [
1269
+ (form)=>{
1270
+ if (!form.id) {
1271
+ return t('Bookmark must not be empty.');
1272
+ }
1273
+ },
1274
+ (form)=>{
1275
+ if (form.id && /\s/.test(form.id)) {
1276
+ return t('Bookmark name cannot contain space characters.');
1277
+ }
1278
+ },
1279
+ (form)=>{
1280
+ const selectedElement = editor.model.document.selection.getSelectedElement();
1281
+ const existingBookmarkForId = bookmarkEditing.getElementForBookmarkId(form.id);
1282
+ // Accept change of bookmark ID if no real change is happening (edit -> submit, without changes).
1283
+ if (selectedElement === existingBookmarkForId) {
1284
+ return;
1285
+ }
1286
+ if (existingBookmarkForId) {
1287
+ return t('Bookmark name already exists.');
1288
+ }
1289
+ }
1290
+ ];
1291
+ }
1292
+
1293
+ /**
1294
+ * The bookmark feature.
1295
+ *
1296
+ * For a detailed overview, check the {@glink features/bookmarks Bookmarks} feature guide.
1297
+ */ class Bookmark extends Plugin {
1298
+ /**
1299
+ * @inheritDoc
1300
+ */ static get pluginName() {
1301
+ return 'Bookmark';
1302
+ }
1303
+ /**
1304
+ * @inheritDoc
1305
+ */ static get requires() {
1306
+ return [
1307
+ BookmarkEditing,
1308
+ BookmarkUI,
1309
+ Widget
1310
+ ];
1311
+ }
1312
+ /**
1313
+ * @inheritDoc
1314
+ */ static get isOfficialPlugin() {
1315
+ return true;
1316
+ }
1317
+ }
1318
+
1319
+ export { Bookmark, BookmarkEditing, BookmarkUI, InsertBookmarkCommand, UpdateBookmarkCommand };
1320
+ //# sourceMappingURL=index.js.map