VueJS CKeditor5 upload images - vue.js

Having trouble with uploading images using CKeditor5 in Vuejs.
First having tried Simple upload Adapter which gave me the following error:
Reason: CKEditorError: ckeditor-duplicated-modules: Some CKEditor 5 modules are duplicated. Read more: https://ckeditor.com/docs/ckeditor5/latest/framework/guides/support/error-codes.html#error-ckeditor-duplicated-modules
I tried making a upload adapter. As a uploadadapter I took the example and modified the url. The uploadadapter.js file looks like the following:
export default class UploadAdapter {
constructor( loader ) {
// The file loader instance to use during the upload.
this.loader = loader;
}
// Starts the upload process.
upload() {
return this.loader.file
.then( file => new Promise( ( resolve, reject ) => {
this._initRequest();
this._initListeners( resolve, reject, file );
this._sendRequest( file );
} ) );
}
// Aborts the upload process.
abort() {
if ( this.xhr ) {
this.xhr.abort();
}
}
// Initializes the XMLHttpRequest object using the URL passed to the constructor.
_initRequest() {
const xhr = this.xhr = new XMLHttpRequest();
xhr.open( 'POST', '<url here>', true );
xhr.responseType = 'json';
}
// Initializes XMLHttpRequest listeners.
_initListeners( resolve, reject, file ) {
const xhr = this.xhr;
const loader = this.loader;
const genericErrorText = `Couldn't upload file: ${ file.name }.`;
xhr.addEventListener( 'error', () => reject( genericErrorText ) );
xhr.addEventListener( 'abort', () => reject() );
xhr.addEventListener( 'load', () => {
const response = xhr.response;
if ( !response || response.error ) {
return reject( response && response.error ? response.error.message : genericErrorText );
}
resolve( {
default: response.url
} );
} );
if ( xhr.upload ) {
xhr.upload.addEventListener( 'progress', evt => {
if ( evt.lengthComputable ) {
loader.uploadTotal = evt.total;
loader.uploaded = evt.loaded;
}
} );
}
}
// Prepares the data and sends the request.
_sendRequest( file ) {
// Prepare the form data.
const data = new FormData();
data.append( 'upload', file );
// Send the request.
this.xhr.send( data );
}
}
The Vue component:
<template>
<form #submit.prevent="store">
<ckeditor
:editor="editor"
v-model="form.content"
:error-messages="errors.content"
:config="editorConfig"
/>
</form>
</template>
<script>
import CKEditor from '#ckeditor/ckeditor5-vue';
import ClassicEditor from '#ckeditor/ckeditor5-build-classic';
import UploadAdapter from '../../UploadAdapter';
export default {
data()
{
return {
form: {
content: null,
},
editor: ClassicEditor,
editorConfig: {
toolbar: [ 'heading', '|', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', '|', 'insertTable', '|', 'imageUpload', 'mediaEmbed', '|', 'undo', 'redo' ],
table: {
toolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
},
extraPlugin: [this.uploader],
language: 'nl',
},
}
},
methods: {
store()
{
// Some code
},
uploader(editor)
{
editor.plugins.get( 'FileRepository' ).createUploadAdapter = ( loader ) => {
return new UploadAdapter( loader );
};
},
},
components: {
ckeditor: CKEditor.component
}
}
</script>
However each time when trying to upload a file the following warning is returned:
filerepository-no-upload-adapter: Upload adapter is not defined. Read more: https://ckeditor.com/docs/ckeditor5/latest/framework/guides/support/error-codes.html#error-filerepository-no-upload-adapter
Have looked at the url but it just sends me in circles thus making no progress. What I'm looking for is an example that at least sends a file to the server without errors/ warnings. If the uploadadapter can be scraped and something else except CKfinder can be used that's fine. For now I guess the problem is most likely to be in the Vue component.

use extraPlugins instead of extraPlugin.

moving uploader function to outside of vue component and then using it directly as
extraPlugin: [uploader]
worked for me.

Related

How to use mocking for test uploading file with jest

I am developing a system with nuxt js and jest that in part of that I want to upload an image.
Here's my html code:
<input
id="photo"
ref="photo"
type="file"
name=""
class="form-control d-flex"
#change="uploadPhoto"
>
Here's my uploadPhoto function in nuxt js:
uploadPhoto () {
const file = this.$refs.photo.files[0]
// upload photo
const formData = new FormData()
formData.append('photo', file)
const returnedData = await this.$axios.$post('/api/photo/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
The question is:
How may I mock uploading photo in jest to test my code?
My jest code is something like this:
test('uploading photo test', () => {
wrapper = mount(UploadPhotoComponent, {
stubs: {
NuxtLink: true
},
mocks: {
$auth: {
loggedIn: true,
$storage: {
_state: {
'_token.local': 'api bearer token'
}
}
},
$axios: {
$post: jest.fn(() => {
return Promise.resolve({
status: 200,
message: 'photo was uploaded successfully.',
entire: []
})
})
}
}
})
})
I don't know how to test uploading file in jest using mocks.
Can anyone help me?
Finally, I have realized how to do that. First of all generate an image blob and file:
import fs from 'fs';
import path from 'path';
const dummyImgData = fs.readFileSync(
path.join(__dirname, '../../libs/dummy-img-2.jpeg')
);
const fbParts = [
new Blob([dummyImgData], {
type: 'image/jpeg'
}),
'Same way as you do with blob',
new Uint16Array([999999999999999])
];
const fbImg = new File(fbParts, 'sample.png', {
lastModified: new Date(2020, 1, 1),
type: 'image/jpeg'
});
And after assign it to $refs object:
wrapper.vm.$refs = {
photo: {
files: [
fbImg
]
}
}

How pass data from component to external js file

I want to use my component's data within my external JavaScript file, containing my Dropzone configuration. I tried unsuccessfully to use Function.prototype.bind:
export const dropzoneConfig = {
url: api.http.baseUrl + '/files',
thumbnailWidth: 150,
maxFilesize: 5,
acceptedFiles: 'image/*',
addRemoveLinks: true,
sending: function (file, xhr, formData) {
formData.append('type', 'photo');
},
success: function (file, xhr) {
file.id = xhr.data.id;
if (this.entry.files === undefined) {
this.entry.files = [];
}
this.entry.files.push(xhr.data);
this.saveNote();
}.bind(this),
headers: api.http.authHeaders().headers
};
In the code above, this.entry and this.saveNote are unavailable because they're from my Vue component. How do I make them accessible to the external file?
A more general solution would be for the component to pass in a success-event handler that has access to the component's data and methods, as shown below. This solution decouples the configuration from the component's internals.
dropzoneConfig.js:
export const dropzoneConfig = ({ onSuccess }) => ({
//...
success(file, xhr) {
//...
onSuccess(xhr.data)
}
})
App.vue:
<script>
import Dropzone from 'dropzone'
import { dropzoneConfig } from './dropzoneConfig'
export default {
data() {
return {
entry: {
files: []
}
}
},
created() {
Dropzone.options.myComponent = dropzoneConfig({
onSuccess: fileData => this.onDropzoneSuccess(fileData)
})
},
methods: {
saveNote() {
//...
},
onDropzoneSuccess(fileData) {
this.entry.files.push(fileData)
this.saveNote()
}
}
}
</script>

Generating and showing PDF inside NativeScript app

I want to show a PDF file generated with pdfMake with the nativescript-pdf-view plugin in a NativeScript-Vue app.
I am unable to show the file inside the plugin widget. I started from this example, but I want to display the file inside the app.
First I generate encode the PDF to base64, which works fine, then I write the encoded string to file. When I pass the file path to the widget nothing shows up. URLs are shown correctly, so the error might happen while writing to file or handling the path.
PDF generation (app/scripts/pdf.js):
import { createPdf } from "pdfmake/build/pdfmake";
import { pdfMake } from "pdfmake/build/vfs_fonts";
import { knownFolders } from "tns-core-modules/file-system";
export default {
generatePdf() {
return new Promise((resolve, reject) => {
var docDefinition = {
pageSize: "A5",
pageMargins: [40, 40, 40, 40],
content: { text: "Test", fontSize: 80 }
};
var file = knownFolders.documents().getFile("document1.pdf");
createPdf(docDefinition, "", "", pdfMake.vfs).getBase64(base64 => {
// decoding this string returns a correct pdf file
file.writeText(base64).then(() => {
// file properties are updated
resolve(file.path);
// path example: "/data/user/0/{app package}/files/document1.pdf"
}).catch(e => reject(e));
});
});
},
// other code
}
Component:
<template>
<GridLayout rows="* *">
<Button row="0" #tap="getPDF" text="get pdf" />
<PDFView row="1" :src="pdf" />
</GridLayout>
</template>
<script>
import pdfModule from "../scripts/pdf";
export default {
data() {
pdf: ""
},
methods: {
async getPDF() {
this.pdf = await pdfModule.generatePdf().catch(e => console.error(e));
}
}
}
</script>
Solved using native byte arrays.
import { createPdf } from "pdfmake/build/pdfmake";
import { pdfMake } from "pdfmake/build/vfs_fonts";
import { knownFolders } from "tns-core-modules/file-system";
import { isAndroid } from "tns-core-modules/platform";
export default {
generatePdf() {
return new Promise((resolve, reject) => {
let docDefinition = {
pageSize: "A5",
pageMargins: [40, 40, 40, 40],
content: { text: "Test", fontSize: 80 }
};
let file = knownFolders.documents().getFile("document1.pdf");
createPdf(docDefinition, "", "", pdfMake.vfs).getBuffer(result => {
file.writeSync(this._bufferToNativeArray(result), e => {
console.error(e);
reject(e);
});
resolve(file.path);
});
});
},
_bufferToNativeArray(byteArray) {
let array;
if (isAndroid) {
array = Array.create("byte", byteArray.byteLength);
for (let i = 0; i < byteArray.length; i++) {
array[i] = new java.lang.Byte(byteArray[i]);
}
} else {
array = NSData.dataWithBytesLength(byteArray, byteArray.byteLength);
}
return array;
}
}

ckeditor5 custom upload adapter

When I try to select an image from the tool 'Insert image' the value of the loader is undefined and the image don't upload.
I've reading the documentation buy I can't find why my images don't move to my local folder 'uploads'.
Here is the code, it's mostly copied from the documentation, but the image don't move.
If I write http://localhost/uploads in the browser, the path is correct.
<html>
<head>
<script src="https://cdn.ckeditor.com/ckeditor5/16.0.0/classic/ckeditor.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<div id="editor"></div>
</body>
</html>
<script>
class MyUploadAdapter {
constructor( loader ) {
// The file loader instance to use during the upload.
this.loader = loader;
}
// Starts the upload process.
upload() {
return this.loader.file
.then( file => new Promise( ( resolve, reject ) => {
this._initRequest();
this._initListeners( resolve, reject, file );
this._sendRequest( file );
} ) );
}
// Aborts the upload process.
abort() {
if ( this.xhr ) {
this.xhr.abort();
}
}
// Initializes the XMLHttpRequest object using the URL passed to the constructor.
_initRequest() {
const xhr = this.xhr = new XMLHttpRequest();
xhr.open( 'POST', 'http://localhost/uploads', true );
xhr.responseType = 'json';
}
// Initializes XMLHttpRequest listeners.
_initListeners( resolve, reject, file ) {
const xhr = this.xhr;
const loader = this.loader;
const genericErrorText = `Couldn't upload file: ${ file.name }.`;
xhr.addEventListener( 'error', () => reject( genericErrorText ) );
xhr.addEventListener( 'abort', () => reject() );
xhr.addEventListener( 'load', () => {
const response = xhr.response;
if ( !response || response.error ) {
return reject( response && response.error ? response.error.message : genericErrorText );
}
resolve( {
default: response.url
} );
} );
if ( xhr.upload ) {
xhr.upload.addEventListener( 'progress', evt => {
if ( evt.lengthComputable ) {
loader.uploadTotal = evt.total;
loader.uploaded = evt.loaded;
}
} );
}
}
_sendRequest( file ) {
const data = new FormData();
data.append( 'upload', file );
this.xhr.send( data );
}
}
function MyCustomUploadAdapterPlugin( editor ) {
editor.plugins.get( 'FileRepository' ).createUploadAdapter = ( loader ) => {
// Configure the URL to the upload script in your back-end here!
return new MyUploadAdapter( loader );
};
}
ClassicEditor
.create( document.querySelector( '#editor' ), {
extraPlugins: [ MyCustomUploadAdapterPlugin ]
} );
</script>

Vue.js: progress upload doesn't render in view

I'm trying to make an uploader component in Vuejs + Firebase Storage. The method works fine, but I can't get to show the percent uploaded in the view. Here my code:
<template>
<div>
<input type="file" multiple accept="image/*" #change="detectFiles($event.target.files)">
<div>{{progressUpload}}</div>
</div>
</template>
<script>
import { storage } from '../firebase'
export default {
data () {
return {
progressUpload: 0,
file: File,
}
},
methods: {
detectFiles (fileList) {
Array.from(Array(fileList.length).keys()).map( x => {
this.upload(fileList[x])
})
},
upload (file) {
let uploadTask = storage.ref('imagenes/articulos').put(file);
uploadTask.on('state_changed', function(snapshot) {
this.progressUpload = Math.floor(snapshot.bytesTransferred / snapshot.totalBytes * 100);
})
uploadTask.then(() => {
console.info('File uploaded!');
})
}
}
}
</script>
Writing {{ progressUpload }} in the HTML template, doesn't show nothing. But in the console, the result is logged perfectly. The file is uploaded properly
Ok, I found the solution. I had to add the uploadProgress calculation into a watcher that returns the new value of the upload task async method. So, my script has been as follows:
<script>
import { storage } from '../firebase'
export default {
data () {
return {
progressUpload: 0,
file: File,
uploadTask: ''
}
},
methods: {
detectFiles (fileList) {
Array.from(Array(fileList.length).keys()).map( x => {
this.upload(fileList[x])
})
},
upload (file) {
this.uploadTask = storage.ref('imagenes/articulos').put(file);
this.uploadTask.then(() => {
console.info('Archivo guardado correctamente');
})
}
},
watch: {
uploadTask: function() {
this.uploadTask.on('state_changed', sp => {
this.progressUpload = Math.floor(sp.bytesTransferred / sp.totalBytes * 100)
})
}
}
}
</script>