typo3-typescript-sources 14.3.7 → 14.3.9

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 (189) hide show
  1. package/.github/workflows/sync-and-publish.yml +240 -191
  2. package/.last-synced-tag +1 -1
  3. package/README.md +13 -5
  4. package/adminpanel/admin-panel.ts +22 -22
  5. package/adminpanel/modules/cache.ts +3 -3
  6. package/backend/action-dispatcher.ts +8 -3
  7. package/backend/bookmark/bookmark-store.ts +5 -5
  8. package/backend/clipboard-panel.ts +2 -2
  9. package/backend/code-editor/element/code-mirror-element.ts +1 -1
  10. package/backend/code-editor/language/typoscript.ts +3 -3
  11. package/backend/code-editor/stream-parser/typoscript.ts +2 -2
  12. package/backend/color-picker.ts +2 -2
  13. package/backend/column-selector-button.ts +11 -8
  14. package/backend/context-help.ts +3 -3
  15. package/backend/context-menu.ts +8 -3
  16. package/backend/drag-tooltip.ts +9 -4
  17. package/backend/drag-uploader.ts +81 -89
  18. package/backend/element/draggable-resizable-element.ts +18 -12
  19. package/backend/element/icon-element.ts +2 -2
  20. package/backend/element/thumbnail-element.ts +2 -2
  21. package/backend/element-browser.ts +1 -1
  22. package/backend/event/consumer-scope.ts +2 -2
  23. package/backend/event/interaction-request-assignment.ts +1 -1
  24. package/backend/event/interaction-request-map.ts +3 -3
  25. package/backend/form-engine/container/flex-form-container-container.ts +2 -2
  26. package/backend/form-engine/container/flex-form-section-container.ts +1 -1
  27. package/backend/form-engine/container/inline-control-container.ts +14 -14
  28. package/backend/form-engine/element/abstract-sortable-select-items.ts +3 -3
  29. package/backend/form-engine/element/color-element.ts +1 -1
  30. package/backend/form-engine/element/datetime-element.ts +1 -1
  31. package/backend/form-engine/element/extra/char-counter.ts +2 -2
  32. package/backend/form-engine/element/extra/select-box-filter.ts +1 -1
  33. package/backend/form-engine/element/mfa-info-element.ts +2 -2
  34. package/backend/form-engine/element/online-media-form-element.ts +8 -4
  35. package/backend/form-engine/element/select-multiple-side-by-side-element.ts +2 -2
  36. package/backend/form-engine/element/select-tree.ts +3 -3
  37. package/backend/form-engine/element/slug-element.ts +7 -7
  38. package/backend/form-engine/element/suggest/result-container.ts +1 -1
  39. package/backend/form-engine/element/table-wizard-element.ts +4 -4
  40. package/backend/form-engine/inline-relation/ajax-dispatcher.ts +3 -3
  41. package/backend/form-engine-validation.ts +11 -5
  42. package/backend/form-engine.ts +19 -23
  43. package/backend/global-event-handler.ts +1 -1
  44. package/backend/hotkeys/hotkey-struct.ts +1 -1
  45. package/backend/hotkeys.ts +3 -3
  46. package/backend/image-manipulation.ts +19 -19
  47. package/backend/layout-module/column-restriction.ts +61 -0
  48. package/backend/layout-module/drag-drop.ts +16 -4
  49. package/backend/layout-module/paste.ts +8 -1
  50. package/backend/live-search/element/result/item/action/action-container.ts +1 -1
  51. package/backend/live-search/element/result/item/item-container.ts +4 -4
  52. package/backend/live-search/element/result/result-container.ts +5 -0
  53. package/backend/live-search/element/result/result-pagination.ts +1 -1
  54. package/backend/live-search/live-search.ts +2 -2
  55. package/backend/localization/label-provider.ts +4 -4
  56. package/backend/localization/localization-wizard.ts +4 -4
  57. package/backend/localization/steps/content-record-selection-step.ts +8 -8
  58. package/backend/localization/steps/handler-selection-step.ts +4 -4
  59. package/backend/localization/steps/mode-step.ts +4 -4
  60. package/backend/localization/steps/source-language-step.ts +4 -4
  61. package/backend/localization/steps/target-language-step.ts +4 -4
  62. package/backend/login-refresh.ts +1 -9
  63. package/backend/login.ts +2 -2
  64. package/backend/modal.ts +38 -5
  65. package/backend/module/iframe.ts +10 -0
  66. package/backend/module/router.ts +10 -0
  67. package/backend/module-menu.ts +3 -3
  68. package/backend/multi-record-selection-action.ts +1 -1
  69. package/backend/multi-record-selection-delete-action.ts +1 -1
  70. package/backend/multi-record-selection.ts +14 -14
  71. package/backend/multi-step-wizard.ts +9 -2
  72. package/backend/new-multiple-pages.ts +2 -2
  73. package/backend/new-record-wizard.ts +15 -15
  74. package/backend/notification.ts +90 -6
  75. package/backend/online-media.ts +1 -3
  76. package/backend/page-wizard/page-wizard.ts +2 -2
  77. package/backend/page-wizard/steps/doktype-step.ts +5 -5
  78. package/backend/pagetsconfig/pagetsconfig-includes.ts +1 -1
  79. package/backend/popover.ts +5 -5
  80. package/backend/record-download-button.ts +5 -6
  81. package/backend/recordlist.ts +11 -6
  82. package/backend/security/element/csp-reports.ts +8 -8
  83. package/backend/setup-module.ts +2 -2
  84. package/backend/sortable-table.ts +10 -4
  85. package/backend/storage/abstract-client-storage.ts +2 -2
  86. package/backend/storage/module-state-storage.ts +2 -2
  87. package/backend/tests/backend/utility/dom-helper-test.ts +2 -2
  88. package/backend/tests/fixtures/modal-autofocus.html +1 -0
  89. package/backend/tests/form-engine/element/extra/select-box-filter-test.ts +3 -3
  90. package/backend/tests/form-engine-validation-test.ts +1 -1
  91. package/backend/tests/modal-test.ts +91 -0
  92. package/backend/tests/notification-test.ts +176 -0
  93. package/backend/tests/tree/tree-test.ts +41 -0
  94. package/backend/tree/file-storage-browser.ts +1 -1
  95. package/backend/tree/file-storage-tree-container.ts +4 -4
  96. package/backend/tree/page-browser.ts +1 -1
  97. package/backend/tree/page-position-select.ts +1 -1
  98. package/backend/tree/page-tree-element.ts +9 -2
  99. package/backend/tree/page-tree.ts +7 -1
  100. package/backend/tree/tree-module-state.ts +5 -5
  101. package/backend/tree/tree.ts +15 -8
  102. package/backend/user-pass-login.ts +2 -2
  103. package/backend/utility/collapse-state-persister.ts +1 -1
  104. package/backend/utility/collapse-state-search.ts +6 -6
  105. package/backend/utility/dom-helper.ts +3 -3
  106. package/backend/utility.ts +6 -6
  107. package/backend/viewport/content-navigation.ts +5 -0
  108. package/backend/viewport/navigation-container.ts +1 -1
  109. package/backend/viewport/toolbar.ts +3 -3
  110. package/backend/window-manager.ts +2 -2
  111. package/backend/wizard/events/auto-advance-event.ts +6 -0
  112. package/backend/wizard/events/before-next-step-event.ts +6 -0
  113. package/backend/wizard/events/step-summary-event.ts +6 -0
  114. package/backend/wizard/move-content-element.ts +1 -1
  115. package/backend/wizard/wizard.ts +8 -10
  116. package/belog/backend-log.ts +2 -2
  117. package/beuser/permissions.ts +2 -2
  118. package/core/ajax/ajax-response.ts +1 -1
  119. package/core/ajax/input-transformer.ts +1 -1
  120. package/core/event/debounce-event.ts +4 -4
  121. package/core/event/event-interface.ts +3 -3
  122. package/core/event/regular-event.ts +7 -7
  123. package/core/event/request-animation-frame-event.ts +4 -4
  124. package/core/event/throttle-event.ts +4 -4
  125. package/core/java-script-item-processor.ts +6 -0
  126. package/core/referrer-refresh.ts +2 -2
  127. package/core/security-utility.ts +1 -1
  128. package/dashboard/dashboard-wizard.ts +2 -2
  129. package/dashboard/dashboard.ts +8 -13
  130. package/dashboard/wizard/steps/preset-step.ts +3 -3
  131. package/extensionmanager/main.ts +7 -7
  132. package/filelist/browse-files.ts +1 -1
  133. package/filelist/browse-folders.ts +2 -2
  134. package/filelist/file-list-dragdrop.ts +5 -5
  135. package/filelist/file-list-rename-handler.ts +3 -6
  136. package/filelist/file-list-replace-handler.ts +3 -6
  137. package/filelist/file-list-transfer-handler.ts +2 -2
  138. package/filelist/file-list.ts +8 -9
  139. package/form/backend/form-editor/component/date-editor.ts +4 -0
  140. package/form/backend/form-editor/component/form-element-selector.ts +4 -0
  141. package/form/backend/form-editor/inspector-component.ts +6 -6
  142. package/form/backend/form-editor/mediator.ts +36 -36
  143. package/form/backend/form-editor/modals-component.ts +115 -6
  144. package/form/backend/form-editor/stage-component.ts +4 -4
  145. package/form/backend/form-editor/view-model.ts +7 -7
  146. package/form/backend/form-manager/view-model.ts +5 -5
  147. package/form/backend/form-wizard/form-wizard.ts +2 -2
  148. package/form/backend/form-wizard/steps/storage-step.ts +1 -1
  149. package/impexp/import-export.ts +1 -1
  150. package/info/translation-status.ts +2 -2
  151. package/install/ajax/ajax-queue.ts +1 -1
  152. package/install/module/abstract-interactable-module.ts +2 -2
  153. package/install/module/environment/image-processing.ts +4 -4
  154. package/install/module/environment/mail-test.ts +2 -2
  155. package/install/module/maintenance/clear-tables.ts +3 -3
  156. package/install/module/maintenance/clear-typo3temp-files.ts +8 -8
  157. package/install/module/maintenance/create-admin.ts +3 -3
  158. package/install/module/maintenance/database-analyzer.ts +9 -7
  159. package/install/module/maintenance/language-packs.ts +4 -4
  160. package/install/module/maintenance/reference-index.ts +1 -1
  161. package/install/module/settings/change-install-tool-password.ts +1 -1
  162. package/install/module/settings/extension-configuration.ts +5 -5
  163. package/install/module/settings/features.ts +1 -1
  164. package/install/module/settings/local-configuration.ts +5 -5
  165. package/install/module/settings/presets.ts +2 -2
  166. package/install/module/settings/system-maintainer.ts +1 -1
  167. package/install/module/upgrade/extension-scanner.ts +5 -5
  168. package/install/module/upgrade/tca-migrations-check.ts +1 -1
  169. package/install/module/upgrade/upgrade-docs.ts +16 -16
  170. package/install/module/upgrade/upgrade-wizards.ts +7 -8
  171. package/install/renderable/language-packs.ts +8 -2
  172. package/lowlevel/query-generator.ts +2 -2
  173. package/package.json +2 -2
  174. package/redirects/event-handler.ts +6 -0
  175. package/rte-ckeditor/ckeditor5.ts +7 -3
  176. package/rte-ckeditor/legacy/ckeditor5-bundle.ts +0 -1
  177. package/rte-ckeditor/plugin/typo3-link.ts +7 -9
  178. package/rte-ckeditor/rte-link-browser.ts +1 -1
  179. package/rte-ckeditor/tests/ckeditor5-test.ts +68 -0
  180. package/scheduler/form-engine/element/timing-options-element.ts +1 -1
  181. package/scheduler/scheduler-add-group.ts +4 -12
  182. package/scheduler/scheduler.ts +2 -2
  183. package/styleguide/checkbox-demo.ts +1 -1
  184. package/tstemplate/template-analyzer.ts +1 -1
  185. package/viewpage/main.ts +1 -1
  186. package/workspaces/backend.ts +5 -5
  187. package/workspaces/preview.ts +41 -2
  188. package/workspaces/renderable/record-table.ts +95 -78
  189. package/workspaces/renderable/send-to-stage-form.ts +1 -1
