[ACS-10323] a11y fix - Adding/removing favorites are not announced by SR (#5002)

* [ACS-10323] a11y fix - Adding/removing favorites are not announced by screen readers

* fix unit test

* [ACS-10323] cr fixes
This commit is contained in:
Mykyta Maliarchuk
2026-01-26 10:58:50 +01:00
committed by GitHub
parent 715f928b3e
commit 0bc5bcc137
13 changed files with 278 additions and 46 deletions
@@ -566,7 +566,9 @@
"comment": "workaround for Recent Files and Search API issue",
"type": "custom",
"order": 400,
"data": "['/favorites', '/favorite/libraries']",
"data": {
"routes": ["/favorites", "/favorite/libraries"]
},
"component": "app.toolbar.toggleFavorite",
"rules": {
"visible": [
@@ -939,7 +941,10 @@
"comment": "workaround for Recent Files and Search API issue",
"type": "custom",
"order": 802,
"data": "['/favorites', '/favorite/libraries']",
"data": {
"routes": ["/favorites", "/favorite/libraries"],
"focusAfterClosed": ".adf-context-menu-source"
},
"component": "app.toolbar.toggleFavorite",
"rules": {
"visible": [
@@ -953,6 +958,9 @@
"type": "custom",
"order": 803,
"component": "app.toolbar.toggleFavoriteLibrary",
"data": {
"focusAfterClosed": ".adf-context-menu-source"
},
"rules": {
"visible": [
"app.selection.library"
@@ -45,7 +45,7 @@
(validationError)="error = $event"
[hasLibrariesConstraint]="hasLibrariesConstraint"
/>
<mat-error *ngIf="error" class="app-search-error">
<mat-error *ngIf="error" class="app-search-error" role="alert" aria-live="assertive">
{{ error | translate }}
</mat-error>
<div id="search-options" class="app-search-options">
@@ -22,7 +22,7 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { ToggleFavoriteLibraryComponent } from './toggle-favorite-library.component';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { Store } from '@ngrx/store';
@@ -30,12 +30,14 @@ import { AppTestingModule } from '../../../testing/app-testing.module';
import { of } from 'rxjs';
import { Router } from '@angular/router';
import { AppHookService, ContentApiService } from '@alfresco/aca-shared';
import { UnitTestingUtils } from '@alfresco/adf-core';
describe('ToggleFavoriteLibraryComponent', () => {
let fixture: ComponentFixture<ToggleFavoriteLibraryComponent>;
let component: ToggleFavoriteLibraryComponent;
let appHookService: AppHookService;
let contentApiService: any;
let unitTestingUtils: UnitTestingUtils;
const selection = { library: { entry: { id: 'libraryId' } } };
const mockRouter = {
@@ -66,6 +68,7 @@ describe('ToggleFavoriteLibraryComponent', () => {
fixture = TestBed.createComponent(ToggleFavoriteLibraryComponent);
component = fixture.componentInstance;
contentApiService = TestBed.inject(ContentApiService);
unitTestingUtils = new UnitTestingUtils(fixture.debugElement);
appHookService = TestBed.inject(AppHookService);
spyOn(contentApiService['favoritesApi'], 'getFavoriteSite').and.returnValue(Promise.resolve(null));
@@ -86,14 +89,24 @@ describe('ToggleFavoriteLibraryComponent', () => {
expect(component.library.isFavorite).toBe(true);
});
it('should emit onToggleEvent() event', async () => {
it('should emit onToggleEvent() event', fakeAsync(() => {
spyOn(appHookService.favoriteLibraryToggle, 'next');
fixture.detectChanges();
await fixture.whenStable();
component.onToggleEvent();
tick(100);
expect(appHookService.favoriteLibraryToggle.next).toHaveBeenCalled();
}));
it('should focus element on toggle when focusAfterClosed is provided', () => {
const mockElement = jasmine.createSpyObj<HTMLElement>('HTMLElement', ['focus']);
spyOn(document, 'querySelector').and.returnValue(mockElement);
component.data = { focusAfterClosed: '.adf-context-menu-source' };
const button = unitTestingUtils.getByCSS('button');
button.triggerEventHandler('toggle', new CustomEvent('toggle'));
expect(document.querySelector).toHaveBeenCalledWith('.adf-context-menu-source');
expect(mockElement.focus).toHaveBeenCalled();
});
});
@@ -22,7 +22,7 @@
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { Component, DestroyRef, inject, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { Component, DestroyRef, inject, Input, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppHookService } from '@alfresco/aca-shared';
import { AppStore, getAppSelection } from '@alfresco/aca-shared/store';
@@ -56,6 +56,8 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
export class ToggleFavoriteLibraryComponent implements OnInit {
library;
@Input() data: { focusAfterClosed?: string };
@ViewChild(MatMenuItem)
menuItem: MatMenuItem;
@@ -84,6 +86,11 @@ export class ToggleFavoriteLibraryComponent implements OnInit {
}
onToggleEvent() {
this.appHookService.favoriteLibraryToggle.next();
if (this.data?.focusAfterClosed) {
document.querySelector<HTMLElement>('.adf-context-menu-source')?.focus();
}
setTimeout(() => {
this.appHookService.favoriteLibraryToggle.next();
}, 100);
}
}
@@ -29,11 +29,14 @@ import { ExtensionService } from '@alfresco/adf-extensions';
import { Router } from '@angular/router';
import { of } from 'rxjs';
import { AppTestingModule } from '../../../testing/app-testing.module';
import { UnitTestingUtils } from '@alfresco/adf-core';
describe('ToggleFavoriteComponent', () => {
let component: ToggleFavoriteComponent;
let fixture;
let router;
let unitTestingUtils: UnitTestingUtils;
const mockRouter = {
url: 'some-url'
};
@@ -54,6 +57,7 @@ describe('ToggleFavoriteComponent', () => {
fixture = TestBed.createComponent(ToggleFavoriteComponent);
component = fixture.componentInstance;
unitTestingUtils = new UnitTestingUtils(fixture.debugElement);
router = TestBed.inject(Router);
});
@@ -66,7 +70,7 @@ describe('ToggleFavoriteComponent', () => {
});
it('should not dispatch reload if route is not specified', () => {
component.data = '["/reload_on_this_route"]';
component.data = { routes: ['/reload_on_this_route'] };
router.url = '/somewhere_over_the_rainbow';
fixture.detectChanges();
@@ -76,7 +80,7 @@ describe('ToggleFavoriteComponent', () => {
});
it('should dispatch reload if route is specified', () => {
component.data = '["/reload_on_this_route"]';
component.data = { routes: ['/reload_on_this_route'] };
router.url = '/reload_on_this_route';
fixture.detectChanges();
@@ -84,4 +88,16 @@ describe('ToggleFavoriteComponent', () => {
expect(mockStore.dispatch).toHaveBeenCalled();
});
it('should focus element on toggle when focusAfterClosed is provided', () => {
const mockElement = jasmine.createSpyObj<HTMLElement>('HTMLElement', ['focus']);
spyOn(document, 'querySelector').and.returnValue(mockElement);
component.data = { routes: [], focusAfterClosed: '.adf-context-menu-source' };
const button = unitTestingUtils.getByCSS('button');
button.triggerEventHandler('toggle', new CustomEvent('toggle'));
expect(document.querySelector).toHaveBeenCalledWith('.adf-context-menu-source');
expect(mockElement.focus).toHaveBeenCalled();
});
});
@@ -64,12 +64,14 @@ export class ToggleFavoriteComponent implements OnInit {
}
ngOnInit() {
if (this.data) {
this.reloadOnRoutes = JSON.parse(this.data.replace(/'/g, '"'));
}
this.reloadOnRoutes = this.data?.routes ?? [];
}
onToggleEvent() {
const focusAfterClosed = this.data?.focusAfterClosed;
if (focusAfterClosed) {
document.querySelector<HTMLElement>(focusAfterClosed)?.focus();
}
if (this.reloadOnRoutes.includes(this.router.url)) {
this.documentListService.reload();
}
@@ -902,6 +902,20 @@ describe('ContentManagementService', () => {
store.dispatch(new DeleteNodesAction(selection));
expect(openSnackMessageActionSpy.calls.argsFor(0)[2].panelClass).toBe('adf-warning-snackbar');
});
it('should focus element when deleting nodes with focusAfterClosed selector', fakeAsync(() => {
const mockElement = jasmine.createSpyObj<HTMLElement>('HTMLElement', ['focus']);
spyOn(document, 'querySelector').and.returnValue(mockElement);
spyOn(contentApi, 'deleteNode').and.returnValue(of(null));
const nodes = [{ entry: { id: '1', name: 'name1' } } as NodeEntry];
contentManagementService.deleteNodes(nodes, false, '.some-button');
tick();
expect(document.querySelector).toHaveBeenCalledWith('.some-button');
expect(mockElement.focus).toHaveBeenCalled();
}));
});
describe('Permanent Delete', () => {
@@ -927,6 +941,20 @@ describe('ContentManagementService', () => {
expect(contentApi.purgeDeletedNode).toHaveBeenCalled();
});
it('should focus element when purging nodes with focusAfterClosed selector', fakeAsync(() => {
const mockElement = jasmine.createSpyObj<HTMLElement>('HTMLElement', ['focus']);
spyOn(document, 'querySelector').and.returnValue(mockElement);
spyOn(contentApi, 'purgeDeletedNode').and.returnValue(of({}));
const nodes = [{ entry: { id: '1', name: 'name1' } } as NodeEntry];
contentManagementService.purgeDeletedNodes(nodes, '.some-button');
tick();
expect(document.querySelector).toHaveBeenCalledWith('.some-button');
expect(mockElement.focus).toHaveBeenCalled();
}));
describe('notification', () => {
it('raises warning on multiple fail and one success', (done) => {
spyOn(contentApi, 'purgeDeletedNode').and.callFake((id) => {
@@ -1307,6 +1335,28 @@ describe('ContentManagementService', () => {
store.dispatch(new RestoreDeletedNodesAction(selection));
expect(openSnackMessageActionSpy.calls.argsFor(0)[2].panelClass).toBe('adf-info-snackbar');
});
it('should focus element when restoring nodes with focusAfterClosed selector', fakeAsync(() => {
const mockElement = jasmine.createSpyObj<HTMLElement>('HTMLElement', ['focus']);
spyOn(document, 'querySelector').and.returnValue(mockElement);
spyOn(contentApi, 'restoreNode').and.returnValue(of({} as NodeEntry));
const path = {
elements: [
{
id: '1-1',
name: 'Company Home'
}
]
};
const nodes = [{ entry: { name: 'node1', id: '1', path } } as NodeEntry];
contentManagementService.restoreDeletedNodes(nodes, '.some-button');
tick(100);
expect(document.querySelector).toHaveBeenCalledWith('.some-button');
expect(mockElement.focus).toHaveBeenCalled();
}));
});
});
@@ -1996,6 +2046,19 @@ describe('ContentManagementService', () => {
expect(showInfoSpy).toHaveBeenCalledWith('APP.MESSAGES.INFO.FAVORITE_NODES_ADDED', null, { number: 2 });
});
it('should focus element when adding favorite with focusAfterClosed selector', fakeAsync(() => {
const mockElement = jasmine.createSpyObj<HTMLElement>('HTMLElement', ['focus']);
spyOn(document, 'querySelector').and.returnValue(mockElement);
spyOn(contentApi, 'addFavorite').and.returnValue(of({ entry: { targetGuid: '', target: '' } }));
contentManagementService.addFavorite([fakeNode1, fakeNode2], '.some-button');
tick(100);
expect(document.querySelector).toHaveBeenCalledWith('.some-button');
expect(mockElement.focus).toHaveBeenCalled();
}));
it('should call proper content api and display proper snackbar message if one node is provided for removeFavorite', () => {
spyOn(contentApi, 'removeFavorite').and.returnValue(of({}));
@@ -2024,5 +2087,18 @@ describe('ContentManagementService', () => {
expect(contentApi.removeFavorite).toHaveBeenCalledWith([fakeNode1, fakeNode2]);
expect(showErrorSpy).toHaveBeenCalledWith('APP.MESSAGES.ERRORS.FAVORITE_NODE_NOT_FOUND', null, { name: 'mock-folder2-name' });
});
it('should focus element when removing favorite with focusAfterClosed selector', fakeAsync(() => {
const mockElement = jasmine.createSpyObj<HTMLElement>('HTMLElement', ['focus']);
spyOn(document, 'querySelector').and.returnValue(mockElement);
spyOn(contentApi, 'removeFavorite').and.returnValue(of({}));
contentManagementService.removeFavorite([fakeNode1, fakeNode2], '.some-button');
tick(100);
expect(document.querySelector).toHaveBeenCalledWith('.some-button');
expect(mockElement.focus).toHaveBeenCalled();
}));
});
});
@@ -93,7 +93,8 @@ export class ContentManagementService {
private readonly documentListService = inject(DocumentListService);
private readonly createMenuButtonSelector = 'app-toolbar-menu button[id="app.toolbar.create"]';
addFavorite(nodes: Array<NodeEntry>) {
addFavorite(nodes: Array<NodeEntry>, focusedElementOnCloseSelector?: string) {
this.focusAfterClose(focusedElementOnCloseSelector);
if (nodes && nodes.length > 0) {
this.contentApi.addFavorite(nodes).subscribe(() => {
const favoriteNodes = nodes.map((node) => {
@@ -112,7 +113,8 @@ export class ContentManagementService {
}
}
removeFavorite(nodes: Array<NodeEntry>) {
removeFavorite(nodes: Array<NodeEntry>, focusedElementOnCloseSelector?: string) {
this.focusAfterClose(focusedElementOnCloseSelector);
if (nodes && nodes.length > 0) {
this.contentApi.removeFavorite(nodes).subscribe({
next: () => {
@@ -373,7 +375,7 @@ export class ContentManagementService {
return this.permission.check(folderNode, ['create']);
}
purgeDeletedNodes(nodes: NodeEntry[]) {
purgeDeletedNodes(nodes: NodeEntry[], focusedElementOnCloseSelector?: string) {
if (!nodes || nodes.length === 0) {
return;
}
@@ -389,6 +391,7 @@ export class ContentManagementService {
});
dialogRef.afterClosed().subscribe((result) => {
this.focusAfterClose(focusedElementOnCloseSelector);
if (result === true) {
const nodesToDelete: NodeInfo[] = nodes.map((node) => {
const { name } = node.entry;
@@ -404,7 +407,7 @@ export class ContentManagementService {
});
}
restoreDeletedNodes(selection: NodeEntry[] = []) {
restoreDeletedNodes(selection: NodeEntry[] = [], focusedElementOnCloseSelector?: string) {
if (!selection.length) {
return;
}
@@ -433,8 +436,11 @@ export class ContentManagementService {
const remainingNodes = this.diff(selectedNodes, nodes.list.entries);
if (!remainingNodes.length) {
this.focusAfterClose(focusedElementOnCloseSelector);
this.showRestoreNotification(status);
this.documentListService.reload();
setTimeout(() => {
this.documentListService.reload();
}, 50);
} else {
this.restoreDeletedNodes(remainingNodes);
}
@@ -689,7 +695,8 @@ export class ContentManagementService {
);
}
deleteNodes(items: NodeEntry[], allowUndo = true): void {
deleteNodes(items: NodeEntry[], allowUndo = true, focusedElementOnCloseSelector?: string): void {
this.focusAfterClose(focusedElementOnCloseSelector);
const batch: Observable<DeletedNodeInfo>[] = [];
items.forEach((node) => {
@@ -0,0 +1,88 @@
/*!
* Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Alfresco Example Content Application
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
*/
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
import { FavoriteEffects } from './favorite.effects';
import { ContentManagementService } from '../../services/content-management.service';
import { AddFavoriteAction, AppStore, RemoveFavoriteAction, SetSelectedNodesAction } from '@alfresco/aca-shared/store';
import { Store } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects';
import { AppTestingModule } from '../../testing/app-testing.module';
import { NodeEntry } from '@alfresco/js-api';
describe('FavoriteEffects', () => {
let contentService: ContentManagementService;
let store: Store<AppStore>;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [AppTestingModule],
providers: [provideEffects([FavoriteEffects])]
});
store = TestBed.inject(Store);
contentService = TestBed.inject(ContentManagementService);
});
it('should call addFavorite with payload when AddFavoriteAction is dispatched', () => {
spyOn(contentService, 'addFavorite').and.stub();
const payload = [{ entry: { id: 'node1' } }] as NodeEntry[];
store.dispatch(new AddFavoriteAction(payload, { focusedElementOnCloseSelector: 'test-selector' }));
expect(contentService.addFavorite).toHaveBeenCalledWith(payload, 'test-selector');
});
it('should call addFavorite with selection when AddFavoriteAction is dispatched without payload', fakeAsync(() => {
spyOn(contentService, 'addFavorite').and.stub();
const selection = [{ entry: { isFile: true } }] as NodeEntry[];
store.dispatch(new SetSelectedNodesAction(selection));
tick(100);
store.dispatch(new AddFavoriteAction(null, { focusedElementOnCloseSelector: 'test-selector' }));
expect(contentService.addFavorite).toHaveBeenCalledWith(selection, 'test-selector');
}));
it('should call removeFavorite with payload when RemoveFavoriteAction is dispatched', () => {
spyOn(contentService, 'removeFavorite').and.stub();
const payload = [{ entry: { id: 'node1' } }] as NodeEntry[];
store.dispatch(new RemoveFavoriteAction(payload, { focusedElementOnCloseSelector: 'test-selector' }));
expect(contentService.removeFavorite).toHaveBeenCalledWith(payload, 'test-selector');
});
it('should call removeFavorite with selection when RemoveFavoriteAction is dispatched without payload', fakeAsync(() => {
spyOn(contentService, 'removeFavorite').and.stub();
const selection: any = [{ entry: { isFile: true } }] as NodeEntry[];
store.dispatch(new SetSelectedNodesAction(selection));
tick(100);
store.dispatch(new RemoveFavoriteAction(null, { focusedElementOnCloseSelector: 'test-selector' }));
expect(contentService.removeFavorite).toHaveBeenCalledWith(selection, 'test-selector');
}));
});
@@ -41,14 +41,14 @@ export class FavoriteEffects {
ofType<AddFavoriteAction>(NodeActionTypes.AddFavorite),
map((action) => {
if (action.payload && action.payload.length > 0) {
this.content.addFavorite(action.payload);
this.content.addFavorite(action.payload, action.configuration.focusedElementOnCloseSelector);
} else {
this.store
.select(getAppSelection)
.pipe(take(1))
.subscribe((selection) => {
if (selection && !selection.isEmpty) {
this.content.addFavorite(selection.nodes);
this.content.addFavorite(selection.nodes, action.configuration.focusedElementOnCloseSelector);
}
});
}
@@ -63,14 +63,14 @@ export class FavoriteEffects {
ofType<RemoveFavoriteAction>(NodeActionTypes.RemoveFavorite),
map((action) => {
if (action.payload && action.payload.length > 0) {
this.content.removeFavorite(action.payload);
this.content.removeFavorite(action.payload, action.configuration.focusedElementOnCloseSelector);
} else {
this.store
.select(getAppSelection)
.pipe(take(1))
.subscribe((selection) => {
if (selection && !selection.isEmpty) {
this.content.removeFavorite(selection.nodes);
this.content.removeFavorite(selection.nodes, action.configuration.focusedElementOnCloseSelector);
}
});
}
@@ -156,7 +156,7 @@ describe('NodeEffects', () => {
const node: any = {};
store.dispatch(new PurgeDeletedNodesAction([node]));
expect(contentService.purgeDeletedNodes).toHaveBeenCalledWith([node]);
expect(contentService.purgeDeletedNodes).toHaveBeenCalledWith([node], undefined);
});
it('should purge nodes from the active selection', fakeAsync(() => {
@@ -167,8 +167,8 @@ describe('NodeEffects', () => {
tick(100);
store.dispatch(new PurgeDeletedNodesAction(null));
expect(contentService.purgeDeletedNodes).toHaveBeenCalledWith([node]);
store.dispatch(new PurgeDeletedNodesAction([node], { focusedElementOnCloseSelector: '.test-selector' }));
expect(contentService.purgeDeletedNodes).toHaveBeenCalledWith([node], '.test-selector');
}));
it('should do nothing if invoking purge with no data', () => {
@@ -187,7 +187,7 @@ describe('NodeEffects', () => {
const node: any = {};
store.dispatch(new RestoreDeletedNodesAction([node]));
expect(contentService.restoreDeletedNodes).toHaveBeenCalledWith([node]);
expect(contentService.restoreDeletedNodes).toHaveBeenCalledWith([node], undefined);
});
it('should restore deleted nodes from the active selection', fakeAsync(() => {
@@ -198,8 +198,8 @@ describe('NodeEffects', () => {
tick(100);
store.dispatch(new RestoreDeletedNodesAction(null));
expect(contentService.restoreDeletedNodes).toHaveBeenCalledWith([node]);
store.dispatch(new RestoreDeletedNodesAction(null, { focusedElementOnCloseSelector: '.test-selector' }));
expect(contentService.restoreDeletedNodes).toHaveBeenCalledWith([node], '.test-selector');
}));
it('should do nothing if invoking restore with no data', () => {
@@ -220,7 +220,7 @@ describe('NodeEffects', () => {
expect(store.dispatch).toHaveBeenCalledWith(jasmine.objectContaining({ ...new DeleteNodesAction([node], true) }));
expect(store.dispatch).toHaveBeenCalledWith(jasmine.objectContaining({ ...new ShowLoaderAction(true) }));
expect(contentService.deleteNodes).toHaveBeenCalledWith([node], true);
expect(contentService.deleteNodes).toHaveBeenCalledWith([node], true, undefined);
});
it('should delete nodes from the active selection', fakeAsync(() => {
@@ -231,11 +231,13 @@ describe('NodeEffects', () => {
tick(100);
store.dispatch(new DeleteNodesAction(null));
store.dispatch(new DeleteNodesAction(null, true, { focusedElementOnCloseSelector: '.test-selector' }));
expect(store.dispatch).toHaveBeenCalledWith(jasmine.objectContaining({ ...new DeleteNodesAction(null, true) }));
expect(store.dispatch).toHaveBeenCalledWith(
jasmine.objectContaining({ ...new DeleteNodesAction(null, true, { focusedElementOnCloseSelector: '.test-selector' }) })
);
expect(store.dispatch).toHaveBeenCalledWith(jasmine.objectContaining({ ...new ShowLoaderAction(true) }));
expect(contentService.deleteNodes).toHaveBeenCalledWith([node], true);
expect(contentService.deleteNodes).toHaveBeenCalledWith([node], true, '.test-selector');
}));
it('should do nothing if invoking delete with no data', () => {
@@ -119,14 +119,14 @@ export class NodeEffects {
ofType<PurgeDeletedNodesAction>(NodeActionTypes.PurgeDeleted),
map((action) => {
if (action?.payload?.length > 0) {
this.contentService.purgeDeletedNodes(action.payload);
this.contentService.purgeDeletedNodes(action.payload, action.configuration?.focusedElementOnCloseSelector);
} else {
this.store
.select(getAppSelection)
.pipe(take(1))
.subscribe((selection) => {
if (selection && selection.count > 0) {
this.contentService.purgeDeletedNodes(selection.nodes);
this.contentService.purgeDeletedNodes(selection.nodes, action.configuration?.focusedElementOnCloseSelector);
}
});
}
@@ -141,14 +141,14 @@ export class NodeEffects {
ofType<RestoreDeletedNodesAction>(NodeActionTypes.RestoreDeleted),
map((action) => {
if (action?.payload?.length > 0) {
this.contentService.restoreDeletedNodes(action.payload);
this.contentService.restoreDeletedNodes(action.payload, action.configuration?.focusedElementOnCloseSelector);
} else {
this.store
.select(getAppSelection)
.pipe(take(1))
.subscribe((selection) => {
if (selection && selection.count > 0) {
this.contentService.restoreDeletedNodes(selection.nodes);
this.contentService.restoreDeletedNodes(selection.nodes, action.configuration?.focusedElementOnCloseSelector);
}
});
}
@@ -164,14 +164,14 @@ export class NodeEffects {
map((action) => {
this.store.dispatch(new ShowLoaderAction(true));
if (action?.payload?.length > 0) {
this.contentService.deleteNodes(action.payload, action.allowUndo);
this.contentService.deleteNodes(action.payload, action.allowUndo, action.configuration?.focusedElementOnCloseSelector);
} else {
this.store
.select(getAppSelection)
.pipe(take(1))
.subscribe((selection) => {
if (selection && selection.count > 0) {
this.contentService.deleteNodes(selection.nodes, action.allowUndo);
this.contentService.deleteNodes(selection.nodes, action.allowUndo, action.configuration?.focusedElementOnCloseSelector);
}
});
}
@@ -63,7 +63,8 @@ export class DeleteNodesAction implements Action {
constructor(
public payload: NodeEntry[] = [],
public allowUndo = true
public allowUndo = true,
public configuration?: ModalConfiguration
) {}
}
@@ -76,13 +77,19 @@ export class UndoDeleteNodesAction implements Action {
export class RestoreDeletedNodesAction implements Action {
readonly type = NodeActionTypes.RestoreDeleted;
constructor(public payload: Array<NodeEntry>) {}
constructor(
public payload: Array<NodeEntry>,
public configuration?: ModalConfiguration
) {}
}
export class PurgeDeletedNodesAction implements Action {
readonly type = NodeActionTypes.PurgeDeleted;
constructor(public payload: Array<NodeEntry>) {}
constructor(
public payload: Array<NodeEntry>,
public configuration?: ModalConfiguration
) {}
}
export class DownloadNodesAction implements Action {
@@ -183,13 +190,19 @@ export class UnlockWriteAction implements Action {
export class AddFavoriteAction implements Action {
readonly type = NodeActionTypes.AddFavorite;
constructor(public payload: Array<NodeEntry>) {}
constructor(
public payload: Array<NodeEntry>,
public configuration?: ModalConfiguration
) {}
}
export class RemoveFavoriteAction implements Action {
readonly type = NodeActionTypes.RemoveFavorite;
constructor(public payload: Array<NodeEntry>) {}
constructor(
public payload: Array<NodeEntry>,
public configuration?: ModalConfiguration
) {}
}
export class ManageAspectsAction implements Action {
readonly type = NodeActionTypes.ChangeAspects;