fit-ui 2.5.7 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/Documentation.html +3 -3
  2. package/dist/Fit.UI.css +102 -6
  3. package/dist/Fit.UI.js +741 -109
  4. package/dist/Fit.UI.min.css +1 -1
  5. package/dist/Fit.UI.min.js +1 -1
  6. package/dist/Resources/CKEditor/build-config.js +7 -3
  7. package/dist/Resources/CKEditor/ckeditor.js +93 -5
  8. package/dist/Resources/CKEditor/lang/da.js +2 -2
  9. package/dist/Resources/CKEditor/lang/de.js +2 -2
  10. package/dist/Resources/CKEditor/lang/en.js +2 -2
  11. package/dist/Resources/CKEditor/plugins/autocomplete/skins/default.css +38 -0
  12. package/dist/Resources/CKEditor/plugins/emoji/assets/iconsall.png +0 -0
  13. package/dist/Resources/CKEditor/plugins/emoji/assets/iconsall.svg +58 -0
  14. package/dist/Resources/CKEditor/plugins/emoji/emoji.json +1 -0
  15. package/dist/Resources/CKEditor/plugins/emoji/skins/default.css +237 -0
  16. package/dist/Resources/CKEditor/plugins/icons.png +0 -0
  17. package/dist/Resources/CKEditor/plugins/icons_hidpi.png +0 -0
  18. package/dist/Resources/CKEditor/skins/moono-lisa/editor.css +5 -5
  19. package/dist/Resources/CKEditor/skins/moono-lisa/editor_gecko.css +5 -5
  20. package/dist/Resources/CKEditor/skins/moono-lisa/editor_ie.css +5 -5
  21. package/dist/Resources/CKEditor/skins/moono-lisa/editor_ie8.css +5 -5
  22. package/dist/Resources/CKEditor/skins/moono-lisa/editor_iequirks.css +5 -5
  23. package/dist/Resources/CKEditor/skins/moono-lisa/icons.png +0 -0
  24. package/dist/Resources/CKEditor/skins/moono-lisa/icons_hidpi.png +0 -0
  25. package/package.json +1 -1
  26. package/types/index.d.ts +497 -136
  27. package/dist/Resources/CKEditor/plugins/htmlwriter/plugin.js +0 -364
  28. package/dist/Resources/CKEditor/plugins/resize/plugin.js +0 -187
package/dist/Fit.UI.js CHANGED
@@ -648,7 +648,7 @@ Fit._internal =
648
648
  {
649
649
  Core:
650
650
  {
651
- VersionInfo: { Major: 2, Minor: 5, Patch: 7 } // Do NOT modify format - version numbers are programmatically changed when releasing new versions - MUST be on a separate line!
651
+ VersionInfo: { Major: 2, Minor: 6, Patch: 0 } // Do NOT modify format - version numbers are programmatically changed when releasing new versions - MUST be on a separate line!
652
652
  }
653
653
  };
654
654
 
@@ -9267,6 +9267,7 @@ Fit.Http.JsonpRequest = function(uri, jsonpCallbackName)
9267
9267
  var timeout = 30000;
9268
9268
  var timer = -1;
9269
9269
  var response = null;
9270
+ var aborted = false;
9270
9271
 
9271
9272
  var onRequestHandlers = [];
9272
9273
  var onSuccessHandlers = [];
@@ -9324,6 +9325,14 @@ Fit.Http.JsonpRequest = function(uri, jsonpCallbackName)
9324
9325
  return timeout;
9325
9326
  }
9326
9327
 
9328
+ /// <function container="Fit.Http.JsonpRequest" name="Abort" access="public">
9329
+ /// <description> Abort request </description>
9330
+ /// </function>
9331
+ this.Abort = function()
9332
+ {
9333
+ aborted = true;
9334
+ }
9335
+
9327
9336
  /// <function container="Fit.Http.JsonpRequest" name="SetParameter" access="public">
9328
9337
  /// <description> Set URL parameter </description>
9329
9338
  /// <param name="key" type="string"> URL parameter key </param>
@@ -9373,6 +9382,8 @@ Fit.Http.JsonpRequest = function(uri, jsonpCallbackName)
9373
9382
  /// </function>
9374
9383
  this.Start = function()
9375
9384
  {
9385
+ aborted = false;
9386
+
9376
9387
  // Fire OnRequest handlers
9377
9388
 
9378
9389
  if (fireEvent(onRequestHandlers) === false)
@@ -9406,7 +9417,10 @@ Fit.Http.JsonpRequest = function(uri, jsonpCallbackName)
9406
9417
  response = resp;
9407
9418
  delete Fit._internal.Http.JsonpRequest.Callbacks[cbId];
9408
9419
 
9409
- fireEvent(onSuccessHandlers);
9420
+ if (aborted === false)
9421
+ {
9422
+ fireEvent(onSuccessHandlers);
9423
+ }
9410
9424
  }
9411
9425
 
9412
9426
  // Configure timeout
@@ -9419,7 +9433,10 @@ Fit.Http.JsonpRequest = function(uri, jsonpCallbackName)
9419
9433
  // NOTICE: Do not remove callback - it would cause a JavaScript error if the situation described above occures.
9420
9434
  Fit._internal.Http.JsonpRequest.Callbacks[cbId] = function(response) { };
9421
9435
 
9422
- fireEvent(onTimeoutHandlers);
9436
+ if (aborted === false)
9437
+ {
9438
+ fireEvent(onTimeoutHandlers);
9439
+ }
9423
9440
  }, timeout);
9424
9441
 
9425
9442
  // Construct request URL
@@ -14233,6 +14250,11 @@ Fit.Controls.DatePicker = function(ctlId)
14233
14250
 
14234
14251
  function moveCalenderWidgetLocally()
14235
14252
  {
14253
+ if (Fit._internal.ControlBase.ReduceDocumentRootPollution !== true)
14254
+ {
14255
+ return;
14256
+ }
14257
+
14236
14258
  // We want the benefits of a connected calendar control (connected to input control),
14237
14259
  // but do not want it rendered in the root of the document. It pollutes the global scope,
14238
14260
  // and it doesn't work with dialogs with light dismiss, since interacting with the calendar
@@ -14308,6 +14330,11 @@ Fit.Controls.DatePicker = function(ctlId)
14308
14330
 
14309
14331
  function moveCalenderWidgetGlobally() // Undo everything done in moveCalenderWidgetLocally()
14310
14332
  {
14333
+ if (Fit._internal.ControlBase.ReduceDocumentRootPollution !== true)
14334
+ {
14335
+ return;
14336
+ }
14337
+
14311
14338
  var calendarWidget = document.getElementById("fitui-datepicker-div");
14312
14339
  Fit.Dom.Add(document.body, calendarWidget);
14313
14340
 
@@ -21585,6 +21612,9 @@ Fit.Controls.Input = function(ctlId)
21585
21612
  var designEditor = null;
21586
21613
  var designEditorDirty = false;
21587
21614
  var designEditorDirtyPending = false;
21615
+ var designEditorConfig = null;
21616
+ var designEditorRestoreButtonState = null;
21617
+ var designEditorSuppressPaste = false;
21588
21618
  //var htmlWrappedInParagraph = false;
21589
21619
  var wasAutoChangedToMultiLineMode = false; // Used to revert to single line if multi line was automatically enabled along with DesignMode(true), Maximizable(true), or Resizable(true)
21590
21620
  var minimizeHeight = -1;
@@ -21678,6 +21708,21 @@ Fit.Controls.Input = function(ctlId)
21678
21708
  }
21679
21709
 
21680
21710
  fireOnChange(); // Only fires OnChange if value has actually changed
21711
+
21712
+ // Restore editor's toolbar buttons in case they were temporarily disabled
21713
+
21714
+ if (designEditor !== null)
21715
+ {
21716
+ restoreDesignEditorButtons();
21717
+ }
21718
+ });
21719
+
21720
+ Fit.Events.AddHandler(me.GetDomElement(), "paste", true, function(e)
21721
+ {
21722
+ if (designEditor !== null && designEditorSuppressPaste === true)
21723
+ {
21724
+ Fit.Events.Stop(e);
21725
+ }
21681
21726
  });