@@ -65,7 +65,7 @@ export class SelectTree extends Tree
65
65
  * Expand all nodes and refresh view
66
66
  */
67
67
  public expandAll(): void {
68
- this.nodes.forEach((node: TreeNodeInterface) => { this.showChildren(node); });
68
+ this.nodes.forEach((node) => { this.showChildren(node); });
69
69
  }
70
70
 
71
71
  /**
@@ -105,7 +105,7 @@ export class SelectTree extends Tree
105
105
  // A plain substring match, no RegExp involved, therefore nothing to escape either
106
106
  const foldedSearchTerm = SelectTree.fold(searchTerm ?? '');
107
107
 
108
- this.nodes.forEach((node: any) => {
108
+ this.nodes.forEach((node) => {
109
109
  // skip the root node in searches
110
110
  if (node === firstNode) {
111
111
  return;
@@ -190,7 +190,7 @@ export class SelectTree extends Tree
190
190
  */
191
191
  private prepareLoadedNodes(evt: CustomEvent): void {
192
192
  const nodes = evt.detail.nodes as Array<TreeNodeInterface>;
193
- evt.detail.nodes = nodes.map((node: TreeNodeInterface) => {
193
+ evt.detail.nodes = nodes.map((node) => {
194
194
  if (node.selectable === false) {
195
195
  this.settings.unselectableElements.push(node.identifier);
196
196
  }
@@ -88,7 +88,7 @@ class SlugElement {
88
88
  }
89
89
 
90
90
  private registerEvents(): void {
91
- const fieldsToListenOnList = Object.values(this.getAvailableFieldsForProposalGeneration()).map((field: HTMLElement) => field.dataset.slugSelector);
91
+ const fieldsToListenOnList = Object.values(this.getAvailableFieldsForProposalGeneration()).map((field) => field.dataset.slugSelector);
92
92
  const recreateButton: HTMLButtonElement = this.fullElement.querySelector(Selectors.recreateButton);
93
93
 
94
94
  // Listen on 'listenerFieldNames' for new pages. This is typically the 'title' field
@@ -158,7 +158,7 @@ class SlugElement {
158
158
  private sendSlugProposal(mode: ProposalModes): void {
159
159
  const input: Record<string, string> = {};
160
160
  if (mode === ProposalModes.AUTO || mode === ProposalModes.RECREATE) {
161
- Object.entries(this.getAvailableFieldsForProposalGeneration()).forEach((entry: [fieldName: string, field: HTMLInputElement|HTMLSelectElement]) => {
161
+ Object.entries(this.getAvailableFieldsForProposalGeneration()).forEach((entry) => {
162
162
  input[entry[0]] = entry[1].value;
163
163
  });
164
164
  if (this.options.includeUidInValues === true) {
@@ -214,18 +214,18 @@ class SlugElement {
214
214
  *
215
215
  * @return Record<string, string>
216
216
  */
217
- private getAvailableFieldsForProposalGeneration(): { [key: string]: HTMLElement } {
218
- const availableFields: { [key: string]: HTMLElement } = {};
217
+ private getAvailableFieldsForProposalGeneration(): { [key: string]: HTMLInputElement|HTMLSelectElement } {
218
+ const availableFields: { [key: string]: HTMLInputElement|HTMLSelectElement } = {};
219
219
  for (const [fieldName, selector] of Object.entries(this.fieldsToListenOn)) {
220
220
  const selectorCandidates = [
221
221
  '[data-formengine-input-name="' + selector + '"]',
222
222
  // Also check for fields, which do not point to a hidden input field (e.g. "input type=hidden", readOnly, or select fields)
223
223
  '[name="' + selector + '"]'
224
224
  ];
225
- let field: HTMLElement;
225
+ let field: HTMLInputElement|HTMLSelectElement;
226
226
  let usedSelector = '';
227
- selectorCandidates.some((testSelector: string) => {
228
- field = document.querySelector(testSelector) as HTMLElement;
227
+ selectorCandidates.some((testSelector) => {
228
+ field = document.querySelector<HTMLInputElement|HTMLSelectElement>(testSelector);
229
229
  usedSelector = testSelector;
230
230
  return field !== null;
231
231
  });
@@ -44,7 +44,7 @@ export class ResultContainer extends LitElement {
44
44
  if (this.results.length === 0) {
45
45
  content = html`<div class="alert alert-info">${labels.get('search.no_records_found')}</div>`;
46
46
  } else {
47
- content = html`${this.results.map((result: ResultItemInterface) => this.renderResultItem(result))}`;
47
+ content = html`${this.results.map((result) => this.renderResultItem(result))}`;
48
48
  }
49
49
  }
50
50
 
@@ -73,7 +73,7 @@ export class TableWizardElement extends LitElement {
73
73
  const textarea: HTMLTextAreaElement = document.querySelector(this.selectorData);
74
74
  const table: string[][] = [];
75
75
 
76
- textarea.value.split('\n').forEach((row: string) => {
76
+ textarea.value.split('\n').forEach((row) => {
77
77
  if (row !== '') {
78
78
  if(this.enclosure) {
79
79
  row = row.replace(new RegExp(this.enclosure, 'g'), '');
@@ -167,7 +167,7 @@ export class TableWizardElement extends LitElement {
167
167
 
168
168
  private renderTemplate(): TemplateResult {
169
169
  this.provideMinimalTable();
170
- const colIndexes = Object.keys(this.firstRow).map((item: string) => parseInt(item, 10));
170
+ const colIndexes = Object.keys(this.firstRow).map((item) => parseInt(item, 10));
171
171
  const lastColIndex = colIndexes[colIndexes.length - 1];
172
172
  const lastRowIndex = this.table.length - 1;
173
173
 
@@ -176,7 +176,7 @@ export class TableWizardElement extends LitElement {
176
176
  <table class="table table-center">
177
177
  <thead>
178
178
  <th>${this.renderTypeButton()}</th>
179
- ${colIndexes.map((colIndex: number) => html`
179
+ ${colIndexes.map((colIndex) => html`
180
180
  <th>${this.renderColButtons(colIndex, lastColIndex)}</th>
181
181
  `)}
182
182
  </thead>
@@ -184,7 +184,7 @@ export class TableWizardElement extends LitElement {
184
184
  ${this.table.map((row: string[], rowIndex: number) => html`
185
185
  <tr>
186
186
  <td>${this.renderRowButtons(rowIndex, lastRowIndex)}</td>
187
- ${row.map((value: string, colIndex: number) => html`
187
+ ${row.map((value, colIndex) => html`
188
188
  <td>${this.renderDataElement(value, rowIndex, colIndex)}</td>
189
189
  `)}
190
190
  </tr>
@@ -57,9 +57,9 @@ export class AjaxDispatcher {
57
57
  throw 'Undefined endpoint for route "' + routeName + '"';
58
58
  }
59
59
 
60
- public send(request: AjaxRequest, params: Array<string>): Promise<AjaxDispatcherResponse> {
61
- const sentRequest = request.post(this.createRequestBody(params)).then(async (response: AjaxResponse): Promise<AjaxDispatcherResponse> => {
62
- return this.processResponse(await response.resolve());
60
+ public send<T extends AjaxDispatcherResponse = AjaxDispatcherResponse>(request: AjaxRequest, params: Array<string>): Promise<T> {
61
+ const sentRequest = request.post(this.createRequestBody(params)).then(async (response: AjaxResponse): Promise<T> => {
62
+ return this.processResponse(await response.resolve()) as T;
63
63
  });
64
64
  sentRequest.catch((reason: Error): void => {
65
65
  if (reason instanceof DOMException && reason.name === 'AbortError') {
@@ -65,7 +65,7 @@ export default class FormEngineValidation {
65
65
  */
66
66
  public static initialize(formEngine: typeof FormEngine): void {
67
67
  formEngineInstance = formEngine;
68
- formEngineInstance.formElement.querySelectorAll('.' + FormEngineValidation.errorClass).forEach((e: HTMLElement) => e.classList.remove(FormEngineValidation.errorClass));
68
+ formEngineInstance.formElement.querySelectorAll<HTMLElement>('.' + FormEngineValidation.errorClass).forEach((e) => e.classList.remove(FormEngineValidation.errorClass));
69
69
 
70
70
  // Initialize input fields
71
71
  FormEngineValidation.initializeInputFields();
@@ -91,7 +91,7 @@ export default class FormEngineValidation {
91
91
  * Initialize all input fields
92
92
  */
93
93
  public static initializeInputFields(): void {
94
- formEngineInstance.formElement.querySelectorAll(FormEngineValidation.inputSelector).forEach((visibleField: FormEngineFieldElement): void => {
94
+ formEngineInstance.formElement.querySelectorAll<FormEngineFieldElement>(FormEngineValidation.inputSelector).forEach((visibleField): void => {
95
95
  // ignore fields which already have been initialized
96
96
  if ('formengineInputInitialized' in visibleField.dataset) {
97
97
  return;
@@ -500,7 +500,7 @@ export default class FormEngineValidation {
500
500
  */
501
501
  public static validate(section?: Element): void {
502
502
  if (typeof section === 'undefined' || section instanceof Document) {
503
- formEngineInstance.formElement.querySelectorAll(FormEngineValidation.markerSelector + ', [role="tablist"] > .nav-item').forEach((tabMenuItem: HTMLElement): void => {
503
+ formEngineInstance.formElement.querySelectorAll<HTMLElement>(FormEngineValidation.markerSelector + ', [role="tablist"] > .nav-item').forEach((tabMenuItem): void => {
504
504
  tabMenuItem.classList.remove(FormEngineValidation.validationErrorClass);
505
505
  });
506
506
  }
@@ -568,8 +568,8 @@ export default class FormEngineValidation {
568
568
  * Find tab by field and mark it as has-validation-error
569
569
  */
570
570
  public static markParentTab(element: FormEngineFieldElement, isValid: boolean): void {
571
- const panes = DomHelper.parents(element, '.tab-pane');
572
- panes.forEach((pane: HTMLElement): void => {
571
+ const panes = DomHelper.parents<HTMLElement>(element, '.tab-pane');
572
+ panes.forEach((pane): void => {
573
573
  if (isValid) {
574
574
  // If incoming element is valid, check for errors in the same sheet
575
575
  isValid = pane.querySelector('.has-error') === null;
@@ -631,3 +631,9 @@ export default class FormEngineValidation {
631
631
  });
632
632
  }
633
633
  }
634
+
635
+ declare global {
636
+ interface HTMLElementEventMap {
637
+ 't3-formengine-postfieldvalidation': CustomEvent<PostValidationEvent>;
638
+ }
639
+ }
@@ -65,7 +65,7 @@ const enum FormAction {
65
65
  saveAndAddRecord = '_savedokaddrecord',
66
66
  }
67
67
 
68
- type OnChangeFieldHandlerCallback = (data: object, e: Event) => void;
68
+ type OnChangeFieldHandlerCallback = (data: never, e: Event) => void;
69
69
  type PreviewActionCallback = (targetName: string, previewUrl: string, $actionElement: JQuery, modal: ModalElement) => void;
70
70
  type NewActionCallback = (targetName: string, $actionElement: JQuery) => void;
71
71
  type DuplicateActionCallback = (targetName: string, $actionElement: JQuery) => void;
@@ -236,7 +236,7 @@ export default (function() {
236
236
  return Modal.advanced({
237
237
  type: Modal.types.iframe,
238
238
  content: FormEngine.browserUrl + '&' + (new URLSearchParams(queryParams)).toString(),
239
- size: Modal.sizes.large
239
+ size: Modal.sizes.full
240
240
  });
241
241
  };
242
242
 
@@ -318,7 +318,7 @@ export default (function() {
318
318
  }
319
319
 
320
320
  if (reenableOptions && typeof optionEl !== 'undefined') {
321
- optionEl.closest('select').querySelectorAll('[disabled]').forEach(function (disabledOption: HTMLOptionElement) {
321
+ optionEl.closest('select').querySelectorAll<HTMLOptionElement>('[disabled]').forEach(function (disabledOption) {
322
322
  disabledOption.classList.remove('hidden');
323
323
  disabledOption.disabled = false;
324
324
  FormEngine.enableOptGroup(disabledOption);
@@ -394,7 +394,7 @@ export default (function() {
394
394
  * @param {HTMLElement} originalFieldEl the hidden form field
395
395
  */
396
396
  FormEngine.updateHiddenFieldValueFromSelect = function(selectFieldEl: HTMLSelectElement, originalFieldEl: HTMLSelectElement): void {
397
- const selectedValues: string[] = Array.from(selectFieldEl.options).map((el: HTMLOptionElement) => el.value);
397
+ const selectedValues: string[] = Array.from(selectFieldEl.options).map((el) => el.value);
398
398
 
399
399
  // make a comma separated list, if it is a multi-select
400
400
  // set the values to the final hidden field
@@ -672,8 +672,8 @@ export default (function() {
672
672
  }
673
673
  };
674
674
 
675
- const minlengthElements = document.querySelectorAll('[minlength]:not([data-input-type="datetimepicker"]):not(.t3js-charcounter-min-initialized)');
676
- minlengthElements.forEach((field: HTMLInputElement|HTMLTextAreaElement) => {
675
+ const minlengthElements = document.querySelectorAll<HTMLInputElement|HTMLTextAreaElement>('[minlength]:not([data-input-type="datetimepicker"]):not(.t3js-charcounter-min-initialized)');
676
+ minlengthElements.forEach((field) => {
677
677
  field.addEventListener('focus', (event) => {
678
678
  const minCharacterCountLeft = FormEngine.getMinCharacterLeftCount(field);
679
679
  if (minCharacterCountLeft > 0) {
@@ -720,7 +720,7 @@ export default (function() {
720
720
  * Initialize input / text field "null" checkbox CSS overlay if no placeholder is set.
721
721
  */
722
722
  FormEngine.initializeNullNoPlaceholderCheckboxes = function(): void {
723
- document.querySelectorAll('.t3-form-field-eval-null-checkbox').forEach((el: HTMLElement) => {
723
+ document.querySelectorAll<HTMLElement>('.t3-form-field-eval-null-checkbox').forEach((el) => {
724
724
  // Add disabled class to "t3js-formengine-field-item" if the null checkbox is NOT set,
725
725
  // This activates a CSS overlay "disabling" the input field and everything around.
726
726
  const checkbox = el.querySelector('input[type="checkbox"]') as HTMLInputElement;
@@ -735,7 +735,7 @@ export default (function() {
735
735
  * Initialize input / text field "null" checkbox placeholder / real field if placeholder is set.
736
736
  */
737
737
  FormEngine.initializeNullWithPlaceholderCheckboxes = function(): void {
738
- document.querySelectorAll('.t3js-form-field-eval-null-placeholder-checkbox').forEach((el: HTMLElement) => {
738
+ document.querySelectorAll<HTMLElement>('.t3js-form-field-eval-null-placeholder-checkbox').forEach((el) => {
739
739
  FormEngine.toggleCheckboxField(el.querySelector('input[type="checkbox"]'), false);
740
740
  });
741
741
  };
@@ -787,7 +787,7 @@ export default (function() {
787
787
  * Disable the input field on load if localization state selector is set to "parent" or "source"
788
788
  */
789
789
  FormEngine.initializeLocalizationStateSelector = function(): void {
790
- document.querySelectorAll('.t3js-l10n-state-container').forEach((el: HTMLElement) => {
790
+ document.querySelectorAll<HTMLElement>('.t3js-l10n-state-container').forEach((el) => {
791
791
  const input = el.closest('.t3js-formengine-field-item')?.querySelector('[data-formengine-input-name]') as HTMLInputElement|null;
792
792
  if (input === undefined || input === null) {
793
793
  return;
@@ -1011,7 +1011,7 @@ export default (function() {
1011
1011
  items.forEach((item) => {
1012
1012
  const handler = onFieldChangeHandlers.get(item.name);
1013
1013
  if (handler instanceof Function) {
1014
- handler.call(null, item.data || null, evt);
1014
+ handler.call(null, (item.data || null) as never, evt);
1015
1015
  }
1016
1016
  });
1017
1017
  };
@@ -1033,8 +1033,12 @@ export default (function() {
1033
1033
  };
1034
1034
 
1035
1035
  /**
1036
- * Enables all doc header buttons.
1037
- * Called after FormEngine initialization is complete to restore button interactivity.
1036
+ * Releases the doc header button bar, which is rendered "inert" as long as the
1037
+ * ui block is shown, to prevent unintended operations while FormEngine is loading.
1038
+ * Called after FormEngine initialization is complete.
1039
+ *
1040
+ * The block is applied to the button bar as a whole, never to the single buttons,
1041
+ * so that the disabled state of individual buttons remains untouched.
1038
1042
  */
1039
1043
  FormEngine.enableDocHeaderButtons = function(): void {
1040
1044
  const docHeaderBar = document.querySelector('.t3js-module-docheader-buttons');
@@ -1042,16 +1046,8 @@ export default (function() {
1042
1046
  return;
1043
1047
  }
1044
1048
 
1045
- // Enable all buttons and links in the doc header
1046
- docHeaderBar.querySelectorAll('button, a, input[type="submit"]').forEach((element: HTMLElement): void => {
1047
- if (element instanceof HTMLButtonElement || element instanceof HTMLInputElement) {
1048
- element.disabled = false;
1049
- } else if (element instanceof HTMLAnchorElement) {
1050
- // For anchor elements - remove TYPO3 disabled state
1051
- element.classList.remove('disabled');
1052
- element.removeAttribute('aria-disabled');
1053
- }
1054
- });
1049
+ docHeaderBar.removeAttribute('inert');
1050
+ docHeaderBar.removeAttribute('aria-busy');
1055
1051
  };
1056
1052
 
1057
1053
  /**
@@ -1164,7 +1160,7 @@ export default (function() {
1164
1160
  }
1165
1161
  }
1166
1162
  const contentElement = document.createElement('p');
1167
- contentStrings.forEach((item: string, i: number): void => {
1163
+ contentStrings.forEach((item, i): void => {
1168
1164
  contentElement.append(item);
1169
1165
  if (i !== contentStrings.length - 1) {
1170
1166
  contentElement.append(document.createElement('br'));
@@ -228,7 +228,7 @@ class GlobalEventHandler {
228
228
  private assignHTMLFormChildElementValue(element: HTMLElement, value: string): void {
229
229
  const type: string = element.getAttribute('type');
230
230
  if (element instanceof HTMLSelectElement) {
231
- Array.from(element.options).some((option: HTMLOptionElement, index: number) => {
231
+ Array.from(element.options).some((option, index) => {
232
232
  if (option.value === value) {
233
233
  element.selectedIndex = index;
234
234
  return true;
@@ -34,7 +34,7 @@ export class HotkeyStruct {
34
34
  }
35
35
 
36
36
  public static fromHotkey(hotkey: string[]): HotkeyStruct {
37
- const nonModifierCodes = hotkey.filter((hotkeyPart: string) => !Object.values<string>(ModifierKeys).includes(hotkeyPart));
37
+ const nonModifierCodes = hotkey.filter((hotkeyPart) => !Object.values<string>(ModifierKeys).includes(hotkeyPart));
38
38
  if (nonModifierCodes.length > 1) {
39
39
  throw new Error('Cannot create HotkeyStruct with more than one non-modifier key, "' + nonModifierCodes.join('+') + '" given.');
40
40
  }
@@ -60,12 +60,12 @@ class Hotkeys {
60
60
  }
61
61
 
62
62
  public register(hotkey: Hotkey, handler: (e: KeyboardEvent) => void, options: Partial<Options> = {}): void {
63
- if (hotkey.filter((hotkeyPart: string) => !Object.values<string>(ModifierKeys).includes(hotkeyPart)).length === 0) {
63
+ if (hotkey.filter((hotkeyPart) => !Object.values<string>(ModifierKeys).includes(hotkeyPart)).length === 0) {
64
64
  throw new Error('Attempted to register hotkey "' + hotkey.join('+') + '" without a non-modifier key.');
65
65
  }
66
66
 
67
67
  // Normalize trigger
68
- hotkey = hotkey.map((h: string) => h.toLowerCase());
68
+ hotkey = hotkey.map((h) => h.toLowerCase());
69
69
 
70
70
  const mergedConfiguration: Options = { ...this.defaultOptions, ...options };
71
71
  if (!this.scopedHotkeyMap.has(mergedConfiguration.scope)) {
@@ -161,7 +161,7 @@ class Hotkeys {
161
161
  }
162
162
 
163
163
  // The standard requires to have modifier keys to be at first
164
- parts.sort((a: string, b: string): number => {
164
+ parts.sort((a, b): number => {
165
165
  const aIsModifierKey = Object.values<string>(ModifierKeys).includes(a);
166
166
  const bIsModifierKey = Object.values<string>(ModifierKeys).includes(b);
167
167
 
@@ -280,7 +280,7 @@ class ImageManipulation {
280
280
  /**
281
281
  * Assign EventListener to cropVariantTriggers
282
282
  */
283
- this.cropVariantTriggers.forEach((el: HTMLElement) =>
283
+ this.cropVariantTriggers.forEach((el) =>
284
284
  el.addEventListener('click', (e: Event): void => {
285
285
  /**
286
286
  * Is the current cropVariantTrigger is active, bail out.
@@ -341,8 +341,8 @@ class ImageManipulation {
341
341
  new RegularEvent('click', (): void => this.openPreview(this.data))
342
342
  .delegateTo(this.currentModal, 'button[name=preview]');
343
343
  } else {
344
- this.currentModal.querySelectorAll('button[name=preview]')
345
- .forEach((previewButton: HTMLElement) => previewButton.style.display = 'none');
344
+ this.currentModal.querySelectorAll<HTMLElement>('button[name=preview]')
345
+ .forEach((previewButton) => previewButton.style.display = 'none');
346
346
  }
347
347
 
348
348
  /**
@@ -400,8 +400,8 @@ class ImageManipulation {
400
400
  this.syncEnabled = target.checked;
401
401
  BrowserSession.set('sync-active-' + imageUid, this.syncEnabled ? 'true' : 'false');
402
402
  if (this.syncEnabled) {
403
- this.currentModal.querySelectorAll('.panel-button[data-exclude-from-sync="true"]')
404
- .forEach((e: HTMLElement) => e.setAttribute('disabled', 'disabled'));
403
+ this.currentModal.querySelectorAll<HTMLElement>('.panel-button[data-exclude-from-sync="true"]')
404
+ .forEach((e) => e.setAttribute('disabled', 'disabled'));
405
405
 
406
406
  // if the currently selected crop variant is now disabled, open the first available one.
407
407
  const isDisabled = this.currentModal.querySelector('.panel-button[data-crop-variant-id="' + this.currentCropVariant.id + '"][data-exclude-from-sync="true"]') !== null;
@@ -412,8 +412,8 @@ class ImageManipulation {
412
412
  }
413
413
  }
414
414
  } else {
415
- this.currentModal.querySelectorAll('.panel-button[data-exclude-from-sync]')
416
- .forEach((e: HTMLElement) => e.removeAttribute('disabled'));
415
+ this.currentModal.querySelectorAll<HTMLElement>('.panel-button[data-exclude-from-sync]')
416
+ .forEach((e) => e.removeAttribute('disabled'));
417
417
  }
418
418
  }
419
419
  ).delegateTo(this.currentModal, '#sync-crop-variants');
@@ -446,7 +446,7 @@ class ImageManipulation {
446
446
  this.imageOriginalSizeFactor = parseInt(image.dataset.originalWidth, 10) / imageData.naturalWidth;
447
447
 
448
448
  // iterate over the crop variants and set up their respective preview
449
- this.cropVariantTriggers.forEach((elem: HTMLElement): void => {
449
+ this.cropVariantTriggers.forEach((elem): void => {
450
450
  const cropVariantId: string = elem.dataset.cropVariantId;
451
451
  const cropArea: Area = this.convertRelativeToAbsoluteCropArea(
452
452
  this.data[cropVariantId].cropArea,
@@ -507,9 +507,9 @@ class ImageManipulation {
507
507
 
508
508
  if (this.syncAvailable && this.syncEnabled) {
509
509
  const data = this.data;
510
- Object.keys(this.data).forEach((cropVariant: string): void => {
510
+ Object.keys(this.data).forEach((cropVariant): void => {
511
511
  let trigger: HTMLElement = null;
512
- this.cropVariantTriggers.forEach((e: HTMLElement) => {
512
+ this.cropVariantTriggers.forEach((e) => {
513
513
  if (e.dataset.cropVariantId === cropVariant) {
514
514
  trigger = e;
515
515
  }
@@ -540,9 +540,9 @@ class ImageManipulation {
540
540
 
541
541
  if (this.syncAvailable && this.syncEnabled) {
542
542
  const data = this.data;
543
- Object.keys(this.data).forEach((cropVariant: string): void => {
543
+ Object.keys(this.data).forEach((cropVariant): void => {
544
544
  let trigger: HTMLElement = null;
545
- this.cropVariantTriggers.forEach((e: HTMLElement) => {
545
+ this.cropVariantTriggers.forEach((e) => {
546
546
  if (e.dataset.cropVariantId === cropVariant) {
547
547
  trigger = e;
548
548
  }
@@ -579,8 +579,8 @@ class ImageManipulation {
579
579
  this.setAspectRatio(selectedRatio);
580
580
  this.setCropArea(temp.cropArea);
581
581
  this.currentCropVariant = Object.assign({}, temp, cropVariant);
582
- this.cropBox?.querySelectorAll(this.coverAreaSelector)?.forEach((el: HTMLElement) => el.remove());
583
- this.cropBox?.querySelectorAll(this.focusAreaSelector)?.forEach((el: HTMLElement) => el.remove());
582
+ this.cropBox?.querySelectorAll<HTMLElement>(this.coverAreaSelector)?.forEach((el) => el.remove());
583
+ this.cropBox?.querySelectorAll<HTMLElement>(this.focusAreaSelector)?.forEach((el) => el.remove());
584
584
 
585
585
  // check if new cropVariant has focusArea
586
586
  if (cropVariant.focusArea) {
@@ -655,7 +655,7 @@ class ImageManipulation {
655
655
  * @param {Array<Area>} coverAreas - An array of areas to construct the cover area elements from
656
656
  */
657
657
  private initCoverAreas(container: HTMLElement, coverAreas: Area[]): void {
658
- coverAreas.forEach((coverArea: Area): void => {
658
+ coverAreas.forEach((coverArea): void => {
659
659
  const styles = {
660
660
  height: ImageManipulation.toCssPercent(coverArea.height),
661
661
  left: ImageManipulation.toCssPercent(coverArea.x),
@@ -782,7 +782,7 @@ class ImageManipulation {
782
782
  return false;
783
783
  }
784
784
  return coverAreas
785
- .some((coverArea: Area): boolean => {
785
+ .some((coverArea): boolean => {
786
786
  return focusArea.x < coverArea.x + coverArea.width
787
787
  && coverArea.x < focusArea.x + focusArea.width
788
788
  && focusArea.y < coverArea.y + coverArea.height
@@ -831,7 +831,7 @@ class ImageManipulation {
831
831
  const imageData: Cropper.ImageData = this.cropper.getImageData();
832
832
 
833
833
  // iterate over the crop variants and set up their respective preview
834
- Object.keys(data).forEach((cropVariantId: string) => {
834
+ Object.keys(data).forEach((cropVariantId) => {
835
835
  const cropVariant: CropVariant = data[cropVariantId];
836
836
  const cropData: Area = this.convertRelativeToAbsoluteCropArea(cropVariant.cropArea, imageData);
837
837
 
@@ -956,8 +956,8 @@ class ImageManipulation {
956
956
 
957
957
  private renderElements(template: TemplateResult, target: HTMLElement, selector?: string): HTMLElement|null {
958
958
  const nodes = renderNodes(template);
959
- const elements = Array.from(nodes).filter((node: Node): boolean => node instanceof HTMLElement);
960
- elements.forEach((element: HTMLElement) => target.appendChild(element));
959
+ const elements = Array.from(nodes).filter((node): node is HTMLElement => node instanceof HTMLElement);
960
+ elements.forEach((element) => target.appendChild(element));
961
961
  return selector ? target.querySelector(selector) : null;
962
962
  }
963
963
  }
@@ -0,0 +1,61 @@
1
+ /*
2
+ * This file is part of the TYPO3 CMS project.
3
+ *
4
+ * It is free software; you can redistribute it and/or modify it under
5
+ * the terms of the GNU General Public License, either version 2
6
+ * of the License, or any later version.
7
+ *
8
+ * For the full copyright and license information, please read the
9
+ * LICENSE.txt file that was distributed with this source code.
10
+ *
11
+ * The TYPO3 project - inspiring people to share!
12
+ */
13
+
14
+ /**
15
+ * Module: @typo3/backend/layout-module/column-restriction
16
+ *
17
+ * Client-side mirror of the server-side backend layout colPos restriction
18
+ * (\TYPO3\CMS\Backend\View\BackendLayoutView::isCTypeAllowedInColPosByPage()).
19
+ * Used by drag-drop and paste in the page layout module to avoid offering
20
+ * actions that the DataHandler would reject anyway.
21
+ *
22
+ * @internal
23
+ */
24
+
25
+ function toList(value: string | undefined): string[] {
26
+ return (value ?? '')
27
+ .split(',')
28
+ .map((item: string): string => item.trim())
29
+ .filter((item: string): boolean => item !== '');
30
+ }
31
+
32
+ /**
33
+ * Evaluates whether the given CType is allowed in the column described by the
34
+ * "data-allowed-content-types" / "data-disallowed-content-types" attributes.
35
+ * Mirrors the server-side logic: disallowed wins, an empty allow list allows all.
36
+ */
37
+ export function isContentTypeAllowedInColumn(cType: string, columnElement: HTMLElement | null): boolean {
38
+ if (columnElement === null) {
39
+ return true;
40
+ }
41
+
42
+ const disallowed = toList(columnElement.dataset.disallowedContentTypes);
43
+ if (disallowed.includes(cType)) {
44
+ return false;
45
+ }
46
+
47
+ const allowed = toList(columnElement.dataset.allowedContentTypes);
48
+ if (allowed.length > 0 && !allowed.includes(cType)) {
49
+ return false;
50
+ }
51
+
52
+ return true;
53
+ }
54
+
55
+ /**
56
+ * Resolves the column of the given element (nearest ancestor carrying a colPos)
57
+ * and evaluates whether the CType is allowed there.
58
+ */
59
+ export function isContentTypeAllowedForTarget(cType: string, target: HTMLElement): boolean {
60
+ return isContentTypeAllowedInColumn(cType, target.closest('[data-colpos]'));
61
+ }
@@ -22,6 +22,7 @@ import RegularEvent from '@typo3/core/event/regular-event';
22
22
  import { DataTransferTypes } from '@typo3/backend/enum/data-transfer-types';
23
23
  import BroadcastService from '@typo3/backend/broadcast-service';
24
24
  import { BroadcastMessage } from '@typo3/backend/broadcast-message';
25
+ import { isContentTypeAllowedInColumn } from '@typo3/backend/layout-module/column-restriction';
25
26
  import type ResponseInterface from '../ajax-data-handler/response-interface';
26
27
  import type { DragDropThumbnail, DragTooltipMetadata } from '@typo3/backend/drag-tooltip';
27
28
  import DragDropUtility from '@typo3/backend/utility/drag-drop-utility';
@@ -36,6 +37,7 @@ export type ContentElementDragDropData = {
36
37
  pid: number;
37
38
  uid: number;
38
39
  language: number;
40
+ cType: string;
39
41
  content: string;
40
42
  moveElementUrl: string;
41
43
  };
@@ -54,6 +56,8 @@ enum Classes {
54
56
  }
55
57
 
56
58
  class DragDrop {
59
+ private draggedContentType: string = '';
60
+
57
61
  constructor() {
58
62
  DocumentService.ready().then((): void => {
59
63
  this.initialize();
@@ -116,10 +120,13 @@ class DragDrop {
116
120
  protected onDragStart(event: DragEvent, target: HTMLElement): void {
117
121
  const content = target.closest(Identifiers.content) as HTMLElement;
118
122
 
123
+ this.draggedContentType = content.dataset.ctype ?? '';
124
+
119
125
  event.dataTransfer.setData(DataTransferTypes.content, JSON.stringify({
120
126
  pid: this.getCurrentPageId(),
121
127
  uid: parseInt(content.dataset.uid, 10),
122
128
  language: parseInt(content.dataset.languageUid, 10),
129
+ cType: this.draggedContentType,
123
130
  content: content.outerHTML,
124
131
  moveElementUrl: content.dataset.moveElementUrl,
125
132
  } as ContentElementDragDropData));
@@ -133,6 +140,7 @@ class DragDrop {
133
140
  }
134
141
 
135
142
  protected onDragEnd(): void {
143
+ this.draggedContentType = '';
136
144
  this.hideDropZones();
137
145
  }
138
146
 
@@ -176,6 +184,7 @@ class DragDrop {
176
184
 
177
185
  // the dragged elements language uid
178
186
  let language = contentElementDragDropData.language;
187
+ // Keep in sync with LanguageMarker::ALL_LANGUAGES on the PHP side.
179
188
  if (language !== -1) {
180
189
  // new elements language must be the same as the column the element is dropped in if element is not -1
181
190
  language = parseInt((dropContainer.closest('[data-language-uid]') as HTMLElement).dataset.languageUid, 10);
@@ -305,9 +314,9 @@ class DragDrop {
305
314
  iconIdentifier = contentElementIcon.dataset.identifier;
306
315
  }
307
316
 
308
- const contentImagePreviews = contentElement.querySelectorAll('.preview-thumbnails-element-image img');
317
+ const contentImagePreviews = contentElement.querySelectorAll<HTMLImageElement>('.preview-thumbnails-element-image img');
309
318
  if (contentImagePreviews.length > 0) {
310
- contentImagePreviews.forEach((image: HTMLImageElement): void => {
319
+ contentImagePreviews.forEach((image): void => {
311
320
  thumbnails.push({
312
321
  src: image.src,
313
322
  height: image.height,
@@ -334,7 +343,10 @@ class DragDrop {
334
343
  }
335
344
 
336
345
  protected showDropZones(): void {
337
- document.querySelectorAll(Identifiers.dropZone).forEach((element: HTMLElement): void => {
346
+ document.querySelectorAll<HTMLElement>(Identifiers.dropZone).forEach((element): void => {
347
+ if (!isContentTypeAllowedInColumn(this.draggedContentType, element.closest('[data-colpos]'))) {
348
+ return;
349
+ }
338
350
  element.hidden = false;
339
351
  const addContentButton = element.parentElement.querySelector(Identifiers.addContent) as HTMLElement;
340
352
  if (addContentButton !== null) {
@@ -345,7 +357,7 @@ class DragDrop {
345
357
  }
346
358
 
347
359
  protected hideDropZones(): void {
348
- document.querySelectorAll(Identifiers.dropZone).forEach((element: HTMLElement): void => {
360
+ document.querySelectorAll<HTMLElement>(Identifiers.dropZone).forEach((element): void => {
349
361
  element.hidden = true;
350
362
  const addContentButton = element.parentElement.querySelector(Identifiers.addContent) as HTMLElement;
351
363
  if (addContentButton !== null) {
@@ -24,18 +24,21 @@ import Severity from '../severity';
24
24
  import '@typo3/backend/element/icon-element';
25
25
  import { SeverityEnum } from '../enum/severity';
26
26
  import RegularEvent from '@typo3/core/event/regular-event';
27
+ import { isContentTypeAllowedForTarget } from '@typo3/backend/layout-module/column-restriction';
27
28
  import type ResponseInterface from '../ajax-data-handler/response-interface';
28
29
  import layoutLabels from '~labels/backend.layout';
29
30
 
30
31
  type PasteOptions = {
31
32
  itemOnClipboardUid: number;
32
33
  itemOnClipboardTitle: string;
34
+ itemOnClipboardCType: string;
33
35
  copyMode: string;
34
36
  };
35
37
 
36
38
  class Paste {
37
39
  private readonly itemOnClipboardUid: number = 0;
38
40
  private readonly itemOnClipboardTitle: string = '';
41
+ private readonly itemOnClipboardCType: string = '';
39
42
  private readonly copyMode: string = '';
40
43
  private readonly elementIdentifier: string = '.t3js-page-ce';
41
44
  private pasteAfterLinkTemplate: string = '';
@@ -47,6 +50,7 @@ class Paste {
47
50
  constructor(args: PasteOptions) {
48
51
  this.itemOnClipboardUid = args.itemOnClipboardUid;
49
52
  this.itemOnClipboardTitle = args.itemOnClipboardTitle;
53
+ this.itemOnClipboardCType = args.itemOnClipboardCType;
50
54
  this.copyMode = args.copyMode;
51
55
 
52
56
  DocumentService.ready().then((): void => {
@@ -95,7 +99,10 @@ class Paste {
95
99
  */
96
100
  private activatePasteIcons(): void {
97
101
  if (this.pasteAfterLinkTemplate && this.pasteIntoLinkTemplate) {
98
- document.querySelectorAll('.t3js-page-new-ce').forEach((el: HTMLElement): void => {
102
+ document.querySelectorAll<HTMLElement>('.t3js-page-new-ce').forEach((el): void => {
103
+ if (!isContentTypeAllowedForTarget(this.itemOnClipboardCType, el)) {
104
+ return;
105
+ }
99
106
  const template = el.parentElement.dataset.page ? this.pasteIntoLinkTemplate : this.pasteAfterLinkTemplate;
100
107
  el.append(document.createRange().createContextualFragment(template));
101
108
  });