@ckeditor/ckeditor5-bookmark 0.0.0-nightly-20241025.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/LICENSE.md +17 -0
  3. package/README.md +26 -0
  4. package/build/bookmark.js +4 -0
  5. package/ckeditor5-metadata.json +24 -0
  6. package/dist/augmentation.d.ts +28 -0
  7. package/dist/bookmark.d.ts +34 -0
  8. package/dist/bookmarkconfig.d.ts +52 -0
  9. package/dist/bookmarkediting.d.ts +55 -0
  10. package/dist/bookmarkui.d.ts +170 -0
  11. package/dist/index-content.css +4 -0
  12. package/dist/index-editor.css +150 -0
  13. package/dist/index.css +195 -0
  14. package/dist/index.css.map +1 -0
  15. package/dist/index.d.ts +18 -0
  16. package/dist/index.js +1322 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/insertbookmarkcommand.d.ts +42 -0
  19. package/dist/ui/bookmarkactionsview.d.ts +106 -0
  20. package/dist/ui/bookmarkformview.d.ts +122 -0
  21. package/dist/updatebookmarkcommand.d.ts +46 -0
  22. package/dist/utils.d.ts +15 -0
  23. package/lang/contexts.json +13 -0
  24. package/package.json +43 -0
  25. package/src/augmentation.d.ts +24 -0
  26. package/src/augmentation.js +5 -0
  27. package/src/bookmark.d.ts +30 -0
  28. package/src/bookmark.js +36 -0
  29. package/src/bookmarkconfig.d.ts +48 -0
  30. package/src/bookmarkconfig.js +5 -0
  31. package/src/bookmarkediting.d.ts +51 -0
  32. package/src/bookmarkediting.js +212 -0
  33. package/src/bookmarkui.d.ts +166 -0
  34. package/src/bookmarkui.js +583 -0
  35. package/src/index.d.ts +14 -0
  36. package/src/index.js +13 -0
  37. package/src/insertbookmarkcommand.d.ts +38 -0
  38. package/src/insertbookmarkcommand.js +113 -0
  39. package/src/ui/bookmarkactionsview.d.ts +102 -0
  40. package/src/ui/bookmarkactionsview.js +154 -0
  41. package/src/ui/bookmarkformview.d.ts +118 -0
  42. package/src/ui/bookmarkformview.js +203 -0
  43. package/src/updatebookmarkcommand.d.ts +42 -0
  44. package/src/updatebookmarkcommand.js +75 -0
  45. package/src/utils.d.ts +11 -0
  46. package/src/utils.js +19 -0
  47. package/theme/bookmark.css +50 -0
  48. package/theme/bookmarkactions.css +44 -0
  49. package/theme/bookmarkform.css +42 -0