21682
21727
 
21683
21728
  try
@@ -21850,7 +21895,7 @@ Fit.Controls.Input = function(ctlId)
21850
21895
  input.value = val;
21851
21896
  }
21852
21897
 
21853
- if (Fit._internal.Controls.Input.BlobManager.RevokeExternalBlobUrlsOnDispose === true)
21898
+ if (designEditorConfig !== null && designEditorConfig.Plugins && designEditorConfig.Plugins.Images && designEditorConfig.Plugins.Images.RevokeExternalBlobUrlsOnDispose === true)
21854
21899
  {
21855
21900
  // Keep track of image blobs added via Value(..) so we can dispose them automatically.
21856
21901
  // When RevokeExternalBlobUrlsOnDispose is True it basically means that the Input control
@@ -21952,7 +21997,7 @@ Fit.Controls.Input = function(ctlId)
21952
21997
  {
21953
21998
  // This will destroy control - it will no longer work!
21954
21999
 
21955
- var curVal = Fit._internal.Controls.Input.BlobManager.RevokeBlobUrlsOnDispose === "UnreferencedOnly" ? me.Value() : null;
22000
+ var curVal = designEditorConfig !== null && designEditorConfig.Plugins && designEditorConfig.Plugins.Images && designEditorConfig.Plugins.Images.RevokeBlobUrlsOnDispose === "UnreferencedOnly" ? me.Value() : null;
21956
22001
 
21957
22002
  if (Fit._internal.Controls.Input.ActiveEditorForDialog === me)
21958
22003
  {
@@ -22020,7 +22065,7 @@ Fit.Controls.Input = function(ctlId)
22020
22065
  debouncedOnChange.Cancel();
22021
22066
  }
22022
22067
 
22023
- if (Fit._internal.Controls.Input.BlobManager.RevokeBlobUrlsOnDispose === "All")
22068
+ if (designEditorConfig === null || !designEditorConfig.Plugins || !designEditorConfig.Plugins.Images || !designEditorConfig.Plugins.Images.RevokeBlobUrlsOnDispose || designEditorConfig.Plugins.Images.RevokeBlobUrlsOnDispose === "All")
22024
22069
  {
22025
22070
  Fit.Array.ForEach(imageBlobUrls, function(imageUrl)
22026
22071
  {
@@ -22038,7 +22083,7 @@ Fit.Controls.Input = function(ctlId)
22038
22083
  });
22039
22084
  }
22040
22085
 
22041
- me = orgVal = preVal = input = cmdResize = designEditor = designEditorDirty = designEditorDirtyPending /*= htmlWrappedInParagraph*/ = wasAutoChangedToMultiLineMode = minimizeHeight = maximizeHeight = minMaxUnit = resizable = nativeResizableAvailable = mutationObserverId = rootedEventId = createWhenReadyIntervalId = isIe8 = debounceOnChangeTimeout = debouncedOnChange = imageBlobUrls = null;
22086
+ me = orgVal = preVal = input = cmdResize = designEditor = designEditorDirty = designEditorDirtyPending = designEditorConfig = designEditorRestoreButtonState = designEditorSuppressPaste /*= htmlWrappedInParagraph*/ = wasAutoChangedToMultiLineMode = minimizeHeight = maximizeHeight = minMaxUnit = resizable = nativeResizableAvailable = mutationObserverId = rootedEventId = createWhenReadyIntervalId = isIe8 = debounceOnChangeTimeout = debouncedOnChange = imageBlobUrls = null;
22042
22087
 
22043
22088
  base();
22044
22089
  });
@@ -22499,16 +22544,220 @@ Fit.Controls.Input = function(ctlId)
22499
22544
  return (cmdResize !== null && Fit.Dom.HasClass(cmdResize, "fa-chevron-up") === true);
22500
22545
  }
22501
22546
 
