@progress/kendo-angular-editor 2.5.0 → 3.0.0-dev.202112211443

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.
@@ -6,12 +6,12 @@ import * as tslib_1 from "tslib";
6
6
  var EditorComponent_1;
7
7
  import { Component, HostBinding, ViewChild, ContentChild, ViewContainerRef, Output, ElementRef, EventEmitter, forwardRef, Input, ChangeDetectorRef, NgZone, isDevMode } from '@angular/core';
8
8
  import { NG_VALUE_ACCESSOR } from '@angular/forms';
9
- import { BehaviorSubject, fromEvent, merge, Subject, zip } from 'rxjs';
10
- import { map, filter, auditTime, take } from 'rxjs/operators';
9
+ import { BehaviorSubject, fromEvent, Subject, zip } from 'rxjs';
10
+ import { map, filter, take } from 'rxjs/operators';
11
11
  import { ToolBarComponent } from '@progress/kendo-angular-toolbar';
12
12
  import { DialogService } from '@progress/kendo-angular-dialog';
13
- import { isDocumentAvailable, KendoInput } from '@progress/kendo-angular-common';
14
- import { buildKeymap, buildListKeymap, hasSameMarkup, getHtml, pasteCleanup, sanitize, removeComments, sanitizeClassAttr, sanitizeStyleAttr, removeAttribute, placeholder, EditorView, EditorState, baseKeymap, keymap, history, parseContent, Plugin, PluginKey, TextSelection, Schema, gapCursor, getSelectionText, imageResizing } from '@progress/kendo-editor-common';
13
+ import { hasObservers, isDocumentAvailable, KendoInput } from '@progress/kendo-angular-common';
14
+ import { buildKeymap, buildListKeymap, getHtml, pasteCleanup, sanitize, removeComments, sanitizeClassAttr, sanitizeStyleAttr, removeAttribute, placeholder, EditorView, EditorState, baseKeymap, keymap, history, parseContent, Plugin, PluginKey, TextSelection, Schema, AllSelection, gapCursor, getSelectionText, imageResizing } from '@progress/kendo-editor-common';
15
15
  import { validatePackage } from '@progress/kendo-licensing';
16
16
  import { packageMetadata } from './package-metadata';
17
17
  import { schema } from './config/schema';
@@ -26,15 +26,16 @@ import { EditorLocalizationService } from './localization/editor-localization.se
26
26
  import { defaultStyle, tablesStyles, rtlStyles } from './common/styles';
27
27
  import { EditorErrorMessages } from './common/error-messages';
28
28
  import { EditorToolsService } from './tools/tools.service';
29
+ import { EditorPasteEvent } from './preventable-events/paste-event';
29
30
  const EMPTY_PARAGRAPH = '<p></p>';
30
31
  const defaultPasteCleanupSettings = {
31
32
  convertMsLists: false,
32
33
  removeAttributes: [],
33
34
  removeHtmlComments: false,
34
- removeInvalidHTML: false,
35
+ removeInvalidHTML: true,
35
36
  removeMsClasses: false,
36
37
  removeMsStyles: false,
37
- stripTags: ['']
38
+ stripTags: []
38
39
  };
39
40
  const removeCommentsIf = conditionallyExecute(removeComments);
40
41
  const removeInvalidHTMLIf = conditionallyExecute(sanitize);
