[MNT-25522] Add support for 'Link document' functionality (#5195)

* [MNT-25522] Add 'Link document' functionality

* [MNT-25522] address copilot comments

* [MNT-25522] cr fixes

* [MNT-25522] address code smell
This commit is contained in:
Mykyta Maliarchuk
2026-05-22 17:10:40 +02:00
committed by GitHub
parent c4ff14f359
commit 286bf1b9b4
23 changed files with 1100 additions and 100 deletions
@@ -71,6 +71,27 @@ describe('app.evaluators', () => {
expect(app.canDownloadSelection(context)).toBe(false);
});
it('should not allow downloading when selection contains a file link', () => {
context.selection.isEmpty = false;
context.selection.nodes = [{ entry: { nodeType: 'app:filelink', isFile: true } }] as NodeEntry[];
expect(app.canDownloadSelection(context)).toBe(false);
});
it('should not allow downloading when selection contains a folder link', () => {
context.selection.isEmpty = false;
context.selection.nodes = [{ entry: { nodeType: 'app:folderlink', isFolder: true } }] as NodeEntry[];
expect(app.canDownloadSelection(context)).toBe(false);
});
it('should not allow downloading when any node in multi-selection is a link', () => {
context.selection.isEmpty = false;
context.selection.nodes = [{ entry: { isFile: true } }, { entry: { nodeType: 'app:filelink', isFile: true } }] as NodeEntry[];
expect(app.canDownloadSelection(context)).toBe(false);
});
});
describe('isWriteLocked', () => {
@@ -212,6 +233,22 @@ describe('app.evaluators', () => {
expect(app.canUploadVersion(context)).toBe(true);
});
it('should return [false] when any selected node is a file link', () => {
context.navigation.url = '/personal-files';
context.selection.isEmpty = false;
context.selection.nodes = [{ entry: { nodeType: 'app:filelink' } }] as NodeEntry[];
expect(app.canUploadVersion(context)).toBe(false);
});
it('should return [false] when any selected node is a folder link', () => {
context.navigation.url = '/personal-files';
context.selection.isEmpty = false;
context.selection.nodes = [{ entry: { nodeType: 'app:folderlink' } }] as NodeEntry[];
expect(app.canUploadVersion(context)).toBe(false);
});
});
describe('isShared', () => {
@@ -397,6 +434,26 @@ describe('app.evaluators', () => {
expect(app.canOpenWithOffice(context)).toBeTruthy();
});
it('should return [false] when any selected node is a file link', () => {
context.appConfig = { get: () => true } as any;
context.selection.isEmpty = false;
context.selection.nodes = [{ entry: { nodeType: 'app:filelink' } }] as NodeEntry[];
context.selection.file = { entry: { name: 'document.docx', isLocked: false, properties: {} } } as NodeEntry;
context.permissions = { check: () => true };
expect(app.canOpenWithOffice(context)).toBeFalsy();
});
it('should return [false] when any selected node is a folder link', () => {
context.appConfig = { get: () => true } as any;
context.selection.isEmpty = false;
context.selection.nodes = [{ entry: { nodeType: 'app:folderlink' } }] as NodeEntry[];
context.selection.file = { entry: { name: 'document.docx', isLocked: false, properties: {} } } as NodeEntry;
context.permissions = { check: () => true };
expect(app.canOpenWithOffice(context)).toBeFalsy();
});
});
describe('canEditAspects', () => {
@@ -736,6 +793,22 @@ describe('app.evaluators', () => {
context.repository.status.isQuickShareEnabled = true;
expect(app.canShareFile(context)).toBeTrue();
});
it('should return false when selection contains a file link node', () => {
context.selection.file = { entry: { properties: {} } } as NodeEntry;
context.selection.nodes = [{ entry: { nodeType: 'app:filelink' } }] as NodeEntry[];
context.navigation.url = '/personal-files';
context.repository.status.isQuickShareEnabled = true;
expect(app.canShareFile(context)).toBeFalse();
});
it('should return false when selection contains a folder link node', () => {
context.selection.file = { entry: { properties: {} } } as NodeEntry;
context.selection.nodes = [{ entry: { nodeType: 'app:folderlink' } }] as NodeEntry[];
context.navigation.url = '/personal-files';
context.repository.status.isQuickShareEnabled = true;
expect(app.canShareFile(context)).toBeFalse();
});
});
describe('canToggleJoinLibrary', () => {
@@ -1133,6 +1206,22 @@ describe('app.evaluators', () => {
context.permissions = { check: () => false };
expect(app.canToggleFileLock(context)).toBeTrue();
});
it('should return false when any selected node is a file link', () => {
context.selection.isEmpty = false;
context.selection.nodes = [{ entry: { nodeType: 'app:filelink' } }] as NodeEntry[];
context.selection.file = { entry: { properties: {} } } as NodeEntry;
context.permissions = { check: () => true };
expect(app.canToggleFileLock(context)).toBeFalse();
});
it('should return false when any selected node is a folder link', () => {
context.selection.isEmpty = false;
context.selection.nodes = [{ entry: { nodeType: 'app:folderlink' } }] as NodeEntry[];
context.selection.file = { entry: { properties: {} } } as NodeEntry;
context.permissions = { check: () => true };
expect(app.canToggleFileLock(context)).toBeFalse();
});
});
describe('canPrintFile', () => {
@@ -1223,6 +1312,49 @@ describe('app.evaluators', () => {
});
});
describe('isNodeLink', () => {
it('should return false when selection is empty', () => {
context.selection.isEmpty = true;
expect(app.isNodeLink(context)).toBeFalse();
});
it('should return false when selected node has no nodeType', () => {
context.selection.isEmpty = false;
context.selection.nodes = [{ entry: { isFile: true } }] as NodeEntry[];
expect(app.isNodeLink(context)).toBeFalse();
});
it('should return false when selected node has an unrelated nodeType', () => {
context.selection.isEmpty = false;
context.selection.nodes = [{ entry: { nodeType: 'cm:content' } }] as NodeEntry[];
expect(app.isNodeLink(context)).toBeFalse();
});
it('should return true when selected node has nodeType app:filelink', () => {
context.selection.isEmpty = false;
context.selection.nodes = [{ entry: { nodeType: 'app:filelink' } }] as NodeEntry[];
expect(app.isNodeLink(context)).toBeTrue();
});
it('should return true when selected node has nodeType app:folderlink', () => {
context.selection.isEmpty = false;
context.selection.nodes = [{ entry: { nodeType: 'app:folderlink' } }] as NodeEntry[];
expect(app.isNodeLink(context)).toBeTrue();
});
it('should return true when any node in multi-selection is a link', () => {
context.selection.isEmpty = false;
context.selection.nodes = [{ entry: { nodeType: 'cm:content' } }, { entry: { nodeType: 'app:filelink' } }] as NodeEntry[];
expect(app.isNodeLink(context)).toBeTrue();
});
it('should return false when no node in multi-selection is a link', () => {
context.selection.isEmpty = false;
context.selection.nodes = [{ entry: { nodeType: 'cm:content' } }, { entry: { nodeType: 'cm:folder' } }] as NodeEntry[];
expect(app.isNodeLink(context)).toBeFalse();
});
});
describe('isSSOEnabled', () => {
it('should return true if sso is enabled', () => {
context.appConfig = { get: () => 'OAUTH' } as any;
+29 -2
View File
@@ -121,7 +121,9 @@ export function canRemoveFavorite(context: RuleContext): boolean {
* JSON ref: `app.selection.file.canShare`
*/
export const canShareFile = (context: RuleContext): boolean =>
[context.selection.file, !navigation.isTrashcan(context), repository.hasQuickShareEnabled(context), !isShared(context)].every(Boolean);
[context.selection.file, !navigation.isTrashcan(context), repository.hasQuickShareEnabled(context), !isShared(context), !isNodeLink(context)].every(
Boolean
);
/**
* Checks if user can perform "Join" or "Cancel Join Request" on a library.
@@ -208,6 +210,9 @@ export function canCreateFolder(context: AcaRuleContext): boolean {
* JSON ref: `app.selection.canDownload`
*/
export function canDownloadSelection(context: RuleContext): boolean {
if (isNodeLink(context)) {
return false;
}
return context.selection.nodes.every((node: any) => node.entry && (node.entry.isFile || node.entry.isFolder || !!node.entry.nodeId));
}
@@ -344,6 +349,10 @@ export function canUnlockFile(context: RuleContext): boolean {
* JSON ref: `app.selection.file.canUploadVersion`
*/
export function canUploadVersion(context: RuleContext): boolean {
if (isNodeLink(context)) {
return false;
}
if (navigation.isFavorites(context) || navigation.isSharedFiles(context)) {
return hasFileSelected(context);
}
@@ -363,6 +372,9 @@ export function canUploadVersion(context: RuleContext): boolean {
*/
export const canPrintFile = (context: RuleContext): boolean => {
const nodeEntry = context.selection.file.entry;
if (!nodeEntry?.content?.mimeType) {
return false;
}
const mediaMimeTypes = ['video/mp4', 'video/webm', 'video/ogg', 'audio/mpeg', 'audio/mp3', 'audio/ogg', 'audio/wav'];
return !mediaMimeTypes.includes(nodeEntry.content.mimeType);
};
@@ -390,7 +402,8 @@ export const canEditAspects = (context: RuleContext): boolean =>
repository.isMajorVersionAvailable(context, '7')
].every(Boolean);
export const canToggleFileLock = (context: RuleContext): boolean => [canLockFile(context) || canUnlockFile(context)].some(Boolean);
export const canToggleFileLock = (context: RuleContext): boolean =>
!isNodeLink(context) && [canLockFile(context) || canUnlockFile(context)].some(Boolean);
/**
* @deprecated Uses workarounds for for recent files and search api issues.
@@ -434,6 +447,10 @@ export function canOpenWithOffice(context: AcaRuleContext): boolean {
return false;
}
if (isNodeLink(context)) {
return false;
}
if (context.navigation?.url?.startsWith('/trashcan')) {
return false;
}
@@ -577,3 +594,13 @@ export const isCheckedOut = (context: RuleContext): boolean => {
}
return false;
};
/**
* Checks if any of the selected nodes is a link node (app:filelink or app:folderlink).
* JSON ref: `app.selection.isNodeLink`
*
* @param context Rule execution context
*/
export const isNodeLink = (context: RuleContext): boolean =>
!context.selection?.isEmpty &&
context.selection.nodes.some((node) => node.entry?.nodeType === 'app:filelink' || node.entry?.nodeType === 'app:folderlink');