ACS-7403: migrate site dropdown to standalone (#9847)

This commit is contained in:
Denys Vuika
2024-07-01 14:25:49 -04:00
committed by GitHub
parent 5fa3afe3a5
commit 0c4259cddf
28 changed files with 244 additions and 335 deletions

View File

@@ -21,15 +21,15 @@ import { By } from '@angular/platform-browser';
import { Node, NodeEntry, NodePaging, RequestScope, ResultSetPaging, SiteEntry, SitePaging, SitePagingList } from '@alfresco/js-api';
import { of } from 'rxjs';
import { ContentNodeSelectorPanelComponent } from './content-node-selector-panel.component';
import { ContentTestingModule } from '../testing/content.testing.module';
import { DocumentListService } from '../document-list/services/document-list.service';
import { DocumentListComponent } from '../document-list/components/document-list.component';
import { CustomResourcesService } from '../document-list/services/custom-resources.service';
import { NodeEntryEvent, ShareDataRow } from '../document-list';
import { SearchQueryBuilderService } from '../search';
import { mockSearchRequest } from '../mock/search-query.mock';
import { SitesService } from '../common/services/sites.service';
import { NodesApiService } from '../common/services/nodes-api.service';
import { ContentTestingModule } from '../../testing/content.testing.module';
import { DocumentListService } from '../../document-list/services/document-list.service';
import { DocumentListComponent } from '../../document-list/components/document-list.component';
import { CustomResourcesService } from '../../document-list/services/custom-resources.service';
import { NodeEntryEvent, ShareDataRow } from '../../document-list';
import { SearchQueryBuilderService } from '../../search';
import { mockSearchRequest } from '../../mock/search-query.mock';
import { SitesService } from '../../common/services/sites.service';
import { NodesApiService } from '../../common/services/nodes-api.service';
const fakeResultSetPaging: ResultSetPaging = {
list: {

View File

@@ -147,8 +147,7 @@ h2.adf-search-results-label {
.adf-datatable-body .adf-datatable-row {
min-height: 40px;
@media screen and (-ms-high-contrast: active),
screen and (-ms-high-contrast: none) {
@media screen and (-ms-high-contrast: active), screen and (-ms-high-contrast: none) {
padding-top: 15px;
}

View File

@@ -20,17 +20,17 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testin
import { By } from '@angular/platform-browser';
import { Node, NodeEntry, NodePaging, ResultSetPaging, Site, SiteEntry, SitePaging, SitePagingList, UserInfo } from '@alfresco/js-api';
import { DataRow, ThumbnailService, DataColumn } from '@alfresco/adf-core';
import { ContentService, UploadService, NodesApiService, SitesService, FileModel, FileUploadStatus, FileUploadCompleteEvent } from '../common';
import { ContentService, UploadService, NodesApiService, SitesService, FileModel, FileUploadStatus, FileUploadCompleteEvent } from '../../common';
import { of, throwError } from 'rxjs';
import { DropdownBreadcrumbComponent } from '../breadcrumb';
import { DropdownBreadcrumbComponent } from '../../breadcrumb';
import { ContentNodeSelectorPanelComponent } from './content-node-selector-panel.component';
import { ContentTestingModule } from '../testing/content.testing.module';
import { DocumentListService } from '../document-list/services/document-list.service';
import { ContentTestingModule } from '../../testing/content.testing.module';
import { DocumentListService } from '../../document-list/services/document-list.service';
import { DropdownSitesComponent } from '../site-dropdown/sites-dropdown.component';
import { NodeEntryEvent, ShareDataRow, ShareDataTableAdapter } from '../document-list';
import { SearchQueryBuilderService } from '../search';
import { NodeEntryEvent, ShareDataRow, ShareDataTableAdapter } from '../../document-list';
import { SearchQueryBuilderService } from '../../search';
import { ContentNodeSelectorPanelService } from './content-node-selector-panel.service';
import { mockContentModelTextProperty } from '../mock/content-model.mock';
import { mockContentModelTextProperty } from '../../mock/content-model.mock';
const fakeResultSetPaging: ResultSetPaging = {
list: {

View File

@@ -25,18 +25,18 @@ import {
DataSorting,
ShowHeaderMode
} from '@alfresco/adf-core';
import { NodesApiService, UploadService, FileUploadCompleteEvent, FileUploadDeleteEvent, SitesService } from '../common';
import { NodesApiService, UploadService, FileUploadCompleteEvent, FileUploadDeleteEvent, SitesService } from '../../common';
import { UntypedFormControl } from '@angular/forms';
import { Node, NodePaging, Pagination, SiteEntry, SitePaging, NodeEntry, SearchRequest, RequestScope } from '@alfresco/js-api';
import { DocumentListComponent } from '../document-list/components/document-list.component';
import { RowFilter } from '../document-list/data/row-filter.model';
import { ImageResolver } from '../document-list/data/image-resolver.model';
import { CustomResourcesService } from '../document-list/services/custom-resources.service';
import { ShareDataRow } from '../document-list/data/share-data-row.model';
import { NodeEntryEvent } from '../document-list/components/node.event';
import { DocumentListComponent } from '../../document-list/components/document-list.component';
import { RowFilter } from '../../document-list/data/row-filter.model';
import { ImageResolver } from '../../document-list/data/image-resolver.model';
import { CustomResourcesService } from '../../document-list/services/custom-resources.service';
import { ShareDataRow } from '../../document-list/data/share-data-row.model';
import { NodeEntryEvent } from '../../document-list/components/node.event';
import { debounceTime, takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { SearchQueryBuilderService } from '../search';
import { SearchQueryBuilderService } from '../../search';
import { ContentNodeSelectorPanelService } from './content-node-selector-panel.service';
export type ValidationFunction = (entry: Node) => boolean;

View File

@@ -16,10 +16,9 @@
*/
import { ContentNodeSelectorPanelService } from './content-node-selector-panel.service';
import { mockContentModelDateProperty, mockContentModelTextProperty, mockConvertedSearchCategoriesFromModels } from '../mock/content-model.mock';
import { mockContentModelDateProperty, mockContentModelTextProperty, mockConvertedSearchCategoriesFromModels } from '../../mock/content-model.mock';
describe('ContentNodeSelectorPanelService', () => {
const contentNodeSelectorPanelService = new ContentNodeSelectorPanelService();
it('should support text type', () => {
@@ -42,7 +41,7 @@ describe('ContentNodeSelectorPanelService', () => {
});
it('should modelPropertyTypeToSearchFilterTypeMap contain only the supported types', () => {
const expectedSupportedTypesMap = new Map<string, string> ();
const expectedSupportedTypesMap = new Map<string, string>();
expectedSupportedTypesMap.set('d:text', 'text');
expectedSupportedTypesMap.set('d:date', 'date-range');
expectedSupportedTypesMap.set('d:datetime', 'datetime-range');

View File

@@ -16,15 +16,14 @@
*/
import { Injectable } from '@angular/core';
import { SearchCategory } from '../search/models/search-category.interface';
import { SearchCategory } from '../../search/models/search-category.interface';
@Injectable({
providedIn: 'root'
})
export class ContentNodeSelectorPanelService {
propertyTypes = ['d:text', 'd:date', 'd:datetime'];
modelPropertyTypeToSearchFilterTypeMap = new Map<string, string> ();
modelPropertyTypeToSearchFilterTypeMap = new Map<string, string>();
customModels: any[];
constructor() {
@@ -35,7 +34,7 @@ export class ContentNodeSelectorPanelService {
convertCustomModelPropertiesToSearchCategories(): SearchCategory[] {
const searchConfig: SearchCategory[] = [];
this.customModels?.forEach( (propertyModel) => {
this.customModels?.forEach((propertyModel) => {
searchConfig.push(this.convertModelPropertyIntoSearchFilter(propertyModel));
});
@@ -46,7 +45,7 @@ export class ContentNodeSelectorPanelService {
let filterSearch: SearchCategory;
if (this.isTypeSupported(modelProperty.dataType)) {
filterSearch = {
id : modelProperty.prefixedName,
id: modelProperty.prefixedName,
name: modelProperty.prefixedName,
expanded: false,
enabled: true,
@@ -66,5 +65,4 @@ export class ContentNodeSelectorPanelService {
isTypeSupported(dataType: string): boolean {
return this.propertyTypes.includes(dataType);
}
}

View File

@@ -29,7 +29,7 @@ import { ContentTestingModule } from '../testing/content.testing.module';
import { DocumentListService } from '../document-list/services/document-list.service';
import { DocumentListComponent } from '../document-list/components/document-list.component';
import { UploadModule } from '../upload';
import { ContentNodeSelectorPanelComponent } from './content-node-selector-panel.component';
import { ContentNodeSelectorPanelComponent } from './content-node-selector-panel/content-node-selector-panel.component';
import { NodeAction } from '../document-list/models/node-action.enum';
import { SitesService } from '../common/services/sites.service';
import { NodesApiService } from '../common/services/nodes-api.service';

View File

@@ -19,10 +19,8 @@ import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MaterialModule } from '../material.module';
import { ContentNodeSelectorPanelComponent } from './content-node-selector-panel.component';
import { ContentNodeSelectorPanelComponent } from './content-node-selector-panel/content-node-selector-panel.component';
import { ContentNodeSelectorComponent } from './content-node-selector.component';
import { SitesDropdownModule } from '../site-dropdown/sites-dropdown.module';
import { BreadcrumbModule } from '../breadcrumb/breadcrumb.module';
import { SearchModule } from '../search/search.module';
import { CoreModule } from '@alfresco/adf-core';
@@ -31,6 +29,7 @@ import { NameLocationCellComponent } from './name-location-cell/name-location-ce
import { UploadModule } from '../upload/upload.module';
import { SearchQueryBuilderService } from '../search/services/search-query-builder.service';
import { ContentDirectiveModule } from '../directives/content-directive.module';
import { DropdownSitesComponent } from './site-dropdown/sites-dropdown.component';
@NgModule({
imports: [
@@ -39,23 +38,16 @@ import { ContentDirectiveModule } from '../directives/content-directive.module';
CoreModule,
CommonModule,
MaterialModule,
SitesDropdownModule,
DropdownSitesComponent,
BreadcrumbModule,
SearchModule,
DocumentListModule,
UploadModule,
ContentDirectiveModule
],
exports: [
ContentNodeSelectorPanelComponent,
NameLocationCellComponent,
ContentNodeSelectorComponent
],
declarations: [
ContentNodeSelectorPanelComponent,
NameLocationCellComponent,
ContentNodeSelectorComponent
ContentDirectiveModule,
NameLocationCellComponent
],
exports: [ContentNodeSelectorPanelComponent, NameLocationCellComponent, ContentNodeSelectorComponent],
declarations: [ContentNodeSelectorPanelComponent, ContentNodeSelectorComponent],
providers: [SearchQueryBuilderService]
})
export class ContentNodeSelectorModule {}

View File

@@ -27,9 +27,7 @@ describe('NameLocationCellComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
NameLocationCellComponent
]
imports: [NameLocationCellComponent]
});
fixture = TestBed.createComponent(NameLocationCellComponent);
component = fixture.componentInstance;

View File

@@ -20,6 +20,7 @@ import { DataRow } from '@alfresco/adf-core';
@Component({
selector: 'adf-name-location-cell',
standalone: true,
template: `
<div class="adf-name-location-cell-name adf-datatable-cell-value" [title]="name">{{ name }}</div>
<div class="adf-name-location-cell-location adf-datatable-cell-value" [title]="path">{{ path }}</div>
@@ -30,7 +31,6 @@ import { DataRow } from '@alfresco/adf-core';
host: { class: 'adf-name-location-cell adf-datatable-content-cell' }
})
export class NameLocationCellComponent implements OnInit {
name: string = '';
path: string = '';

View File

@@ -16,10 +16,11 @@
*/
export * from './name-location-cell/name-location-cell.component';
export * from './site-dropdown/sites-dropdown.component';
export * from './content-node-selector.component-data.interface';
export * from './content-node-selector-panel.component';
export * from './content-node-selector-panel/content-node-selector-panel.component';
export * from './content-node-selector.component';
export * from './content-node-dialog.service';
export * from './content-node-selector-panel.service';
export * from './content-node-selector-panel/content-node-selector-panel.service';
export * from './content-node-selector.module';

View File

@@ -4,12 +4,10 @@
<mat-select
adf-infinite-select-scroll
(scrollEnd)="loadAllOnScroll()"
#siteSelect
data-automation-id="site-my-files-option"
class="adf-site-dropdown-list-element"
id="site-dropdown"
placeholder="{{placeholder | translate}}"
floatPlaceholder="never"
[(value)]="selected"
(selectionChange)="selectedSite($event)">
<mat-select-trigger class="adf-sites-dropdown-select-trigger">

View File

@@ -25,9 +25,9 @@ import {
getFakeSitePagingFirstPage,
getFakeSitePagingLastPage,
getFakeSitePagingWithMembers
} from '../mock';
import { ContentTestingModule } from '../testing/content.testing.module';
import { SitesService } from '../common/services/sites.service';
} from '../../mock';
import { ContentTestingModule } from '../../testing/content.testing.module';
import { SitesService } from '../../common/services/sites.service';
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatSelectHarness } from '@angular/material/select/testing';

View File

@@ -18,10 +18,12 @@
import { Component, EventEmitter, Input, OnInit, Output, ViewEncapsulation } from '@angular/core';
import { InfiniteSelectScrollDirective, AuthenticationService } from '@alfresco/adf-core';
import { SitePaging, SiteEntry, Site } from '@alfresco/js-api';
import { MatSelectChange } from '@angular/material/select';
import { MatSelectChange, MatSelectModule } from '@angular/material/select';
import { LiveAnnouncer } from '@angular/cdk/a11y';
import { TranslateService } from '@ngx-translate/core';
import { SitesService } from '../common/services/sites.service';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { SitesService } from '../../common/services/sites.service';
import { CommonModule } from '@angular/common';
import { MatFormFieldModule } from '@angular/material/form-field';
/* eslint-disable no-shadow */
/* eslint-disable @typescript-eslint/naming-convention */
@@ -33,6 +35,8 @@ export enum Relations {
@Component({
selector: 'adf-sites-dropdown',
standalone: true,
imports: [CommonModule, TranslateModule, MatFormFieldModule, MatSelectModule, InfiniteSelectScrollDirective],
templateUrl: './sites-dropdown.component.html',
encapsulation: ViewEncapsulation.None,
host: { class: 'adf-sites-dropdown' }
@@ -75,7 +79,7 @@ export class DropdownSitesComponent implements OnInit {
* an empty model is emitted.
*/
@Output()
change: EventEmitter<SiteEntry> = new EventEmitter();
change = new EventEmitter<SiteEntry>();
@Output()
error = new EventEmitter<any>();

View File

@@ -26,7 +26,6 @@ import { TagModule } from './tag/tag.module';
import { DocumentListModule } from './document-list/document-list.module';
import { UploadModule } from './upload/upload.module';
import { SearchModule } from './search/search.module';
import { SitesDropdownModule } from './site-dropdown/sites-dropdown.module';
import { BreadcrumbModule } from './breadcrumb/breadcrumb.module';
import { VersionManagerModule } from './version-manager/version-manager.module';
import { ContentNodeSelectorModule } from './content-node-selector/content-node-selector.module';
@@ -49,6 +48,7 @@ import { ContentUserInfoModule } from './content-user-info/content-user-info.mod
import { CategoriesModule } from './category/category.module';
import { contentAuthLoaderFactory } from './auth-loader/content-auth-loader-factory';
import { ContentAuthLoaderService } from './auth-loader/content-auth-loader.service';
import { DropdownSitesComponent } from './content-node-selector/site-dropdown/sites-dropdown.component';
@NgModule({
imports: [
@@ -64,7 +64,7 @@ import { ContentAuthLoaderService } from './auth-loader/content-auth-loader.serv
ContentUserInfoModule,
UploadModule,
MaterialModule,
SitesDropdownModule,
DropdownSitesComponent,
BreadcrumbModule,
ContentNodeSelectorModule,
ContentNodeShareModule,
@@ -90,7 +90,7 @@ import { ContentAuthLoaderService } from './auth-loader/content-auth-loader.serv
ContentUserInfoModule,
UploadModule,
SearchModule,
SitesDropdownModule,
DropdownSitesComponent,
BreadcrumbModule,
ContentNodeSelectorModule,
ContentNodeShareModule,

View File

@@ -30,18 +30,19 @@ import { ContentNodeDialogService } from '../../content-node-selector/content-no
providedIn: 'root'
})
export class DocumentActionsService {
permissionEvent = new Subject<PermissionModel>();
error = new Subject<Error>();
success = new Subject<string>();
private handlers: { [id: string]: ContentActionHandler } = {};
constructor(private nodeActionsService: NodeActionsService,
private contentNodeDialogService: ContentNodeDialogService,
private translation: TranslationService,
private documentListService?: DocumentListService,
private contentService?: ContentService) {
constructor(
private nodeActionsService: NodeActionsService,
private contentNodeDialogService: ContentNodeDialogService,
private translation: TranslationService,
private documentListService?: DocumentListService,
private contentService?: ContentService
) {
this.setupActionHandlers();
}
@@ -114,32 +115,34 @@ export class DocumentActionsService {
}
private prepareHandlers(actionObservable: Subject<string>): void {
actionObservable.subscribe(
(fileOperationMessage) => {
this.success.next(fileOperationMessage);
},
this.error.next.bind(this.error)
);
actionObservable.subscribe((fileOperationMessage) => {
this.success.next(fileOperationMessage);
}, this.error.next.bind(this.error));
}
private deleteNode(node: NodeEntry, _target?: any, permission?: string): Observable<any> {
if (this.canExecuteAction(node)) {
if (this.contentService.hasAllowableOperations(node.entry, permission)) {
const handlerObservable = this.documentListService.deleteNode(node.entry.id);
handlerObservable.subscribe(() => {
const message = this.translation.instant('CORE.DELETE_NODE.SINGULAR', { name: node.entry.name });
this.success.next(message);
}, () => {
const message = this.translation.instant('CORE.DELETE_NODE.ERROR_SINGULAR', { name: node.entry.name });
this.error.next(message);
});
handlerObservable.subscribe(
() => {
const message = this.translation.instant('CORE.DELETE_NODE.SINGULAR', { name: node.entry.name });
this.success.next(message);
},
() => {
const message = this.translation.instant('CORE.DELETE_NODE.ERROR_SINGULAR', { name: node.entry.name });
this.error.next(message);
}
);
return handlerObservable;
} else {
this.permissionEvent.next(new PermissionModel({
type: 'content',
action: 'delete',
permission
}));
this.permissionEvent.next(
new PermissionModel({
type: 'content',
action: 'delete',
permission
})
);
return throwError(new Error('No permission to delete'));
}
}

View File

@@ -32,21 +32,21 @@ import { NodeAction } from '../models/node-action.enum';
})
// eslint-disable-next-line @angular-eslint/directive-class-suffix
export class NodeActionsService {
@Output()
error = new EventEmitter<any>();
constructor(private contentDialogService: ContentNodeDialogService,
public dialogRef: MatDialog,
public content: ContentService,
private documentListService?: DocumentListService,
private apiService?: AlfrescoApiService,
private dialog?: MatDialog,
private downloadService?: DownloadService) {}
constructor(
private contentDialogService: ContentNodeDialogService,
public dialogRef: MatDialog,
public content: ContentService,
private documentListService?: DocumentListService,
private apiService?: AlfrescoApiService,
private dialog?: MatDialog,
private downloadService?: DownloadService
) {}
downloadNode(node: NodeEntry) {
new NodeDownloadDirective(this.apiService, this.downloadService, this.dialog)
.downloadNode(node);
new NodeDownloadDirective(this.apiService, this.downloadService, this.dialog).downloadNode(node);
}
/**
@@ -102,23 +102,29 @@ export class NodeActionsService {
* @param permission permission which is needed to apply the action
* @returns operation result
*/
private doFileOperation(action: NodeAction.COPY | NodeAction.MOVE, type: 'content' | 'folder', contentEntry: Node, permission?: string): Subject<string> {
private doFileOperation(
action: NodeAction.COPY | NodeAction.MOVE,
type: 'content' | 'folder',
contentEntry: Node,
permission?: string
): Subject<string> {
const observable = new Subject<string>();
this.contentDialogService
.openCopyMoveDialog(action, contentEntry, permission)
.subscribe((selections: Node[]) => {
this.contentDialogService.openCopyMoveDialog(action, contentEntry, permission).subscribe(
(selections: Node[]) => {
const selection = selections[0];
this.documentListService[`${action.toLowerCase()}Node`].call(this.documentListService, contentEntry.id, selection.id)
this.documentListService[`${action.toLowerCase()}Node`]
.call(this.documentListService, contentEntry.id, selection.id)
.subscribe(
observable.next.bind(observable, `OPERATION.SUCCESS.${type.toUpperCase()}.${action}`),
observable.error.bind(observable)
observable.next.bind(observable, `OPERATION.SUCCESS.${type.toUpperCase()}.${action}`),
observable.error.bind(observable)
);
},
(error) => {
observable.error(error);
return observable;
});
}
);
return observable;
}
}

View File

@@ -16,7 +16,7 @@
*/
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { ContentNodeSelectorPanelService } from '../../../content-node-selector/content-node-selector-panel.service';
import { ContentNodeSelectorPanelService } from '../../../content-node-selector/content-node-selector-panel/content-node-selector-panel.service';
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
@Component({

View File

@@ -1,18 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './public-api';

View File

@@ -1,20 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './sites-dropdown.component';
export * from './sites-dropdown.module';

View File

@@ -1,41 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 Hyland Software, Inc. and its affiliates. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { MaterialModule } from '../material.module';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { DropdownSitesComponent } from './sites-dropdown.component';
import { CoreModule } from '@alfresco/adf-core';
@NgModule({
imports: [
CommonModule,
MaterialModule,
FormsModule,
ReactiveFormsModule,
CoreModule
],
exports: [
DropdownSitesComponent
],
declarations: [
DropdownSitesComponent
]
})
export class SitesDropdownModule {}

View File

@@ -21,7 +21,6 @@ export * from './lib/document-list/index';
export * from './lib/content-user-info/index';
export * from './lib/upload/index';
export * from './lib/search/index';
export * from './lib/site-dropdown/index';
export * from './lib/breadcrumb/index';
export * from './lib/version-manager/index';
export * from './lib/content-node-selector/index';