Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to specify which files choosen from CKBox are downloadable. #17712

Merged
merged 5 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 36 additions & 8 deletions packages/ckeditor5-ckbox/src/ckboxcommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type {
CKBoxAssetImageDefinition,
CKBoxAssetLinkAttributesDefinition,
CKBoxAssetLinkDefinition,
CKBoxConfig,
CKBoxRawAssetDefinition
} from './ckboxconfig.js';

Expand Down Expand Up @@ -189,6 +190,7 @@ export default class CKBoxCommand extends Command {
const editor = this.editor;
const model = editor.model;
const shouldInsertDataId = !editor.config.get( 'ckbox.ignoreDataId' );
const downloadableFilesConfig = editor.config.get( 'ckbox.downloadableFiles' );

// Refresh the command after firing the `ckbox:*` event.
this.on<CKBoxEvent>( 'ckbox', () => {
Expand Down Expand Up @@ -230,6 +232,7 @@ export default class CKBoxCommand extends Command {

const assetsToProcess = prepareAssets( {
assets,
downloadableFilesConfig,
isImageAllowed: imageCommand.isEnabled,
isLinkAllowed: linkCommand.isEnabled
} );
Expand Down Expand Up @@ -379,7 +382,8 @@ export default class CKBoxCommand extends Command {
* Parses the chosen assets into the internal data format. Filters out chosen assets that are not allowed.
*/
function prepareAssets(
{ assets, isImageAllowed, isLinkAllowed }: {
{ downloadableFilesConfig, assets, isImageAllowed, isLinkAllowed }: {
downloadableFilesConfig: CKBoxConfig[ 'downloadableFiles' ];
assets: Array<CKBoxRawAssetDefinition>;
isImageAllowed: boolean;
isLinkAllowed: boolean;
Expand All @@ -395,7 +399,7 @@ function prepareAssets(
{
id: asset.data.id,
type: 'link',
attributes: prepareLinkAssetAttributes( asset )
attributes: prepareLinkAssetAttributes( downloadableFilesConfig, asset )
} as const
)
.filter( asset => asset.type === 'image' ? isImageAllowed : isLinkAllowed );
Expand Down Expand Up @@ -424,12 +428,16 @@ export function prepareImageAssetAttributes( asset: CKBoxRawAssetDefinition ): C
/**
* Parses the assets attributes into the internal data format.
*
* @param origin The base URL for assets inserted into the editor.
* @param config The CKBox download asset configuration.
* @param asset The asset to prepare the attributes for.
*/
function prepareLinkAssetAttributes( asset: CKBoxRawAssetDefinition ): CKBoxAssetLinkAttributesDefinition {
function prepareLinkAssetAttributes(
config: CKBoxConfig[ 'downloadableFiles' ],
asset: CKBoxRawAssetDefinition
): CKBoxAssetLinkAttributesDefinition {
Mati365 marked this conversation as resolved.
Show resolved Hide resolved
return {
linkName: asset.data.name,
linkHref: getAssetUrl( asset )
linkHref: getAssetUrl( config, asset )
};
}

Expand All @@ -449,16 +457,36 @@ function isImage( asset: CKBoxRawAssetDefinition ) {
/**
* Creates the URL for the asset.
*
* @param origin The base URL for assets inserted into the editor.
* @param config The CKBox download asset configuration.
* @param asset The asset to create the URL for.
*/
function getAssetUrl( asset: CKBoxRawAssetDefinition ) {
function getAssetUrl( config: CKBoxConfig[ 'downloadableFiles' ], asset: CKBoxRawAssetDefinition ) {
Mati365 marked this conversation as resolved.
Show resolved Hide resolved
const url = new URL( asset.data.url );

url.searchParams.set( 'download', 'true' );
if ( isDownloadableAsset( config, asset ) ) {
url.searchParams.set( 'download', 'true' );
}

return url.toString();
}

/**
* Determines if download should be enabled for given asset based on configuration.
*
* @param config The CKBox download asset configuration.
* @param asset The asset to check.
*/
function isDownloadableAsset(
Mati365 marked this conversation as resolved.
Show resolved Hide resolved
config: CKBoxConfig[ 'downloadableFiles' ],
asset: CKBoxRawAssetDefinition
): boolean {
if ( typeof config === 'function' ) {
return config( asset );
}

return true;
}

/**
* Fired when the command is executed, the dialog is closed or the assets are chosen.
*
Expand Down
18 changes: 18 additions & 0 deletions packages/ckeditor5-ckbox/src/ckboxconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,19 @@ export interface CKBoxConfig {
* ```
*/
choosableFileExtensions?: Array<string>;

/**
* Controls when to enable the download attribute for inserted links.
*
* By default, files are downloadable.
*
* ```ts
* const ckboxConfig = {
* downloadableFiles: asset => asset.data.extension !== 'pdf'
* };
* ```
*/
downloadableFiles?: ( asset: CKBoxRawAssetDefinition ) => boolean;
}

export interface CKBoxDialogConfig {
Expand Down Expand Up @@ -461,6 +474,11 @@ export interface CKBoxRawAssetDataDefinition {
* The asset location.
*/
url: string;

/**
* The asset type.
*/
extension?: string;
}

/**
Expand Down
137 changes: 137 additions & 0 deletions packages/ckeditor5-ckbox/tests/ckboxcommand.js
Original file line number Diff line number Diff line change
Expand Up @@ -1209,6 +1209,143 @@ describe( 'CKBoxCommand', () => {

sinon.assert.calledOnce( focusSpy );
} );

describe( 'downloadable files configuration', () => {
let command;

beforeEach( async () => {
assets = {
images: [
{
data: {
id: 'image-id1',
extension: 'png',
metadata: {
width: 100,
height: 100
},
name: 'image1',
imageUrls: {
100: 'https://example.com/workspace1/assets/image-id1/images/100.webp',
default: 'https://example.com/workspace1/assets/image-id1/images/100.png'
},
url: 'https://example.com/workspace1/assets/image-id1/file'
}
}
],
links: [
{
data: {
id: 'link-id1',
extension: 'pdf',
name: 'file1',
url: 'https://example.com/workspace1/assets/link-id1/file'
}
},
{
data: {
id: 'link-id2',
extension: 'zip',
name: 'file2',
url: 'https://example.com/workspace1/assets/link-id2/file'
}
}
]
};
} );

it( 'should add download parameter to URLs by default', async () => {
const editor = await createTestEditor( {
ckbox: {
tokenUrl: 'foo'
}
} );

command = editor.commands.get( 'ckbox' );
onChoose = command._prepareOptions().assets.onChoose;

onChoose( [ assets.links[ 1 ] ] );

expect( getModelData( editor.model ) ).to.equal(
'<paragraph>' +
'[<$text ' +
'ckboxLinkId="link-id2" ' +
'linkHref="https://example.com/workspace1/assets/link-id2/file?download=true">' +
'file2' +
'</$text>]' +
'</paragraph>'
);

await editor.destroy();
} );

it( 'should allow custom function for determining downloadable files', async () => {
const editor = await createTestEditor( {
ckbox: {
tokenUrl: 'foo',
downloadableFiles: asset => asset.data.name === 'file1'
}
} );

const command = editor.commands.get( 'ckbox' );
const onChoose = command._prepareOptions().assets.onChoose;

// `file1` should not have download parameter.
Mati365 marked this conversation as resolved.
Show resolved Hide resolved
onChoose( [ assets.links[ 0 ] ] );

expect( getModelData( editor.model ) ).to.equal(
'<paragraph>' +
'[<$text ' +
'ckboxLinkId="link-id1" ' +
'linkHref="https://example.com/workspace1/assets/link-id1/file?download=true">' +
'file1' +
'</$text>]' +
'</paragraph>'
);

// `file2` should not have download parameter.
editor.setData( '' );
onChoose( [ assets.links[ 1 ] ] );

expect( getModelData( editor.model ) ).to.equal(
'<paragraph>' +
'[<$text ' +
'ckboxLinkId="link-id2" ' +
'linkHref="https://example.com/workspace1/assets/link-id2/file">' +
'file2' +
'</$text>]' +
'</paragraph>'
);

await editor.destroy();
} );

it( 'should not affect image assets', async () => {
const editor = await createTestEditor( {
ckbox: {
tokenUrl: 'foo'
}
} );

const command = editor.commands.get( 'ckbox' );
const onChoose = command._prepareOptions().assets.onChoose;

onChoose( [ assets.images[ 0 ] ] );

expect( getModelData( editor.model ) ).to.equal(
'[<imageBlock ' +
'alt="" ' +
'ckboxImageId="image-id1" ' +
'height="100" ' +
'sources="[object Object]" ' +
'src="https://example.com/workspace1/assets/image-id1/images/100.png" ' +
'width="100">' +
'</imageBlock>]'
);

await editor.destroy();
} );
} );
} );
} );
} );
Expand Down
3 changes: 2 additions & 1 deletion packages/ckeditor5-ckbox/tests/manual/ckbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ ClassicEditor
ckbox: {
tokenUrl: TOKEN_URL,
forceDemoLabel: true,
allowExternalImagesEditing: [ /^data:/, /^i.imgur.com\//, 'origin' ]
allowExternalImagesEditing: [ /^data:/, /^i.imgur.com\//, 'origin' ],
downloadableFiles: asset => asset.data.extension !== 'pdf'
}
} )
.then( editor => {
Expand Down
Loading