diff --git a/docs/extensions/components/preview-extension.component.md b/docs/extensions/components/preview-extension.component.md
index 369af0bc06..3ba64711a4 100644
--- a/docs/extensions/components/preview-extension.component.md
+++ b/docs/extensions/components/preview-extension.component.md
@@ -96,6 +96,29 @@ You also need to provide a [viewer component](../../core/components/viewer.compo
}
```
+You can also use `*` wildcard to register a single component that opens all files:
+
+```json
+{
+ "$version": "1.0.0",
+ "$name": "my viewer extension",
+ "$description": "my viewer plugin",
+ "features": {
+ "viewer": {
+ "content": [
+ {
+ "id": "dev.tools.viewer.viewer",
+ "fileExtension": ["*"],
+ "component": "your-extension.main.component"
+ }
+ ]
+ }
+ }
+}
+```
+
+> It is recommended to use wildcard replacement only when introducing your own Viewer implementation.
+
See the [App extensions](../../user-guide/app-extensions.md) page for
further details of how to develop extensions.
diff --git a/lib/core/viewer/components/viewer.component.html b/lib/core/viewer/components/viewer.component.html
index dcacaa3142..347b9356d8 100644
--- a/lib/core/viewer/components/viewer.component.html
+++ b/lib/core/viewer/components/viewer.component.html
@@ -199,8 +199,17 @@
fxFlexOrder="1"
fxFlex="1 1 auto">
-
+
+
+
+
+
{
let element: HTMLElement;
let dialog: MatDialog;
let uploadService: UploadService;
+ let extensionService: AppExtensionService;
setupTestBed({
imports: [
@@ -173,12 +175,65 @@ describe('ViewerComponent', () => {
uploadService = TestBed.inject(UploadService);
alfrescoApiService = TestBed.inject(AlfrescoApiService);
dialog = TestBed.inject(MatDialog);
+ extensionService = TestBed.inject(AppExtensionService);
+ });
+
+ afterEach(() => {
+ fixture.destroy();
});
describe('Extension Type Test', () => {
+ it('should use external viewer via wildcard notation', async () => {
+ const extension: ViewerExtensionRef = {
+ component: 'custom.component',
+ id: 'custom.component.id',
+ fileExtension: '*'
+ };
+ spyOn(extensionService, 'getViewerExtensions').and.returnValue([extension]);
- afterEach(() => {
- fixture.destroy();
+ fixture = TestBed.createComponent(ViewerComponent);
+ element = fixture.nativeElement;
+ component = fixture.componentInstance;
+
+ component.urlFile = 'fake-test-file.pdf';
+ component.ngOnChanges();
+
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ expect(component.externalExtensions.includes('*')).toBe(true);
+ expect(component.externalViewer).toBe(extension);
+ expect(component.viewerType).toBe('external');
+ expect(element.querySelector('[data-automation-id="custom.component"]')).not.toBeNull();
+ });
+
+ it('should use first external viewer provided', async () => {
+ const extensions: ViewerExtensionRef[] = [
+ {
+ component: 'custom.component.1',
+ id: 'custom.component.id',
+ fileExtension: '*'
+ },
+ {
+ component: 'custom.component.2',
+ id: 'custom.component.id',
+ fileExtension: '*'
+ }
+ ];
+ spyOn(extensionService, 'getViewerExtensions').and.returnValue(extensions);
+
+ fixture = TestBed.createComponent(ViewerComponent);
+ element = fixture.nativeElement;
+ component = fixture.componentInstance;
+
+ component.urlFile = 'fake-test-file.pdf';
+ component.ngOnChanges();
+
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ expect(element.querySelector('[data-automation-id="custom.component.1"]')).not.toBeNull();
+ expect(element.querySelector('[data-automation-id="custom.component.2"]')).toBeNull();
});
it('should extension file pdf be loaded', (done) => {
@@ -244,11 +299,6 @@ describe('ViewerComponent', () => {
});
describe('MimeType handling', () => {
-
- afterEach(() => {
- fixture.destroy();
- });
-
it('should display an image file identified by mimetype when the filename has no extension', (done) => {
component.urlFile = 'fake-content-img';
component.mimeType = 'image/png';
diff --git a/lib/core/viewer/components/viewer.component.ts b/lib/core/viewer/components/viewer.component.ts
index 0e21fcbeb5..7a660dfeb7 100644
--- a/lib/core/viewer/components/viewer.component.ts
+++ b/lib/core/viewer/components/viewer.component.ts
@@ -225,17 +225,39 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
versionEntry: VersionEntry;
extensionTemplates: { template: TemplateRef, isVisible: boolean }[] = [];
- externalExtensions: string[] = [];
urlFileContent: string;
otherMenu: any;
extension: string;
sidebarRightTemplateContext: { node: Node } = { node: null };
sidebarLeftTemplateContext: { node: Node } = { node: null };
fileTitle: string;
- viewerExtensions: Array = [];
+
+ /**
+ * Returns a list of the active Viewer content extensions.
+ */
+ get viewerExtensions(): ViewerExtensionRef[] {
+ return this.extensionService.getViewerExtensions();
+ }
+
+ /**
+ * Provides a list of file extensions supported by external plugins.
+ */
+ get externalExtensions(): string[] {
+ return this.viewerExtensions.map(ext => ext.fileExtension);
+ }
+
+ private _externalViewer: ViewerExtensionRef;
+ get externalViewer(): ViewerExtensionRef {
+ if (!this._externalViewer) {
+ this._externalViewer = this.viewerExtensions.find(ext => ext.fileExtension === '*');
+ }
+
+ return this._externalViewer;
+ }
+
readOnly = true;
- private cacheBusterNumber;
+ private cacheBusterNumber: number;
cacheTypeForContent = '';
// Extensions that are supported by the Viewer without conversion
@@ -316,18 +338,9 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
});
this.closeOverlayManager();
- this.loadExtensions();
this.cacheTypeForContent = '';
}
- private loadExtensions() {
- this.viewerExtensions = this.extensionService.getViewerExtensions();
- this.viewerExtensions
- .forEach((extension: ViewerExtensionRef) => {
- this.externalExtensions.push(extension.fileExtension);
- });
- }
-
private getNodeVersionProperty(node: Node): string {
return node?.properties['cm:versionLabel'] ?? '';
}
@@ -427,13 +440,8 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
this.fileTitle = this.getDisplayName(filenameFromUrl);
this.extension = this.getFileExtension(filenameFromUrl);
this.urlFileContent = this.urlFile;
-
this.fileName = this.displayName;
-
- this.viewerType = this.urlFileViewer || this.getViewerTypeByExtension(this.extension);
- if (this.viewerType === 'unknown') {
- this.viewerType = this.getViewerTypeByMimeType(this.mimeType);
- }
+ this.viewerType = this.urlFileViewer || this.getViewerType(this.extension, this.mimeType);
this.extensionChange.emit(this.extension);
this.scrollTop();
@@ -442,8 +450,6 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
private async setUpNodeFile(nodeData: Node, versionData?: Version) {
this.readOnly = !this.contentService.hasAllowableOperations(nodeData, 'update');
- let setupNode;
-
if (versionData && versionData.content) {
this.mimeType = versionData.content.mimeType;
} else if (nodeData.content) {
@@ -461,13 +467,10 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
this.urlFileContent + '&' + currentFileVersion;
this.extension = this.getFileExtension(versionData ? versionData.name : nodeData.name);
-
this.fileName = versionData ? versionData.name : nodeData.name;
+ this.viewerType = this.getViewerType(this.extension, this.mimeType);
- this.viewerType = this.getViewerTypeByExtension(this.extension);
- if (this.viewerType === 'unknown') {
- this.viewerType = this.getViewerTypeByMimeType(this.mimeType);
- }
+ let setupNode: Promise;
if (this.viewerType === 'unknown') {
if (versionData) {
@@ -485,18 +488,23 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
return setupNode;
}
+ private getViewerType(extension: string, mimeType: string): string {
+ let viewerType = this.getViewerTypeByExtension(extension);
+
+ if (viewerType === 'unknown') {
+ viewerType = this.getViewerTypeByMimeType(mimeType);
+ }
+
+ return viewerType;
+ }
+
private setUpSharedLinkFile(details: any) {
this.mimeType = details.entry.content.mimeType;
this.fileTitle = this.getDisplayName(details.entry.name);
this.extension = this.getFileExtension(details.entry.name);
this.fileName = details.entry.name;
-
this.urlFileContent = this.contentApi.getSharedLinkContentUrl(this.sharedLinkId, false);
-
- this.viewerType = this.getViewerTypeByMimeType(this.mimeType);
- if (this.viewerType === 'unknown') {
- this.viewerType = this.getViewerTypeByExtension(this.extension);
- }
+ this.viewerType = this.getViewerType(this.extension, this.mimeType);
if (this.viewerType === 'unknown') {
this.displaySharedLinkRendition(this.sharedLinkId);
@@ -552,6 +560,10 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
extension = extension.toLowerCase();
}
+ if (this.isExternalViewer()) {
+ return 'external';
+ }
+
if (this.isCustomViewerExtension(extension)) {
return 'custom';
}
@@ -628,8 +640,12 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
return null;
}
+ private isExternalViewer(): boolean {
+ return !!this.viewerExtensions.find(ext => ext.fileExtension === '*');
+ }
+
isCustomViewerExtension(extension: string): boolean {
- const extensions: any = this.externalExtensions || [];
+ const extensions = this.externalExtensions || [];
if (extension && extensions.length > 0) {
extension = extension.toLowerCase();