@@ -0,0 +1,583 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * @module bookmark/bookmarkui
7
+ */
8
+ import { Plugin, icons } from 'ckeditor5/src/core.js';
9
+ import { ButtonView, ContextualBalloon, CssTransitionDisablerMixin, MenuBarMenuListItemButtonView, clickOutsideHandler } from 'ckeditor5/src/ui.js';
10
+ import { ClickObserver } from 'ckeditor5/src/engine.js';
11
+ import BookmarkFormView from './ui/bookmarkformview.js';
12
+ import BookmarkActionsView from './ui/bookmarkactionsview.js';
13
+ import BookmarkEditing from './bookmarkediting.js';
14
+ const bookmarkIcon = icons.bookmark;
15
+ const VISUAL_SELECTION_MARKER_NAME = 'bookmark-ui';
16
+ /**
17
+ * The UI plugin of the bookmark feature.
18
+ *
19
+ * It registers the `'bookmark'` UI button in the editor's {@link module:ui/componentfactory~ComponentFactory component factory}
20
+ * which inserts the `bookmark` element upon selection.
21
+ */
22
+ export default class BookmarkUI extends Plugin {
23
+ constructor() {
24
+ super(...arguments);
25
+ /**
26
+ * The actions view displayed inside of the balloon.
27
+ */
28
+ this.actionsView = null;
29
+ /**
30
+ * The form view displayed inside the balloon.
31
+ */
32
+ this.formView = null;
33
+ }
34
+ /**
35
+ * @inheritDoc
36
+ */
37
+ static get requires() {
38
+ return [BookmarkEditing, ContextualBalloon];
39
+ }
40
+ /**
41
+ * @inheritDoc
42
+ */
43
+ static get pluginName() {
44
+ return 'BookmarkUI';
45
+ }
46
+ /**
47
+ * @inheritDoc
48
+ */
49
+ static get isOfficialPlugin() {
50
+ return true;
51
+ }
52
+ /**
53
+ * @inheritDoc
54
+ */
55
+ init() {
56
+ const editor = this.editor;
57
+ editor.editing.view.addObserver(ClickObserver);
58
+ this._balloon = editor.plugins.get(ContextualBalloon);
59
+ // Create toolbar buttons.
60
+ this._createToolbarBookmarkButton();
61
+ this._enableBalloonActivators();
62
+ // Renders a fake visual selection marker on an expanded selection.
63
+ editor.conversion.for('editingDowncast').markerToHighlight({
64
+ model: VISUAL_SELECTION_MARKER_NAME,
65
+ view: {
66
+ classes: ['ck-fake-bookmark-selection']
67
+ }
68
+ });
69
+ // Renders a fake visual selection marker on a collapsed selection.
70
+ editor.conversion.for('editingDowncast').markerToElement({
71
+ model: VISUAL_SELECTION_MARKER_NAME,
72
+ view: (data, { writer }) => {
73
+ if (!data.markerRange.isCollapsed) {
74
+ return null;
75
+ }
76
+ const markerElement = writer.createUIElement('span');
77
+ writer.addClass(['ck-fake-bookmark-selection', 'ck-fake-bookmark-selection_collapsed'], markerElement);
78
+ return markerElement;
79
+ }
80
+ });
81
+ }
82
+ /**
83
+ * @inheritDoc
84
+ */
85
+ destroy() {
86
+ super.destroy();
87
+ // Destroy created UI components as they are not automatically destroyed (see ckeditor5#1341).
88
+ if (this.formView) {
89
+ this.formView.destroy();
90
+ }
91
+ if (this.actionsView) {
92
+ this.actionsView.destroy();
93
+ }
94
+ }
95
+ /**
96
+ * Creates views.
97
+ */
98
+ _createViews() {
99
+ this.actionsView = this._createActionsView();
100
+ this.formView = this._createFormView();
101
+ // Attach lifecycle actions to the the balloon.
102
+ this._enableUserBalloonInteractions();
103
+ }
104
+ /**
105
+ * Creates the {@link module:bookmark/ui/bookmarkactionsview~BookmarkActionsView} instance.
106
+ */
107
+ _createActionsView() {
108
+ const editor = this.editor;
109
+ const actionsView = new BookmarkActionsView(editor.locale);
110
+ const updateBookmarkCommand = editor.commands.get('updateBookmark');
111
+ const deleteCommand = editor.commands.get('delete');
112
+ actionsView.bind('id').to(updateBookmarkCommand, 'value');
113
+ actionsView.editButtonView.bind('isEnabled').to(updateBookmarkCommand);
114
+ actionsView.removeButtonView.bind('isEnabled').to(deleteCommand);
115
+ // Display edit form view after clicking on the "Edit" button.
116
+ this.listenTo(actionsView, 'edit', () => {
117
+ this._addFormView();
118
+ });
119
+ // Execute remove command after clicking on the "Remove" button.
120
+ this.listenTo(actionsView, 'remove', () => {
121
+ this._hideUI();
122
+ editor.execute('delete');
123
+ });
124
+ // Close the panel on esc key press when the **actions have focus**.
125
+ actionsView.keystrokes.set('Esc', (data, cancel) => {
126
+ this._hideUI();
127
+ cancel();
128
+ });
129
+ return actionsView;
130
+ }
131
+ /**
132
+ * Creates the {@link module:bookmark/ui/bookmarkformview~BookmarkFormView} instance.
133
+ */
134
+ _createFormView() {
135
+ const editor = this.editor;
136
+ const locale = editor.locale;
137
+ const insertBookmarkCommand = editor.commands.get('insertBookmark');
138
+ const updateBookmarkCommand = editor.commands.get('updateBookmark');
139
+ const commands = [insertBookmarkCommand, updateBookmarkCommand];
140
+ const formView = new (CssTransitionDisablerMixin(BookmarkFormView))(locale, getFormValidators(editor));
141
+ formView.idInputView.fieldView.bind('value').to(updateBookmarkCommand, 'value');
142
+ // Form elements should be read-only when corresponding commands are disabled.
143
+ formView.idInputView.bind('isEnabled').toMany(commands, 'isEnabled', (...areEnabled) => areEnabled.some(isEnabled => isEnabled));
144
+ // Disable the "save" button if the command is disabled.
145
+ formView.buttonView.bind('isEnabled').toMany(commands, 'isEnabled', (...areEnabled) => areEnabled.some(isEnabled => isEnabled));
146
+ // Execute link command after clicking the "Save" button.
147
+ this.listenTo(formView, 'submit', () => {
148
+ if (formView.isValid()) {
149
+ const value = formView.id;
150
+ if (this._getSelectedBookmarkElement()) {
151
+ editor.execute('updateBookmark', { bookmarkId: value });
152
+ }
153
+ else {
154
+ editor.execute('insertBookmark', { bookmarkId: value });
155
+ }
156
+ this._closeFormView();
157
+ }
158
+ });
159
+ // Update balloon position when form error changes.
160
+ this.listenTo(formView.idInputView, 'change:errorText', () => {
161
+ editor.ui.update();
162
+ });
163
+ // Close the panel on esc key press when the **form has focus**.
164
+ formView.keystrokes.set('Esc', (data, cancel) => {
165
+ this._closeFormView();
166
+ cancel();
167
+ });
168
+ return formView;
169
+ }
170
+ /**
171
+ * Creates a toolbar Bookmark button. Clicking this button will show
172
+ * a {@link #_balloon} attached to the selection.
173
+ */
174
+ _createToolbarBookmarkButton() {
175
+ const editor = this.editor;
176
+ editor.ui.componentFactory.add('bookmark', () => {
177
+ const buttonView = this._createButton(ButtonView);
178
+ buttonView.set({
179
+ tooltip: true
180
+ });
181
+ return buttonView;
182
+ });
183
+ editor.ui.componentFactory.add('menuBar:bookmark', () => {
184
+ return this._createButton(MenuBarMenuListItemButtonView);
185
+ });
186
+ }
187
+ /**
188
+ * Creates a button for `bookmark` command to use either in toolbar or in menu bar.
189
+ */
190
+ _createButton(ButtonClass) {
191
+ const editor = this.editor;
192
+ const locale = editor.locale;
193
+ const view = new ButtonClass(locale);
194
+ const insertCommand = editor.commands.get('insertBookmark');
195
+ const updateCommand = editor.commands.get('updateBookmark');
196
+ const t = locale.t;
197
+ view.set({
198
+ label: t('Bookmark'),
199
+ icon: bookmarkIcon
200
+ });
201
+ // Execute the command.
202
+ this.listenTo(view, 'execute', () => this._showUI(true));
203
+ view.bind('isEnabled').toMany([insertCommand, updateCommand], 'isEnabled', (...areEnabled) => areEnabled.some(isEnabled => isEnabled));
204
+ view.bind('isOn').to(updateCommand, 'value', value => !!value);
205
+ return view;
206
+ }
207
+ /**
208
+ * Attaches actions that control whether the balloon panel containing the
209
+ * {@link #formView} should be displayed.
210
+ */
211
+ _enableBalloonActivators() {
212
+ const editor = this.editor;
213
+ const viewDocument = editor.editing.view.document;
214
+ // Handle click on view document and show panel when selection is placed inside the bookmark element.
215
+ // Keep panel open until selection will be inside the same bookmark element.
216
+ this.listenTo(viewDocument, 'click', () => {
217
+ const bookmark = this._getSelectedBookmarkElement();
218
+ if (bookmark) {
219
+ // Then show panel but keep focus inside editor editable.
220
+ this._showUI();
221
+ }
222
+ });
223
+ }
224
+ /**
225
+ * Attaches actions that control whether the balloon panel containing the
226
+ * {@link #formView} is visible or not.
227
+ */
228
+ _enableUserBalloonInteractions() {
229
+ // Focus the form if the balloon is visible and the Tab key has been pressed.
230
+ this.editor.keystrokes.set('Tab', (data, cancel) => {
231
+ if (this._areActionsVisible && !this.actionsView.focusTracker.isFocused) {
232
+ this.actionsView.focus();
233
+ cancel();
234
+ }
235
+ }, {
236
+ // Use the high priority because the bookmark UI navigation is more important
237
+ // than other feature's actions, e.g. list indentation.
238
+ priority: 'high'
239
+ });
240
+ // Close the panel on the Esc key press when the editable has focus and the balloon is visible.
241
+ this.editor.keystrokes.set('Esc', (data, cancel) => {
242
+ if (this._isUIVisible) {
243
+ this._hideUI();
244
+ cancel();
245
+ }
246
+ });
247
+ // Close on click outside of balloon panel element.
248
+ clickOutsideHandler({
249
+ emitter: this.formView,
250
+ activator: () => this._isUIInPanel,
251
+ contextElements: () => [this._balloon.view.element],
252
+ callback: () => this._hideUI()
253
+ });
254
+ }
255
+ /**
256
+ * Updates the button label. If bookmark is selected label is set to 'Update' otherwise
257
+ * it is 'Insert'.
258
+ */
259
+ _updateFormButtonLabel(isBookmarkSelected) {
260
+ const t = this.editor.locale.t;
261
+ this.formView.buttonView.label = isBookmarkSelected ? t('Update') : t('Insert');
262
+ }
263
+ /**
264
+ * Adds the {@link #actionsView} to the {@link #_balloon}.
265
+ *
266
+ * @internal
267
+ */
268
+ _addActionsView() {
269
+ if (!this.actionsView) {
270
+ this._createViews();
271
+ }
272
+ if (this._areActionsInPanel) {
273
+ return;
274
+ }
275
+ this._balloon.add({
276
+ view: this.actionsView,
277
+ position: this._getBalloonPositionData()
278
+ });
279
+ }
280
+ /**
281
+ * Adds the {@link #formView} to the {@link #_balloon}.
282
+ */
283
+ _addFormView() {
284
+ if (!this.formView) {
285
+ this._createViews();
286
+ }
287
+ if (this._isFormInPanel) {
288
+ return;
289
+ }
290
+ const editor = this.editor;
291
+ const updateBookmarkCommand = editor.commands.get('updateBookmark');
292
+ this.formView.disableCssTransitions();
293
+ this.formView.resetFormStatus();
294
+ this._balloon.add({
295
+ view: this.formView,
296
+ position: this._getBalloonPositionData()
297
+ });
298
+ this.formView.idInputView.fieldView.value = updateBookmarkCommand.value || '';
299
+ // Select input when form view is currently visible.
300
+ if (this._balloon.visibleView === this.formView) {
301
+ this.formView.idInputView.fieldView.select();
302
+ }
303
+ this.formView.enableCssTransitions();
304
+ }
305
+ /**
306
+ * Closes the form view. Decides whether the balloon should be hidden completely.
307
+ */
308
+ _closeFormView() {
309
+ const updateBookmarkCommand = this.editor.commands.get('updateBookmark');
310
+ if (updateBookmarkCommand.value !== undefined) {
311
+ this._removeFormView();
312
+ }
313
+ else {
314
+ this._hideUI();
315
+ }
316
+ }
317
+ /**
318
+ * Removes the {@link #formView} from the {@link #_balloon}.
319
+ */
320
+ _removeFormView() {
321
+ if (this._isFormInPanel) {
322
+ // Blur the input element before removing it from DOM to prevent issues in some browsers.
323
+ // See https://github.com/ckeditor/ckeditor5/issues/1501.
324
+ this.formView.buttonView.focus();
325
+ // Reset the ID field to update the state of the submit button.
326
+ this.formView.idInputView.fieldView.reset();
327
+ this._balloon.remove(this.formView);
328
+ // Because the form has an input which has focus, the focus must be brought back
329
+ // to the editor. Otherwise, it would be lost.
330
+ this.editor.editing.view.focus();
331
+ this._hideFakeVisualSelection();
332
+ }
333
+ }
334
+ /**
335
+ * Shows the correct UI type. It is either {@link #formView} or {@link #actionsView}.
336
+ */
337
+ _showUI(forceVisible = false) {
338
+ if (!this.formView) {
339
+ this._createViews();
340
+ }
341
+ // When there's no bookmark under the selection, go straight to the editing UI.
342
+ if (!this._getSelectedBookmarkElement()) {
343
+ // Show visual selection on a text without a bookmark when the contextual balloon is displayed.
344
+ this._showFakeVisualSelection();
345
+ this._addActionsView();
346
+ // Be sure panel with bookmark is visible.
347
+ if (forceVisible) {
348
+ this._balloon.showStack('main');
349
+ }
350
+ this._addFormView();
351
+ }
352
+ // If there's a bookmark under the selection...
353
+ else {
354
+ // Go to the editing UI if actions are already visible.
355
+ if (this._areActionsVisible) {
356
+ this._addFormView();
357
+ }
358
+ // Otherwise display just the actions UI.
359
+ else {
360
+ this._addActionsView();
361
+ }
362
+ // Be sure panel with bookmark is visible.
363
+ if (forceVisible) {
364
+ this._balloon.showStack('main');
365
+ }
366
+ }
367
+ // Begin responding to ui#update once the UI is added.
368
+ this._startUpdatingUI();
369
+ }
370
+ /**
371
+ * Removes the {@link #formView} from the {@link #_balloon}.
372
+ *
373
+ * See {@link #_addFormView}, {@link #_addActionsView}.
374
+ */
375
+ _hideUI() {
376
+ if (!this._isUIInPanel) {
377
+ return;
378
+ }
379
+ const editor = this.editor;
380
+ this.stopListening(editor.ui, 'update');
381
+ this.stopListening(this._balloon, 'change:visibleView');
382
+ // Make sure the focus always gets back to the editable _before_ removing the focused form view.
383
+ // Doing otherwise causes issues in some browsers. See https://github.com/ckeditor/ckeditor5-link/issues/193.
384
+ editor.editing.view.focus();
385
+ // Remove form first because it's on top of the stack.
386
+ this._removeFormView();
387
+ // Then remove the actions view because it's beneath the form.
388
+ this._balloon.remove(this.actionsView);
389
+ this._hideFakeVisualSelection();
390
+ }
391
+ /**
392
+ * Makes the UI react to the {@link module:ui/editorui/editorui~EditorUI#event:update} event to
393
+ * reposition itself when the editor UI should be refreshed.
394
+ *
395
+ * See: {@link #_hideUI} to learn when the UI stops reacting to the `update` event.
396
+ */
397
+ _startUpdatingUI() {
398
+ const editor = this.editor;
399
+ const viewDocument = editor.editing.view.document;
400
+ let prevSelectedBookmark = this._getSelectedBookmarkElement();
401
+ let prevSelectionParent = getSelectionParent();
402
+ this._updateFormButtonLabel(!!prevSelectedBookmark);
403
+ const update = () => {
404
+ const selectedBookmark = this._getSelectedBookmarkElement();
405
+ const selectionParent = getSelectionParent();
406
+ // Hide the panel if:
407
+ //
408
+ // * the selection went out of the EXISTING bookmark element. E.g. user moved the caret out
409
+ // of the bookmark,
410
+ // * the selection went to a different parent when creating a NEW bookmark. E.g. someone
411
+ // else modified the document.
412
+ // * the selection has expanded (e.g. displaying bookmark actions then pressing SHIFT+Right arrow).
413
+ //
414
+ if ((prevSelectedBookmark && !selectedBookmark) ||
415
+ (!prevSelectedBookmark && selectionParent !== prevSelectionParent)) {
416
+ this._hideUI();
417
+ }
418
+ // Update the position of the panel when:
419
+ // * bookmark panel is in the visible stack
420
+ // * the selection remains on the original bookmark element,
421
+ // * there was no bookmark element in the first place, i.e. creating a new bookmark
422
+ else if (this._isUIVisible) {
423
+ // If still in a bookmark element, simply update the position of the balloon.
424
+ // If there was no bookmark (e.g. inserting one), the balloon must be moved
425
+ // to the new position in the editing view (a new native DOM range).
426
+ this._balloon.updatePosition(this._getBalloonPositionData());
427
+ }
428
+ this._updateFormButtonLabel(!!prevSelectedBookmark);
429
+ prevSelectedBookmark = selectedBookmark;
430
+ prevSelectionParent = selectionParent;
431
+ };
432
+ function getSelectionParent() {
433
+ return viewDocument.selection.focus.getAncestors()
434
+ .reverse()
435
+ .find((node) => node.is('element'));
436
+ }
437
+ this.listenTo(editor.ui, 'update', update);
438
+ this.listenTo(this._balloon, 'change:visibleView', update);
439
+ }
440
+ /**
441
+ * Returns `true` when {@link #formView} is in the {@link #_balloon}.
442
+ */
443
+ get _isFormInPanel() {
444
+ return !!this.formView && this._balloon.hasView(this.formView);
445
+ }
446
+ /**
447
+ * Returns `true` when {@link #actionsView} is in the {@link #_balloon}.
448
+ */
449
+ get _areActionsInPanel() {
450
+ return !!this.actionsView && this._balloon.hasView(this.actionsView);
451
+ }
452
+ /**
453
+ * Returns `true` when {@link #actionsView} is in the {@link #_balloon} and it is
454
+ * currently visible.
455
+ */
456
+ get _areActionsVisible() {
457
+ return !!this.actionsView && this._balloon.visibleView === this.actionsView;
458
+ }
459
+ /**
460
+ * Returns `true` when {@link #actionsView} or {@link #formView} is in the {@link #_balloon}.
461
+ */
462
+ get _isUIInPanel() {
463
+ return this._isFormInPanel || this._areActionsInPanel;
464
+ }
465
+ /**
466
+ * Returns `true` when {@link #actionsView} or {@link #formView} is in the {@link #_balloon} and it is
467
+ * currently visible.
468
+ */
469
+ get _isUIVisible() {
470
+ const visibleView = this._balloon.visibleView;
471
+ return !!this.formView && visibleView == this.formView || this._areActionsVisible;
472
+ }
473
+ /**
474
+ * Returns positioning options for the {@link #_balloon}. They control the way the balloon is attached
475
+ * to the target element or selection.
476
+ */
477
+ _getBalloonPositionData() {
478
+ const view = this.editor.editing.view;
479
+ const model = this.editor.model;
480
+ let target;
481
+ const bookmarkElement = this._getSelectedBookmarkElement();
482
+ if (model.markers.has(VISUAL_SELECTION_MARKER_NAME)) {
483
+ // There are cases when we highlight selection using a marker (#7705, #4721).
484
+ const markerViewElements = Array.from(this.editor.editing.mapper.markerNameToElements(VISUAL_SELECTION_MARKER_NAME));
485
+ const newRange = view.createRange(view.createPositionBefore(markerViewElements[0]), view.createPositionAfter(markerViewElements[markerViewElements.length - 1]));
486
+ target = view.domConverter.viewRangeToDom(newRange);
487
+ }
488
+ else if (bookmarkElement) {
489
+ target = () => {
490
+ const mapper = this.editor.editing.mapper;
491
+ const domConverter = view.domConverter;
492
+ const viewElement = mapper.toViewElement(bookmarkElement);
493
+ return domConverter.mapViewToDom(viewElement);
494
+ };
495
+ }
496
+ return target && { target };
497
+ }
498
+ /**
499
+ * Returns the bookmark {@link module:engine/view/attributeelement~AttributeElement} under
500
+ * the {@link module:engine/view/document~Document editing view's} selection or `null`
501
+ * if there is none.
502
+ */
503
+ _getSelectedBookmarkElement() {
504
+ const selection = this.editor.model.document.selection;
505
+ const element = selection.getSelectedElement();
506
+ if (element && element.is('element', 'bookmark')) {
507
+ return element;
508
+ }
509
+ return null;
510
+ }
511
+ /**
512
+ * Displays a fake visual selection when the contextual balloon is displayed.
513
+ *
514
+ * This adds a 'bookmark-ui' marker into the document that is rendered as a highlight on selected text fragment.
515
+ */
516
+ _showFakeVisualSelection() {
517
+ const model = this.editor.model;
518
+ model.change(writer => {
519
+ const range = model.document.selection.getFirstRange();
520
+ if (model.markers.has(VISUAL_SELECTION_MARKER_NAME)) {
521
+ writer.updateMarker(VISUAL_SELECTION_MARKER_NAME, { range });
522
+ }
523
+ else {
524
+ if (range.start.isAtEnd) {
525
+ const startPosition = range.start.getLastMatchingPosition(({ item }) => !model.schema.isContent(item), { boundaries: range });
526
+ writer.addMarker(VISUAL_SELECTION_MARKER_NAME, {
527
+ usingOperation: false,
528
+ affectsData: false,
529
+ range: writer.createRange(startPosition, range.end)
530
+ });
531
+ }
532
+ else {
533
+ writer.addMarker(VISUAL_SELECTION_MARKER_NAME, {
534
+ usingOperation: false,
535
+ affectsData: false,
536
+ range
537
+ });
538
+ }
539
+ }
540
+ });
541
+ }
542
+ /**
543
+ * Hides the fake visual selection created in {@link #_showFakeVisualSelection}.
544
+ */
545
+ _hideFakeVisualSelection() {
546
+ const model = this.editor.model;
547
+ if (model.markers.has(VISUAL_SELECTION_MARKER_NAME)) {
548
+ model.change(writer => {
549
+ writer.removeMarker(VISUAL_SELECTION_MARKER_NAME);
550
+ });
551
+ }
552
+ }
553
+ }
554
+ /**
555
+ * Returns bookmark form validation callbacks.
556
+ */
557
+ function getFormValidators(editor) {
558
+ const { t } = editor;
559
+ const bookmarkEditing = editor.plugins.get(BookmarkEditing);
560
+ return [
561
+ form => {
562
+ if (!form.id) {
563
+ return t('Bookmark must not be empty.');
564
+ }
565
+ },
566
+ form => {
567
+ if (form.id && /\s/.test(form.id)) {
568
+ return t('Bookmark name cannot contain space characters.');
569
+ }
570
+ },
571
+ form => {
572
+ const selectedElement = editor.model.document.selection.getSelectedElement();
573
+ const existingBookmarkForId = bookmarkEditing.getElementForBookmarkId(form.id);
574
+ // Accept change of bookmark ID if no real change is happening (edit -> submit, without changes).
575
+ if (selectedElement === existingBookmarkForId) {
576
+ return;
577
+ }
578
+ if (existingBookmarkForId) {
579
+ return t('Bookmark name already exists.');
580
+ }
581
+ }
582
+ ];
583
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * @module bookmark
7
+ */
8
+ export { default as Bookmark } from './bookmark.js';
9
+ export { default as BookmarkEditing } from './bookmarkediting.js';
10
+ export { default as BookmarkUI } from './bookmarkui.js';
11
+ export { default as InsertBookmarkCommand } from './insertbookmarkcommand.js';
12
+ export { default as UpdateBookmarkCommand } from './updatebookmarkcommand.js';
13
+ export type { BookmarkConfig } from './bookmarkconfig.js';
14
+ import './augmentation.js';
package/src/index.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ /**
6
+ * @module bookmark
7
+ */
8
+ export { default as Bookmark } from './bookmark.js';
9
+ export { default as BookmarkEditing } from './bookmarkediting.js';
10
+ export { default as BookmarkUI } from './bookmarkui.js';
11
+ export { default as InsertBookmarkCommand } from './insertbookmarkcommand.js';
12
+ export { default as UpdateBookmarkCommand } from './updatebookmarkcommand.js';
13
+ import './augmentation.js';
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
4
+ */
5
+ import { Command } from 'ckeditor5/src/core.js';
6
+ /**
7
+ * The insert bookmark command.
8
+ *
9
+ * The command is registered by {@link module:bookmark/bookmarkediting~BookmarkEditing} as `'insertBookmark'`.
10
+ *
11
+ * To insert a bookmark element at place where is the current collapsed selection or where is the beginning of document selection,
12
+ * execute the command passing the bookmark id as a parameter:
13
+ *
14
+ * ```ts
15
+ * editor.execute( 'insertBookmark', { bookmarkId: 'foo_bar' } );
16
+ * ```
17
+ */
18
+ export default class InsertBookmarkCommand extends Command {
19
+ /**
20
+ * @inheritDoc
21
+ */
22
+ refresh(): void;
23
+ /**
24
+ * Executes the command.
25
+ *
26
+ * @fires execute
27
+ * @param options Command options.
28
+ * @param options.bookmarkId The value of the `bookmarkId` attribute.
29
+ */
30
+ execute(options: {
31
+ bookmarkId: string;
32
+ }): void;
33
+ /**
34
+ * Returns the position where the bookmark can be inserted. And if it is not possible to insert a bookmark,
35
+ * check if it is possible to insert a paragraph.
36
+ */
37
+ private _getPositionToInsertBookmark;
38
+ }