Using editor from here: https://github.com/surmon-china/vue-quill-editor
I want to save images from Quill editor to MySQL database, but bigger images in base64 are too long to insert.
I tried to write custom image handler, so that it auto uploads image to server and server returns image URL but now I'm stuck.
Here is my current HTML:
<quill-editor v-model="content"
:options="editorOption"
#onEditorBlur($event)"
#onEditorFocus($event)"
#onEditorReady($event)"
#onEditorChange($event)">
</quill-editor>
Adding image handler to editor like this:
onEditorReady(editor) {
editor.getModule('toolbar').addHandler('image', this.imageHandler);
console.log('editor ready!', editor);
},
And my own handler:
imageHandler(image, callback){
console.log(image); // Always true
console.log(callback); // Always undefined
// Should get image in here somehow..
var sendData = {
image: 'SomethingShouldBeInHere',
};
// Send image to server,
// Server will return link to image
axios.put('/testImageUpload', sendData)
.then(function(cbData) {
// In here should add image tag to editor somehow..
})
.catch(function (error) {
console.log(error);
});
},
I tried this: Add support for custom image handler
But it doesn't work, since image is always true and callback is undefined.
Tried this too: Quill imageHandler Demo
It has same problems first link.
Currently server is hardcoded to return "http://localhost/images/php.jpg"
If possible I would not use any libraries (jQuery, dropzone, etc)
I thought I can perhaps check if image was inserted in onEditorChange(), then send request to server, get URL, search for this base64 in editor and replace it with URL, but it doesn't seem right.
set handlers in your options like this
editorOption: {
modules: {
toolbar: {
container: [['image'], ...],
handlers: {
'image': function(){
document.getElementById('getFile').click()
}
}
}
}
}
methods: {
uploadFunction(e){
//you can get images data in e.target.files
//an single example for using formData to post to server
var form = new FormData()
form.append('file[]', e.target.files[0])
//do your post
}
}
<template>
<quill-editor v-model="content"
:options="editorOption"
#change="oneEditorChange($event)">
</quill-editor>
<input type="file" id="getFile" #change="uploadFunction($event)" />
</template>
This my source code....
//Template
<input type="file" #change="uploadFunction" id="file" hidden>
<quill-editor
v-model="model"
:options="editorOption"
ref="quillEdit">
</quill-editor>
and script
//script
import "quill/dist/quill.core.css";
import "quill/dist/quill.snow.css";
import "quill/dist/quill.bubble.css";
import Quill from "quill";
import { quillEditor } from "vue-quill-editor";
import ImageResize from "quill-image-resize-module";
import axios from '~/plugins/axios'
export default {
data() {
model: '',
selectedFile : '',
editorOption: {
// some quill options
modules: {
toolbar: {
container: [["bold", "image"]],
handlers: {
image: function() {
document.getElementById('file').click()
}
}
},
imageResize: {
modules: ["Resize", "DisplaySize", "Toolbar"]
}
}
},
},
methods: {
uploadFunction(e){
this.selectedFile = e.target.files[0];
var form = new FormData();
form.append("file", this.selectedFile);
form.append("name", this.selectedFile.name);
//upload image to server
axios.post('media-save', form,{
'headers': {
'Content-Type': "multipart/form-data"
}
})
.then(r => {
console.log('success')
//this code to set your position cursor
const range = this.$refs.quillEdit.quill.getSelection()
//this code to set image on your server to quill editor
this.$refs.quillEdit.quill.insertEmbed(range.index , 'image', `http://your.api/${r}`)
})
.catch(e => {
console.log('error')
}
}
}
Related
Uppy is used for uploading files by drag & drop interface.
I want to pass a value input given by user from html interface
but the javascript is already processed before the user gives his input.
if i use .onchange() with the userinput then it does not destroy/clear the previously created file drop area but creates too many new file drop areas inside #drag-drop-area
<input id="userinput" value="Car">
<div id="#drag-drop-area"></div>
var userinput = document.getElementById("userinput").value
var URL = 'example.com/post-submit/?category='+userinput;
var uppy = Uppy.Core()
.use(Uppy.Dashboard, {
inline: true,
target: '#drag-drop-area',
inputName: 'imageFile[]',
height:"250px",
width:"100%"
})
.use(Uppy.XHRUpload, {endpoint: URL", headers: {
'X-CSRF-Token': " {{csrf_token()}} "}
})
uppy.on('complete', (result) => {
console.log('Upload complete! ', result.successful)
})
In vuejs i would like to have image preview from what image selected by user, this following code work fine and i can select image but unfortunately after doing this action reader.readAsDataURL(this.avatar); return incorrect url for me as http://localhost/fa/panel/true and i get 404 error in console.
currect url which i show the this form is http://localhost/fa/panel/user-profile which that supported with Laravel framework.
html image and input tag
<img v-bind:src="image_preview" width="120" height="120" v-show="show_preview"/>
<input type="file" class="file-styled" name="avatar_path" #change="onFileChange">
vuejs code:
import {mapGetters, mapMutations} from "vuex";
export default {
data() {
return {
avatar: null,
image_preview: null,
show_preview: false,
}
},
methods: {
onFileChange(event) {
this.avatar = event.target.files[0];
let reader = new FileReader();
reader.addEventListener("load", function () {
console.log(reader.result) // return base-64 of image
this.image_preview = true;
this.show_preview = reader.result;
}.bind(this), false);
if (this.avatar) {
if (/\.(jpe?g|png)$/i.test(this.avatar.name)) {
console.log(this.avatar); // result is object image
reader.readAsDataURL(this.avatar);
}
}
}
}
};
I have 2 Problems I can't seem to find a solution for. I use
Laravel 5.6
Vue JS Component
TinyMCE 4.9.1
I have crated a vueJs Component for my Form which includes TinyMce and it all seems to work so far. On the Server side I create a folder for each Post I create and the Folder Name gets saved in the Post Table so when I delete a Post the Folder with all the Photos should be deleted.
First of all, I have a understanding Problem. I thought that if I set automatic_uploads: false,
the Photo would not be uploaded straight away, but it does. If that would not happen, then my first Problem would be fixed, because all Photos would be uploaded at the same time.
Lets supply the Sourcecode for a better understanding:
my vueJs Component
<template>
<div>
<form method="post" action="" #submit.prevent="onSubmit">
<fieldset class="add-form"><legend class="add-form">
<h3>Article Details</h3></legend>
<label class="required" for="fname">Headline</label>
<input class="form-control" v-model="post.title" id="fname">
<span class="invalid-feedback" v-text="errors.get('title')"></span>
<input class="form-control" v-model="folder" id="folder">
<tinymce v-model="post.body"
:plugins="myPlugins"
:toolbar ="myToolbar1"
:init="myInit"
>
</tinymce>
<script>
import Editor from '#tinymce/tinymce-vue';
// Import TinyMCE
import tinymce from 'tinymce/tinymce';
// A theme is also required
import 'tinymce/themes/modern/theme';
export default{
components: {
'tinymce': Editor // <- Important part
},
data () {
return {
name: 'app',
folder: null,
myModel:'',
theme: "modern",
myToolbar1: 'undo redo | bold italic underline forecolor backcolor | alignleft aligncenter alignright alignjustify | hr bullist numlist outdent indent | link image table | code preview',
myPlugins: "link image code preview imagetools table lists textcolor hr wordcount",
myInit: {
setup: function(editor){
automatic_uploads: false,
editor.on('NodeChange', function (e) {
if(e.element.tagName === "IMG"){
//e.element.setAttribute("data-original", e.element.currentSrc);
// e.element.setAttribute("src", newSrc);
}
});
},
images_dataimg_filter: function(img) {
return false;
return img.hasAttribute('internal-blob');
},
convert_urls : false,
height:500,
automatic_uploads: false,
images_upload_base_path: '/../../',
relative_urls : false,
// override default upload handler to simulate successful upload
images_upload_handler: function (blobInfo, success, failure) {
var xhr, formData;
xhr = new XMLHttpRequest();
xhr.withCredentials = false;
xhr.open('POST', '/memberarea/api/upload-image');
var token = document.head.querySelector("[name=csrf-token]").content;
xhr.setRequestHeader("X-CSRF-Token", token);
xhr.onload = function() {
var json;
var folder2;
if (xhr.status != 200) {
failure('HTTP Error: ' + xhr.status);
return;
}
json = JSON.parse(xhr.responseText);
if (!json || typeof json.location != 'string') {
failure('Invalid JSON: ' + xhr.responseText);
return;
}
success(json.location);
this.folder = json.folder;
};
formData = new FormData();
formData.append('file', blobInfo.blob(), blobInfo.filename());
xhr.send(formData);
}
},
result:[],
"post": {
title: '',
teaser:'',
body: '',
tags:[],
},
errors: new Errors(),
availableTags:'',
tags:[],
}},
computed: {},
mounted: function () {
this.getTags();
},
methods: {
getTags(){
axios.get('/api/tags/id')
.then((response) => {
this.availableTags = response.data;
})
.catch(function () {
});
},
onSubmit(){
{
let uri = '/admin/news/save-post';
var input = this.post;
axios.post(uri, input)
.then((response) => {
window.location.href = response.data.redirect;
})
.catch(error => this.errors.record(error.response.data.errors));
}
}
}
}
The Image Controller to upload the Image:
public function uploadImages()
{
$folder = uniqid();
if (!\Storage::exists($folder)) {
\Storage::disk('posts')->makeDirectory($folder);
}
$imgpath = \Storage::disk('posts')->put($folder,request()->file('file'));
return \Response::json(['folder' => $folder, 'location' => '/storage/uploads/posts/'.$imgpath]);
}
I have now got the following Problem. I select a Photo, a Folder gets created the Photo uploaded. If I add another Photo a new Folder gets created instead of uploading it into the same Folder. So I thought to return the Foldername from the Upload Function back to the Component, save it in a hidden Input Field and sent it back to the Upload Method again, unfortunately I fail already to set the Value into the Hidden Input Field. Question now, how can that be solved best? any suggestion welcome.
Second Problem, I integrated the imagetools Plugin, but as soon I use it it turns the Image into a Blob. again I do not understand the instructions correctly, but I thought by adding below it would switch this off, but no luck. So I might have an Image saved as a Blob and at the same time saved in a Folder and the next time only the Path is saved in the database. How can I solve this?
images_dataimg_filter: function(img) {
return false;
// return img.hasAttribute('internal-blob');
},
For your first problem, according to tinymce documentation automatic_upload will do nothing if images_upload_url is not specified. If you implement images_upload_url upload will get triggered whenever you call editor.uploadImages().
I am getting this response from the server for a jpeg image get request(with CORS):
ÿØÿàJFIFÿÛ ( %!1"%)+.............
After converting this to base64 it using btoa(encodeURIComponent(data)) looks like:
data:image/jpeg;base64,JUVGJUJGJUJEJUVGJUJGJUJEJUVGJUJGJUJEJUVGJUJGJUJEJTAwJTEwSkZJRiUwMCUwMSUwMSUwMSUwMEglMDBIJTAwJTAwJUVGJUJGJUJEJUVGJUJGJUJEJTAwJUVGJUJGJUJERXhpZiUwMCUwME1NJTAwKiUwMCUwMCUwMCUwOCUwMCUw...............................
On setting the img tag src attribute to the base64 above the image is not rendering in the browser.
However, when I open the image link in the browser tab, it loads properly. Also, on sending the same request in postman it renders the image in the response, in postman.
Even using the blob approach doesn't work (used bower: angular-img-http-src)
$scope.$watch('objectURL', function (objectURL) {
elem.attr('src', objectURL);
});
$scope.$on('$destroy', function () {
revokeObjectURL();
});
$http.get(url)
.then(function (response) {
var blob = new Blob(
[ response.data ],
{ type: response.headers('Content-Type') }
);
$scope.objectURL = URL.createObjectURL(blob);
});
Kindly help here.
In Service:
yourService : function(options) {
return $http.get(url, {
responseType : 'arraybuffer'
}
).success(function(data) {
var file = new Blob([ data ], {
type : 'image/jpeg'
});
var fileURL = URL.createObjectURL(file);
if(options && options.successCallBack) {
return options.successCallBack(fileURL, {});
}
});
},
In Controller:
function yourImageSuccessHandler(fileUrl, options) {
$scope.objectUrl= fileUrl; // now you will have fileUrl in
// controller
}
yourService.getDownloadDoc(empId, {
successCallBack: yourImageSuccessHandler
});
In template HTML:
<img ng-src={{objectUrl}}></img>
Though I don't know if there's a specific angular routine, the general JS solution for created images goes something like this...
function addIMG(durl, callback) {
var img = new Image();
img.addEventListener('load', function() {
callback(this);
}, false);
img.src = durl;
}
addIMG(objectURL, function(img) {
$('#element').appendChild(img);
});
Hope that helped :)
I am not far from it to get the file upload working with Ember-data. But I do not get the value binding right. Below the relevant code.
This is the App.js
App.LandcodeNewRoute = Ember.Route.extend({
model: function () {
return this.store.createRecord('landcode');
},
actions: {
saveLandcode: function () {
this.currentModel.save();
}
}
});
// REST & Model
App.ApplicationAdapter = DS.RESTAdapter.extend({
namespace: 'api'
});
App.Store = DS.Store.extend({
adapter: 'App.ApplicationAdapter'
});
App.Landcode = DS.Model.extend({
code: DS.attr('string'),
image: DS.attr('string')
});
// Views
App.UploadFile = Ember.TextField.extend({
tagName: 'input',
attributeBindings: ['name'],
type: 'file',
change: function (e) {
var reader, that;
that = this;
reader = new FileReader();
reader.onload = function (e) {
var fileToUpload = e.target.result;
console.log(e.target.result); // this spams the console with the image content
console.log(that.get('controller')); // output: Class {imageBinding: Binding,
that.get('controller').set(that.get('name'), fileToUpload);
};
return reader.readAsText(e.target.files[0]);
}
});
HTML
<script type="text/x-handlebars" data-template-name="landcode/new">
Code: {{input value=code}}<br />
Image: {{view App.UploadFile name="image" imageBinding="Landcode.image" }}
<button {{action 'saveLandcode'}}>Save</button>
</script>
As you can see in the HTML part is that I try to bind the imagecontent to the Landcode model attribute image. Tried it also without capital L.
I think I cant bind the image as such, because it is a custom view object? And also normally it would bind automatically I think. Maybe I am just doing some things twice.
References:
http://emberjs.com/api/classes/Ember.Binding.html
http://devblog.hedtek.com/2012/04/brief-foray-into-html5-file-apis.html
File upload with Ember data
How: File Upload with ember.js
http://discuss.emberjs.com/t/file-uploads-is-there-a-better-solution/765
http://chrismeyers.org/2012/06/12/ember-js-handlebars-view-content-inheritance-image-upload-preview-view-object-binding/
I updated your code to the following:
App.LandcodeNewRoute = Ember.Route.extend({
model: function () {
return this.store.createRecord('landcode');
},
actions: {
saveLandcode: function () {
this.currentModel.save();
}
}
});
// REST & Model
App.ApplicationAdapter = DS.RESTAdapter.extend({
namespace: 'api'
});
App.Landcode = DS.Model.extend({
code: DS.attr('string'),
image: DS.attr('string')
});
// views
App.UploadFile = Ember.TextField.extend({
tagName: 'input',
attributeBindings: ['name'],
type: 'file',
file: null,
change: function (e) {
var reader = new FileReader(),
that = this;
reader.onload = function (e) {
var fileToUpload = e.target.result;
Ember.run(function() {
that.set('file', fileToUpload);
});
};
return reader.readAsDataURL(e.target.files[0]);
}
});
In the App.UploadFile instead of reference the controller directlly, I set the file property. So you can bind your model property, with the view using:
{{view App.UploadFile name="image" file=image }}
The Ember.run is used to you don't have problems when testing the app.
Please give a look in that jsfiddle http://jsfiddle.net/marciojunior/LxEsF/
Just fill the inputs and click in the save button. And you will see in the browser console, the data that will be send to the server.
I found that using attaching a data URI to a model attribute didn't allow files more than about 60k to be uploaded. Instead I ended up writing a form data adapter for ember-data. This will upload all the model data using FormData. Sorry that it's in CoffeeScript rather than JS but it's pasted from my blog post. You can read the whole thing at http://blog.mattbeedle.name/posts/file-uploads-in-ember-data/
`import ApplicationAdapter from 'appkit/adapters/application'`
get = Ember.get
FormDataAdapter = ApplicationAdapter.extend
ajaxOptions: (url, type, hash) ->
hash = hash || {}
hash.url = url
hash.type = type
hash.dataType = 'json'
hash.context = #
if hash.data and type != 'GET' and type != 'DELETE'
hash.processData = false
hash.contentType = false
fd = new FormData()
root = Object.keys(hash.data)[0]
for key in Object.keys(hash.data[root])
if hash.data[root][key]
fd.append("#{root}[#{key}]", hash.data[root][key])
hash.data = fd
headers = get(#, 'headers')
if headers != undefined
hash.beforeSend = (xhr) ->
for key in Ember.keys(headers)
xhr.setRequestHeader(key, headers[key])
hash
`export default FormDataAdapter`