@@ -71,14 +72,6 @@ let EditorComponent = EditorComponent_1 = class EditorComponent {
71
72
  * @default false
72
73
  */
73
74
  this.applyToWord = false;
74
- /**
75
- * By design, the Editor emits `valueChange`, updates the model and the ToolBar state on each keystroke.
76
- * When you are interested in ignoring the new values for a given amout of time and take only the most recent one, you can use the `updateInterval` property.
77
- * A possible use case is to get the new values and to update the ToolBar state at a maximum rate per second in order to speed up your application.
78
- * The specified interval (in milliseconds) should be a positive number.
79
- * By default the `updateInterval` is set to 100 miliseconds. If set to zero the delay will be disabled entirely.
80
- */
81
- this.updateInterval = 100;
82
75
  /**
83
76
  * By default, whitespace is collapsed as per HTML's rules.
84
77
  * Set to `true` to preserve whitespace, but normalize newlines to spaces.
@@ -104,6 +97,11 @@ let EditorComponent = EditorComponent_1 = class EditorComponent {
104
97
  * Fires when the content area of the Editor is focused ([see example]({% slug overview_editor %}#toc-events)).
105
98
  */
106
99
  this.onFocus = new EventEmitter();
100
+ /**
101
+ * Fires when the user performs paste in the content area of the Editor ([see example]({% slug overview_editor %}#toc-events)).
102
+ * The event is preventable. If you cancel it, the Editor content will not change.
103
+ */
104
+ this.paste = new EventEmitter();
107
105
  /**
108
106
  * Fires when the content area of the Editor is blurred ([see example]({% slug overview_editor %}#toc-events)).
109
107
  */
@@ -119,10 +117,73 @@ let EditorComponent = EditorComponent_1 = class EditorComponent {
119
117
  this.valueModified = new Subject();
120
118
  this._readonly = false;
121
119
  this._placeholder = '';
120
+ this.inForm = false;
122
121
  this.afterViewInit = new Subject();
123
122
  this.contentAreaLoaded = new Subject();
124
- this.onChangeCallback = (_) => { }; // tslint:disable-line:no-empty
123
+ this.dispatchTransaction = (tr) => {
124
+ const docChanged = tr.docChanged;
125
+ if (docChanged) {
126
+ const doc = tr.doc;
127
+ const html = getHtml({ doc });
128
+ this.trOnChange = tr;
129
+ this.htmlOnChange = html;
130
+ this.ngZone.run(() => {
131
+ this.valueModified.next(html);
132
+ });
133
+ }
134
+ if (!docChanged || this.inForm) {
135
+ this.view.updateState(this.view.state.apply(tr));
136
+ }
137
+ };
138
+ this.transformPastedHTML = (dirtyHtml) => {
139
+ const pasteCleanupSettings = Object.assign({}, defaultPasteCleanupSettings, this.pasteCleanupSettings);
140
+ const html = pipe(removeInvalidHTMLIf(pasteCleanupSettings.removeInvalidHTML), removeCommentsIf(pasteCleanupSettings.removeHtmlComments))(dirtyHtml);
141
+ const clean = pasteCleanup(html, {
142
+ convertMsLists: pasteCleanupSettings.convertMsLists,
143
+ stripTags: pasteCleanupSettings.stripTags.join('|'),
144
+ attributes: getPasteCleanupAttributes(pasteCleanupSettings)
145
+ });
146
+ if (hasObservers(this.paste)) {
147
+ const event = new EditorPasteEvent(clean, dirtyHtml, this._pasteEvent);
148
+ this.ngZone.run(() => this.paste.emit(event));
149
+ return event.isDefaultPrevented() ? '' : event.cleanedHtml;
150
+ }
151
+ return clean;
152
+ };
153
+ this.changeValue = (value) => {
154
+ const prev = this._value;
155
+ this._value = value;
156
+ if (!this._view) {
157
+ return;
158
+ }
159
+ if (this.htmlOnChange === value && this.trOnChange) {
160
+ this.view.updateState(this.view.state.apply(this.trOnChange));
161
+ }
162
+ else {
163
+ if ((prev || '') !== (value || '')) {
164
+ const iframeContentWindowNotPresent = this.iframe && !this.container.element.nativeElement.contentWindow;
165
+ if (iframeContentWindowNotPresent) {
166
+ return;
167
+ }
168
+ const state = this.view.state;
169
+ const nextDoc = parseContent(value || '', state.schema, { preserveWhitespace: this.preserveWhitespace });
170
+ const tr = state.tr
171
+ .setSelection(new AllSelection(state.doc))
172
+ .replaceSelectionWith(nextDoc);
173
+ this.view.updateState(state.apply(tr));
174
+ }
175
+ }
176
+ this.trOnChange = null;
177
+ this.htmlOnChange = null;
178
+ };
179
+ this.onChangeCallback = (value) => {
180
+ this.changeValue(value);
181
+ };
125
182
  this.onTouchedCallback = (_) => { }; // tslint:disable-line:no-empty
183
+ this.onPaste = (_view, nativeEvent) => {
184
+ this._pasteEvent = nativeEvent;
185
+ return false;
186
+ };
126
187
  validatePackage(packageMetadata);
127
188
  this.direction = localization.rtl ? 'rtl' : 'ltr';
128
189
  // https://stackoverflow.com/questions/56572483/chrome-is-synchronously-handling-iframe-loading-whereas-firefox-handles-it-asyn
@@ -132,25 +193,12 @@ let EditorComponent = EditorComponent_1 = class EditorComponent {
132
193
  * Sets the value of the Editor ([see example]({% slug overview_editor %}#toc-basic-usage)).
133
194
  */
134
195
  set value(value) {
135
- if (this._previousValue === value) {
136
- return;
137
- }
138
- this._value = value;
139
- this._previousValue = value;
140
- if (this._view) {
141
- const iframeContentWindowNotPresent = this.iframe && !this.container.element.nativeElement.contentWindow;
142
- if (iframeContentWindowNotPresent) {
143
- return;
144
- }
145
- this.exec('setHTML', {
146
- content: this._value || '',
147
- parseOptions: {
148
- preserveWhitespace: this.preserveWhitespace
149
- }
150
- });
151
- }
196
+ this.changeValue(value);
152
197
  }
153
198
  get value() {
199
+ if (this.trOnChange) {
200
+ return this.htmlOnChange;
201
+ }
154
202
  let value = this._view ? this.getSource() : this._value;
155
203
  if (value === EMPTY_PARAGRAPH) {
156
204
  return this._value ? '' : this._value;
@@ -340,6 +388,9 @@ let EditorComponent = EditorComponent_1 = class EditorComponent {
340
388
  }
341
389
  }
342
390
  ngOnChanges(changes) {
391
+ if (changes.value && this.view) {
392
+ this.changeValue(changes.value.currentValue);
393
+ }
343
394
  if (changes.iframe && !changes.iframe.isFirstChange()) {
344
395
  this.ngZone.onStable.pipe(take(1)).subscribe(() => this.initialize());
345
396
  }
@@ -425,8 +476,6 @@ let EditorComponent = EditorComponent_1 = class EditorComponent {
425
476
  const command = editorCommands[commandName](attr);
426
477
  // Executes a ProseMirror command.
427
478
  command(this._view.state, this._view.dispatch, this._view);
428
- // See the `dispatchTransaction` comments.
429
- // this.stateChange.emit(updateToolBar(this.view));
430
479
  }
431
480
  /**
432
481
  * Opens a dialog.
@@ -511,6 +560,7 @@ let EditorComponent = EditorComponent_1 = class EditorComponent {
511
560
  * @hidden
512
561
  */
513
562
  writeValue(value) {
563
+ this.inForm = true;
514
564
  // To avoid confusion, non-existent values are always undefined.
515
565
  this.value = value === null ? undefined : value;
516
566
  }
@@ -538,7 +588,6 @@ let EditorComponent = EditorComponent_1 = class EditorComponent {
538
588
  return;
539
589
  }
540
590
  const currentSchema = this.schema || schema;
541
- const that = this;
542
591
  const containerNativeElement = this.container.element.nativeElement;
543
592
  const contentNode = parseContent((this.value || '').trim(), currentSchema, { preserveWhitespace: this.preserveWhitespace });
544
593
  if (this.iframe) {
@@ -582,7 +631,7 @@ let EditorComponent = EditorComponent_1 = class EditorComponent {
582
631
  }),
583
632
  new Plugin({
584
633
  key: new PluginKey('editor-filter-disabled-state'),
585
- filterTransaction: () => !this.disabled
634
+ filterTransaction: () => !this.disabled || this.inForm
586
635
  }),
587
636
  history(),
588
637
  keymap(buildListKeymap(currentSchema)),
@@ -609,31 +658,10 @@ let EditorComponent = EditorComponent_1 = class EditorComponent {
609
658
  this._view = new EditorView({ mount: this.viewMountElement }, {
610
659
  state,
611
660
  editable: () => !this.readonly,
612
- dispatchTransaction: function (tr) {
613
- // `this` is bound to the view instance.
614
- this.updateState(this.state.apply(tr));
615
- // that.cdr.detectChanges();
616
- // When the user utilizes keyboard shortcuts&mdash;for example, `Ctrl`+`b`&mdash;
617
- // `tr.docChanged` is `true` and the toolbar is not updated.
618
- // A possible future solution is to move the keymaps to the service.
619
- // if (!tr.docChanged) {
620
- // that.stateChange.emit(updateToolBar(that.view));
621
- // }
622
- const value = that.value;
623
- if (!hasSameMarkup(value, that._previousValue, this.state.schema)) {
624
- that._previousValue = value;
625
- that.ngZone.run(() => that.valueModified.next(value));
626
- }
627
- },
628
- transformPastedHTML: (html) => {
629
- const pasteCleanupSettings = Object.assign({}, defaultPasteCleanupSettings, this.pasteCleanupSettings);
630
- const clean = pipe(removeCommentsIf(pasteCleanupSettings.removeHtmlComments), removeInvalidHTMLIf(pasteCleanupSettings.removeInvalidHTML))(html);
631
- const attributes = getPasteCleanupAttributes(pasteCleanupSettings);
632
- return pasteCleanup(clean, {
633
- convertMsLists: pasteCleanupSettings.convertMsLists,
634
- stripTags: pasteCleanupSettings.stripTags.join('|'),
635
- attributes
636
- });
661
+ dispatchTransaction: this.dispatchTransaction,
662
+ transformPastedHTML: this.transformPastedHTML,
663
+ handleDOMEvents: {
664
+ paste: this.onPaste
637
665
  }
638
666
  });
639
667
  });
@@ -674,7 +702,7 @@ let EditorComponent = EditorComponent_1 = class EditorComponent {
674
702
  this.cdr.detectChanges();
675
703
  }
676
704
  }));
677
- this.subs.add(merge(this.valueModified.pipe(filter(() => this.updateInterval > 0), auditTime(this.updateInterval)), this.valueModified.pipe(filter(() => this.updateInterval === 0))).subscribe((value) => {
705
+ this.subs.add(this.valueModified.subscribe((value) => {
678
706
  this.onChangeCallback(value);
679
707
  this.valueChange.emit(value);
680
708
  this.cdr.markForCheck();
@@ -767,10 +795,6 @@ tslib_1.__decorate([
767
795
  tslib_1.__metadata("design:type", String),
768
796
  tslib_1.__metadata("design:paramtypes", [String])
769
797
  ], EditorComponent.prototype, "placeholder", null);
770
- tslib_1.__decorate([
771
- Input(),
772
- tslib_1.__metadata("design:type", Number)
773
- ], EditorComponent.prototype, "updateInterval", void 0);
774
798
  tslib_1.__decorate([
775
799
  Input(),
776
800
  tslib_1.__metadata("design:type", Object)
@@ -791,6 +815,10 @@ tslib_1.__decorate([
791
815
  Output('focus'),
792
816
  tslib_1.__metadata("design:type", EventEmitter)
793
817
  ], EditorComponent.prototype, "onFocus", void 0);
818
+ tslib_1.__decorate([
819
+ Output(),
820
+ tslib_1.__metadata("design:type", EventEmitter)
821
+ ], EditorComponent.prototype, "paste", void 0);
794
822
  tslib_1.__decorate([
795
823
  Output('blur'),
796
824
  tslib_1.__metadata("design:type", EventEmitter)
@@ -1038,7 +1066,7 @@ EditorComponent = EditorComponent_1 = tslib_1.__decorate([
1038
1066
  <div *ngIf="!iframe" #content [attr.dir]="direction" class="k-editor-content"></div>
1039
1067
 
1040
1068
  <div class="k-editor-content" *ngIf="iframe">
1041
- <iframe #content frameborder="0" class="k-iframe" (load)="iframeOnLoad()"></iframe>
1069
+ <iframe #content srcdoc="<!DOCTYPE html>" frameborder="0" class="k-iframe" (load)="iframeOnLoad()"></iframe>
1042
1070
  </div>
1043
1071
 
1044
1072
  <ng-container #dialogsContainer></ng-container>