22547
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeConfigPluginsImagesConfig">
22548
+ /// <description> Configuration for image plugins </description>
22549
+ /// <member name="Enabled" type="boolean"> Flag indicating whether to enable image plugins or not (defaults to False) </member>
22550
+ /// <member name="EmbedType" type="'base64' | 'blob'" default="undefined">
22551
+ /// How to store and embed images. Base64 is persistent while blob (default) is temporary
22552
+ /// and must be extracted from memory and uploaded/stored to be permanantly persisted.
22553
+ /// References to blobs can be parsed from the HTML value produced by the editor.
22554
+ /// </member>
22555
+ /// <member name="RevokeBlobUrlsOnDispose" type="'All' | 'UnreferencedOnly'" default="undefined">
22556
+ /// This option is in effect when EmbedType is blob.
22557
+ /// Dispose images from blob storage (revoke blob URLs) added though image plugins when control is disposed.
22558
+ /// If "UnreferencedOnly" is specified, the component using Fit.UI's input control will be responsible for
22559
+ /// disposing referenced blobs. Failing to do so may cause a memory leak. Defaults to All.
22560
+ /// </member>
22561
+ /// <member name="RevokeExternalBlobUrlsOnDispose" type="boolean" default="undefined">
22562
+ /// This option is in effect when EmbedType is blob.
22563
+ /// Dispose images from blob storage (revoke blob URLs) added through Value(..)
22564
+ /// function when control is disposed. Basically ownership of these blobs are handed
22565
+ /// over to the control for the duration of its life time.
22566
+ /// These images are furthermore subject to the rule set in RevokeBlobUrlsOnDispose.
22567
+ /// Defaults to False.
22568
+ /// </member>
22569
+ /// </container>
22570
+
22571
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeConfigPlugins">
22572
+ /// <description> Additional plugins enabled in DesignMode </description>
22573
+ /// <member name="Emojis" type="boolean" default="undefined"> Plugin(s) related to emoji support (defaults to False) </member>
22574
+ /// <member name="Images" type="Fit.Controls.InputTypeDefs.DesignModeConfigPluginsImagesConfig" default="undefined"> Plugin(s) related to support for images (defaults to False) </member>
22575
+ /// </container>
22576
+
22577
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeConfigToolbar">
22578
+ /// <description> Toolbar buttons enabled in DesignMode </description>
22579
+ /// <member name="Formatting" type="boolean" default="undefined"> Enable text formatting (bold, italic, underline) (defaults to True) </member>
22580
+ /// <member name="Justify" type="boolean" default="undefined"> Enable text alignment (defaults to True) </member>
22581
+ /// <member name="Lists" type="boolean" default="undefined"> Enable ordered and unordered lists with indentation (defaults to True) </member>
22582
+ /// <member name="Links" type="boolean" default="undefined"> Enable links (defaults to True) </member>
22583
+ /// <member name="Emojis" type="boolean" default="undefined"> Enable emoji button (defaults to False) </member>
22584
+ /// <member name="Images" type="boolean" default="undefined"> Enable image button (defaults to false) </member>
22585
+ /// </container>
22586
+
22587
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeConfigInfoPanel">
22588
+ /// <description> Information panel at the bottom of the editor </description>
22589
+ /// <member name="Text" type="string" default="undefined"> Text to display </member>
22590
+ /// <member name="Alignment" type="'Left' | 'Center' | 'Right'" default="undefined"> Text alignment - defaults to Center </member>
22591
+ /// </container>
22592
+
22593
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeTagsOnRequestEventHandlerArgs">
22594
+ /// <description> Request handler event arguments </description>
22595
+ /// <member name="Sender" type="Fit.Controls.Input"> Instance of control </member>
22596
+ /// <member name="Request" type="Fit.Http.JsonRequest | Fit.Http.JsonpRequest"> Instance of JsonRequest or JsonpRequest </member>
22597
+ /// <member name="Query" type="{ Marker: string, Query: string }"> Query information </member>
22598
+ /// </container>
22599
+ /// <function container="Fit.Controls.InputTypeDefs" name="DesignModeTagsOnRequest" returns="boolean | void">
22600
+ /// <description> Cancelable request event handler </description>
22601
+ /// <param name="sender" type="Fit.Controls.Input"> Instance of control </param>
22602
+ /// <param name="eventArgs" type="Fit.Controls.InputTypeDefs.DesignModeTagsOnRequestEventHandlerArgs"> Event arguments </param>
22603
+ /// </function>
22604
+
22605
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeTagsOnResponseJsonTag">
22606
+ /// <description> JSON object representing tag </description>
22607
+ /// <member name="Value" type="string"> Unique value </member>
22608
+ /// <member name="Title" type="string"> Title </member>
22609
+ /// <member name="Icon" type="string" default="undefined"> Optional URL to icon/image </member>
22610
+ /// <member name="Url" type="string" default="undefined"> Optional URL to associate with tag </member>
22611
+ /// <member name="Data" type="string" default="undefined"> Optional data to associate with tag </member>
22612
+ /// </container>
22613
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeTagsOnResponseEventHandlerArgs">
22614
+ /// <description> Response handler event arguments </description>
22615
+ /// <member name="Sender" type="Fit.Controls.Input"> Instance of control </member>
22616
+ /// <member name="Request" type="Fit.Http.JsonRequest | Fit.Http.JsonpRequest"> Instance of JsonRequest or JsonpRequest </member>
22617
+ /// <member name="Query" type="{ Marker: string, Query: string }"> Query information </member>
22618
+ /// <member name="Tags" type="Fit.Controls.InputTypeDefs.DesignModeTagsOnResponseJsonTag[]"> Tags received from WebService </member>
22619
+ /// </container>
22620
+ /// <function container="Fit.Controls.InputTypeDefs" name="DesignModeTagsOnResponse">
22621
+ /// <description> Response event handler </description>
22622
+ /// <param name="sender" type="Fit.Controls.Input"> Instance of control </param>
22623
+ /// <param name="eventArgs" type="Fit.Controls.InputTypeDefs.DesignModeTagsOnResponseEventHandlerArgs"> Event arguments </param>
22624
+ /// </function>
22625
+
22626
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeTagsTagCreatorReturnType">
22627
+ /// <description> JSON object representing tag to be inserted into editor </description>
22628
+ /// <member name="Title" type="string"> Tag title </member>
22629
+ /// <member name="Value" type="string"> Tag value (ID) </member>
22630
+ /// <member name="Type" type="string"> Tag type (marker) </member>
22631
+ /// <member name="Url" type="string" default="undefined"> Optional tag URL </member>
22632
+ /// <member name="Data" type="string" default="undefined"> Optional tag data </member>
22633
+ /// </container>
22634
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeTagsTagCreatorCallbackArgs">
22635
+ /// <description> TagCreator event arguments </description>
22636
+ /// <member name="Sender" type="Fit.Controls.Input"> Instance of control </member>
22637
+ /// <member name="QueryMarker" type="string"> Query marker </member>
22638
+ /// <member name="Tag" type="Fit.Controls.InputTypeDefs.DesignModeTagsOnResponseJsonTag"> Tag received from WebService </member>
22639
+ /// </container>
22640
+ /// <function container="Fit.Controls.InputTypeDefs" name="DesignModeTagsTagCreator" returns="Fit.Controls.InputTypeDefs.DesignModeTagsTagCreatorReturnType | null | void">
22641
+ /// <description>
22642
+ /// Function producing JSON object representing tag to be inserted into editor.
22643
+ /// Returning nothing or Null results in default tag being inserted into editor.
22644
+ /// </description>
22645
+ /// <param name="sender" type="Fit.Controls.Input"> Instance of control </param>
22646
+ /// <param name="eventArgs" type="Fit.Controls.InputTypeDefs.DesignModeTagsTagCreatorCallbackArgs"> Event arguments </param>
22647
+ /// </function>
22648
+
22649
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeConfigTags">
22650
+ /// <description> Configuration for tags in DesignMode </description>
22651
+ /// <member name="Triggers" type="{ Marker: string, MinimumCharacters?: integer, DebounceQuery?: integer }[]"> Markers triggering tags request and context menu </member>
22652
+ /// <member name="QueryUrl" type="string">
22653
+ /// URL to request data from. Endpoint receives the following payload:
22654
+ /// { Marker: "@", Query: "search" }
22655
+ ///
22656
+ /// Data is expected to be returned in the following format:
22657
+ /// [
22658
+ /// { Value: "t-1", Title: "Tag 1", Icon: "images/img1.jpeg", Url: "show/1", Data: "..." },
22659
+ /// { Value: "t-2", Title: "Tag 2", Icon: "images/img2.jpeg", Url: "show/2", Data: "..." }, ...
22660
+ /// ]
22661
+ ///
22662
+ /// The Value and Title properties are required. The Icon property is optional and must specify the path to an image.
22663
+ /// The Url property is optional and must specify a path to a related page/resource.
22664
+ /// The Data property is optional and allows for additional data to be associated with the tag.
22665
+ /// To hold multiple values, consider using a base64 encoded JSON object:
22666
+ /// btoa(JSON.stringify({ creationDate: new Date(), active: true }))
22667
+ ///
22668
+ /// The data eventuelly results in a tag being added to the editor with the following format:
22669
+ /// <a data-tag-type="@" data-tag-id="unique id 1" data-tag-data="..." href="show/1">Tag name 1</a>
22670
+ /// The data-tag-data attribute is only declared if the corresponding Data property is defined in data.
22671
+ /// </member>
22672
+ /// <member name="JsonpCallback" type="string" default="undefined"> Name of URL parameter receiving name of JSONP callback function (only for JSONP services) </member>
22673
+ /// <member name="JsonpTimeout" type="integer" default="undefined"> Number of milliseconds to allow JSONP request to wait for a response before aborting (only for JSONP services) </member>
22674
+ /// <member name="OnRequest" type="Fit.Controls.InputTypeDefs.DesignModeTagsOnRequest" default="undefined">
22675
+ /// Event handler invoked when tags are requested. Request may be canceled by returning False.
22676
+ /// Function receives two arguments:
22677
+ /// Sender (Fit.Controls.Input) and EventArgs object.
22678
+ /// EventArgs object contains the following properties:
22679
+ /// - Sender: Fit.Controls.Input instance
22680
+ /// - Request: Fit.Http.JsonpRequest or Fit.Http.JsonRequest instance
22681
+ /// - Query: Contains query information in its Marker and Query property
22682
+ /// </member>
22683
+ /// <member name="OnResponse" type="Fit.Controls.InputTypeDefs.DesignModeTagsOnResponse" default="undefined">
22684
+ /// Event handler invoked when tags data is received, allowing for data transformation.
22685
+ /// Function receives two arguments:
22686
+ /// Sender (Fit.Controls.Input) and EventArgs object.
22687
+ /// EventArgs object contains the following properties:
22688
+ /// - Sender: Fit.Controls.Input instance
22689
+ /// - Request: Fit.Http.JsonpRequest or Fit.Http.JsonRequest instance
22690
+ /// - Query: Contains query information in its Marker and Query property
22691
+ /// - Tags: JSON tags array received from WebService
22692
+ /// </member>
22693
+ /// <member name="TagCreator" type="Fit.Controls.InputTypeDefs.DesignModeTagsTagCreator" default="undefined">
22694
+ /// Callback invoked when a tag is being inserted into editor, allowing
22695
+ /// for customization to the title and attributes associated with the tag.
22696
+ /// Function receives two arguments:
22697
+ /// Sender (Fit.Controls.Input) and EventArgs object.
22698
+ /// EventArgs object contains the following properties:
22699
+ /// - Sender: Fit.Controls.Input instance
22700
+ /// - QueryMarker: String containing query marker
22701
+ /// - Tag: JSON tag received from WebService
22702
+ /// </member>
22703
+ /// </container>
22704
+
22705
+ /// <container name="Fit.Controls.InputTypeDefs.DesignModeConfig">
22706
+ /// <description> Configuration for DesignMode </description>
22707
+ /// <member name="Plugins" type="Fit.Controls.InputTypeDefs.DesignModeConfigPlugins" default="undefined"> Plugins configuration </member>
22708
+ /// <member name="Toolbar" type="Fit.Controls.InputTypeDefs.DesignModeConfigToolbar" default="undefined"> Toolbar configuration </member>
22709
+ /// <member name="InfoPanel" type="Fit.Controls.InputTypeDefs.DesignModeConfigInfoPanel" default="undefined"> Information panel configuration </member>
22710
+ /// <member name="Tags" type="Fit.Controls.InputTypeDefs.DesignModeConfigTags" default="undefined"> Tags configuration </member>
22711
+ /// </container>
22712
+
22502
22713
  /// <function container="Fit.Controls.Input" name="DesignMode" access="public" returns="boolean">
