@ckeditor/ckeditor5-bookmark 0.0.0-nightly-20241025.0 → 0.0.1

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