@ckeditor/ckeditor5-link 44.3.0 → 45.0.0-alpha.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 (337) hide show
  1. package/LICENSE.md +1 -1
  2. package/build/link.js +2 -2
  3. package/build/translations/af.js +1 -1
  4. package/build/translations/ar.js +1 -1
  5. package/build/translations/ast.js +1 -1
  6. package/build/translations/az.js +1 -1
  7. package/build/translations/be.js +1 -0
  8. package/build/translations/bg.js +1 -1
  9. package/build/translations/bn.js +1 -1
  10. package/build/translations/bs.js +1 -1
  11. package/build/translations/ca.js +1 -1
  12. package/build/translations/cs.js +1 -1
  13. package/build/translations/da.js +1 -1
  14. package/build/translations/de-ch.js +1 -1
  15. package/build/translations/de.js +1 -1
  16. package/build/translations/el.js +1 -1
  17. package/build/translations/en-au.js +1 -1
  18. package/build/translations/en-gb.js +1 -1
  19. package/build/translations/eo.js +1 -1
  20. package/build/translations/es-co.js +1 -1
  21. package/build/translations/es.js +1 -1
  22. package/build/translations/et.js +1 -1
  23. package/build/translations/eu.js +1 -1
  24. package/build/translations/fa.js +1 -1
  25. package/build/translations/fi.js +1 -1
  26. package/build/translations/fr.js +1 -1
  27. package/build/translations/gl.js +1 -1
  28. package/build/translations/gu.js +1 -1
  29. package/build/translations/he.js +1 -1
  30. package/build/translations/hi.js +1 -1
  31. package/build/translations/hr.js +1 -1
  32. package/build/translations/hu.js +1 -1
  33. package/build/translations/hy.js +1 -1
  34. package/build/translations/id.js +1 -1
  35. package/build/translations/it.js +1 -1
  36. package/build/translations/ja.js +1 -1
  37. package/build/translations/jv.js +1 -1
  38. package/build/translations/kk.js +1 -1
  39. package/build/translations/km.js +1 -1
  40. package/build/translations/kn.js +1 -1
  41. package/build/translations/ko.js +1 -1
  42. package/build/translations/ku.js +1 -1
  43. package/build/translations/lt.js +1 -1
  44. package/build/translations/lv.js +1 -1
  45. package/build/translations/ms.js +1 -1
  46. package/build/translations/nb.js +1 -1
  47. package/build/translations/ne.js +1 -1
  48. package/build/translations/nl.js +1 -1
  49. package/build/translations/no.js +1 -1
  50. package/build/translations/oc.js +1 -1
  51. package/build/translations/pl.js +1 -1
  52. package/build/translations/pt-br.js +1 -1
  53. package/build/translations/pt.js +1 -1
  54. package/build/translations/ro.js +1 -1
  55. package/build/translations/ru.js +1 -1
  56. package/build/translations/si.js +1 -1
  57. package/build/translations/sk.js +1 -1
  58. package/build/translations/sl.js +1 -1
  59. package/build/translations/sq.js +1 -1
  60. package/build/translations/sr-latn.js +1 -1
  61. package/build/translations/sr.js +1 -1
  62. package/build/translations/sv.js +1 -1
  63. package/build/translations/th.js +1 -1
  64. package/build/translations/ti.js +1 -1
  65. package/build/translations/tk.js +1 -1
  66. package/build/translations/tr.js +1 -1
  67. package/build/translations/tt.js +1 -1
  68. package/build/translations/ug.js +1 -1
  69. package/build/translations/uk.js +1 -1
  70. package/build/translations/ur.js +1 -1
  71. package/build/translations/uz.js +1 -1
  72. package/build/translations/vi.js +1 -1
  73. package/build/translations/zh-cn.js +1 -1
  74. package/build/translations/zh.js +1 -1
  75. package/ckeditor5-metadata.json +2 -2
  76. package/dist/index-editor.css +87 -47
  77. package/dist/index.css +108 -58
  78. package/dist/index.css.map +1 -1
  79. package/dist/index.js +1161 -425
  80. package/dist/index.js.map +1 -1
  81. package/dist/translations/af.js +1 -1
  82. package/dist/translations/af.umd.js +1 -1
  83. package/dist/translations/ar.js +1 -1
  84. package/dist/translations/ar.umd.js +1 -1
  85. package/dist/translations/ast.js +1 -1
  86. package/dist/translations/ast.umd.js +1 -1
  87. package/dist/translations/az.js +1 -1
  88. package/dist/translations/az.umd.js +1 -1
  89. package/dist/translations/be.d.ts +8 -0
  90. package/dist/translations/be.js +5 -0
  91. package/dist/translations/be.umd.js +11 -0
  92. package/dist/translations/bg.js +1 -1
  93. package/dist/translations/bg.umd.js +1 -1
  94. package/dist/translations/bn.js +1 -1
  95. package/dist/translations/bn.umd.js +1 -1
  96. package/dist/translations/bs.js +1 -1
  97. package/dist/translations/bs.umd.js +1 -1
  98. package/dist/translations/ca.js +1 -1
  99. package/dist/translations/ca.umd.js +1 -1
  100. package/dist/translations/cs.js +1 -1
  101. package/dist/translations/cs.umd.js +1 -1
  102. package/dist/translations/da.js +1 -1
  103. package/dist/translations/da.umd.js +1 -1
  104. package/dist/translations/de-ch.js +1 -1
  105. package/dist/translations/de-ch.umd.js +1 -1
  106. package/dist/translations/de.js +1 -1
  107. package/dist/translations/de.umd.js +1 -1
  108. package/dist/translations/el.js +1 -1
  109. package/dist/translations/el.umd.js +1 -1
  110. package/dist/translations/en-au.js +1 -1
  111. package/dist/translations/en-au.umd.js +1 -1
  112. package/dist/translations/en-gb.js +1 -1
  113. package/dist/translations/en-gb.umd.js +1 -1
  114. package/dist/translations/en.js +1 -1
  115. package/dist/translations/en.umd.js +1 -1
  116. package/dist/translations/eo.js +1 -1
  117. package/dist/translations/eo.umd.js +1 -1
  118. package/dist/translations/es-co.js +1 -1
  119. package/dist/translations/es-co.umd.js +1 -1
  120. package/dist/translations/es.js +1 -1
  121. package/dist/translations/es.umd.js +1 -1
  122. package/dist/translations/et.js +1 -1
  123. package/dist/translations/et.umd.js +1 -1
  124. package/dist/translations/eu.js +1 -1
  125. package/dist/translations/eu.umd.js +1 -1
  126. package/dist/translations/fa.js +1 -1
  127. package/dist/translations/fa.umd.js +1 -1
  128. package/dist/translations/fi.js +1 -1
  129. package/dist/translations/fi.umd.js +1 -1
  130. package/dist/translations/fr.js +1 -1
  131. package/dist/translations/fr.umd.js +1 -1
  132. package/dist/translations/gl.js +1 -1
  133. package/dist/translations/gl.umd.js +1 -1
  134. package/dist/translations/gu.js +1 -1
  135. package/dist/translations/gu.umd.js +1 -1
  136. package/dist/translations/he.js +1 -1
  137. package/dist/translations/he.umd.js +1 -1
  138. package/dist/translations/hi.js +1 -1
  139. package/dist/translations/hi.umd.js +1 -1
  140. package/dist/translations/hr.js +1 -1
  141. package/dist/translations/hr.umd.js +1 -1
  142. package/dist/translations/hu.js +1 -1
  143. package/dist/translations/hu.umd.js +1 -1
  144. package/dist/translations/hy.js +1 -1
  145. package/dist/translations/hy.umd.js +1 -1
  146. package/dist/translations/id.js +1 -1
  147. package/dist/translations/id.umd.js +1 -1
  148. package/dist/translations/it.js +1 -1
  149. package/dist/translations/it.umd.js +1 -1
  150. package/dist/translations/ja.js +1 -1
  151. package/dist/translations/ja.umd.js +1 -1
  152. package/dist/translations/jv.js +1 -1
  153. package/dist/translations/jv.umd.js +1 -1
  154. package/dist/translations/kk.js +1 -1
  155. package/dist/translations/kk.umd.js +1 -1
  156. package/dist/translations/km.js +1 -1
  157. package/dist/translations/km.umd.js +1 -1
  158. package/dist/translations/kn.js +1 -1
  159. package/dist/translations/kn.umd.js +1 -1
  160. package/dist/translations/ko.js +1 -1
  161. package/dist/translations/ko.umd.js +1 -1
  162. package/dist/translations/ku.js +1 -1
  163. package/dist/translations/ku.umd.js +1 -1
  164. package/dist/translations/lt.js +1 -1
  165. package/dist/translations/lt.umd.js +1 -1
  166. package/dist/translations/lv.js +1 -1
  167. package/dist/translations/lv.umd.js +1 -1
  168. package/dist/translations/ms.js +1 -1
  169. package/dist/translations/ms.umd.js +1 -1
  170. package/dist/translations/nb.js +1 -1
  171. package/dist/translations/nb.umd.js +1 -1
  172. package/dist/translations/ne.js +1 -1
  173. package/dist/translations/ne.umd.js +1 -1
  174. package/dist/translations/nl.js +1 -1
  175. package/dist/translations/nl.umd.js +1 -1
  176. package/dist/translations/no.js +1 -1
  177. package/dist/translations/no.umd.js +1 -1
  178. package/dist/translations/oc.js +1 -1
  179. package/dist/translations/oc.umd.js +1 -1
  180. package/dist/translations/pl.js +1 -1
  181. package/dist/translations/pl.umd.js +1 -1
  182. package/dist/translations/pt-br.js +1 -1
  183. package/dist/translations/pt-br.umd.js +1 -1
  184. package/dist/translations/pt.js +1 -1
  185. package/dist/translations/pt.umd.js +1 -1
  186. package/dist/translations/ro.js +1 -1
  187. package/dist/translations/ro.umd.js +1 -1
  188. package/dist/translations/ru.js +1 -1
  189. package/dist/translations/ru.umd.js +1 -1
  190. package/dist/translations/si.js +1 -1
  191. package/dist/translations/si.umd.js +1 -1
  192. package/dist/translations/sk.js +1 -1
  193. package/dist/translations/sk.umd.js +1 -1
  194. package/dist/translations/sl.js +1 -1
  195. package/dist/translations/sl.umd.js +1 -1
  196. package/dist/translations/sq.js +1 -1
  197. package/dist/translations/sq.umd.js +1 -1
  198. package/dist/translations/sr-latn.js +1 -1
  199. package/dist/translations/sr-latn.umd.js +1 -1
  200. package/dist/translations/sr.js +1 -1
  201. package/dist/translations/sr.umd.js +1 -1
  202. package/dist/translations/sv.js +1 -1
  203. package/dist/translations/sv.umd.js +1 -1
  204. package/dist/translations/th.js +1 -1
  205. package/dist/translations/th.umd.js +1 -1
  206. package/dist/translations/ti.js +1 -1
  207. package/dist/translations/ti.umd.js +1 -1
  208. package/dist/translations/tk.js +1 -1
  209. package/dist/translations/tk.umd.js +1 -1
  210. package/dist/translations/tr.js +1 -1
  211. package/dist/translations/tr.umd.js +1 -1
  212. package/dist/translations/tt.js +1 -1
  213. package/dist/translations/tt.umd.js +1 -1
  214. package/dist/translations/ug.js +1 -1
  215. package/dist/translations/ug.umd.js +1 -1
  216. package/dist/translations/uk.js +1 -1
  217. package/dist/translations/uk.umd.js +1 -1
  218. package/dist/translations/ur.js +1 -1
  219. package/dist/translations/ur.umd.js +1 -1
  220. package/dist/translations/uz.js +1 -1
  221. package/dist/translations/uz.umd.js +1 -1
  222. package/dist/translations/vi.js +1 -1
  223. package/dist/translations/vi.umd.js +1 -1
  224. package/dist/translations/zh-cn.js +1 -1
  225. package/dist/translations/zh-cn.umd.js +1 -1
  226. package/dist/translations/zh.js +1 -1
  227. package/dist/translations/zh.umd.js +1 -1
  228. package/lang/contexts.json +4 -3
  229. package/lang/translations/af.po +10 -6
  230. package/lang/translations/ar.po +11 -7
  231. package/lang/translations/ast.po +10 -6
  232. package/lang/translations/az.po +10 -6
  233. package/lang/translations/be.po +68 -0
  234. package/lang/translations/bg.po +11 -7
  235. package/lang/translations/bn.po +11 -7
  236. package/lang/translations/bs.po +10 -6
  237. package/lang/translations/ca.po +11 -7
  238. package/lang/translations/cs.po +11 -7
  239. package/lang/translations/da.po +11 -7
  240. package/lang/translations/de-ch.po +10 -6
  241. package/lang/translations/de.po +11 -7
  242. package/lang/translations/el.po +11 -7
  243. package/lang/translations/en-au.po +11 -7
  244. package/lang/translations/en-gb.po +11 -7
  245. package/lang/translations/en.po +11 -7
  246. package/lang/translations/eo.po +10 -6
  247. package/lang/translations/es-co.po +10 -6
  248. package/lang/translations/es.po +11 -7
  249. package/lang/translations/et.po +11 -7
  250. package/lang/translations/eu.po +10 -6
  251. package/lang/translations/fa.po +10 -6
  252. package/lang/translations/fi.po +11 -7
  253. package/lang/translations/fr.po +11 -7
  254. package/lang/translations/gl.po +10 -6
  255. package/lang/translations/gu.po +10 -6
  256. package/lang/translations/he.po +11 -7
  257. package/lang/translations/hi.po +11 -7
  258. package/lang/translations/hr.po +10 -6
  259. package/lang/translations/hu.po +11 -7
  260. package/lang/translations/hy.po +10 -6
  261. package/lang/translations/id.po +11 -7
  262. package/lang/translations/it.po +11 -7
  263. package/lang/translations/ja.po +11 -7
  264. package/lang/translations/jv.po +10 -6
  265. package/lang/translations/kk.po +10 -6
  266. package/lang/translations/km.po +10 -6
  267. package/lang/translations/kn.po +10 -6
  268. package/lang/translations/ko.po +11 -7
  269. package/lang/translations/ku.po +10 -6
  270. package/lang/translations/lt.po +11 -7
  271. package/lang/translations/lv.po +11 -7
  272. package/lang/translations/ms.po +11 -7
  273. package/lang/translations/nb.po +10 -6
  274. package/lang/translations/ne.po +10 -6
  275. package/lang/translations/nl.po +11 -7
  276. package/lang/translations/no.po +11 -7
  277. package/lang/translations/oc.po +10 -6
  278. package/lang/translations/pl.po +11 -7
  279. package/lang/translations/pt-br.po +11 -7
  280. package/lang/translations/pt.po +11 -7
  281. package/lang/translations/ro.po +11 -7
  282. package/lang/translations/ru.po +11 -7
  283. package/lang/translations/si.po +10 -6
  284. package/lang/translations/sk.po +11 -7
  285. package/lang/translations/sl.po +10 -6
  286. package/lang/translations/sq.po +10 -6
  287. package/lang/translations/sr-latn.po +10 -6
  288. package/lang/translations/sr.po +11 -7
  289. package/lang/translations/sv.po +11 -7
  290. package/lang/translations/th.po +11 -7
  291. package/lang/translations/ti.po +10 -6
  292. package/lang/translations/tk.po +10 -6
  293. package/lang/translations/tr.po +11 -7
  294. package/lang/translations/tt.po +10 -6
  295. package/lang/translations/ug.po +10 -6
  296. package/lang/translations/uk.po +11 -7
  297. package/lang/translations/ur.po +10 -6
  298. package/lang/translations/uz.po +10 -6
  299. package/lang/translations/vi.po +11 -7
  300. package/lang/translations/zh-cn.po +11 -7
  301. package/lang/translations/zh.po +11 -7
  302. package/package.json +12 -12
  303. package/src/autolink.js +3 -0
  304. package/src/index.d.ts +1 -2
  305. package/src/index.js +0 -1
  306. package/src/linkcommand.d.ts +17 -10
  307. package/src/linkcommand.js +212 -82
  308. package/src/linkconfig.d.ts +28 -0
  309. package/src/linkediting.d.ts +18 -0
  310. package/src/linkediting.js +19 -9
  311. package/src/linkimageui.d.ts +1 -1
  312. package/src/linkimageui.js +4 -4
  313. package/src/linkui.d.ts +215 -24
  314. package/src/linkui.js +517 -109
  315. package/src/ui/linkbuttonview.d.ts +31 -0
  316. package/src/ui/linkbuttonview.js +54 -0
  317. package/src/ui/linkformview.d.ts +34 -49
  318. package/src/ui/linkformview.js +163 -134
  319. package/src/ui/linkpreviewbuttonview.d.ts +35 -0
  320. package/src/ui/linkpreviewbuttonview.js +43 -0
  321. package/src/ui/linkpropertiesview.d.ts +88 -0
  322. package/src/ui/linkpropertiesview.js +170 -0
  323. package/src/ui/linkprovideritemsview.d.ts +114 -0
  324. package/src/ui/linkprovideritemsview.js +207 -0
  325. package/src/utils/automaticdecorators.js +5 -7
  326. package/src/utils/manualdecorator.js +27 -0
  327. package/src/utils.d.ts +5 -5
  328. package/src/utils.js +12 -32
  329. package/theme/linkform.css +11 -33
  330. package/theme/linkproperties.css +4 -0
  331. package/theme/linkprovideritems.css +18 -0
  332. package/theme/linktoolbar.css +12 -0
  333. package/src/ui/linkactionsview.d.ts +0 -117
  334. package/src/ui/linkactionsview.js +0 -173
  335. package/theme/icons/link.svg +0 -1
  336. package/theme/icons/unlink.svg +0 -1
  337. package/theme/linkactions.css +0 -32