22503
22714
  /// <description>
22504
22715
  /// Get/set value indicating whether control is in Design Mode allowing for rich HTML content.
22505
22716
  /// Notice that this control type requires dimensions (Width/Height) to be specified in pixels.
22506
22717
  /// </description>
22507
22718
  /// <param name="val" type="boolean" default="undefined"> If defined, True enables Design Mode, False disables it </param>
22719
+ /// <param name="editorConfig" type="Fit.Controls.InputTypeDefs.DesignModeConfig" default="undefined">
22720
+ /// If provided and DesignMode is enabled, configuration is applied when editor is created.
22721
+ /// </param>
22508
22722
  /// </function>
22509
- this.DesignMode = function(val)
22723
+ this.DesignMode = function(val, editorConfig)
22510
22724
  {
22511
22725
  Fit.Validation.ExpectBoolean(val, true);
22726
+ Fit.Validation.ExpectObject(editorConfig, true);
22727
+ Fit.Validation.ExpectObject((editorConfig || {}).Plugins, true);
22728
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Plugins || {}).Emojis, true);
22729
+ Fit.Validation.ExpectObject(((editorConfig || {}).Plugins || {}).Images, true);
22730
+ Fit.Validation.ExpectBoolean((((editorConfig || {}).Plugins || {}).Images || {}).Enabled, true);
22731
+ Fit.Validation.ExpectStringValue((((editorConfig || {}).Plugins || {}).Images || {}).EmbedType, true);
22732
+ Fit.Validation.ExpectStringValue((((editorConfig || {}).Plugins || {}).Images || {}).RevokeBlobUrlsOnDispose, true);
22733
+ Fit.Validation.ExpectBoolean((((editorConfig || {}).Plugins || {}).Images || {}).RevokeExternalBlobUrlsOnDispose, true);
22734
+ Fit.Validation.ExpectObject((editorConfig || {}).Toolbar, true);
22735
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Toolbar || {}).Formatting, true);
22736
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Toolbar || {}).Justify, true);
22737
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Toolbar || {}).Lists, true);
22738
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Toolbar || {}).Links, true);
22739
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Toolbar || {}).Emojis, true);
22740
+ Fit.Validation.ExpectBoolean(((editorConfig || {}).Toolbar || {}).Images, true);
22741
+ Fit.Validation.ExpectObject((editorConfig || {}).InfoPanel, true);
22742
+ Fit.Validation.ExpectString(((editorConfig || {}).InfoPanel || {}).Text, true);
22743
+ Fit.Validation.ExpectString(((editorConfig || {}).InfoPanel || {}).Alignment, true);
22744
+ Fit.Validation.ExpectObject((editorConfig || {}).Tags, true);
22745
+
22746
+ if (editorConfig && editorConfig.Tags)
22747
+ {
22748
+ Fit.Validation.ExpectTypeArray(editorConfig.Tags.Triggers, function(trigger)
22749
+ {
22750
+ Fit.Validation.ExpectStringValue(trigger.Marker);
22751
+ Fit.Validation.ExpectInteger(trigger.MinimumCharacters, true);
22752
+ Fit.Validation.ExpectInteger(trigger.DebounceQuery, true);
22753
+ });
22754
+ Fit.Validation.ExpectStringValue(editorConfig.Tags.QueryUrl);
22755
+ Fit.Validation.ExpectStringValue(editorConfig.Tags.JsonpCallback, true);
22756
+ Fit.Validation.ExpectInteger(editorConfig.Tags.JsonpTimeout, true);
22757
+ Fit.Validation.ExpectFunction(editorConfig.Tags.OnRequest, true);
22758
+ Fit.Validation.ExpectFunction(editorConfig.Tags.OnResponse, true);
22759
+ Fit.Validation.ExpectFunction(editorConfig.Tags.TagCreator, true);
22760
+ }
22512
22761
 
22513
22762
  if (Fit.Validation.IsSet(val) === true)
22514
22763
  {
@@ -22519,6 +22768,11 @@ Fit.Controls.Input = function(ctlId)
22519
22768
 
22520
22769
  if (val === true && designMode === false)
22521
22770
  {
22771
+ if (Fit.Validation.IsSet(editorConfig) === true)
22772
+ {
22773
+ designEditorConfig = editorConfig;
22774
+ }
22775
+
22522
22776
  if (Fit._internal.Controls.Input.ActiveEditorForDialog === me)
22523
22777
  {
22524
22778
  // Control is actually already in Design Mode, but waiting
@@ -22633,23 +22887,26 @@ Fit.Controls.Input = function(ctlId)
22633
22887
  return;
22634
22888
  }
22635
22889
 
22636
- // Move dialog to control - otherwise placed in the root of the document where it pollutes,
22637
- // and makes it impossible to interact with the dialog in light dismissable panels and callouts.
22638
- // Dialog is placed alongside control and not within the control's container, to prevent Fit.UI
22639
- // styling from affecting the dialog.
22640
- // DISABLED: It breaks file picker controls in dialogs which are hosted in iframes.
22641
- // When an iframe is re-rooted in DOM it reloads, and any dynamically created content is lost.
22642
- // We will have to increase the z-index to make sure dialogs open on top of modal layers.
22643
- // EDIT 2021-08-20: Enabled again. The base64image plugin has now been altered so it no longer
22644
- // uses CKEditor's built-in file picker which is wrapped in an iFrame. Therefore the dialog can
22645
- // once again be mounted next to the Input control.
22646
-
22647
- var ckeDialogElement = this.getElement().$;
22648
- Fit.Dom.InsertAfter(Fit._internal.Controls.Input.ActiveEditorForDialog.GetDomElement(), ckeDialogElement);
22649
-
22650
- // 2nd+ time dialog is opened it remains invisible - make it appear and position it
22651
- ckeDialogElement.style.display = !CKEDITOR.env.ie || CKEDITOR.env.edge ? "flex" : ""; // https://github.com/ckeditor/ckeditor4/blob/8b208d05d1338d046cdc8f971c9faf21604dd75d/plugins/dialog/plugin.js#L152
22652
- this.layout(); // 'this' is the dialog instance - layout() positions dialog
22890
+ if (Fit._internal.ControlBase.ReduceDocumentRootPollution === true)
22891
+ {
22892
+ // Move dialog to control - otherwise placed in the root of the document where it pollutes,
22893
+ // and makes it impossible to interact with the dialog in light dismissable panels and callouts.
22894
+ // Dialog is placed alongside control and not within the control's container, to prevent Fit.UI
22895
+ // styling from affecting the dialog.
22896
+ // DISABLED: It breaks file picker controls in dialogs which are hosted in iframes.
22897
+ // When an iframe is re-rooted in DOM it reloads, and any dynamically created content is lost.
22898
+ // We will have to increase the z-index to make sure dialogs open on top of modal layers.
22899
+ // EDIT 2021-08-20: Enabled again. The base64image plugin has now been altered so it no longer
22900
+ // uses CKEditor's built-in file picker which is wrapped in an iFrame. Therefore the dialog can
22901
+ // once again be mounted next to the Input control.
22902
+
22903
+ var ckeDialogElement = this.getElement().$;
22904
+ Fit.Dom.InsertAfter(Fit._internal.Controls.Input.ActiveEditorForDialog.GetDomElement(), ckeDialogElement);
22905
+
22906
+ // 2nd+ time dialog is opened it remains invisible - make it appear and position it
22907
+ ckeDialogElement.style.display = !CKEDITOR.env.ie || CKEDITOR.env.edge ? "flex" : ""; // https://github.com/ckeditor/ckeditor4/blob/8b208d05d1338d046cdc8f971c9faf21604dd75d/plugins/dialog/plugin.js#L152
22908
+ this.layout(); // 'this' is the dialog instance - layout() positions dialog
22909
+ }
22653
22910
  });
