typo3-typescript-sources 13.4.20 → 13.4.22

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.
@@ -178,6 +178,10 @@ jobs:
178
178
  cat > README.md << 'EOF'
179
179
  # TYPO3 TypeScript Sources
180
180
 
181
+ [![Sync and Publish](https://github.com/maikschneider/typo3-typescript-sources/actions/workflows/sync-and-publish.yml/badge.svg)](https://github.com/maikschneider/typo3-typescript-sources/actions/workflows/sync-and-publish.yml)
182
+ [![npm version](https://badge.fury.io/js/typo3-typescript-sources.svg)](https://www.npmjs.com/package/typo3-typescript-sources)
183
+ [![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-blue.svg)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
184
+
181
185
  This repository contains the TypeScript sources from the TYPO3 Core, automatically extracted and published to npm.
182
186
 
183
187
  ## 📦 Installation
@@ -225,12 +229,12 @@ jobs:
225
229
  Source: https://github.com/TYPO3/typo3/tree/${TYPO3_VERSION}
226
230
  Path: Build/Sources/TypeScript/"
227
231
 
228
- # Erstelle Tag
232
+ # Erstelle Tag (entferne v-Prefix falls vorhanden)
229
233
  NPM_VERSION="${TYPO3_VERSION#v}"
230
- git tag -a "v${NPM_VERSION}" -m "Release ${NPM_VERSION} from TYPO3 ${TYPO3_VERSION}"
234
+ git tag -a "${NPM_VERSION}" -m "Release ${NPM_VERSION} from TYPO3 ${TYPO3_VERSION}"
231
235
 
232
236
  git push origin main
233
- git push origin "v${NPM_VERSION}"
237
+ git push origin "${NPM_VERSION}"
234
238
 
235
239
  - name: Setup Node.js
236
240
  uses: actions/setup-node@v4
@@ -254,7 +258,7 @@ jobs:
254
258
  env:
255
259
  GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
256
260
  with:
257
- tag_name: v${{ needs.check-and-sync.outputs.typo3_version }}
261
+ tag_name: ${{ needs.check-and-sync.outputs.typo3_version }}
258
262
  release_name: TYPO3 ${{ needs.check-and-sync.outputs.typo3_version }}
259
263
  body: |
260
264
  Synchronized with TYPO3 Core ${{ needs.check-and-sync.outputs.typo3_version }}
package/.last-synced-tag CHANGED
@@ -1 +1 @@
1
- v13.4.20
1
+ v13.4.22
package/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # TYPO3 TypeScript Sources
2
2
 
3
+ [![Sync and Publish](https://github.com/maikschneider/typo3-typescript-sources/actions/workflows/sync-and-publish.yml/badge.svg)](https://github.com/maikschneider/typo3-typescript-sources/actions/workflows/sync-and-publish.yml)
4
+ [![npm version](https://badge.fury.io/js/typo3-typescript-sources.svg)](https://www.npmjs.com/package/typo3-typescript-sources)
5
+ [![License: GPL v2](https://img.shields.io/badge/License-GPL%20v2-blue.svg)](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
6
+
3
7
  This repository contains the TypeScript sources from the TYPO3 Core, automatically extracted and published to npm.
4
8
 
5
9
  ## 📦 Installation
@@ -12,7 +16,7 @@ npm install typo3-typescript-sources
12
16
 
13
17
  - **Original Repository:** [TYPO3/typo3](https://github.com/TYPO3/typo3)
14
18
  - **Source Path:** `Build/Sources/TypeScript/`
15
- - **Current TYPO3 Version:** `v13.4.20`
19
+ - **Current TYPO3 Version:** `v13.4.22`
16
20
 
17
21
  ## 📝 License
18
22
 
@@ -185,6 +185,8 @@ class DateTimePicker {
185
185
  // collides with using altInput – sigh.
186
186
  self.altInput.id = self.input.id;
187
187
  self.input.removeAttribute('id');
188
+ // Disable browser autofill to prevent interference with the datepicker
189
+ self.altInput.setAttribute('autocomplete', 'off');
188
190
  self.altInput.clearable();
189
191
  if (self.input.dataset.formengineInputName !== undefined) {
190
192
  self.altInput.dataset.formengineDatepickerRealInputName = self.input.dataset.formengineInputName;
@@ -196,6 +198,13 @@ class DateTimePicker {
196
198
  self.altInput.classList.toggle('has-error', !e.detail.isValid);
197
199
  }
198
200
  });
201
+
202
+ // Move the hidden input (self.input) to right after the clearable wrapper
203
+ // This prevents it from affecting :first-child CSS selectors while maintaining proper DOM order
204
+ const wrapper = self.altInput.closest('.form-control-clearable-wrapper');
205
+ if (wrapper !== null) {
206
+ wrapper.insertAdjacentElement('afterend', self.input);
207
+ }
199
208
  }
200
209
  },
201
210
  onChange: (dates: Date[], currentDateString: string, self: flatpickr.Instance): void => {
@@ -72,9 +72,13 @@ export default (function() {
72
72
  const attributeValues = LinkBrowser.getLinkAttributeValues();
73
73
  // encode link on server
74
74
  attributeValues.url = input;
75
+ const searchParams = new URLSearchParams();
76
+ for (const [key, value] of Object.entries(attributeValues)) {
77
+ searchParams.set(key, value);
78
+ }
75
79
 
76
80
  (new AjaxRequest(TYPO3.settings.ajaxUrls.link_browser_encodetypolink))
77
- .withQueryArguments(attributeValues)
81
+ .withQueryArguments(searchParams)
78
82
  .get()
79
83
  .then(async (response: AjaxResponse): Promise<void> => {
80
84
  const data: Response = await response.resolve();
@@ -96,6 +96,7 @@ export class CspReports extends LitElement {
96
96
  @state() suggestions: MutationSuggestion[] = [];
97
97
 
98
98
  private peripheralEvent: RegularEvent;
99
+ private previouslyFocusedRow: HTMLElement | null = null;
99
100
 
100
101
  public override connectedCallback(): void {
101
102
  super.connectedCallback();
@@ -114,6 +115,17 @@ export class CspReports extends LitElement {
114
115
  this.peripheralEvent?.release();
115
116
  }
116
117
 
118
+ protected override updated(changedProperties: Map<string, any>): void {
119
+ super.updated(changedProperties);
120
+ if (changedProperties.has('selectedReport') && this.selectedReport !== null) {
121
+ // Focus the details panel when a report is selected
122
+ this.updateComplete.then(() => {
123
+ const detailsPanel = this.querySelector('#report-details') as HTMLElement;
124
+ detailsPanel?.focus();
125
+ });
126
+ }
127
+ }
128
+
117
129
  protected override createRenderRoot(): HTMLElement | ShadowRoot {
118
130
  return this;
119
131
  }
@@ -148,7 +160,11 @@ export class CspReports extends LitElement {
148
160
  ` : nothing}
149
161
  ${this.reports.map((report: SummarizedCspReport) => html`
150
162
  <tr class=${classMap({ 'table-info': this.selectedReport === report })} data-mutation-group=${report.mutationHashes.join('-')}
151
- @click=${() => this.selectReport(report)}>
163
+ @click=${() => this.selectReport(report)}
164
+ @keydown=${(e: KeyboardEvent) => this.handleReportKeydown(e, report)}
165
+ tabindex="0"
166
+ role="button"
167
+ aria-label="${lll('label.showDetails', report.details.effectiveDirective, report.details.blockedUri) || 'Show details'}">
152
168
  <td>${report.created}</td>
153
169
  <td>${report.scope}</td>
154
170
  <td>
@@ -218,7 +234,7 @@ export class CspReports extends LitElement {
218
234
  const report = this.selectedReport;
219
235
  return html`${report ? html`
220
236
  <div class="infolist-info-record">
221
- <div class="card mb-0">
237
+ <div class="card mb-0" tabindex="-1" id="report-details" @keydown=${(e: KeyboardEvent) => this.handleDetailsKeydown(e)}>
222
238
  <div class="card-header">
223
239
  <h3>${ lll('label.details') || 'Details'}</h3>
224
240
  </div>
@@ -309,14 +325,75 @@ export class CspReports extends LitElement {
309
325
  private selectReport(report: SummarizedCspReport): void {
310
326
  this.suggestions = [];
311
327
  if (report !== null && this.selectedReport !== report) {
328
+ // Store the currently focused element before changing selection
329
+ this.previouslyFocusedRow = document.activeElement as HTMLElement;
312
330
  this.selectedReport = report;
313
331
  this.invokeHandleReportAction(report)
314
332
  .then((suggestions: MutationSuggestion[]) => this.suggestions = suggestions);
315
333
  } else {
316
334
  this.selectedReport = null;
335
+ // Restore focus to the previously focused row when closing
336
+ if (this.previouslyFocusedRow) {
337
+ this.updateComplete.then(() => {
338
+ this.previouslyFocusedRow?.focus();
339
+ this.previouslyFocusedRow = null;
340
+ });
341
+ }
342
+ }
343
+ }
344
+
345
+ private handleReportKeydown(event: KeyboardEvent, report: SummarizedCspReport): void {
346
+ // Handle Enter/Space to select report
347
+ if (event.key === 'Enter' || event.key === 'Space' || event.key === ' ') {
348
+ event.preventDefault();
349
+ this.selectReport(report);
350
+ return;
351
+ }
352
+
353
+ // Arrow key navigation
354
+ const currentRow = event.currentTarget as HTMLElement;
355
+ let targetRow: HTMLElement | null = null;
356
+
357
+ switch (event.key) {
358
+ case 'ArrowDown':
359
+ event.preventDefault();
360
+ targetRow = currentRow.nextElementSibling as HTMLElement;
361
+ break;
362
+ case 'ArrowUp':
363
+ event.preventDefault();
364
+ targetRow = currentRow.previousElementSibling as HTMLElement;
365
+ break;
366
+ case 'Home':
367
+ event.preventDefault();
368
+ targetRow = currentRow.parentElement?.firstElementChild as HTMLElement;
369
+ break;
370
+ case 'End':
371
+ event.preventDefault();
372
+ targetRow = currentRow.parentElement?.lastElementChild as HTMLElement;
373
+ break;
374
+ default:
375
+ return;
376
+ }
377
+
378
+ if (targetRow && targetRow.hasAttribute('tabindex')) {
379
+ targetRow.focus();
380
+ }
381
+ }
382
+
383
+ private handleDetailsKeydown(event: KeyboardEvent): void {
384
+ if (event.key === 'Escape') {
385
+ event.preventDefault();
386
+ this.selectReport(null);
317
387
  }
318
388
  }
319
389
 
390
+ private focusFirstReportRow(): void {
391
+ this.updateComplete.then(() => {
392
+ const firstRow = this.querySelector('tbody tr[tabindex="0"]') as HTMLElement;
393
+ firstRow?.focus();
394
+ });
395
+ }
396
+
320
397
  private selectScope(scope: string): void {
321
398
  this.selectedScope = scope;
322
399
  this.fetchReports();
@@ -363,14 +440,16 @@ export class CspReports extends LitElement {
363
440
  (new AjaxRequest(this.controlUri))
364
441
  .post({ action: 'muteReport', summaries: [report.summary] })
365
442
  .then((response: AjaxResponse) => response.resolve('application/json'))
366
- .then((response: CspReportUuids) => this.filterReports(...response.uuids));
443
+ .then((response: CspReportUuids) => this.filterReports(...response.uuids))
444
+ .then(() => this.focusFirstReportRow());
367
445
  }
368
446
 
369
447
  private invokeDeleteReportAction(report: SummarizedCspReport): void {
370
448
  (new AjaxRequest(this.controlUri))
371
449
  .post({ action: 'deleteReport', summaries: [report.summary] })
372
450
  .then((response: AjaxResponse) => response.resolve('application/json'))
373
- .then((response: CspReportUuids) => this.filterReports(...response.uuids));
451
+ .then((response: CspReportUuids) => this.filterReports(...response.uuids))
452
+ .then(() => this.focusFirstReportRow());
374
453
  }
375
454
 
376
455
  private invokeDeleteReportsAction(): void {
@@ -1117,22 +1117,26 @@ export class Tree extends LitElement {
1117
1117
 
1118
1118
  protected createNodeContentLabel(node: TreeNodeInterface): TemplateResult
1119
1119
  {
1120
- let label = (node.prefix || '') + node.name + (node.suffix || '');
1121
- // make a text node out of it, and strip out any HTML (this is because the return value uses html()
1122
- // instead of text() which is needed to avoid XSS in a page title
1123
- const labelNode = document.createElement('div');
1124
- labelNode.textContent = label;
1125
- label = labelNode.innerHTML;
1120
+ const label = (node.prefix || '') + node.name + (node.suffix || '');
1121
+
1122
+ let nodeContent: Array<TemplateResult|string> | string = label;
1126
1123
  if (this.searchTerm) {
1127
1124
  // Escape all meta characters of regular expressions: ( ) [ ] $ * + ? . { } / | ^ -
1128
- const regexp = new RegExp(this.searchTerm.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&'), 'gi');
1129
- label = label.replace(regexp, '<span class="node-highlight-text">$&</span>');
1125
+ // Also wrap the search term in a capture group so the regex split retains the separator
1126
+ const regexp = new RegExp(`(${this.searchTerm.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&')})`, 'gi');
1127
+ const parts = label.split(regexp);
1128
+ if (parts.length > 1) {
1129
+ // grouped regex splits contain the match in every second spot, to keep things short we use a modulo-flip with the index
1130
+ nodeContent = parts.map((part, index): TemplateResult | string =>
1131
+ (index % 2 === 1) ? html`<span class="node-highlight-text">${part}</span>` : part
1132
+ );
1133
+ }
1130
1134
  }
1131
1135
 
1132
1136
  return html`
1133
1137
  <div class="node-contentlabel">
1134
- <div class="node-name" .innerHTML="${label}"></div>
1135
- ${node.note ? html`<div class="node-note">${node.note}</div>` : nothing }
1138
+ <div class="node-name">${nodeContent}</div>
1139
+ ${node.note ? html`<div class="node-note">${node.note}</div>` : nothing}
1136
1140
  </div>`;
1137
1141
  }
1138
1142
 
@@ -279,6 +279,7 @@ export class ExtensionMatrixElement extends LitElement {
279
279
  <button class="btn btn-default" title=${tooltip}
280
280
  @click=${() => this.dispatchEvent(new CustomEvent<DownloadPacksEvent>('download-packs', eventData))}>
281
281
  <typo3-backend-icon identifier="actions-download" size="small"></typo3-backend-icon>
282
+ ${language}
282
283
  </button>
283
284
  </td>
284
285
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typo3-typescript-sources",
3
- "version": "13.4.20",
3
+ "version": "13.4.22",
4
4
  "description": "TYPO3 Core TypeScript Sources - Extracted from TYPO3/typo3 repository",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "typo3": {
30
30
  "sourceRepository": "https://github.com/TYPO3/typo3",
31
- "sourceTag": "v13.4.20",
31
+ "sourceTag": "v13.4.22",
32
32
  "sourcePath": "Build/Sources/TypeScript"
33
33
  }
34
34
  }
@@ -254,6 +254,7 @@ export class Typo3UnlinkCommand extends Core.Command {
254
254
  writer.removeAttribute('linkTarget', range);
255
255
  writer.removeAttribute('linkTitle', range);
256
256
  writer.removeAttribute('linkRel', range);
257
+ writer.removeAttribute('linkDataRteError', range);
257
258
  }
258
259
  });
259
260
  }
@@ -735,11 +736,43 @@ export class Typo3LinkUI extends Core.Plugin {
735
736
  return position.getAncestors().find((ancestor: any) => LinkUtils.isLinkElement(ancestor));
736
737
  }
737
738
 
739
+ /**
740
+ * Expands the current selection to encompass the entire link element.
741
+ * This prevents link splitting when editing a link with partial text selection.
742
+ */
743
+ private expandSelectionToFullLink(): void {
744
+ const model = this.editor.model;
745
+ const selection = model.document.selection;
746
+
747
+ // Only expand if we have a link attribute in the selection
748
+ if (!selection.hasAttribute('linkHref')) {
749
+ return;
750
+ }
751
+
752
+ const linkHref = selection.getAttribute('linkHref') as string;
753
+ if (!linkHref) {
754
+ return;
755
+ }
756
+
757
+ model.change(writer => {
758
+ const position = selection.getFirstPosition();
759
+ // Find the full range of the link using the linkHref attribute
760
+ const linkRange = Typing.findAttributeRange(position, 'linkHref', linkHref, model);
761
+
762
+ if (linkRange) {
763
+ // Expand selection to cover the entire link
764
+ writer.setSelection(linkRange);
765
+ }
766
+ });
767
+ }
768
+
738
769
  private openLinkBrowser(editor: Core.Editor): void {
739
770
  const linkCommand = editor.commands.get('link') as unknown as Typo3LinkCommand;
740
771
  let additionalParameters = '';
741
772
 
773
+ // If editing an existing link, expand selection to full link first
742
774
  if (linkCommand.value) {
775
+ this.expandSelectionToFullLink();
743
776
  additionalParameters += '&P[curUrl][url]=' + encodeURIComponent(linkCommand.value);
744
777
  for (const [attr, value] of Object.entries(linkCommand.attrs)) {
745
778
  additionalParameters += '&P[curUrl][' + encodeURIComponent(attr) + ']=' + encodeURIComponent(value);
@@ -40,6 +40,7 @@ enum Identifiers {
40
40
  * buttons, preview mode selector, preview slider and so on.
41
41
  */
42
42
  class Preview extends Workspaces {
43
+ override ajaxRoute: string = 'workspace_preview';
43
44
  private currentSlidePosition: number = 100;
44
45
  private readonly elements: { [key: string]: HTMLElement } = {};
45
46
 
@@ -19,6 +19,8 @@ import { default as Modal, type ModalElement } from '@typo3/backend/modal';
19
19
  import { html } from 'lit';
20
20
 
21
21
  export default class Workspaces {
22
+ protected readonly ajaxRoute: string = 'workspace_dispatch';
23
+
22
24
  /**
23
25
  * Renders the send to stage window
24
26
  * @param {Object} response
@@ -70,7 +72,7 @@ export default class Workspaces {
70
72
  protected sendRemoteRequest(payload: object, progressContainer: string = '#workspace-content-wrapper'): Promise<AjaxResponse> {
71
73
  NProgress.configure({ parent: progressContainer, showSpinner: false });
72
74
  NProgress.start();
73
- return (new AjaxRequest(TYPO3.settings.ajaxUrls.workspace_dispatch)).post(
75
+ return (new AjaxRequest(TYPO3.settings.ajaxUrls[this.ajaxRoute])).post(
74
76
  payload,
75
77
  {
76
78
  headers: {