package/src/linkui.js CHANGED
@@ -6,13 +6,19 @@
6
6
  * @module link/linkui
7
7
  */
8
8
  import { Plugin } from 'ckeditor5/src/core.js';
9
+ import { IconLink, IconPencil, IconUnlink, IconSettings } from 'ckeditor5/src/icons.js';
9
10
  import { ClickObserver } from 'ckeditor5/src/engine.js';
10
- import { ButtonView, ContextualBalloon, clickOutsideHandler, CssTransitionDisablerMixin, MenuBarMenuListItemButtonView } from 'ckeditor5/src/ui.js';
11
+ import { ButtonView, SwitchButtonView, ContextualBalloon, clickOutsideHandler, CssTransitionDisablerMixin, MenuBarMenuListItemButtonView, ToolbarView } from 'ckeditor5/src/ui.js';
12
+ import { Collection } from 'ckeditor5/src/utils.js';
11
13
  import { isWidget } from 'ckeditor5/src/widget.js';
14
+ import LinkEditing from './linkediting.js';
15
+ import LinkPreviewButtonView from './ui/linkpreviewbuttonview.js';
12
16
  import LinkFormView from './ui/linkformview.js';
13
- import LinkActionsView from './ui/linkactionsview.js';
14
- import { addLinkProtocolIfApplicable, isLinkElement, createBookmarkCallbacks, LINK_KEYSTROKE } from './utils.js';
15
- import linkIcon from '../theme/icons/link.svg';
17
+ import LinkProviderItemsView from './ui/linkprovideritemsview.js';
18
+ import LinkPropertiesView from './ui/linkpropertiesview.js';
19
+ import LinkButtonView from './ui/linkbuttonview.js';
20
+ import { addLinkProtocolIfApplicable, ensureSafeUrl, isLinkElement, extractTextFromLinkRange, LINK_KEYSTROKE } from './utils.js';
21
+ import '../theme/linktoolbar.css';
16
22
  const VISUAL_SELECTION_MARKER_NAME = 'link-ui';