22654
22911
 
22655
22912
  dialog.on("hide", function(ev) // Fires when user closes dialog, or when hide() is called on dialog, or if destroy() is called on editor instance from Dispose() or DesignMode(false)
@@ -22921,6 +23178,277 @@ Fit.Controls.Input = function(ctlId)
22921
23178
  var langSupport = ["da", "de", "en"];
22922
23179
  var locale = Fit.Internationalization.Locale().length === 2 ? Fit.Internationalization.Locale() : Fit.Internationalization.Locale().substring(0, 2);
22923
23180
  var lang = Fit.Array.Contains(langSupport, locale) === true ? locale : "en";
23181
+ var plugins = [];
23182
+ var toolbar = [];
23183
+ var mentions = [];
23184
+
23185
+ var config = designEditorConfig || {};
23186
+
23187
+ // Enable additional plugins not compiled into CKEditor by default
23188
+
23189
+ if ((config.Plugins && config.Plugins.Emojis === true) || (config.Toolbar && config.Toolbar.Emojis === true))
23190
+ {
23191
+ Fit.Array.Add(plugins, "emoji");
23192
+ }
23193
+
23194
+ if ((config.Plugins && config.Plugins.Images && config.Plugins.Images.Enabled === true) || (config.Toolbar && config.Toolbar.Images === true))
23195
+ {
23196
+ if (config.Toolbar && config.Toolbar.Images === true)
23197
+ {
23198
+ Fit.Array.Add(plugins, "base64image");
23199
+ }
23200
+
23201
+ plugins = Fit.Array.Merge(plugins, ["base64imagepaste", "dragresize"]);
23202
+ }
23203
+
23204
+ // Add toolbar buttons
23205
+
23206
+ if (!config.Toolbar || config.Toolbar.Formatting !== false)
23207
+ {
23208
+ Fit.Array.Add(toolbar,
23209
+ {
23210
+ name: "BasicFormatting",
23211
+ items: [ "Bold", "Italic", "Underline" ]
23212
+ });
23213
+ }
23214
+
23215
+ if (!config.Toolbar || config.Toolbar.Justify !== false)
23216
+ {
23217
+ Fit.Array.Add(toolbar,
23218
+ {
23219
+ name: "Justify",
23220
+ items: [ "JustifyLeft", "JustifyCenter", "JustifyRight" ]
23221
+ });
23222
+ }
23223
+
23224
+ if (!config.Toolbar || config.Toolbar.Lists !== false)
23225
+ {
23226
+ Fit.Array.Add(toolbar,
23227
+ {
23228
+ name: "Lists",
23229
+ items: [ "NumberedList", "BulletedList", "Indent", "Outdent" ]
23230
+ });
23231
+ }
23232
+
23233
+ if (!config.Toolbar || config.Toolbar.Links !== false)
23234
+ {
23235
+ Fit.Array.Add(toolbar,
23236
+ {
23237
+ name: "Links",
23238
+ items: [ "Link", "Unlink" ]
23239
+ });
23240
+ }
23241
+
23242
+ if (config.Toolbar)
23243
+ {
23244
+ var insert = [];
23245
+
23246
+ if (config.Toolbar.Emojis === true)
23247
+ {
23248
+ Fit.Array.Add(insert, "EmojiPanel");
23249
+ }
23250
+
23251
+ if (config.Toolbar.Images === true)
23252
+ {
23253
+ Fit.Array.Add(insert, "base64image");
23254
+ }
23255
+
23256
+ if (insert.length > 0)
23257
+ {
23258
+ Fit.Array.Add(toolbar,
23259
+ {
23260
+ name: "Insert",
23261
+ items: insert
23262
+ });
23263
+ }
23264
+ }
23265
+
23266
+ // Configure tags/mentions plugin
23267
+
23268
+ if (config.Tags)
23269
+ {
23270
+ var requestAwaiting = null;
23271
+
23272
+ var createEventArgs = function(marker, query, request) // EventsArgs for OnRequest and OnResponse
23273
+ {
23274
+ return { Sender: me, Query: { Marker: marker, Query: query }, Request: request };
23275
+ };
23276
+
23277
+ Fit.Array.ForEach(config.Tags.Triggers, function(trigger)
23278
+ {
23279
+ var mention =
23280
+ {
23281
+ marker: trigger.Marker,
23282
+ minChars: trigger.MinimumCharacters || 0,
23283
+ throttle: 0, // Throttling is not debouncing - it merely ensures that no more than 1 request is made every X milliseconds when value is changed (defaults to 200ms) - real debouncing implemented further down, which reduce and cancel network calls as user types - also a work around for https://github.com/ckeditor/ckeditor4/issues/5036
23284
+ feed: function(args, resolve)
23285
+ {
23286
+ // WebService is expected to return tag items in an array like so:
23287
+ // [ { Title: string, Value: string, Icon?: string, Url?: string, Data?: string }, { ... }, ... ]
23288
+
23289
+ var req = null;
23290
+
23291
+ if (config.Tags.JsonpCallback)
23292
+ {
23293
+ req = new Fit.Http.JsonpRequest(config.Tags.QueryUrl, config.Tags.JsonpCallback);
23294
+ config.Tags.JsonpTimeout && req.Timeout(config.Tags.JsonpTimeout);
23295
+ req.SetParameter("Marker", args.marker);
23296
+ req.SetParameter("Query", args.query);
23297
+ }
23298
+ else
23299
+ {
23300
+ req = new Fit.Http.JsonRequest(config.Tags.QueryUrl);
23301
+ req.SetData({ Marker: args.marker, Query: args.query });
23302
+ }
23303
+
23304
+ if (config.Tags.OnRequest)
23305
+ {
23306
+ var eventArgs = createEventArgs(args.marker, args.query, req);
23307
+
23308
+ if (config.Tags.OnRequest(me, eventArgs) === false)
23309
+ {
23310
+ resolve([]);
23311
+ return;
23312
+ }
23313
+
23314
+ if (eventArgs.Request !== req)
23315
+ {
23316
+ // Support for changing request instans to
23317
+ // take control over webservice communication.
23318
+
23319
+ // Restrict to support for Fit.Http.Request or classes derived from this
23320
+ Fit.Validation.ExpectInstance(eventArgs.Request, Fit.Http.Request);
23321
+
23322
+ req = eventArgs.Request;
23323
+ }
23324
+ }
23325
+
23326
+ var processDataAndResolve = function(items)
23327
+ {
23328
+ if (config.Tags.OnResponse) // OnResponse is allowed to manipulate tags
23329
+ {
23330
+ var eventArgs = Fit.Core.Merge(createEventArgs(args.marker, args.query, req), { Tags: items });
23331
+ config.Tags.OnResponse(me, eventArgs);
23332
+
23333
+ items = eventArgs.Tags; // In case OnResponse event handler assigned new collection
23334
+ }
23335
+
23336
+ Fit.Array.ForEach(items, function(item)
23337
+ {
23338
+ // Set properties required by mentions plugin
23339
+ item.id = item.Value;
23340
+ item.name = item.Title;
23341
+ });
23342
+
23343
+ resolve(items);
23344
+
23345
+ if (Fit._internal.ControlBase.ReduceDocumentRootPollution === true)
23346
+ {
23347
+ // Calling resolve(..) above immediately opens the context menu from which
23348
+ // a tag can be selected. However, it is placed in the root of the document
23349
+ // where it pollutes the global scope. Move it next to the Fit.UI control.
23350
+ // We do not mount it within the Fit.UI control as it could cause Fit.UI styles
23351
+ // to take effect on the context menu.
23352
+
23353
+ // Get the autocomplete context menu currently open. There can be only one
23354
+ // such menu open at any time. Each editor can declare multiple autocomplete
23355
+ // context menus since each tag marker is associated with its own context menu.
23356
+ var ctm = document.querySelector("ul.cke_autocomplete_opened");
23357
+ Fit.Dom.InsertAfter(me.GetDomElement(), ctm);
23358
+ }
23359
+ };
23360
+
23361
+ if (Fit.Core.InstanceOf(req, Fit.Http.JsonpRequest) === true)
23362
+ {
23363
+ req.OnSuccess(function(sender)
23364
+ {
23365
+ var response = req.GetResponse();
23366
+ var items = ((response instanceof Array) ? response : []);
23367
+
23368
+ processDataAndResolve(items);
23369
+ });
23370
+
23371
+ req.OnTimeout(function(sender)
23372
+ {
23373
+ resolve([]);
23374
+ Fit.Validation.ThrowError("Unable to get tags - request did not return data in time (JSONP timeout reached)");
23375
+ });
23376
+ }
23377
+ else
23378
+ {
23379
+ req.OnSuccess(function(sender)
23380
+ {
23381
+ var response = req.GetResponseJson();
23382
+ var items = ((response instanceof Array) ? response : []);
23383
+
23384
+ processDataAndResolve(items);
23385
+ });
23386
+
23387
+ req.OnFailure(function(sender)
23388
+ {
23389
+ resolve([]);
23390
+ Fit.Validation.ThrowError("Unable to get tags - request failed with HTTP Status code " + req.GetHttpStatus());
23391
+ });
23392
+ }
23393
+
23394
+ if (requestAwaiting !== null)
23395
+ {
23396
+ requestAwaiting.Abort();
23397
+ }
23398
+
23399
+ requestAwaiting = req;
23400
+ req.Start();
23401
+ },
23402
+ itemTemplate: function(item) // Item must define "name" and "id" properties - the {name} placeholder is replaced by "@" + the value of the "name" property - to get rid of "@" simply use an alternative property such as nameWithoutTag:"Some username"
23403
+ {
23404
+ if (item.Icon)
23405
+ {
23406
+ return '<li data-id="' + item.Value + '"><img src="' + item.Icon + '" style="width: 24px; height: 24px; border-radius: 24px; vertical-align: middle" alt=""><span style="display: inline-block; width: 150px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; vertical-align: middle; margin-left: 5px">' + item.Title + '</span></li>';
23407
+ }
23408
+ else
23409
+ {
23410
+ return '<li data-id="' + item.Value + '">' + item.Title + '</li>';
23411
+ }
23412
+ },
23413
+ outputTemplate: function(item)
23414
+ {
23415
+ // IMPORTANT: Output produced must respect ACF (Advanced Content Filter).
23416
+ // So the tag produced must be allowed, and any attributes contained must be allowed.
23417
+
23418
+ var alternativeItem = null;
23419
+
23420
+ if (config.Tags.TagCreator)
23421
+ {
23422
+ var callbackArgs = { Sender: me, QueryMarker: trigger.Marker, Tag: Fit.Core.Clone(item) };
23423
+ alternativeItem = config.Tags.TagCreator(me, callbackArgs) || null;
23424
+ }
23425
+
23426
+ // Function should return a link for tags to "just work". Returning a <span> requires the span to be whitelisted in
23427
+ // extraAllowedContent configuration, but even then the editor will continue writing text within the <span> element,
23428
+ // rather than next to it. So one would expect something like: We will assign <span data-tag-type="@" ..>@James Bond</span> to this mission.
23429
+ // But what we get instead is something like: We will assign <span data-tag-type="@" ..>@James Bond to this mission</span>.
23430
+ // The same happens to link tags if the href attribute is removed, which is why we always add it, even when no URL is defined.
23431
+
23432
+ if (alternativeItem !== null)
23433
+ {
23434
+ return '<a data-tag-type="' + (alternativeItem.Type || trigger.Marker) + '" data-tag-id="' + (alternativeItem.Value || item.Value) + '"' + (alternativeItem.Data || item.Data ? ' data-tag-data="' + (alternativeItem.Data || item.Data) + '"' : '') + (alternativeItem.Url || item.Url ? ' href="' + (alternativeItem.Url || item.Url) + '"' : 'href=""') + '>' + (alternativeItem.Title || (trigger.Marker + item.Title)) + '</a>';
23435
+ }
23436
+ else
23437
+ {
23438
+ return '<a data-tag-type="' + trigger.Marker + '" data-tag-id="' + item.Value + '"' + (item.Data ? ' data-tag-data="' + item.Data + '"' : '') + (item.Url ? ' href="' + item.Url + '"' : 'href=""') + '>' + trigger.Marker + item.Title + '</a>';
23439
+ }
23440
+ }
23441
+ };
23442
+
23443
+ if (trigger.DebounceQuery !== 0) // A value of 0 (zero) disables debouncing
23444
+ {
23445
+ // Wrap feed handler in debounce function so that every time it gets invoked, it cancels the previous invocation
23446
+ mention.feed = Fit.Core.CreateDebouncer(mention.feed, trigger.DebounceQuery || 300).Invoke;
23447
+ }
23448
+
23449
+ Fit.Array.Add(mentions, mention)
23450
+ });
23451
+ }
22924
23452
 
22925
23453
  // Prevent control from losing focus when HTML editor is initialized,
22926
23454
  // e.g. if Design Mode is enabled when ordinary input control gains focus.
@@ -22974,50 +23502,30 @@ Fit.Controls.Input = function(ctlId)
22974
23502
  designEditor = CKEDITOR.replace(me.GetId() + "_DesignMode",
22975
23503
  {
22976
23504
  //allowedContent: true, // http://docs.ckeditor.com/#!/guide/dev_allowed_content_rules and http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-allowedContent
23505
+ extraAllowedContent: "a[data-tag-type,data-tag-id,data-tag-data]", // https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-extraAllowedContent
22977
23506
  language: lang,
22978
23507
  disableNativeSpellChecker: me.CheckSpelling() === false,
22979
23508
  readOnly: me.Enabled() === false,
22980
23509
  tabIndex: me.Enabled() === false ? -1 : 0,
22981
23510
  title: "",
22982
23511
  startupFocus: focused === true ? "end" : false,
22983
- extraPlugins: Fit._internal.Controls.Input.Editor.Plugins.join(","), // "justify,pastefromword,base64image,base64imagepaste,dragresize",
23512
+ extraPlugins: plugins.join(","),
22984
23513
  clipboard_handleImages: false, // Disable native support for image pasting - allow base64imagepaste plugin to handle image data if loaded
22985
23514
  base64image: // Custom property used by base64image plugin if loaded
22986
23515
  {
22987
- storage: "blob", // "base64" (default) or "blob" - base64 will always be provided by browsers not supporting blob storage
23516
+ storage: designEditorConfig !== null && designEditorConfig.Plugins && designEditorConfig.Plugins.Images && designEditorConfig.Plugins.Images.EmbedType === "blob" ? "blob" : "base64", // "base64" (default) or "blob" - base64 will always be provided by browsers not supporting blob storage
22988
23517
  onImageAdded: onImageAdded
22989
23518
  },
22990
23519
  base64imagepaste: // Custom property used by base64imagepaste plugin if loaded - notice that IE has native support for image pasting as base64 so plugin is not triggered in IE
22991
23520
  {
22992
- storage: "blob", // "base64" (default) or "blob" - base64 will always be provided by browsers not supporting blob storage
23521
+ storage: designEditorConfig !== null && designEditorConfig.Plugins && designEditorConfig.Plugins.Images && designEditorConfig.Plugins.Images.EmbedType === "blob" ? "blob" : "base64", // "base64" (default) or "blob" - base64 will always be provided by browsers not supporting blob storage
22993
23522
  onImageAdded: onImageAdded
22994
23523
  },
22995
23524
  resize_enabled: resizable !== Fit.Controls.InputResizing.Disabled,
22996
23525
  resize_dir: resizable === Fit.Controls.InputResizing.Enabled ? "both" : resizable === Fit.Controls.InputResizing.Vertical ? "vertical" : resizable === Fit.Controls.InputResizing.Horizontal ? "horizontal" : "none", // Specific to resize plugin (horizontal | vertical | both - https://ckeditor.com/docs/ckeditor4/latest/features/resize.html)
22997
- toolbar: Fit._internal.Controls.Input.Editor.Toolbar,
22998
- /*[
22999
- {
23000
- name: "BasicFormatting",
23001
- items: [ "Bold", "Italic", "Underline" ]
23002
- },
23003
- {
23004
- name: "Justify",
23005
- items: [ "JustifyLeft", "JustifyCenter", "JustifyRight" ]
23006
- },
23007
- {
23008
- name: "Lists",
23009
- items: [ "NumberedList", "BulletedList", "Indent", "Outdent" ]
23010
- },
23011
- {
23012
- name: "Links",
23013
- items: [ "Link", "Unlink" ]
23014
- },
23015
- {
23016
- name: "Insert",
23017
- items: [ "base64image" ]
23018
- }
23019
- ],*/
23526
+ toolbar: toolbar,
23020
23527
  removeButtons: "", // Set to empty string to prevent CKEditor from removing buttons such as Underline
23528
+ mentions: mentions,
23021
23529
  on:
23022
23530
  {
23023
23531
  instanceReady: function()
@@ -23062,6 +23570,60 @@ Fit.Controls.Input = function(ctlId)
23062
23570
  me.Maximized(true);
23063
23571
  }
23064
23572
 
23573
+ if (config.InfoPanel && config.InfoPanel.Text)
23574
+ {
23575
+ var infoPanel = document.createElement("div");
23576
+ infoPanel.className = "FitUiControlInputInfoPanel";
23577
+ infoPanel.innerHTML = config.InfoPanel.Text;
23578
+ infoPanel.style.cssText = "text-align: " + (config.InfoPanel.Alignment ? config.InfoPanel.Alignment.toLowerCase() : "center");
23579
+
23580
+ var ckEditorInner = designEditor.container.$.querySelector(".cke_inner"); // Div in modern browsers, span in legacy IE
23581
+ var ckEditorBottom = designEditor.container.$.querySelector(".cke_inner span.cke_bottom"); // Only present if resize handle is enable
23582
+
23583
+ if (ckEditorInner !== null)
23584
+ {
23585
+ if (ckEditorBottom !== null)
23586
+ {
23587
+ Fit.Dom.InsertBefore(ckEditorBottom, infoPanel);
23588
+ }
23589
+ else
23590
+ {
23591
+ Fit.Dom.Add(ckEditorInner, infoPanel);
23592
+ }
23593
+ }
23594
+ }
23595
+
23596
+ // DISABLED: Doesn't work! Emoji panel contains an iFrame. When it is re-mounted
23597
+ // in DOM, the iframe reloads, and dynamically added content is lost. Also, this makes
23598
+ // CKEditor throw errors and the dialog never appears.
23599
+ /*if (Fit._internal.ControlBase.ReduceDocumentRootPollution === true)
23600
+ {
23601
+ // Move emoji dialog to control - otherwise placed in the root of the document where it pollutes,
23602
+ // and makes it impossible to interact with the dialog in light dismissable panels and callouts.
23603
+ // Dialog is placed alongside control and not within the control's container, to prevent Fit.UI
23604
+ // styling from affecting the dialog.
23605
+ if (config.Toolbar && config.Toolbar.Emojis === true)
23606
+ {
23607
+ var emojiButton = designEditor.container.$.querySelector("a.cke_button__emojipanel");
23608
+
23609
+ if (emojiButton !== null)
23610
+ {
23611
+ Fit.Events.AddHandler(emojiButton, "click", function(e)
23612
+ {
23613
+ setTimeout(function() // Postpone - made visible after click event
23614
+ {
23615
+ var emojiPanel = document.querySelector("div.cke_emoji-panel:not([style*='display: none'])");
23616
+
23617
+ if (emojiPanel !== null)
23618
+ {
23619
+ Fit.Dom.InsertAfter(me.GetDomElement(), emojiPanel);
23620
+ }
23621
+ }, 0);
23622
+ });
23623
+ }
23624
+ }
23625
+ }*/
23626
+
23065
23627
  designEditor._isReadyForInteraction = true;
23066
23628
 
23067
23629
  // Make editor assume configured width and height.
@@ -23095,8 +23657,73 @@ Fit.Controls.Input = function(ctlId)
23095
23657
  me._internal.Data("resized", "true");
23096
23658
  repaint();
23097
23659
  },
23660
+ selectionChange: function(ev)
23661
+ {
23662
+ // Disable/enable toolbar buttons, depending on whether a tag/mention is selected
23663
+
23664
+ var elm = ev.data.selection.getStartElement().$;
23665
+
23666
+ if (elm.tagName === "A" && Fit.Dom.Data(elm, "tag-id") !== null)
23667
+ {
23668
+ designEditorSuppressPaste = true;
23669
+ setTimeout(function() // Postpone - otherwise we won't be able to temporarily disable some of the buttons (https://jsfiddle.net/ymv56znq/14/)
23670
+ {
23671
+ disableDesignEditorButtons();
23672
+ }, 0);
23673
+ }
23674
+ else
23675
+ {
23676
+ designEditorSuppressPaste = false;
23677
+ restoreDesignEditorButtons();
23678
+ }
23679
+ },
23680
+ doubleclick: function(ev)
23681
+ {
23682
+ // Suppress link dialog for tags (similar code found in beforeCommandExec handler below)
23683
+ if (Fit.Dom.Data(ev.data.element.$, "tag-id") !== null)
23684
+ {
23685
+ ev.cancel();
23686
+ return;
23687
+ }
23688
+ },
23689
+ paste: function(ev)
23690
+ {
23691
+ // Prevent pasting (especially images) into tags.
23692
+ // OnPaste is suppressed using an OnPaste handler in capture phase, which will prevent the operation entirely
23693
+ // on supported browsers. On legacy browsers we handle this by invoking undo on the editor instance instead.
23694
+ //var path = ev.editor.elementPath(); // Null if dialog button is triggered without placing text cursor in editor first
23695
+ //if (Fit.Dom.Data(path.lastElement.$, "tag-id") !== null)
23696
+ if (designEditorSuppressPaste === true) // Also handled in a native OnPaste event handler (capture phase) for supported browsers, which suppresses the event entirely
23697
+ {
23698
+ setTimeout(function() // Postpone - allow editor to create snapshot
23699
+ {
23700
+ ev.editor.execCommand("undo"); // Undo change - paste event cannot be canceled, as it has already happened
23701
+ }, 0);
23702
+ return;
23703
+ }
23704
+ },
23098
23705
  beforeCommandExec: function(ev)
23099
23706
  {
23707
+ // Suppress any command (formatting, link dialog etc.) for tags (similar code found in doubleclick handler above).
23708
+ // Commmands can be triggered in multiple ways, e.g. using toolbar buttons, using keyboard shortcuts, and programmatically.
23709
+ var path = ev.editor.elementPath(); // Null if dialog button is triggered without placing text cursor in editor first
23710
+ if (path === null && ev.editor.getData().indexOf("<p><a data-tag-id=") === 0)
23711
+ {
23712
+ // Text cursor has not been placed in editor, but a command such as Bold or "insert image"
23713
+ // has been triggered, and editor content starts with a tag. This results in command being
23714
+ // applied to the tag, which we do not want. Usually this is prevented by the toolbar being
23715
+ // disabled when a tag is selected (see selectionChange event handler further up), but that
23716
+ // is not the case when the user has not yet placed the cursor in the editor.
23717
+ ev.cancel();
23718
+ return;
23719
+ }
23720
+ else if (path !== null && Fit.Dom.Data(path.lastElement.$, "tag-id") !== null && ev.data.name !== "undo") // Allow undo within tag, in case user typed something by mistake
23721
+ {
23722
+ // Cursor is currently placed in a tag - do not allow formatting
23723
+ ev.cancel();
23724
+ return;
23725
+ }
23726
+
23100
23727
  if (ev && ev.data && ev.data.command && ev.data.command.dialogName)
23101
23728
  {
23102
23729
  // Command triggered was a dialog
@@ -23153,6 +23780,68 @@ Fit.Controls.Input = function(ctlId)
23153
23780
  });
23154
23781
  }
23155
23782
 
23783
+ function disableDesignEditorButtons() // Might be called multiple times, e.g. if navigating from one tag/mention to another - buttons must be disabled every time since CKEditor itself re-enable buttons when navigating elements in editor
23784
+ {
23785
+ var preserveButtonState = designEditorRestoreButtonState === null;
23786
+
23787
+ if (preserveButtonState === true)
23788
+ {
23789
+ designEditorRestoreButtonState = {};
23790
+ }
23791
+
23792
+ Fit.Array.ForEach(designEditor.toolbar, function(toolbarGroup)
23793
+ {
23794
+ var items = toolbarGroup.items;
23795
+
23796
+ Fit.Array.ForEach(toolbarGroup.items, function(item)
23797
+ {
23798
+ if (item.command) // Buttons have a command identifier which can be used to resolve the actual command instance
23799
+ {
23800
+ var cmd = designEditor.getCommand(item.command);
23801
+
23802
+ if (preserveButtonState === true && cmd.state !== CKEDITOR.TRISTATE_DISABLED) // https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_command.html#property-state
23803
+ {
23804
+ designEditorRestoreButtonState[item.command] = true;
23805
+ }
23806
+
23807
+ cmd.disable();
23808
+ }
23809
+ else if (item.setState) // MenuButtons allow for direct manipulation of enabled/disabled state
23810
+ {
23811
+ if (preserveButtonState === true && item.getState() !== CKEDITOR.TRISTATE_DISABLED) // https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_command.html#property-state
23812
+ {
23813
+ designEditorRestoreButtonState[item.name] = item;
23814
+ }
23815
+
23816
+ item.setState(CKEDITOR.TRISTATE_DISABLED);
23817
+ }
23818
+ });
23819
+ });
23820
+ }
23821
+
23822
+ function restoreDesignEditorButtons()
23823
+ {
23824
+ console.log("RESTORING toolbar buttons");
23825
+
23826
+ if (designEditorRestoreButtonState !== null)
23827
+ {
23828
+ Fit.Array.ForEach(designEditorRestoreButtonState, function(commandKey)
23829
+ {
23830
+ if (designEditorRestoreButtonState[commandKey] === true) // Command button
23831
+ {
23832
+ var cmd = designEditor.getCommand(commandKey);
23833
+ cmd.enable();
23834
+ }
23835
+ else // MenuButton
23836
+ {
23837
+ designEditorRestoreButtonState[commandKey].setState(CKEDITOR.TRISTATE_OFF); // Enabled but not highlighted/activated like e.g. a bold button would be when selecting bold text
23838
+ }
23839
+ });
23840
+
23841
+ designEditorRestoreButtonState = null;
23842
+ }
23843
+ };
23844
+
23156
23845
  function updateDesignEditorSize()
23157
23846
  {
23158
23847
  if (designEditor !== null)
@@ -23371,66 +24060,9 @@ Fit._internal.Controls.Input.Editor =
23371
24060
  /// <member container="Fit._internal.Controls.Input.Editor" name="Skin" access="public" static="true" type="'bootstrapck' | 'moono-lisa' | null">
23372
24061
  /// <description> Skin used with DesignMode - must be set before an editor is created and cannot be changed for each individual control </description>
23373
24062
  /// </member>
23374
- Skin: null, // Notice: CKEditor does not support multiple different skins on the same page - do not change value once an editor has been created
23375
-
23376
- /// <member container="Fit._internal.Controls.Input.Editor" name="Plugins" access="public" static="true" type="('htmlwriter' | 'justify' | 'pastefromword' | 'resize' | 'base64image' | 'base64imagepaste' | 'dragresize')[]">
23377
- /// <description> Additional plugins used with DesignMode </description>
23378
- /// </member>
23379
- Plugins: ["htmlwriter", "justify", "pastefromword", "resize" /*"base64image", "base64imagepaste", "dragresize"*/], // Regarding base64imagepaste and dragresize: IE11 has native support for pasting images as base64 and IE8+ has native support for image resizing, so plugins are not in effect in IE, even when enabled
23380
-
23381
- /// <member container="Fit._internal.Controls.Input.Editor" name="Toolbar" access="public" static="true" type="( { name: 'BasicFormatting', items: ('Bold' | 'Italic' | 'Underline')[] } | { name: 'Justify', items: ('JustifyLeft' | 'JustifyCenter' | 'JustifyRight')[] } | { name: 'Lists', items: ('NumberedList' | 'BulletedList' | 'Indent' | 'Outdent')[] } | { name: 'Links', items: ('Link' | 'Unlink')[] } | { name: 'Insert', items: ('base64image')[] } )[]">
23382
- /// <description> Toolbar buttons used with DesignMode - make sure necessary plugins are loaded (see Fit._internal.Controls.Input.EditorPlugins) </description>
23383
- /// </member>
23384
- Toolbar:
23385
- [
23386
- {
23387
- name: "BasicFormatting",
23388
- items: [ "Bold", "Italic", "Underline" ]
23389
- },
23390
- {
23391
- name: "Justify",
23392
- items: [ "JustifyLeft", "JustifyCenter", "JustifyRight" ]
23393
- },
23394
- {
23395
- name: "Lists",
23396
- items: [ "NumberedList", "BulletedList", "Indent", "Outdent" ]
23397
- },
23398
- {
23399
- name: "Links",
23400
- items: [ "Link", "Unlink" ]
23401
- }/*,
23402
- {
23403
- name: "Insert",
23404
- items: [ "base64image" ]
23405
- }*/
23406
- ]
24063
+ Skin: null // Notice: CKEditor does not support multiple different skins on the same page - do not change value once an editor has been created
23407
24064
  };
23408
24065
 
23409
- /// <container name="Fit._internal.Controls.Input.BlobManager">
23410
- /// Internal settings related to blob storage management in HTML Editor (Design Mode)
23411
- /// </container>
23412
- Fit._internal.Controls.Input.BlobManager =
23413
- {
23414
- /// <member container="Fit._internal.Controls.Input.BlobManager" name="RevokeBlobUrlsOnDispose" access="public" static="true" type="'All' | 'UnreferencedOnly'">
23415
- /// <description>
23416
- /// Dispose images from blob storage (revoke blob URLs) added though image plugins when control is disposed.
23417
- /// If "UnreferencedOnly" is specified, the component using Fit.UI's input control will be responsible for
23418
- /// disposing referenced blobs. Failing to do so may cause a memory leak.
23419
- /// </description>
23420
- /// </member>
23421
- RevokeBlobUrlsOnDispose: "All", // "All" | "UnreferencedOnly"
23422
-
23423
- /// <member container="Fit._internal.Controls.Input.BlobManager" name="RevokeExternalBlobUrlsOnDispose" access="public" static="true" type="boolean">
23424
- /// <description>
23425
- /// Dispose images from blob storage (revoke blob URLs) added through Value(..)
23426
- /// function when control is disposed. Basically ownership of these blobs are handed
23427
- /// over to the control for the duration of its life time.
23428
- /// These images are furthermore subject to the rule set in RevokeBlobUrlsOnDispose.
23429
- /// </description>
23430
- /// </member>
23431
- RevokeExternalBlobUrlsOnDispose: false
23432
- }
23433
-
23434
24066
  /// <container name="Fit.Controls.InputResizing">
23435
24067
  /// <description> Resizing options </description>
23436
24068
  /// <member name="Enabled" access="public" static="true" type="string" default="Enabled"> Allow for resizing both vertically and horizontally </member>