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.
- package/.github/workflows/sync-and-publish.yml +240 -191
- package/.last-synced-tag +1 -1
- package/README.md +13 -5
- package/adminpanel/admin-panel.ts +22 -22
- package/adminpanel/modules/cache.ts +3 -3
- package/backend/action-dispatcher.ts +8 -3
- package/backend/bookmark/bookmark-store.ts +5 -5
- package/backend/clipboard-panel.ts +2 -2
- package/backend/code-editor/element/code-mirror-element.ts +1 -1
- package/backend/code-editor/language/typoscript.ts +3 -3
- package/backend/code-editor/stream-parser/typoscript.ts +2 -2
- package/backend/color-picker.ts +2 -2
- package/backend/column-selector-button.ts +11 -8
- package/backend/context-help.ts +3 -3
- package/backend/context-menu.ts +8 -3
- package/backend/drag-tooltip.ts +9 -4
- package/backend/drag-uploader.ts +81 -89
- package/backend/element/draggable-resizable-element.ts +18 -12
- package/backend/element/icon-element.ts +2 -2
- package/backend/element/thumbnail-element.ts +2 -2
- package/backend/element-browser.ts +1 -1
- package/backend/event/consumer-scope.ts +2 -2
- package/backend/event/interaction-request-assignment.ts +1 -1
- package/backend/event/interaction-request-map.ts +3 -3
- package/backend/form-engine/container/flex-form-container-container.ts +2 -2
- package/backend/form-engine/container/flex-form-section-container.ts +1 -1
- package/backend/form-engine/container/inline-control-container.ts +14 -14
- package/backend/form-engine/element/abstract-sortable-select-items.ts +3 -3
- package/backend/form-engine/element/color-element.ts +1 -1
- package/backend/form-engine/element/datetime-element.ts +1 -1
- package/backend/form-engine/element/extra/char-counter.ts +2 -2
- package/backend/form-engine/element/extra/select-box-filter.ts +1 -1
- package/backend/form-engine/element/mfa-info-element.ts +2 -2
- package/backend/form-engine/element/online-media-form-element.ts +8 -4
- package/backend/form-engine/element/select-multiple-side-by-side-element.ts +2 -2
- package/backend/form-engine/element/select-tree.ts +3 -3
- package/backend/form-engine/element/slug-element.ts +7 -7
- package/backend/form-engine/element/suggest/result-container.ts +1 -1
- package/backend/form-engine/element/table-wizard-element.ts +4 -4
- package/backend/form-engine/inline-relation/ajax-dispatcher.ts +3 -3
- package/backend/form-engine-validation.ts +11 -5
- package/backend/form-engine.ts +19 -23
- package/backend/global-event-handler.ts +1 -1
- package/backend/hotkeys/hotkey-struct.ts +1 -1
- package/backend/hotkeys.ts +3 -3
- package/backend/image-manipulation.ts +19 -19
- package/backend/layout-module/column-restriction.ts +61 -0
- package/backend/layout-module/drag-drop.ts +16 -4
- package/backend/layout-module/paste.ts +8 -1
- package/backend/live-search/element/result/item/action/action-container.ts +1 -1
- package/backend/live-search/element/result/item/item-container.ts +4 -4
- package/backend/live-search/element/result/result-container.ts +5 -0
- package/backend/live-search/element/result/result-pagination.ts +1 -1
- package/backend/live-search/live-search.ts +2 -2
- package/backend/localization/label-provider.ts +4 -4
- package/backend/localization/localization-wizard.ts +4 -4
- package/backend/localization/steps/content-record-selection-step.ts +8 -8
- package/backend/localization/steps/handler-selection-step.ts +4 -4
- package/backend/localization/steps/mode-step.ts +4 -4
- package/backend/localization/steps/source-language-step.ts +4 -4
- package/backend/localization/steps/target-language-step.ts +4 -4
- package/backend/login-refresh.ts +1 -9
- package/backend/login.ts +2 -2
- package/backend/modal.ts +38 -5
- package/backend/module/iframe.ts +10 -0
- package/backend/module/router.ts +10 -0
- package/backend/module-menu.ts +3 -3
- package/backend/multi-record-selection-action.ts +1 -1
- package/backend/multi-record-selection-delete-action.ts +1 -1
- package/backend/multi-record-selection.ts +14 -14
- package/backend/multi-step-wizard.ts +9 -2
- package/backend/new-multiple-pages.ts +2 -2
- package/backend/new-record-wizard.ts +15 -15
- package/backend/notification.ts +90 -6
- package/backend/online-media.ts +1 -3
- package/backend/page-wizard/page-wizard.ts +2 -2
- package/backend/page-wizard/steps/doktype-step.ts +5 -5
- package/backend/pagetsconfig/pagetsconfig-includes.ts +1 -1
- package/backend/popover.ts +5 -5
- package/backend/record-download-button.ts +5 -6
- package/backend/recordlist.ts +11 -6
- package/backend/security/element/csp-reports.ts +8 -8
- package/backend/setup-module.ts +2 -2
- package/backend/sortable-table.ts +10 -4
- package/backend/storage/abstract-client-storage.ts +2 -2
- package/backend/storage/module-state-storage.ts +2 -2
- package/backend/tests/backend/utility/dom-helper-test.ts +2 -2
- package/backend/tests/fixtures/modal-autofocus.html +1 -0
- package/backend/tests/form-engine/element/extra/select-box-filter-test.ts +3 -3
- package/backend/tests/form-engine-validation-test.ts +1 -1
- package/backend/tests/modal-test.ts +91 -0
- package/backend/tests/notification-test.ts +176 -0
- package/backend/tests/tree/tree-test.ts +41 -0
- package/backend/tree/file-storage-browser.ts +1 -1
- package/backend/tree/file-storage-tree-container.ts +4 -4
- package/backend/tree/page-browser.ts +1 -1
- package/backend/tree/page-position-select.ts +1 -1
- package/backend/tree/page-tree-element.ts +9 -2
- package/backend/tree/page-tree.ts +7 -1
- package/backend/tree/tree-module-state.ts +5 -5
- package/backend/tree/tree.ts +15 -8
- package/backend/user-pass-login.ts +2 -2
- package/backend/utility/collapse-state-persister.ts +1 -1
- package/backend/utility/collapse-state-search.ts +6 -6
- package/backend/utility/dom-helper.ts +3 -3
- package/backend/utility.ts +6 -6
- package/backend/viewport/content-navigation.ts +5 -0
- package/backend/viewport/navigation-container.ts +1 -1
- package/backend/viewport/toolbar.ts +3 -3
- package/backend/window-manager.ts +2 -2
- package/backend/wizard/events/auto-advance-event.ts +6 -0
- package/backend/wizard/events/before-next-step-event.ts +6 -0
- package/backend/wizard/events/step-summary-event.ts +6 -0
- package/backend/wizard/move-content-element.ts +1 -1
- package/backend/wizard/wizard.ts +8 -10
- package/belog/backend-log.ts +2 -2
- package/beuser/permissions.ts +2 -2
- package/core/ajax/ajax-response.ts +1 -1
- package/core/ajax/input-transformer.ts +1 -1
- package/core/event/debounce-event.ts +4 -4
- package/core/event/event-interface.ts +3 -3
- package/core/event/regular-event.ts +7 -7
- package/core/event/request-animation-frame-event.ts +4 -4
- package/core/event/throttle-event.ts +4 -4
- package/core/java-script-item-processor.ts +6 -0
- package/core/referrer-refresh.ts +2 -2
- package/core/security-utility.ts +1 -1
- package/dashboard/dashboard-wizard.ts +2 -2
- package/dashboard/dashboard.ts +8 -13
- package/dashboard/wizard/steps/preset-step.ts +3 -3
- package/extensionmanager/main.ts +7 -7
- package/filelist/browse-files.ts +1 -1
- package/filelist/browse-folders.ts +2 -2
- package/filelist/file-list-dragdrop.ts +5 -5
- package/filelist/file-list-rename-handler.ts +3 -6
- package/filelist/file-list-replace-handler.ts +3 -6
- package/filelist/file-list-transfer-handler.ts +2 -2
- package/filelist/file-list.ts +8 -9
- package/form/backend/form-editor/component/date-editor.ts +4 -0
- package/form/backend/form-editor/component/form-element-selector.ts +4 -0
- package/form/backend/form-editor/inspector-component.ts +6 -6
- package/form/backend/form-editor/mediator.ts +36 -36
- package/form/backend/form-editor/modals-component.ts +115 -6
- package/form/backend/form-editor/stage-component.ts +4 -4
- package/form/backend/form-editor/view-model.ts +7 -7
- package/form/backend/form-manager/view-model.ts +5 -5
- package/form/backend/form-wizard/form-wizard.ts +2 -2
- package/form/backend/form-wizard/steps/storage-step.ts +1 -1
- package/impexp/import-export.ts +1 -1
- package/info/translation-status.ts +2 -2
- package/install/ajax/ajax-queue.ts +1 -1
- package/install/module/abstract-interactable-module.ts +2 -2
- package/install/module/environment/image-processing.ts +4 -4
- package/install/module/environment/mail-test.ts +2 -2
- package/install/module/maintenance/clear-tables.ts +3 -3
- package/install/module/maintenance/clear-typo3temp-files.ts +8 -8
- package/install/module/maintenance/create-admin.ts +3 -3
- package/install/module/maintenance/database-analyzer.ts +9 -7
- package/install/module/maintenance/language-packs.ts +4 -4
- package/install/module/maintenance/reference-index.ts +1 -1
- package/install/module/settings/change-install-tool-password.ts +1 -1
- package/install/module/settings/extension-configuration.ts +5 -5
- package/install/module/settings/features.ts +1 -1
- package/install/module/settings/local-configuration.ts +5 -5
- package/install/module/settings/presets.ts +2 -2
- package/install/module/settings/system-maintainer.ts +1 -1
- package/install/module/upgrade/extension-scanner.ts +5 -5
- package/install/module/upgrade/tca-migrations-check.ts +1 -1
- package/install/module/upgrade/upgrade-docs.ts +16 -16
- package/install/module/upgrade/upgrade-wizards.ts +7 -8
- package/install/renderable/language-packs.ts +8 -2
- package/lowlevel/query-generator.ts +2 -2
- package/package.json +2 -2
- package/redirects/event-handler.ts +6 -0
- package/rte-ckeditor/ckeditor5.ts +7 -3
- package/rte-ckeditor/legacy/ckeditor5-bundle.ts +0 -1
- package/rte-ckeditor/plugin/typo3-link.ts +7 -9
- package/rte-ckeditor/rte-link-browser.ts +1 -1
- package/rte-ckeditor/tests/ckeditor5-test.ts +68 -0
- package/scheduler/form-engine/element/timing-options-element.ts +1 -1
- package/scheduler/scheduler-add-group.ts +4 -12
- package/scheduler/scheduler.ts +2 -2
- package/styleguide/checkbox-demo.ts +1 -1
- package/tstemplate/template-analyzer.ts +1 -1
- package/viewpage/main.ts +1 -1
- package/workspaces/backend.ts +5 -5
- package/workspaces/preview.ts +41 -2
- package/workspaces/renderable/record-table.ts +95 -78
- package/workspaces/renderable/send-to-stage-form.ts +1 -1
|
@@ -31,9 +31,9 @@ describe('@typo3/backend/utility/dom-helper', () => {
|
|
|
31
31
|
const wrapperElement = document.createElement('div');
|
|
32
32
|
wrapperElement.append(document.createRange().createContextualFragment(markup));
|
|
33
33
|
|
|
34
|
-
const parents = DomHelper.parents(wrapperElement.querySelector('#enter-here'), '.dummy-matching-element');
|
|
34
|
+
const parents = DomHelper.parents<HTMLElement>(wrapperElement.querySelector('#enter-here'), '.dummy-matching-element');
|
|
35
35
|
expect(parents).to.length(3);
|
|
36
|
-
expect(parents.map((el
|
|
36
|
+
expect(parents.map((el) => el.tagName)).to.have.ordered.members(['SPAN', 'P', 'DIV']);
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
it('nextAll() returns a proper list of all next elements', () => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<form id="modal-ajax-form"><input name="filter" autofocus></form>
|
|
@@ -39,9 +39,9 @@ describe('@typo3/backend/form-engine/element/extra/select-box-filter', () => {
|
|
|
39
39
|
});
|
|
40
40
|
|
|
41
41
|
const visibleOptions = (): string[] => Array
|
|
42
|
-
.from(root.querySelectorAll('select[multiple] option'))
|
|
43
|
-
.filter((option
|
|
44
|
-
.map((option
|
|
42
|
+
.from(root.querySelectorAll<HTMLOptionElement>('select[multiple] option'))
|
|
43
|
+
.filter((option): boolean => !option.hidden)
|
|
44
|
+
.map((option): string => option.textContent);
|
|
45
45
|
|
|
46
46
|
const filterBy = (value: string): void => {
|
|
47
47
|
filterField.value = value;
|
|
@@ -35,7 +35,7 @@ type Values = any;
|
|
|
35
35
|
* @param {Function|Array|Object} values
|
|
36
36
|
* @param {Function} func
|
|
37
37
|
*/
|
|
38
|
-
function using(values: (() => Values)|Values, func: (...args:
|
|
38
|
+
function using(values: (() => Values)|Values, func: (...args: any[]) => void): void {
|
|
39
39
|
if (values instanceof Function) {
|
|
40
40
|
values = values();
|
|
41
41
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
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
|
+
import { html } from 'lit';
|
|
15
|
+
import { expect } from '@open-wc/testing';
|
|
16
|
+
import Modal, { type ModalElement } from '@typo3/backend/modal.js';
|
|
17
|
+
import type { } from 'mocha';
|
|
18
|
+
|
|
19
|
+
describe('@typo3/backend/modal', () => {
|
|
20
|
+
let modal: ModalElement;
|
|
21
|
+
const nativeRequestAnimationFrame = window.requestAnimationFrame;
|
|
22
|
+
|
|
23
|
+
const shown = (element: ModalElement): Promise<void> => new Promise(resolve => {
|
|
24
|
+
element.addEventListener('typo3-modal-shown', () => resolve(), { once: true });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
before(() => {
|
|
28
|
+
// The test page runs in the background, animation frames never fire there
|
|
29
|
+
window.requestAnimationFrame = (callback: FrameRequestCallback): number => window.setTimeout(() => callback(performance.now()), 0);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
after(() => {
|
|
33
|
+
window.requestAnimationFrame = nativeRequestAnimationFrame;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
// The dialog close event does not fire in the background either
|
|
38
|
+
modal?.hideModal();
|
|
39
|
+
modal?.remove();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('binds a footer button to the form given by its configuration', async () => {
|
|
43
|
+
modal = Modal.advanced({
|
|
44
|
+
content: html`<form id="modal-test-form"><input name="username"><input name="password"></form>`,
|
|
45
|
+
buttons: [{ text: 'ok', btnClass: 'btn-primary', form: 'modal-test-form' }],
|
|
46
|
+
});
|
|
47
|
+
await shown(modal);
|
|
48
|
+
|
|
49
|
+
const button = modal.querySelector<HTMLButtonElement>('.modal-footer button');
|
|
50
|
+
expect(button.type).to.equal('submit');
|
|
51
|
+
expect(button.form?.id).to.equal('modal-test-form');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('keeps the focus on an autofocus field instead of the active button', async () => {
|
|
55
|
+
modal = Modal.advanced({
|
|
56
|
+
content: html`<form><input name="password" autofocus></form>`,
|
|
57
|
+
buttons: [{ text: 'ok', btnClass: 'btn-primary', active: true }],
|
|
58
|
+
});
|
|
59
|
+
await shown(modal);
|
|
60
|
+
|
|
61
|
+
expect(document.activeElement.matches('input[name="password"]')).to.be.true;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('focuses an autofocus field of ajax content loaded after the modal was shown', async () => {
|
|
65
|
+
const nativeFetch = window.fetch;
|
|
66
|
+
window.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
|
67
|
+
await shown(modal);
|
|
68
|
+
return nativeFetch(input, init);
|
|
69
|
+
};
|
|
70
|
+
modal = Modal.advanced({
|
|
71
|
+
type: Modal.types.ajax,
|
|
72
|
+
content: '/Build/Sources/TypeScript/backend/tests/fixtures/modal-autofocus.html',
|
|
73
|
+
buttons: [{ text: 'ok', btnClass: 'btn-primary', active: true }],
|
|
74
|
+
});
|
|
75
|
+
const loaded = new Promise<void>(resolve => modal.addEventListener('modal-loaded', () => resolve(), { once: true }));
|
|
76
|
+
await loaded;
|
|
77
|
+
window.fetch = nativeFetch;
|
|
78
|
+
|
|
79
|
+
expect(document.activeElement.matches('input[name="filter"]')).to.be.true;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('focuses the active button when the body has no autofocus field', async () => {
|
|
83
|
+
modal = Modal.advanced({
|
|
84
|
+
content: html`<form><input name="password"></form>`,
|
|
85
|
+
buttons: [{ text: 'ok', btnClass: 'btn-primary', active: true }],
|
|
86
|
+
});
|
|
87
|
+
await shown(modal);
|
|
88
|
+
|
|
89
|
+
expect(document.activeElement.matches('.modal-footer button')).to.be.true;
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -106,6 +106,124 @@ describe('@typo3/backend/notification:', () => {
|
|
|
106
106
|
}
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
+
describe('auto dismissal', () => {
|
|
110
|
+
async function showNotification(): Promise<{ element: LitElement, isCleared: () => boolean }> {
|
|
111
|
+
Notification.success('Title', 'Message', 1);
|
|
112
|
+
const element = document.querySelector('#alert-container typo3-notification-message:last-child') as LitElement;
|
|
113
|
+
await element.updateComplete;
|
|
114
|
+
let cleared = false;
|
|
115
|
+
element.addEventListener('typo3-notification-clear', (): void => {
|
|
116
|
+
cleared = true;
|
|
117
|
+
});
|
|
118
|
+
// Notifications appear with a delay, the countdown starts afterwards
|
|
119
|
+
await clock.tickAsync(200);
|
|
120
|
+
return { element, isCleared: (): boolean => cleared };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
it('pauses while the notification is pointed at', async () => {
|
|
124
|
+
const { element, isCleared } = await showNotification();
|
|
125
|
+
|
|
126
|
+
element.dispatchEvent(new MouseEvent('mouseenter'));
|
|
127
|
+
await clock.tickAsync(5000);
|
|
128
|
+
expect(isCleared()).to.be.false;
|
|
129
|
+
|
|
130
|
+
element.dispatchEvent(new MouseEvent('mouseleave'));
|
|
131
|
+
await clock.tickAsync(1100);
|
|
132
|
+
expect(isCleared()).to.be.true;
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('pauses while the notification has the focus', async () => {
|
|
136
|
+
const { element, isCleared } = await showNotification();
|
|
137
|
+
|
|
138
|
+
element.dispatchEvent(new FocusEvent('focusin'));
|
|
139
|
+
await clock.tickAsync(5000);
|
|
140
|
+
expect(isCleared()).to.be.false;
|
|
141
|
+
|
|
142
|
+
element.dispatchEvent(new FocusEvent('focusout'));
|
|
143
|
+
await clock.tickAsync(1100);
|
|
144
|
+
expect(isCleared()).to.be.true;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('continues with the time that was left', async () => {
|
|
148
|
+
const { element, isCleared } = await showNotification();
|
|
149
|
+
|
|
150
|
+
await clock.tickAsync(600);
|
|
151
|
+
element.dispatchEvent(new MouseEvent('mouseenter'));
|
|
152
|
+
await clock.tickAsync(5000);
|
|
153
|
+
element.dispatchEvent(new MouseEvent('mouseleave'));
|
|
154
|
+
|
|
155
|
+
await clock.tickAsync(300);
|
|
156
|
+
expect(isCleared()).to.be.false;
|
|
157
|
+
await clock.tickAsync(200);
|
|
158
|
+
expect(isCleared()).to.be.true;
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('stays paused when the pointer leaves while the focus is still inside', async () => {
|
|
162
|
+
const { element, isCleared } = await showNotification();
|
|
163
|
+
|
|
164
|
+
element.dispatchEvent(new FocusEvent('focusin'));
|
|
165
|
+
element.dispatchEvent(new MouseEvent('mouseenter'));
|
|
166
|
+
element.dispatchEvent(new MouseEvent('mouseleave'));
|
|
167
|
+
await clock.tickAsync(5000);
|
|
168
|
+
expect(isCleared()).to.be.false;
|
|
169
|
+
|
|
170
|
+
element.dispatchEvent(new FocusEvent('focusout'));
|
|
171
|
+
await clock.tickAsync(1100);
|
|
172
|
+
expect(isCleared()).to.be.true;
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('stays paused when the pointer rests on the notification before it appeared', async () => {
|
|
176
|
+
Notification.success('Title', 'Message', 1);
|
|
177
|
+
const element = document.querySelector('#alert-container typo3-notification-message:last-child') as LitElement;
|
|
178
|
+
await element.updateComplete;
|
|
179
|
+
let cleared = false;
|
|
180
|
+
element.addEventListener('typo3-notification-clear', (): void => {
|
|
181
|
+
cleared = true;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
element.dispatchEvent(new MouseEvent('mouseenter'));
|
|
185
|
+
await clock.tickAsync(5000);
|
|
186
|
+
expect(cleared).to.be.false;
|
|
187
|
+
|
|
188
|
+
element.dispatchEvent(new MouseEvent('mouseleave'));
|
|
189
|
+
await clock.tickAsync(1100);
|
|
190
|
+
expect(cleared).to.be.true;
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('is not cleared by the countdown while an action is executing', async () => {
|
|
194
|
+
let finishAction: () => void;
|
|
195
|
+
Notification.success('Title', 'Message', 1, [
|
|
196
|
+
{
|
|
197
|
+
label: 'Undo',
|
|
198
|
+
action: new DeferredAction((): Promise<void> => new Promise((resolve): void => {
|
|
199
|
+
finishAction = resolve;
|
|
200
|
+
})),
|
|
201
|
+
},
|
|
202
|
+
]);
|
|
203
|
+
const element = document.querySelector('#alert-container typo3-notification-message:last-child') as LitElement;
|
|
204
|
+
await element.updateComplete;
|
|
205
|
+
let cleared = false;
|
|
206
|
+
element.addEventListener('typo3-notification-clear', (): void => {
|
|
207
|
+
cleared = true;
|
|
208
|
+
});
|
|
209
|
+
await clock.tickAsync(200);
|
|
210
|
+
|
|
211
|
+
(element.querySelector('.alert-actions a') as HTMLAnchorElement).click();
|
|
212
|
+
await clock.tickAsync(5000);
|
|
213
|
+
expect(cleared).to.be.false;
|
|
214
|
+
|
|
215
|
+
finishAction();
|
|
216
|
+
await clock.tickAsync(0);
|
|
217
|
+
expect(cleared).to.be.true;
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('announces notifications to assistive technology', async () => {
|
|
222
|
+
Notification.info('Info message', 'Some text', 1);
|
|
223
|
+
await (document.querySelector('#alert-container typo3-notification-message:last-child') as LitElement).updateComplete;
|
|
224
|
+
expect(document.querySelector('#alert-container .alert-list').getAttribute('aria-live')).to.equal('polite');
|
|
225
|
+
});
|
|
226
|
+
|
|
109
227
|
it('can render action buttons', async () => {
|
|
110
228
|
Notification.info(
|
|
111
229
|
'Info message',
|
|
@@ -158,4 +276,62 @@ describe('@typo3/backend/notification:', () => {
|
|
|
158
276
|
await (document.querySelector('#alert-container typo3-notification-message:last-child') as LitElement).updateComplete;
|
|
159
277
|
expect(called).to.be.true;
|
|
160
278
|
});
|
|
279
|
+
|
|
280
|
+
it('deferred action is called with its link', async () => {
|
|
281
|
+
let called = false;
|
|
282
|
+
Notification.info('Info message', 'Some text', 0, [
|
|
283
|
+
{
|
|
284
|
+
label: 'My deferred action',
|
|
285
|
+
action: new DeferredAction(async (): Promise<void> => {
|
|
286
|
+
called = true;
|
|
287
|
+
}),
|
|
288
|
+
},
|
|
289
|
+
]);
|
|
290
|
+
const element = document.querySelector('#alert-container typo3-notification-message:last-child') as LitElement;
|
|
291
|
+
await element.updateComplete;
|
|
292
|
+
|
|
293
|
+
(element.querySelector('.alert-actions a') as HTMLAnchorElement).click();
|
|
294
|
+
await clock.tickAsync(0);
|
|
295
|
+
|
|
296
|
+
expect(called).to.be.true;
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('executes an action only once while it is executing', async () => {
|
|
300
|
+
let calls = 0;
|
|
301
|
+
let finishAction: () => void;
|
|
302
|
+
Notification.info('Info message', 'Some text', 0, [
|
|
303
|
+
{
|
|
304
|
+
label: 'Deferred action',
|
|
305
|
+
action: new DeferredAction((): Promise<void> => {
|
|
306
|
+
calls++;
|
|
307
|
+
return new Promise((resolve): void => {
|
|
308
|
+
finishAction = resolve;
|
|
309
|
+
});
|
|
310
|
+
}),
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
label: 'Other action',
|
|
314
|
+
action: new ImmediateAction((): void => {
|
|
315
|
+
calls++;
|
|
316
|
+
}),
|
|
317
|
+
},
|
|
318
|
+
]);
|
|
319
|
+
const element = document.querySelector('#alert-container typo3-notification-message:last-child') as LitElement;
|
|
320
|
+
await element.updateComplete;
|
|
321
|
+
const [first, second] = Array.from(element.querySelectorAll('.alert-actions a')) as HTMLAnchorElement[];
|
|
322
|
+
|
|
323
|
+
first.click();
|
|
324
|
+
await clock.tickAsync(0);
|
|
325
|
+
// Keyboard activation still reaches a link which ignores the pointer
|
|
326
|
+
first.click();
|
|
327
|
+
second.click();
|
|
328
|
+
await clock.tickAsync(0);
|
|
329
|
+
expect(calls).to.equal(1);
|
|
330
|
+
expect(first.getAttribute('aria-disabled')).to.equal('true');
|
|
331
|
+
expect(second.getAttribute('aria-disabled')).to.equal('true');
|
|
332
|
+
|
|
333
|
+
finishAction();
|
|
334
|
+
await clock.tickAsync(0);
|
|
335
|
+
expect(calls).to.equal(1);
|
|
336
|
+
});
|
|
161
337
|
});
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
import { expect } from '@open-wc/testing';
|
|
15
|
+
import { Tree } from '@typo3/backend/tree/tree.js';
|
|
16
|
+
import type { TreeNodeInterface } from '@typo3/backend/tree/tree-node.js';
|
|
17
|
+
import type { } from 'mocha';
|
|
18
|
+
|
|
19
|
+
class TestTree extends Tree {
|
|
20
|
+
public enhance(nodes: TreeNodeInterface[]): TreeNodeInterface[] {
|
|
21
|
+
return this.enhanceNodes(nodes);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
customElements.define('typo3-test-storage-tree', TestTree);
|
|
26
|
+
|
|
27
|
+
describe('file storage tree search', () => {
|
|
28
|
+
it('expands every matching storage root', () => {
|
|
29
|
+
const tree = document.createElement('typo3-test-storage-tree') as TestTree;
|
|
30
|
+
tree.searchTerm = 'needle';
|
|
31
|
+
const nodes = [
|
|
32
|
+
{ identifier: '1:/', depth: 0, loaded: false, hasChildren: true },
|
|
33
|
+
{ identifier: '1:/needle', depth: 1, loaded: true, hasChildren: false },
|
|
34
|
+
{ identifier: '2:/', depth: 0, loaded: false, hasChildren: true },
|
|
35
|
+
{ identifier: '2:/needle', depth: 1, loaded: true, hasChildren: false },
|
|
36
|
+
] as unknown as TreeNodeInterface[];
|
|
37
|
+
|
|
38
|
+
expect(tree.enhance(nodes).filter(node => node.depth === 0).map(node => node.__expanded))
|
|
39
|
+
.to.deep.equal([true, true]);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -119,7 +119,7 @@ export class FileStorageBrowser extends LitElement {
|
|
|
119
119
|
private readonly selectActiveNode = (evt: CustomEvent): void => {
|
|
120
120
|
// Activate the current node
|
|
121
121
|
const nodes = evt.detail.nodes as Array<TreeNodeInterface>;
|
|
122
|
-
evt.detail.nodes = nodes.map((node
|
|
122
|
+
evt.detail.nodes = nodes.map((node) => {
|
|
123
123
|
if (decodeURIComponent(node.identifier) === this.activeFolder) {
|
|
124
124
|
node.checked = true;
|
|
125
125
|
}
|
|
@@ -62,7 +62,7 @@ export class EditableFileStorageTree extends FileStorageTree {
|
|
|
62
62
|
const fileOperationCollection = FileOperationCollection.fromNodePositionOptions(options);
|
|
63
63
|
const operationConflicts = fileOperationCollection.getConflictingOperationsForTreeNode(options.target);
|
|
64
64
|
if (operationConflicts.length > 0) {
|
|
65
|
-
operationConflicts.forEach((operation
|
|
65
|
+
operationConflicts.forEach((operation) => {
|
|
66
66
|
Notification.showMessage(
|
|
67
67
|
layoutLabels.get('drop.conflict'),
|
|
68
68
|
layoutLabels.get('mess.drop.conflict', [
|
|
@@ -161,7 +161,7 @@ export class EditableFileStorageTree extends FileStorageTree {
|
|
|
161
161
|
const fileOperationCollection = FileOperationCollection.fromDataTransfer(event.dataTransfer, targetResource);
|
|
162
162
|
const operationConflicts = fileOperationCollection.getConflictingOperationsForTreeNode(node);
|
|
163
163
|
if (operationConflicts.length > 0) {
|
|
164
|
-
operationConflicts.forEach((operation
|
|
164
|
+
operationConflicts.forEach((operation) => {
|
|
165
165
|
Notification.showMessage(
|
|
166
166
|
layoutLabels.get('drop.conflict'),
|
|
167
167
|
layoutLabels.get('mess.drop.conflict', [
|
|
@@ -448,12 +448,12 @@ class FileOperationCollection {
|
|
|
448
448
|
}
|
|
449
449
|
|
|
450
450
|
public getConflictingOperationsForTreeNode(node: TreeNodeInterface): FileOperation[] {
|
|
451
|
-
return this.operations.filter((operation
|
|
451
|
+
return this.operations.filter((operation) => operation.hasConflictWithTreeNode(node));
|
|
452
452
|
}
|
|
453
453
|
|
|
454
454
|
public getResources(): ResourceInterface[] {
|
|
455
455
|
const resources: ResourceInterface[] = [];
|
|
456
|
-
this.operations.forEach((operation
|
|
456
|
+
this.operations.forEach((operation) => {
|
|
457
457
|
resources.push(operation.resource);
|
|
458
458
|
});
|
|
459
459
|
|
|
@@ -194,7 +194,7 @@ export class PageBrowser extends LitElement {
|
|
|
194
194
|
private readonly selectActivePageInTree = (evt: CustomEvent): void => {
|
|
195
195
|
// Activate the current node
|
|
196
196
|
const nodes = evt.detail.nodes as Array<TreeNodeInterface>;
|
|
197
|
-
evt.detail.nodes = nodes.map((node
|
|
197
|
+
evt.detail.nodes = nodes.map((node) => {
|
|
198
198
|
if (parseInt(node.identifier, 10) === this.activePageId) {
|
|
199
199
|
node.checked = true;
|
|
200
200
|
}
|
|
@@ -204,7 +204,7 @@ export class PagePositionSelect extends LitElement {
|
|
|
204
204
|
|
|
205
205
|
private syncActiveInsertPositionState(): TreeNodeInterface|null {
|
|
206
206
|
let activeNode: TreeNodeInterface|null = null;
|
|
207
|
-
this.tree.nodes.forEach((node
|
|
207
|
+
this.tree.nodes.forEach((node) => {
|
|
208
208
|
node.checked = this.isActivePagePositionNode(node);
|
|
209
209
|
if (node.checked) {
|
|
210
210
|
activeNode = node;
|
|
@@ -110,7 +110,9 @@ export class EditablePageTree extends PageTree {
|
|
|
110
110
|
'&data[pages][' + data.node.identifier + '][title]=' + encodeURIComponent(newData.title) +
|
|
111
111
|
'&data[pages][' + data.node.identifier + '][doktype]=' + encodeURIComponent(newData.doktype);
|
|
112
112
|
} else if (data.command === TreeNodeCommandEnum.EDIT) {
|
|
113
|
-
|
|
113
|
+
// The tree may show nav_title instead of title, so write back to the field that is displayed
|
|
114
|
+
const nameSourceField = data.node.nameSourceField || 'title';
|
|
115
|
+
params = '&data[pages][' + data.node.identifier + '][' + nameSourceField + ']=' + encodeURIComponent(data.title);
|
|
114
116
|
} else if (data.command === TreeNodeCommandEnum.DELETE) {
|
|
115
117
|
// @todo currently it's "If uid of deleted record (data.uid) is still selected, randomly select the first node"
|
|
116
118
|
const moduleStateStorage = ModuleStateStorage.current('web');
|
|
@@ -245,6 +247,11 @@ export class EditablePageTree extends PageTree {
|
|
|
245
247
|
command: TreeNodeCommandEnum.MOVE
|
|
246
248
|
};
|
|
247
249
|
|
|
250
|
+
if (!this.settings.displayCopyMoveConfirmation) {
|
|
251
|
+
this.sendChangeCommand(options);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
248
255
|
let modalText = '';
|
|
249
256
|
const languageArguments = [node.name, target.name] as const;
|
|
250
257
|
switch(position) {
|
|
@@ -282,7 +289,7 @@ export class EditablePageTree extends PageTree {
|
|
|
282
289
|
]
|
|
283
290
|
);
|
|
284
291
|
|
|
285
|
-
modal.addEventListener('button.clicked', (e:
|
|
292
|
+
modal.addEventListener('button.clicked', (e: Event) => {
|
|
286
293
|
const target = e.target as HTMLInputElement;
|
|
287
294
|
if (target.name === 'move') {
|
|
288
295
|
options.command = TreeNodeCommandEnum.MOVE;
|
|
@@ -57,7 +57,7 @@ export class PageTree extends Tree
|
|
|
57
57
|
return Promise.resolve();
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
if (this.nodes.find((node
|
|
60
|
+
if (this.nodes.find((node) => node.checked)) {
|
|
61
61
|
return Promise.resolve();
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -170,3 +170,9 @@ export class PageTree extends Tree
|
|
|
170
170
|
return false;
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
|
+
|
|
174
|
+
declare global {
|
|
175
|
+
interface DocumentEventMap {
|
|
176
|
+
'typo3:pagetree:mountPoint': CustomEvent<{ pageId: number }>;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -26,12 +26,12 @@ export const TreeModuleState = <T extends Constructor<LitElement>>(superClass: T
|
|
|
26
26
|
|
|
27
27
|
public override connectedCallback(): void {
|
|
28
28
|
super.connectedCallback();
|
|
29
|
-
document.addEventListener('typo3:module-state-storage:update:' + this.moduleStateType, this.moduleStateUpdated);
|
|
29
|
+
document.addEventListener('typo3:module-state-storage:update:' + this.moduleStateType, this.moduleStateUpdated as unknown as EventListener);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
public override disconnectedCallback(): void {
|
|
33
33
|
super.disconnectedCallback();
|
|
34
|
-
document.removeEventListener('typo3:module-state-storage:update:' + this.moduleStateType, this.moduleStateUpdated);
|
|
34
|
+
document.removeEventListener('typo3:module-state-storage:update:' + this.moduleStateType, this.moduleStateUpdated as unknown as EventListener);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
protected transformModuleStateIdentifierToNodeIdentifier(moduleStateIdentifier: string): string {
|
|
@@ -102,7 +102,7 @@ export const TreeModuleState = <T extends Constructor<LitElement>>(superClass: T
|
|
|
102
102
|
return;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
if (this.tree.nodes.find((node
|
|
105
|
+
if (this.tree.nodes.find((node) => node.checked)) {
|
|
106
106
|
return;
|
|
107
107
|
}
|
|
108
108
|
|
|
@@ -115,7 +115,7 @@ export const TreeModuleState = <T extends Constructor<LitElement>>(superClass: T
|
|
|
115
115
|
/* @todo store all module state updates in a stack to be consumed by a "slow" tree initialization */
|
|
116
116
|
return;
|
|
117
117
|
}
|
|
118
|
-
if (identifier && identifier === e.detail.oldState.identifier && this.tree.nodes.find((node
|
|
118
|
+
if (identifier && identifier === e.detail.oldState.identifier && this.tree.nodes.find((node) => node.checked)) {
|
|
119
119
|
return;
|
|
120
120
|
}
|
|
121
121
|
|
|
@@ -130,7 +130,7 @@ export const TreeModuleState = <T extends Constructor<LitElement>>(superClass: T
|
|
|
130
130
|
|
|
131
131
|
const nodeIdentifier = this.transformModuleStateIdentifierToNodeIdentifier(identifier);
|
|
132
132
|
const node = this.tree.nodes.find((node) => node.identifier === nodeIdentifier);
|
|
133
|
-
const selectedNode = this.tree.nodes.find((node
|
|
133
|
+
const selectedNode = this.tree.nodes.find((node) => node.checked);
|
|
134
134
|
if (node && node === selectedNode) {
|
|
135
135
|
// node is already selected, only ensure parents are expanded
|
|
136
136
|
await this.tree.expandNodeParents(node);
|
package/backend/tree/tree.ts
CHANGED
|
@@ -227,7 +227,7 @@ export class Tree extends LitElement {
|
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
if (parentNode !== null) {
|
|
230
|
-
nodes = nodes.filter((node
|
|
230
|
+
nodes = nodes.filter((node) => {
|
|
231
231
|
// Filter (already processed) parentNode from server response
|
|
232
232
|
// (if given. note: filetree does not deliver this, pagetree does)
|
|
233
233
|
return node.identifier !== parentNode.identifier;
|
|
@@ -244,7 +244,7 @@ export class Tree extends LitElement {
|
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
const all = await Promise.all(
|
|
247
|
-
nodes.map(async (node
|
|
247
|
+
nodes.map(async (node): Promise<TreeNodeInterface[]> => {
|
|
248
248
|
const parentNodeTreeIdentifier = node.__parents.join('_');
|
|
249
249
|
const parentNode = nodes.find(p => p.__treeIdentifier === parentNodeTreeIdentifier) || null;
|
|
250
250
|
const isVisible = parentNode === null || parentNode.__expanded;
|
|
@@ -535,7 +535,7 @@ export class Tree extends LitElement {
|
|
|
535
535
|
return;
|
|
536
536
|
}
|
|
537
537
|
if (Array.isArray(error)) {
|
|
538
|
-
error.forEach((message
|
|
538
|
+
error.forEach((message) => { Notification.error(
|
|
539
539
|
message.title,
|
|
540
540
|
message.message
|
|
541
541
|
);});
|
|
@@ -549,7 +549,7 @@ export class Tree extends LitElement {
|
|
|
549
549
|
}
|
|
550
550
|
|
|
551
551
|
public getSelectedNodes(): TreeNodeInterface[] {
|
|
552
|
-
return this.nodes.filter((node
|
|
552
|
+
return this.nodes.filter((node) => node.checked);
|
|
553
553
|
}
|
|
554
554
|
|
|
555
555
|
public getNodeByTreeIdentifier(treeIdentifier: string): TreeNodeInterface|null {
|
|
@@ -631,7 +631,7 @@ export class Tree extends LitElement {
|
|
|
631
631
|
|
|
632
632
|
// State
|
|
633
633
|
if (this.searchTerm) {
|
|
634
|
-
node.__expanded = node.loaded && node.hasChildren;
|
|
634
|
+
node.__expanded = (node.loaded || node.depth === 0) && node.hasChildren;
|
|
635
635
|
} else if (node.hasChildren) {
|
|
636
636
|
node.__expanded = (this.settings.expandUpToLevel !== null)
|
|
637
637
|
? node.depth < this.settings.expandUpToLevel
|
|
@@ -717,7 +717,7 @@ export class Tree extends LitElement {
|
|
|
717
717
|
node.__hidden !== true &&
|
|
718
718
|
!node.__treeParents.some(parentTreeIdentifier => this.getNodeByTreeIdentifier(parentTreeIdentifier).__expanded === false)
|
|
719
719
|
));
|
|
720
|
-
this.displayNodes.forEach((node
|
|
720
|
+
this.displayNodes.forEach((node, i) => {
|
|
721
721
|
node.__x = node.depth * this.indentWidth;
|
|
722
722
|
node.__y = i * this.nodeHeight;
|
|
723
723
|
});
|
|
@@ -725,7 +725,7 @@ export class Tree extends LitElement {
|
|
|
725
725
|
this.lastRenderScrollPosition = this.currentScrollPosition;
|
|
726
726
|
const visibleRows = Math.ceil(this.currentVisibleHeight / this.nodeHeight);
|
|
727
727
|
const position = Math.floor(this.currentScrollPosition / this.nodeHeight);
|
|
728
|
-
const visibleNodes = this.displayNodes.filter((node
|
|
728
|
+
const visibleNodes = this.displayNodes.filter((node, index) => {
|
|
729
729
|
// first node is fallback target for tabindex, needs to be available every time
|
|
730
730
|
if (this.getFirstNode() === node) {
|
|
731
731
|
return true;
|
|
@@ -800,7 +800,7 @@ export class Tree extends LitElement {
|
|
|
800
800
|
}
|
|
801
801
|
|
|
802
802
|
protected resetSelectedNodes(): void {
|
|
803
|
-
this.getSelectedNodes().forEach((node
|
|
803
|
+
this.getSelectedNodes().forEach((node) => {
|
|
804
804
|
if (node.checked === true) {
|
|
805
805
|
node.checked = false;
|
|
806
806
|
}
|
|
@@ -1671,3 +1671,10 @@ export class Tree extends LitElement {
|
|
|
1671
1671
|
}
|
|
1672
1672
|
}
|
|
1673
1673
|
}
|
|
1674
|
+
|
|
1675
|
+
declare global {
|
|
1676
|
+
interface HTMLElementEventMap {
|
|
1677
|
+
'typo3:tree:node-selected': CustomEvent<{ node: TreeNodeInterface, propagate: boolean }>;
|
|
1678
|
+
'typo3:tree:nodes-prepared': CustomEvent<{ nodes: TreeNodeInterface[] }>;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
@@ -41,8 +41,8 @@ class UserPassLogin {
|
|
|
41
41
|
// register submit handler
|
|
42
42
|
Login.options.submitHandler = this.resetPassword;
|
|
43
43
|
|
|
44
|
-
[usernameField, passwordField].forEach((field
|
|
45
|
-
['input', 'change'].forEach((eventName
|
|
44
|
+
[usernameField, passwordField].forEach((field) => new RegularEvent('keypress', this.showCapslockWarning).bindTo(field));
|
|
45
|
+
['input', 'change'].forEach((eventName) => new RegularEvent(eventName, this.showPasswordToggle).bindTo(passwordField));
|
|
46
46
|
new RegularEvent('keydown', this.toggleCopyright).bindTo(copyrightLink);
|
|
47
47
|
|
|
48
48
|
// if the login screen is shown in the login_frameset window for re-login,
|
|
@@ -102,7 +102,7 @@ export class CollapseStatePersister {
|
|
|
102
102
|
|
|
103
103
|
private recoverStates(): void {
|
|
104
104
|
const collapseStateElements: NodeListOf<HTMLElement>|null = document.querySelectorAll('.collapse[data-persist-collapse-state="true"]');
|
|
105
|
-
collapseStateElements.forEach((element
|
|
105
|
+
collapseStateElements.forEach((element) => {
|
|
106
106
|
const suffix: string = element.dataset.persistCollapseStateSuffix ?? this.localStorageKeyDefaultSuffix;
|
|
107
107
|
const currentStates = this.fromStorage(suffix);
|
|
108
108
|
const id: string = element.id;
|
|
@@ -107,7 +107,7 @@ class CollapseStateSearch {
|
|
|
107
107
|
numberOfSearchMatchesContainer.innerText = labels.get('collapse_state_search.numberOfSearchMatches', { count: matchingNodes.length });
|
|
108
108
|
numberOfSearchMatchesContainer.classList.remove('hidden');
|
|
109
109
|
|
|
110
|
-
matchingNodes.forEach((match
|
|
110
|
+
matchingNodes.forEach((match): void => {
|
|
111
111
|
if (match === null) {
|
|
112
112
|
return;
|
|
113
113
|
}
|
|
@@ -153,28 +153,28 @@ class CollapseStateSearch {
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
private findNodesByIdentifier(term: string, treeContainer: HTMLElement): Element[] {
|
|
156
|
-
return Array.from(treeContainer.querySelectorAll('.treelist-label')).filter((el
|
|
156
|
+
return Array.from(treeContainer.querySelectorAll('.treelist-label')).filter((el): boolean => {
|
|
157
157
|
return el.textContent.toLowerCase().includes(term);
|
|
158
158
|
});
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
private findNodesByValue(term: string, treeContainer: HTMLElement): Element[] {
|
|
162
|
-
const matchingValueNodes = Array.from(treeContainer.querySelectorAll('.treelist-value')).filter((el
|
|
162
|
+
const matchingValueNodes = Array.from(treeContainer.querySelectorAll('.treelist-value')).filter((el): boolean => {
|
|
163
163
|
return el.textContent.toLowerCase().includes(term);
|
|
164
164
|
});
|
|
165
|
-
return matchingValueNodes.map((node
|
|
165
|
+
return matchingValueNodes.map((node): Element => {
|
|
166
166
|
return node.previousElementSibling;
|
|
167
167
|
});
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
private findNodesByComment(term: string, treeContainer: HTMLElement): Element[] {
|
|
171
|
-
return Array.from(treeContainer.querySelectorAll('.treelist-comment')).filter((el
|
|
171
|
+
return Array.from(treeContainer.querySelectorAll('.treelist-comment')).filter((el): boolean => {
|
|
172
172
|
return el.textContent.toLowerCase().includes(term);
|
|
173
173
|
});
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
private findNodesByConstantSubstitution(term: string, treeContainer: HTMLElement): Element[] {
|
|
177
|
-
return Array.from(treeContainer.querySelectorAll('.treelist-constant-substitution')).filter((el
|
|
177
|
+
return Array.from(treeContainer.querySelectorAll('.treelist-constant-substitution')).filter((el): boolean => {
|
|
178
178
|
return el.textContent.toLowerCase().includes(term);
|
|
179
179
|
});
|
|
180
180
|
}
|