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