17
23
  /**
18
24
  * The link UI plugin. It introduces the `'link'` and `'unlink'` buttons and support for the <kbd>Ctrl+K</kbd> keystroke.
@@ -21,22 +27,35 @@ const VISUAL_SELECTION_MARKER_NAME = 'link-ui';
21
27
  * {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon contextual balloon plugin}.
22
28
  */
23
29
  export default class LinkUI extends Plugin {
24
- constructor() {
25
- super(...arguments);
26
- /**
27
- * The actions view displayed inside of the balloon.
28
- */
29
- this.actionsView = null;
30
- /**
31
- * The form view displayed inside the balloon.
32
- */
33
- this.formView = null;
34
- }
30
+ /**
31
+ * The toolbar view displayed inside of the balloon.
32
+ */
33
+ toolbarView = null;
34
+ /**
35
+ * The form view displayed inside the balloon.
36
+ */
37
+ formView = null;
38
+ /**
39
+ * The view displaying links list.
40
+ */
41
+ linkProviderItemsView = null;
42
+ /**
43
+ * The form view displaying properties link settings.
44
+ */
45
+ propertiesView = null;
46
+ /**
47
+ * The contextual balloon plugin instance.
48
+ */
49
+ _balloon;
50
+ /**
51
+ * The collection of the link providers.
52
+ */
53
+ _linksProviders = new Collection();
35
54
  /**
36
55
  * @inheritDoc
37
56
  */
38
57
  static get requires() {
39
- return [ContextualBalloon];
58
+ return [ContextualBalloon, LinkEditing];
40
59
  }
41
60
  /**
42
61
  * @inheritDoc
@@ -56,10 +75,12 @@ export default class LinkUI extends Plugin {
56
75
  init() {
57
76
  const editor = this.editor;
58
77
  const t = this.editor.t;
78
+ this.set('selectedLinkableText', undefined);
59
79
  editor.editing.view.addObserver(ClickObserver);
60
80
  this._balloon = editor.plugins.get(ContextualBalloon);
61
81
  // Create toolbar buttons.
62
- this._createToolbarLinkButton();
82
+ this._registerComponents();
83
+ this._registerEditingOpeners();
63
84
  this._enableBalloonActivators();
64
85
  // Renders a fake visual selection marker on an expanded selection.
65
86
  editor.conversion.for('editingDowncast').markerToHighlight({
@@ -103,73 +124,104 @@ export default class LinkUI extends Plugin {
103
124
  destroy() {
104
125
  super.destroy();
105
126
  // Destroy created UI components as they are not automatically destroyed (see ckeditor5#1341).
127
+ if (this.propertiesView) {
128
+ this.propertiesView.destroy();
129
+ }
106
130
  if (this.formView) {
107
131
  this.formView.destroy();
108
132
  }
109
- if (this.actionsView) {
110
- this.actionsView.destroy();
133
+ if (this.toolbarView) {
134
+ this.toolbarView.destroy();
135
+ }
136
+ if (this.linkProviderItemsView) {
137
+ this.linkProviderItemsView.destroy();
111
138
  }
112
139
  }
140
+ /**
141
+ * Registers list of buttons below the link form view that
142
+ * open a list of links provided by the clicked provider.
143
+ */
144
+ registerLinksListProvider(provider) {
145
+ const insertIndex = this._linksProviders
146
+ .filter(existing => (existing.order || 0) <= (provider.order || 0))
147
+ .length;
148
+ this._linksProviders.add(provider, insertIndex);
149
+ }
113
150
  /**
114
151
  * Creates views.
115
152
  */
116
153
  _createViews() {
117
- this.actionsView = this._createActionsView();
154
+ const linkCommand = this.editor.commands.get('link');
155
+ this.toolbarView = this._createToolbarView();
118
156
  this.formView = this._createFormView();
157
+ if (linkCommand.manualDecorators.length) {
158
+ this.propertiesView = this._createPropertiesView();
159
+ }
119
160
  // Attach lifecycle actions to the the balloon.
120
161
  this._enableUserBalloonInteractions();
121
162
  }
122
163
  /**
123
- * Creates the {@link module:link/ui/linkactionsview~LinkActionsView} instance.
164
+ * Creates the ToolbarView instance.
124
165
  */
125
- _createActionsView() {
166
+ _createToolbarView() {
126
167
  const editor = this.editor;
127
- const actionsView = new LinkActionsView(editor.locale, editor.config.get('link'), createBookmarkCallbacks(editor));
168
+ const toolbarView = new ToolbarView(editor.locale);
128
169
  const linkCommand = editor.commands.get('link');
129
- const unlinkCommand = editor.commands.get('unlink');
130
- actionsView.bind('href').to(linkCommand, 'value');
131
- actionsView.editButtonView.bind('isEnabled').to(linkCommand);
132
- actionsView.unlinkButtonView.bind('isEnabled').to(unlinkCommand);
133
- // Execute unlink command after clicking on the "Edit" button.
134
- this.listenTo(actionsView, 'edit', () => {
135
- this._addFormView();
136
- });
137
- // Execute unlink command after clicking on the "Unlink" button.
138
- this.listenTo(actionsView, 'unlink', () => {
139
- editor.execute('unlink');
140
- this._hideUI();
141
- });
142
- // Close the panel on esc key press when the **actions have focus**.
143
- actionsView.keystrokes.set('Esc', (data, cancel) => {
170
+ toolbarView.class = 'ck-link-toolbar';
171
+ // Remove the linkProperties button if there are no manual decorators, as it would be useless.
172
+ let toolbarItems = editor.config.get('link.toolbar');
173
+ if (!linkCommand.manualDecorators.length) {
174
+ toolbarItems = toolbarItems.filter(item => item !== 'linkProperties');
175
+ }
176
+ toolbarView.fillFromConfig(toolbarItems, editor.ui.componentFactory);
177
+ // Close the panel on esc key press when the **link toolbar have focus**.
178
+ toolbarView.keystrokes.set('Esc', (data, cancel) => {
144
179
  this._hideUI();
145
180
  cancel();
146
181
  });
147
- // Open the form view on Ctrl+K when the **actions have focus**..
148
- actionsView.keystrokes.set(LINK_KEYSTROKE, (data, cancel) => {
182
+ // Open the form view on Ctrl+K when the **link toolbar have focus**..
183
+ toolbarView.keystrokes.set(LINK_KEYSTROKE, (data, cancel) => {
149
184
  this._addFormView();
150
185
  cancel();
151
186
  });
152
- return actionsView;
187
+ // Register the toolbar, so it becomes available for Alt+F10 and Esc navigation.
188
+ // TODO this should be registered earlier to be able to open this toolbar without previously opening it by click or Ctrl+K
189
+ editor.ui.addToolbar(toolbarView, {
190
+ isContextual: true,
191
+ beforeFocus: () => {
192
+ if (this._getSelectedLinkElement() && !this._isToolbarVisible) {
193
+ this._showUI(true);
194
+ }
195
+ },
196
+ afterBlur: () => {
197
+ this._hideUI(false);
198
+ }
199
+ });
200
+ return toolbarView;
153
201
  }
154
202
  /**
155
203
  * Creates the {@link module:link/ui/linkformview~LinkFormView} instance.
156
204
  */
157
205
  _createFormView() {
158
206
  const editor = this.editor;
207
+ const t = editor.locale.t;
159
208
  const linkCommand = editor.commands.get('link');
160
209
  const defaultProtocol = editor.config.get('link.defaultProtocol');
161
- const formView = new (CssTransitionDisablerMixin(LinkFormView))(editor.locale, linkCommand, getFormValidators(editor));
162
- formView.urlInputView.fieldView.bind('value').to(linkCommand, 'value');
210
+ const formView = new (CssTransitionDisablerMixin(LinkFormView))(editor.locale, getFormValidators(editor));
211
+ formView.displayedTextInputView.bind('isEnabled').to(this, 'selectedLinkableText', value => value !== undefined);
163
212
  // Form elements should be read-only when corresponding commands are disabled.
164
213
  formView.urlInputView.bind('isEnabled').to(linkCommand, 'isEnabled');
165
214
  // Disable the "save" button if the command is disabled.
166
215
  formView.saveButtonView.bind('isEnabled').to(linkCommand, 'isEnabled');
216
+ // Change the "Save" button label depending on the command state.
217
+ formView.saveButtonView.bind('label').to(linkCommand, 'value', value => value ? t('Update') : t('Insert'));
167
218
  // Execute link command after clicking the "Save" button.
168
219
  this.listenTo(formView, 'submit', () => {
169
220
  if (formView.isValid()) {
170
- const { value } = formView.urlInputView.fieldView.element;
171
- const parsedUrl = addLinkProtocolIfApplicable(value, defaultProtocol);
172
- editor.execute('link', parsedUrl, formView.getDecoratorSwitchesState());
221
+ const url = formView.urlInputView.fieldView.element.value;
222
+ const parsedUrl = addLinkProtocolIfApplicable(url, defaultProtocol);
223
+ const displayedText = formView.displayedTextInputView.fieldView.element.value;
224
+ editor.execute('link', parsedUrl, this._getDecoratorSwitchesState(), displayedText !== this.selectedLinkableText ? displayedText : undefined);
173
225
  this._closeFormView();
174
226
  }
175
227
  });
@@ -186,13 +238,124 @@ export default class LinkUI extends Plugin {
186
238
  this._closeFormView();
187
239
  cancel();
188
240
  });
241
+ // Watch adding new link providers and add them to the buttons list.
242
+ formView.providersListChildren.bindTo(this._linksProviders).using(provider => this._createLinksListProviderButton(provider));
189
243
  return formView;
190
244
  }
191
245
  /**
192
- * Creates a toolbar Link button. Clicking this button will show
193
- * a {@link #_balloon} attached to the selection.
246
+ * Creates a sorted array of buttons with link names.
247
+ */
248
+ _createLinkProviderListView(provider) {
249
+ return provider.getListItems().map(({ href, label, icon }) => {
250
+ const buttonView = new ButtonView();
251
+ buttonView.set({
252
+ label,
253
+ icon,
254
+ tooltip: false,
255
+ withText: true
256
+ });
257
+ buttonView.on('execute', () => {
258
+ this.formView.resetFormStatus();
259
+ this.formView.urlInputView.fieldView.value = href;
260
+ // Set focus to the editing view to prevent from losing it while current view is removed.
261
+ this.editor.editing.view.focus();
262
+ this._removeLinksProviderView();
263
+ // Set the focus to the URL input field.
264
+ this.formView.focus();
265
+ });
266
+ return buttonView;
267
+ });
268
+ }
269
+ /**
270
+ * Creates a view for links provider.
194
271
  */
195
- _createToolbarLinkButton() {
272
+ _createLinkProviderItemsView(provider) {
273
+ const editor = this.editor;
274
+ const t = editor.locale.t;
275
+ const view = new LinkProviderItemsView(editor.locale);
276
+ const { emptyListPlaceholder, label } = provider;
277
+ view.emptyListPlaceholder = emptyListPlaceholder || t('No links available');
278
+ view.title = label;
279
+ // Hide the panel after clicking the "Cancel" button.
280
+ this.listenTo(view, 'cancel', () => {
281
+ // Set focus to the editing view to prevent from losing it while current view is removed.
282
+ editor.editing.view.focus();
283
+ this._removeLinksProviderView();
284
+ // Set the focus to the URL input field.
285
+ this.formView.focus();
286
+ });
287
+ return view;
288
+ }
289
+ /**
290
+ * Creates the {@link module:link/ui/linkpropertiesview~LinkPropertiesView} instance.
291
+ */
292
+ _createPropertiesView() {
293
+ const editor = this.editor;
294
+ const linkCommand = this.editor.commands.get('link');
295
+ const view = new (CssTransitionDisablerMixin(LinkPropertiesView))(editor.locale);
296
+ // Hide the panel after clicking the back button.
297
+ this.listenTo(view, 'back', () => {
298
+ // Move focus back to the editing view to prevent from losing it while current view is removed.
299
+ editor.editing.view.focus();
300
+ this._removePropertiesView();
301
+ });
302
+ view.listChildren.bindTo(linkCommand.manualDecorators).using(manualDecorator => {
303
+ const button = new SwitchButtonView(editor.locale);
304
+ button.set({
305
+ label: manualDecorator.label,
306
+ withText: true
307
+ });
308
+ button.bind('isOn').toMany([manualDecorator, linkCommand], 'value', (decoratorValue, commandValue) => {
309
+ return commandValue === undefined && decoratorValue === undefined ?
310
+ !!manualDecorator.defaultValue :
311
+ !!decoratorValue;
312
+ });
313
+ button.on('execute', () => {
314
+ manualDecorator.set('value', !button.isOn);
315
+ editor.execute('link', linkCommand.value, this._getDecoratorSwitchesState());
316
+ });
317
+ return button;
318
+ });
319
+ return view;
320
+ }
321
+ /**
322
+ * Obtains the state of the manual decorators.
323
+ */
324
+ _getDecoratorSwitchesState() {
325
+ const linkCommand = this.editor.commands.get('link');
326
+ return Array
327
+ .from(linkCommand.manualDecorators)
328
+ .reduce((accumulator, manualDecorator) => {
329
+ const value = linkCommand.value === undefined && manualDecorator.value === undefined ?
330
+ manualDecorator.defaultValue :
331
+ manualDecorator.value;
332
+ return {
333
+ ...accumulator,
334
+ [manualDecorator.id]: !!value
335
+ };
336
+ }, {});
337
+ }
338
+ /**
339
+ * Registers listeners used in editing plugin, used to open links.
340
+ */
341
+ _registerEditingOpeners() {
342
+ const linkEditing = this.editor.plugins.get(LinkEditing);
343
+ linkEditing._registerLinkOpener(href => {
344
+ const match = this._getLinkProviderLinkByHref(href);
345
+ if (!match) {
346
+ return false;
347
+ }
348
+ const { item, provider } = match;
349
+ if (provider.navigate) {
350
+ return provider.navigate(item);
351
+ }
352
+ return false;
353
+ });
354
+ }
355
+ /**
356
+ * Registers components in the ComponentFactory.
357
+ */
358
+ _registerComponents() {
196
359
  const editor = this.editor;
197
360
  editor.ui.componentFactory.add('link', () => {
198
361
  const button = this._createButton(ButtonView);
@@ -208,6 +371,113 @@ export default class LinkUI extends Plugin {
208
371
  });
209
372
  return button;
210
373
  });
374
+ editor.ui.componentFactory.add('linkPreview', locale => {
375
+ const button = new LinkPreviewButtonView(locale);
376
+ const allowedProtocols = editor.config.get('link.allowedProtocols');
377
+ const linkCommand = editor.commands.get('link');
378
+ const t = locale.t;
379
+ button.bind('isEnabled').to(linkCommand, 'value', href => !!href);
380
+ button.bind('href').to(linkCommand, 'value', href => {
381
+ return href && ensureSafeUrl(href, allowedProtocols);
382
+ });
383
+ const setHref = (href) => {
384
+ if (!href) {
385
+ button.label = undefined;
386
+ button.icon = undefined;
387
+ button.tooltip = t('Open link in new tab');
388
+ return;
389
+ }
390
+ const selectedLinksProviderLink = this._getLinkProviderLinkByHref(href);
391
+ if (selectedLinksProviderLink) {
392
+ const { label, tooltip, icon } = selectedLinksProviderLink.item;
393
+ button.label = label;
394
+ button.tooltip = tooltip || false;
395
+ button.icon = icon;
396
+ }
397
+ else {
398
+ button.label = href;
399
+ button.icon = undefined;
400
+ button.tooltip = t('Open link in new tab');
401
+ }
402
+ };
403
+ setHref(linkCommand.value);
404
+ this.listenTo(linkCommand, 'change:value', (evt, name, href) => {
405
+ setHref(href);
406
+ });
407
+ this.listenTo(button, 'navigate', (evt, href, cancel) => {
408
+ const selectedLinksProviderLink = this._getLinkProviderLinkByHref(href);
409
+ if (!selectedLinksProviderLink) {
410
+ return;
411
+ }
412
+ const { provider, item } = selectedLinksProviderLink;
413
+ const { navigate } = provider;
414
+ if (navigate && navigate(item)) {
415
+ evt.stop();
416
+ cancel();
417
+ }
418
+ });
419
+ return button;
420
+ });
421
+ editor.ui.componentFactory.add('unlink', locale => {
422
+ const unlinkCommand = editor.commands.get('unlink');
423
+ const button = new ButtonView(locale);
424
+ const t = locale.t;
425
+ button.set({
426
+ label: t('Unlink'),
427
+ icon: IconUnlink,
428
+ tooltip: true
429
+ });
430
+ button.bind('isEnabled').to(unlinkCommand);
431
+ this.listenTo(button, 'execute', () => {
432
+ editor.execute('unlink');
433
+ this._hideUI();
434
+ });
435
+ return button;
436
+ });
437
+ editor.ui.componentFactory.add('editLink', locale => {
438
+ const linkCommand = editor.commands.get('link');
439
+ const button = new ButtonView(locale);
440
+ const t = locale.t;
441
+ button.set({
442
+ label: t('Edit link'),
443
+ icon: IconPencil,
444
+ tooltip: true
445
+ });
446
+ button.bind('isEnabled').to(linkCommand);
447
+ this.listenTo(button, 'execute', () => {
448
+ this._addFormView();
449
+ });
450
+ return button;
451
+ });
452
+ editor.ui.componentFactory.add('linkProperties', locale => {
453
+ const linkCommand = editor.commands.get('link');
454
+ const button = new ButtonView(locale);
455
+ const t = locale.t;
456
+ button.set({
457
+ label: t('Link properties'),
458
+ icon: IconSettings,
459
+ tooltip: true
460
+ });
461
+ button.bind('isEnabled').to(linkCommand, 'isEnabled', linkCommand, 'value', linkCommand, 'manualDecorators', (isEnabled, href, manualDecorators) => isEnabled && !!href && manualDecorators.length > 0);
462
+ this.listenTo(button, 'execute', () => {
463
+ this._addPropertiesView();
464
+ });
465
+ return button;
466
+ });
467
+ }
468
+ /**
469
+ * Creates a links button view.
470
+ */
471
+ _createLinksListProviderButton(linkProvider) {
472
+ const locale = this.editor.locale;
473
+ const linksButton = new LinkButtonView(locale);
474
+ linksButton.set({
475
+ label: linkProvider.label
476
+ });
477
+ this.listenTo(linksButton, 'execute', () => {
478
+ this._showLinksProviderView(linkProvider);
479
+ });
480
+ return linksButton;
211
481
  }
212
482
  /**
213
483
  * Creates a button for link command to use either in toolbar or in menu bar.
@@ -220,14 +490,21 @@ export default class LinkUI extends Plugin {
220
490
  const t = locale.t;
221
491
  view.set({
222
492
  label: t('Link'),
223
- icon: linkIcon,
493
+ icon: IconLink,
224
494
  keystroke: LINK_KEYSTROKE,
225
495
  isToggleable: true
226
496
  });
227
497
  view.bind('isEnabled').to(command, 'isEnabled');
228
498
  view.bind('isOn').to(command, 'value', value => !!value);
229
499
  // Show the panel on button click.
230
- this.listenTo(view, 'execute', () => this._showUI(true));
500
+ this.listenTo(view, 'execute', () => {
501
+ this._showUI(true);
502
+ // Open the form view on-top of the toolbar view if it's already visible.
503
+ // It should be visible every time the link is selected.
504
+ if (this._getSelectedLinkElement()) {
505
+ this._addFormView();
506
+ }
507
+ });
231
508
  return view;
232
509
  }
233
510
  /**
@@ -262,8 +539,8 @@ export default class LinkUI extends Plugin {
262
539
  _enableUserBalloonInteractions() {
263
540
  // Focus the form if the balloon is visible and the Tab key has been pressed.
264
541
  this.editor.keystrokes.set('Tab', (data, cancel) => {
265
- if (this._areActionsVisible && !this.actionsView.focusTracker.isFocused) {
266
- this.actionsView.focus();
542
+ if (this._isToolbarVisible && !this.toolbarView.focusTracker.isFocused) {
543
+ this.toolbarView.focus();
267
544
  cancel();
268
545
  }
269
546
  }, {
@@ -288,20 +565,21 @@ export default class LinkUI extends Plugin {
288
565
  });
289
566
  }
290
567
  /**
291
- * Adds the {@link #actionsView} to the {@link #_balloon}.
568
+ * Adds the {@link #toolbarView} to the {@link #_balloon}.
292
569
  *
293
570
  * @internal
294
571
  */
295
- _addActionsView() {
296
- if (!this.actionsView) {
572
+ _addToolbarView() {
573
+ if (!this.toolbarView) {
297
574
  this._createViews();
298
575
  }
299
- if (this._areActionsInPanel) {
576
+ if (this._isToolbarInPanel) {
300
577
  return;
301
578
  }
302
579
  this._balloon.add({
303
- view: this.actionsView,
304
- position: this._getBalloonPositionData()
580
+ view: this.toolbarView,
581
+ position: this._getBalloonPositionData(),
582
+ balloonClassName: 'ck-toolbar-container'
305
583
  });
306
584
  }
307
585
  /**
@@ -314,20 +592,22 @@ export default class LinkUI extends Plugin {
314
592
  if (this._isFormInPanel) {
315
593
  return;
316
594
  }
317
- const editor = this.editor;
318
- const linkCommand = editor.commands.get('link');
595
+ const linkCommand = this.editor.commands.get('link');
319
596
  this.formView.disableCssTransitions();
320
597
  this.formView.resetFormStatus();
598
+ this.formView.backButtonView.isVisible = linkCommand.isEnabled && !!linkCommand.value;
321
599
  this._balloon.add({
322
600
  view: this.formView,
323
601
  position: this._getBalloonPositionData()
324
602
  });
325
- // Make sure that each time the panel shows up, the URL field remains in sync with the value of
603
+ // Make sure that each time the panel shows up, the fields remains in sync with the value of
326
604
  // the command. If the user typed in the input, then canceled the balloon (`urlInputView.fieldView#value` stays
327
605
  // unaltered) and re-opened it without changing the value of the link command (e.g. because they
328
606
  // clicked the same link), they would see the old value instead of the actual value of the command.
329
607
  // https://github.com/ckeditor/ckeditor5-link/issues/78
330
608
  // https://github.com/ckeditor/ckeditor5-link/issues/123
609
+ this.selectedLinkableText = this._getSelectedLinkableText();
610
+ this.formView.displayedTextInputView.fieldView.value = this.selectedLinkableText || '';
331
611
  this.formView.urlInputView.fieldView.value = linkCommand.value || '';
332
612
  // Select input when form view is currently visible.
333
613
  if (this._balloon.visibleView === this.formView) {
@@ -335,18 +615,55 @@ export default class LinkUI extends Plugin {
335
615
  }
336
616
  this.formView.enableCssTransitions();
337
617
  }
618
+ /**
619
+ * Adds the {@link #propertiesView} to the {@link #_balloon}.
620
+ */
621
+ _addPropertiesView() {
622
+ if (!this.propertiesView) {
623
+ this._createViews();
624
+ }
625
+ if (this._arePropertiesInPanel) {
626
+ return;
627
+ }
628
+ this.propertiesView.disableCssTransitions();
629
+ this._balloon.add({
630
+ view: this.propertiesView,
631
+ position: this._getBalloonPositionData()
632
+ });
633
+ this.propertiesView.enableCssTransitions();
634
+ this.propertiesView.focus();
635
+ }
636
+ /**
637
+ * Shows the view with links provided by the given provider.
638
+ */
639
+ _showLinksProviderView(provider) {
640
+ if (this.linkProviderItemsView) {
641
+ this._removeLinksProviderView();
642
+ }
643
+ this.linkProviderItemsView = this._createLinkProviderItemsView(provider);
644
+ this._addLinkProviderItemsView(provider);
645
+ }
646
+ /**
647
+ * Adds the {@link #linkProviderItemsView} to the {@link #_balloon}.
648
+ */
649
+ _addLinkProviderItemsView(provider) {
650
+ // Clear the collection of links.
651
+ this.linkProviderItemsView.listChildren.clear();
652
+ // Add links to the collection.
653
+ this.linkProviderItemsView.listChildren.addMany(this._createLinkProviderListView(provider));
654
+ this._balloon.add({
655
+ view: this.linkProviderItemsView,
656
+ position: this._getBalloonPositionData()
657
+ });
658
+ this.linkProviderItemsView.focus();
659
+ }
338
660
  /**
339
661
  * Closes the form view. Decides whether the balloon should be hidden completely or if the action view should be shown. This is
340
662
  * decided upon the link command value (which has a value if the document selection is in the link).
341
- *
342
- * Additionally, if any {@link module:link/linkconfig~LinkConfig#decorators} are defined in the editor configuration, the state of
343
- * switch buttons responsible for manual decorator handling is restored.
344
663
  */
345
664
  _closeFormView() {
346
665
  const linkCommand = this.editor.commands.get('link');
347
- // Restore manual decorator states to represent the current model state. This case is important to reset the switch buttons
348
- // when the user cancels the editing form.
349
- linkCommand.restoreManualDecoratorStates();
666
+ this.selectedLinkableText = undefined;
350
667
  if (linkCommand.value !== undefined) {
351
668
  this._removeFormView();
352
669
  }
@@ -354,6 +671,22 @@ export default class LinkUI extends Plugin {
354
671
  this._hideUI();
355
672
  }
356
673
  }
674
+ /**
675
+ * Removes the {@link #propertiesView} from the {@link #_balloon}.
676
+ */
677
+ _removePropertiesView() {
678
+ if (this._arePropertiesInPanel) {
679
+ this._balloon.remove(this.propertiesView);
680
+ }
681
+ }
682
+ /**
683
+ * Removes the {@link #linkProviderItemsView} from the {@link #_balloon}.
684
+ */
685
+ _removeLinksProviderView() {
686
+ if (this._isLinksListInPanel) {
687
+ this._balloon.remove(this.linkProviderItemsView);
688
+ }
689
+ }
357
690
  /**
358
691
  * Removes the {@link #formView} from the {@link #_balloon}.
359
692
  */
@@ -362,7 +695,8 @@ export default class LinkUI extends Plugin {
362
695
  // Blur the input element before removing it from DOM to prevent issues in some browsers.
363
696
  // See https://github.com/ckeditor/ckeditor5/issues/1501.
364
697
  this.formView.saveButtonView.focus();
365
- // Reset the URL field to update the state of the submit button.
698
+ // Reset fields to update the state of the submit button.
699
+ this.formView.displayedTextInputView.fieldView.reset();
366
700
  this.formView.urlInputView.fieldView.reset();
367
701
  this._balloon.remove(this.formView);
368
702
  // Because the form has an input which has focus, the focus must be brought back
@@ -372,7 +706,7 @@ export default class LinkUI extends Plugin {
372
706
  }
373
707
  }
374
708
  /**
375
- * Shows the correct UI type. It is either {@link #formView} or {@link #actionsView}.
709
+ * Shows the correct UI type. It is either {@link #formView} or {@link #toolbarView}.
376
710
  *
377
711
  * @internal
378
712
  */
@@ -385,7 +719,7 @@ export default class LinkUI extends Plugin {
385
719
  // Show visual selection on a text without a link when the contextual balloon is displayed.
386
720
  // See https://github.com/ckeditor/ckeditor5/issues/4721.
387
721
  this._showFakeVisualSelection();
388
- this._addActionsView();
722
+ this._addToolbarView();
389
723
  // Be sure panel with link is visible.
390
724
  if (forceVisible) {
391
725
  this._balloon.showStack('main');
@@ -394,13 +728,13 @@ export default class LinkUI extends Plugin {
394
728
  }
395
729
  // If there's a link under the selection...
396
730
  else {
397
- // Go to the editing UI if actions are already visible.
398
- if (this._areActionsVisible) {
731
+ // Go to the editing UI if toolbar is already visible.
732
+ if (this._isToolbarVisible) {
399
733
  this._addFormView();
400
734
  }
401
- // Otherwise display just the actions UI.
735
+ // Otherwise display just the toolbar.
402
736
  else {
403
- this._addActionsView();
737
+ this._addToolbarView();
404
738
  }
405
739
  // Be sure panel with link is visible.
406
740
  if (forceVisible) {
@@ -413,22 +747,30 @@ export default class LinkUI extends Plugin {
413
747
  /**
414
748
  * Removes the {@link #formView} from the {@link #_balloon}.
415
749
  *
416
- * See {@link #_addFormView}, {@link #_addActionsView}.
750
+ * See {@link #_addFormView}, {@link #_addToolbarView}.
417
751
  */
418
- _hideUI() {
752
+ _hideUI(updateFocus = true) {
753
+ const editor = this.editor;
419
754
  if (!this._isUIInPanel) {
420
755
  return;
421
756
  }
422
- const editor = this.editor;
423
757
  this.stopListening(editor.ui, 'update');
424
758
  this.stopListening(this._balloon, 'change:visibleView');
425
759
  // Make sure the focus always gets back to the editable _before_ removing the focused form view.
426
760
  // Doing otherwise causes issues in some browsers. See https://github.com/ckeditor/ckeditor5-link/issues/193.
427
- editor.editing.view.focus();
428
- // Remove form first because it's on top of the stack.
761
+ if (updateFocus) {
762
+ editor.editing.view.focus();
763
+ }
764
+ // If the links view is visible, remove it because it can be on top of the stack.
765
+ this._removeLinksProviderView();
766
+ // If the properties form view is visible, remove it because it can be on top of the stack.
767
+ this._removePropertiesView();
768
+ // Then remove the form view because it's beneath the properties form.
429
769
  this._removeFormView();
430
- // Then remove the actions view because it's beneath the form.
431
- this._balloon.remove(this.actionsView);
770
+ // Finally, remove the link toolbar view because it's last in the stack.
771
+ if (this._isToolbarInPanel) {
772
+ this._balloon.remove(this.toolbarView);
773
+ }
432
774
  this._hideFakeVisualSelection();
433
775
  }
434
776
  /**
@@ -451,7 +793,7 @@ export default class LinkUI extends Plugin {
451
793
  // of the link,
452
794
  // * the selection went to a different parent when creating a NEW link. E.g. someone
453
795
  // else modified the document.
454
- // * the selection has expanded (e.g. displaying link actions then pressing SHIFT+Right arrow).
796
+ // * the selection has expanded (e.g. displaying link toolbar then pressing SHIFT+Right arrow).
455
797
  //
456
798
  // Note: #_getSelectedLinkElement will return a link for a non-collapsed selection only
457
799
  // when fully selected.
@@ -480,6 +822,18 @@ export default class LinkUI extends Plugin {
480
822
  this.listenTo(editor.ui, 'update', update);
481
823
  this.listenTo(this._balloon, 'change:visibleView', update);
482
824
  }
825
+ /**
826
+ * Returns `true` when {@link #propertiesView} is in the {@link #_balloon}.
827
+ */
828
+ get _arePropertiesInPanel() {
829
+ return !!this.propertiesView && this._balloon.hasView(this.propertiesView);
830
+ }
831
+ /**
832
+ * Returns `true` when {@link #linkProviderItemsView} is in the {@link #_balloon}.
833
+ */
834
+ get _isLinksListInPanel() {
835
+ return !!this.linkProviderItemsView && this._balloon.hasView(this.linkProviderItemsView);
836
+ }
483
837
  /**
484
838
  * Returns `true` when {@link #formView} is in the {@link #_balloon}.
485
839
  */
@@ -487,31 +841,45 @@ export default class LinkUI extends Plugin {
487
841
  return !!this.formView && this._balloon.hasView(this.formView);
488
842
  }
489
843
  /**
490
- * Returns `true` when {@link #actionsView} is in the {@link #_balloon}.
844
+ * Returns `true` when {@link #toolbarView} is in the {@link #_balloon}.
491
845
  */
492
- get _areActionsInPanel() {
493
- return !!this.actionsView && this._balloon.hasView(this.actionsView);
846
+ get _isToolbarInPanel() {
847
+ return !!this.toolbarView && this._balloon.hasView(this.toolbarView);
494
848
  }
495
849
  /**
496
- * Returns `true` when {@link #actionsView} is in the {@link #_balloon} and it is
850
+ * Returns `true` when {@link #propertiesView} is in the {@link #_balloon} and it is
497
851
  * currently visible.
498
852
  */
499
- get _areActionsVisible() {
500
- return !!this.actionsView && this._balloon.visibleView === this.actionsView;
853
+ get _isPropertiesVisible() {
854
+ return !!this.propertiesView && this._balloon.visibleView === this.propertiesView;
501
855
  }
502
856
  /**
503
- * Returns `true` when {@link #actionsView} or {@link #formView} is in the {@link #_balloon}.
857
+ * Returns `true` when {@link #formView} is in the {@link #_balloon} and it is
858
+ * currently visible.
504
859
  */
505
- get _isUIInPanel() {
506
- return this._isFormInPanel || this._areActionsInPanel;
860
+ get _isFormVisible() {
861
+ return !!this.formView && this._balloon.visibleView == this.formView;
507
862
  }
508
863
  /**
509
- * Returns `true` when {@link #actionsView} or {@link #formView} is in the {@link #_balloon} and it is
864
+ * Returns `true` when {@link #toolbarView} is in the {@link #_balloon} and it is
510
865
  * currently visible.
511
866
  */
867
+ get _isToolbarVisible() {
868
+ return !!this.toolbarView && this._balloon.visibleView === this.toolbarView;
869
+ }
870
+ /**
871
+ * Returns `true` when {@link #propertiesView}, {@link #toolbarView}, {@link #linkProviderItemsView}
872
+ * or {@link #formView} is in the {@link #_balloon}.
873
+ */
874
+ get _isUIInPanel() {
875
+ return this._arePropertiesInPanel || this._isLinksListInPanel || this._isFormInPanel || this._isToolbarInPanel;
876
+ }
877
+ /**
878
+ * Returns `true` when {@link #propertiesView}, {@link #linkProviderItemsView}, {@link #toolbarView}
879
+ * or {@link #formView} is in the {@link #_balloon} and it is currently visible.
880
+ */
512
881
  get _isUIVisible() {
513
- const visibleView = this._balloon.visibleView;
514
- return !!this.formView && visibleView == this.formView || this._areActionsVisible;
882
+ return this._isPropertiesVisible || this._isLinksListInPanel || this._isFormVisible || this._isToolbarVisible;
515
883
  }
516
884
  /**
517
885
  * Returns positioning options for the {@link #_balloon}. They control the way the balloon is attached
@@ -522,30 +890,34 @@ export default class LinkUI extends Plugin {
522
890
  */
523
891
  _getBalloonPositionData() {
524
892
  const view = this.editor.editing.view;
525
- const model = this.editor.model;
526
893
  const viewDocument = view.document;
527
- let target;
894
+ const model = this.editor.model;
528
895
  if (model.markers.has(VISUAL_SELECTION_MARKER_NAME)) {
529
896
  // There are cases when we highlight selection using a marker (#7705, #4721).
530
- const markerViewElements = Array.from(this.editor.editing.mapper.markerNameToElements(VISUAL_SELECTION_MARKER_NAME));
531
- const newRange = view.createRange(view.createPositionBefore(markerViewElements[0]), view.createPositionAfter(markerViewElements[markerViewElements.length - 1]));
532
- target = view.domConverter.viewRangeToDom(newRange);
897
+ const markerViewElements = this.editor.editing.mapper.markerNameToElements(VISUAL_SELECTION_MARKER_NAME);
898
+ // Marker could be removed by link text override and end up in the graveyard.
899
+ if (markerViewElements) {
900
+ const markerViewElementsArray = Array.from(markerViewElements);
901
+ const newRange = view.createRange(view.createPositionBefore(markerViewElementsArray[0]), view.createPositionAfter(markerViewElementsArray[markerViewElementsArray.length - 1]));
902
+ return {
903
+ target: view.domConverter.viewRangeToDom(newRange)
904
+ };
905
+ }
533
906
  }
534
- else {
535
- // Make sure the target is calculated on demand at the last moment because a cached DOM range
536
- // (which is very fragile) can desynchronize with the state of the editing view if there was
537
- // any rendering done in the meantime. This can happen, for instance, when an inline widget
538
- // gets unlinked.
539
- target = () => {
907
+ // Make sure the target is calculated on demand at the last moment because a cached DOM range
908
+ // (which is very fragile) can desynchronize with the state of the editing view if there was
909
+ // any rendering done in the meantime. This can happen, for instance, when an inline widget
910
+ // gets unlinked.
911
+ return {
912
+ target: () => {
540
913
  const targetLink = this._getSelectedLinkElement();
541
914
  return targetLink ?
542
915
  // When selection is inside link element, then attach panel to this element.
543
916
  view.domConverter.mapViewToDom(targetLink) :
544
917
  // Otherwise attach panel to the selection.
545
918
  view.domConverter.viewRangeToDom(viewDocument.selection.getFirstRange());
546
- };
547
- }
548
- return { target };
919
+ }
920
+ };
549
921
  }
550
922
  /**
551
923
  * Returns the link {@link module:engine/view/attributeelement~AttributeElement} under
@@ -582,6 +954,42 @@ export default class LinkUI extends Plugin {
582
954
  }
583
955
  }
584
956
  }
957
+ /**
958
+ * Returns selected link text content.
959
+ * If link is not selected it returns the selected text.
960
+ * If selection or link includes non text node (inline object or block) then returns undefined.
961
+ */
962
+ _getSelectedLinkableText() {
963
+ const model = this.editor.model;
964
+ const editing = this.editor.editing;
965
+ const selectedLink = this._getSelectedLinkElement();
966
+ if (!selectedLink) {
967
+ return extractTextFromLinkRange(model.document.selection.getFirstRange());
968
+ }
969
+ const viewLinkRange = editing.view.createRangeOn(selectedLink);
970
+ const linkRange = editing.mapper.toModelRange(viewLinkRange);
971
+ return extractTextFromLinkRange(linkRange);
972
+ }
973
+ /**
974
+ * Returns a provider by its URL.
975
+ *
976
+ * @param href URL of the link.
977
+ * @returns Link provider and item or `null` if not found.
978
+ */
979
+ _getLinkProviderLinkByHref(href) {
980
+ if (!href) {
981
+ return null;
982
+ }
983
+ for (const provider of this._linksProviders) {
984
+ const item = provider.getItem ?
985
+ provider.getItem(href) :
986
+ provider.getListItems().find(item => item.href === href);
987
+ if (item) {
988
+ return { provider, item };
989
+ }
990
+ }
991
+ return null;
992
+ }
585
993
  /**
586
994
  * Displays a fake visual selection when the contextual balloon is displayed.
587
995
  *