Skip to content

Commit 3d28b46

Browse files
authored
Merge pull request #13477 from nextcloud/ckeditor-files-picker
feat(composer): insert images from Files
2 parents de6c0dd + feb8e15 commit 3d28b46

2 files changed

Lines changed: 98 additions & 0 deletions

File tree

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { getFilePickerBuilder, showError } from '@nextcloud/dialogs'
7+
import { translate as t } from '@nextcloud/l10n'
8+
import { ButtonView, IconImageAssetManager, ImageInsertUI, MenuBarMenuListItemButtonView, Plugin } from 'ckeditor5'
9+
import { getClient } from '../../dav/client.js'
10+
import logger from '../../logger.js'
11+
12+
const MIME_TYPES = ['image/png', 'image/jpeg', 'image/gif', 'image/bmp', 'image/webp']
13+
14+
const SIZE_LIMIT = 10 * 1024 * 1024
15+
16+
/**
17+
* Nextcloud file picker for CKEditor.
18+
*/
19+
export default class FilesImagePlugin extends Plugin {
20+
static get requires() {
21+
return [ImageInsertUI] as const
22+
}
23+
24+
static get pluginName() {
25+
return 'FilesImage' as const
26+
}
27+
28+
init(): void {
29+
const editor = this.editor
30+
31+
editor.plugins.get('ImageInsertUI').registerIntegration({
32+
name: 'assetManager',
33+
observable: () => editor.commands.get('insertImage')!,
34+
buttonViewCreator: () => this._setUpButton(new ButtonView(editor.locale), false),
35+
formViewCreator: () => this._setUpButton(new ButtonView(editor.locale), true),
36+
menuBarButtonViewCreator: () => this._setUpButton(new MenuBarMenuListItemButtonView(editor.locale), true),
37+
})
38+
}
39+
40+
_setUpButton<T extends ButtonView>(button: T, withText: boolean): T {
41+
button.set({
42+
// TRANSLATORS: "Files" is the name of the Nextcloud files app
43+
label: t('mail', 'Insert image from Files'),
44+
icon: IconImageAssetManager,
45+
tooltip: !withText,
46+
withText,
47+
})
48+
button.bind('isEnabled').to(this.editor.commands.get('insertImage')!)
49+
button.on('execute', () => this._pickAndInsert())
50+
51+
return button
52+
}
53+
54+
async _pickAndInsert(): Promise<void> {
55+
let node
56+
try {
57+
const nodes = await getFilePickerBuilder(t('mail', 'Choose an image'))
58+
.setMimeTypeFilter(MIME_TYPES)
59+
.setMultiSelect(false)
60+
// TRANSLATORS: button of the file picker to insert the selected image
61+
.addButton({ label: t('mail', 'Insert'), type: 'primary', callback: () => {} })
62+
.build()
63+
.pickNodes()
64+
node = nodes[0]
65+
} catch {
66+
// Picker closed without picking
67+
return
68+
}
69+
70+
if (node.size! > SIZE_LIMIT) {
71+
showError(t('mail', 'The selected image is too large to embed'))
72+
return
73+
}
74+
75+
try {
76+
const response = await getClient('files').getFileContents(node.path, { details: true })
77+
const blob = new Blob([response.data as BlobPart], { type: response.headers['content-type'] })
78+
const dataUri = await this._readBlobAsDataUri(blob)
79+
80+
this.editor.execute('insertImage', { source: dataUri })
81+
this.editor.editing.view.focus()
82+
} catch (error) {
83+
logger.error('Could not insert image from Files', { error })
84+
showError(t('mail', 'Could not insert the selected image'))
85+
}
86+
}
87+
88+
_readBlobAsDataUri(blob: Blob): Promise<string> {
89+
return new Promise((resolve, reject) => {
90+
const reader = new FileReader()
91+
reader.onload = () => resolve(reader.result as string)
92+
reader.onerror = () => reject(reader.error)
93+
reader.readAsDataURL(blob)
94+
})
95+
}
96+
}

src/components/TextEditor.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import {
5454
} from 'ckeditor5'
5555
import { getLinkWithPicker, searchProvider } from '@nextcloud/vue/components/NcRichText'
5656
import TextDirectionPlugin from '../ckeditor/direction/TextDirectionPlugin.js'
57+
import FilesImagePlugin from '../ckeditor/image/FilesImagePlugin.ts'
5758
import MailPlugin from '../ckeditor/mail/MailPlugin.js'
5859
import QuotePlugin from '../ckeditor/quote/QuotePlugin.js'
5960
import SignaturePlugin from '../ckeditor/signature/SignaturePlugin.js'
@@ -150,6 +151,7 @@ export default {
150151
Image,
151152
ImageUpload,
152153
ImageResize,
154+
FilesImagePlugin,
153155
Font,
154156
RemoveFormat,
155157
Base64UploadAdapter,

0 commit comments

Comments
 (0)