Quill (https://quilljs.com/) makes it simple to embed a quality text editor in a web page. When pasting html content in the editor, it filters the pasted html and then puts it into the text editor. My question is: How can I configure Quill so it pastes only plain text in the text editor? It would filter out all tags and leave the plain text only.
The documentation about the Clipboard module (http://quilljs.com/docs/modules/clipboard/) says that it is possible to add custom Matchers to the Clipboard, that will kind of filter the pasted text.
I don't know how to write a matcher that only allows plain text. Any help and any examples are much appreciated - thanks!
After trial and error, I found the answer. The following matcher will cause the editor to paste plain text only:
quill.clipboard.addMatcher (Node.ELEMENT_NODE, function (node, delta) {
var plaintext = $ (node).text ();
return new Delta().insert (plaintext);
});
It uses jQuery. :)
Couldn't get the answer to work. Here's another method that patches the clipboard module to accept plain text only.
GitHub Gist:
https://gist.github.com/prodrammer/d4d205594b2993224b8ad111cebe1a13
Clipboard implementation:
import Quill from 'quill'
const Clipboard = Quill.import('modules/clipboard')
const Delta = Quill.import('delta')
class PlainClipboard extends Clipboard {
onPaste (e) {
e.preventDefault()
const range = this.quill.getSelection()
const text = e.clipboardData.getData('text/plain')
const delta = new Delta()
.retain(range.index)
.delete(range.length)
.insert(text)
const index = text.length + range.index
const length = 0
this.quill.updateContents(delta, 'silent')
this.quill.setSelection(index, length, 'silent')
this.quill.scrollIntoView()
}
}
export default PlainClipboard
Example usage:
import Quill from 'quill'
import PlainClipboard from './PlainClipboard'
Quill.register('modules/clipboard', PlainClipboard, true)
Updated solution of teusbenschop - works without jQuery and also fix problem with missing Delta object.
quill.clipboard.addMatcher (Node.ELEMENT_NODE, function (node, delta) {
var plaintext = node.innerText
var Delta = Quill.import('delta')
return new Delta().insert(plaintext)
})
For the googlers;
I created a Quill plugin, that removes all tags and attributes that are not supported. Unless otherwise configured it detects that by looking into the toolbar module.
I thought I post it here so others will not have to struggle :)
https://www.npmjs.com/package/quill-paste-smart
import Quill from 'quill';
quill.clipboard.addMatcher(Node.ELEMENT_NODE, function (node, delta) {
const plaintext = node.innerText
const Delta = Quill.import('delta')
return new Delta().insert(plaintext)
});
Related
My need: I'd like to add an "upload from clipboard" functionality into a Vaadin 23 application so that the user can paste a screenshot into an Upload field.
Known pieces of the puzzle: I know that there is a paste event (see here https://stackoverflow.com/a/51586232/10318272 or here https://developer.mozilla.org/en-US/docs/Web/API/Element/paste_event ) and there's the Vaadin Upload component ( https://vaadin.com/docs/latest/components/upload ).
Question: How can I transfer the pasted data into the Upload field?
Why initially intended solution does not work: It seems that uploading a screenshot via an Upload field is not feasible because the FileList (= model of a file input field) does not allow to add/append a new File object.
(Working) Workaround: So my workaround is a TextArea with a paste-EventListener that does a remote procedure call of a #ClientCallable method at the server.
Left component is the TextArea, right component is a preview Image.
Code:
import com.vaadin.flow.component.ClientCallable;
import com.vaadin.flow.component.html.Image;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.server.StreamResource;
import java.io.ByteArrayInputStream;
import java.util.Base64;
public class PasteScreenshotField extends HorizontalLayout {
private final Image previewImg;
public PasteScreenshotField() {
// This could be any focusable type of component, I guess.
TextArea textField = new TextArea();
textField.setWidth("50px");
textField.setHeight("50px");
this.add(textField);
String pasteFunction = "for(const item of event.clipboardData.items) {"
+ " if(item.type.startsWith(\"image/\")) {"
+ " var blob = item.getAsFile();"
+ " var reader = new FileReader();"
+ " reader.onload = function(onloadevent) {$1.$server.upload(onloadevent.target.result);};"
+ " reader.readAsDataURL(blob);"
+ " }"
+ "}";
this.getElement().executeJs("$0.addEventListener(\"paste\", event => {"+pasteFunction+"})", textField.getElement(), this);
// Optional: Preview of the uploaded screenshot
previewImg = new Image();
// TODO: Fixed size of 50px x 50px stretches the image. Could be better.
previewImg.setWidth("50px");
previewImg.setHeight("50px");
this.add(previewImg);
}
#ClientCallable()
private void upload(String dataUrl) {
System.out.println("DataUrl: "+dataUrl);
if (dataUrl.startsWith("data:")) {
byte[] imgBytes = Base64.getDecoder().decode(dataUrl.substring(dataUrl.indexOf(',') + 1));
// Showing a preview is just one of the possible scenarios.
// TODO: check filename extension. Maybe it's not a png.
previewImg.setSrc(new StreamResource("preview.png", () -> new ByteArrayInputStream(imgBytes)));
}
}
}
Extendability: Instead of previewImg.setSrc you could do whatever you want with the uploaded file. The preview is just the proof that the screenshot goes to the server (and could go back to the client again).
Possible connection to Upload component:
If you've got an Upload component and want to extend it with this paste functionality, you can register the paste listener at the Upload component (or at some other component) and instead of previewImg.setSrc you just call this (whereas the onSucceededRunner is a BiConsumer<String, String> in my case that runs the onSucceeded stuff (updating thumbnails, setting attributes at the bound bean, ...)):
String filename = "screenshot.png";
String mimeType = "image/png";
OutputStream outputStream = uploadField.getReceiver().receiveUpload(filename, mimeType);
outputStream.write(imgBytes);
outputStream.flush();
outputStream.close();
onSucceededRunner.accept(filename, mimeTypeString);
Final result:
This is what my custom upload field looks like in the end (assembled from the code above plus an Upload field plus a TextField for the file name and a thumbnail preview). The user now has to click somewhere at that field (=focus it, because there could be more than one in a form), press Ctrl+V and then a screenshot gets uploaded (if there is any) from clipboard to Vaadin application at the server.
How can I make possible that the app will load all of the images from the specific folder and then put in array and choose one image randomly? When chose one then pass to the fronted to show the image. How to do that too?
I am C# developer but not long time ago I found ElectronJS and this framework does everything easier so therefore I am moving to this framework.
I did in C# programming this way:
// basic settings.
var ext = new List<string> { ".jpg", ".gif", ".png" };
// we use same directory where program is.
string targetDirectory = Directory.GetCurrentDirectory() + "\\assets\\" + "images\\" + "animals\\";
// Here we create our list of files
// New list
// Use GetFiles to getfilenames
// Filter unwanted stuff away (like our program)
if (Directory.Exists(targetDirectory))
{
Files = new List<string>
(Directory.GetFiles(targetDirectory, "*.*", SearchOption.TopDirectoryOnly)
.Where(s => ext.Any(es => s.EndsWith(es))));
// Show first picture so we dont need wait 3 secs.
ChangePicture();
}
else
{
panel5.BackgroundImage = new Bitmap(Resources.doggy);
}
I don't know how to do in ElectronJS.
Thank you in advance the answers.
Alright. I found the solution.
However I don't understand the people who are giving negative reputation for the opened question. If they are giving negative reputation then they could explain why.
Well anyway, I did fix this issue with this way:
I created images.js file and added this:
var fs = require('fs');
function getRandImage() {
var files = fs.readdirSync('./assets/images/animals/')
/* now files is an Array of the name of the files in the folder and you can pick a random name inside of that array */
let chosenFile = files[Math.floor(Math.random() * files.length)]
console.log('../assets/images/animals/' + chosenFile);
return '../assets/images/animals/' + chosenFile;
}
module.exports = { getRandImage }
I used console to see if the value is correct, otherwise others can delete that part.
Sending the data to the renderer process:
const { getRandImage } = require('./images');
child.webContents.send('random-image', getRandImage());
I did put in the preload.js file the following (I used the starter pack electronjs github to start with something):
var { ipcRenderer } = require('electron');
ipcRenderer.on('random-image', function (event, store) {
document.getElementById("randompic").src = store;
console.log(store);
});
Same here, I did use console.log just for test the value is correct and I used to change the randompic ID related image src html to the randomly chosen image.
Hopefully I did helping those people who are newbie as me.
I have a script here, which is extracting the links within a web page. It is comming back with the error 'content is not defined'.
// extract the links
var links = new Array();
for (i = 0; i < content.document.links.length; i++) {
var thisLink = content.document.links[i].toString();
//links.push(content.document.links[i].toString());
console.log(thisLink);
}
In order to interact with the HTML documents within the Firefox SDK, do I need to import a library?
It depends on when the self executing anonymous function is running. It is possible that it is running before window.document is defined.
In that case, try adding a listener
window.addEventListener('load', yourFunction, false);
// ..... or
window.addEventListener('DOMContentLoaded', yourFunction, false);
yourFunction () {
// some ocde
}
So I'm learning dart and web development in general. Right now I'm experimenting with the history API. I have:
import 'dart:html';
void main() {
ParagraphElement paragraph = querySelector('.parag');
ButtonElement buttonOne = querySelector('.testaja');
buttonOne.onClick.listen((_) {
window.history.pushState(null, 'test title', '/testdata/');
window.history.forward();
});
ButtonElement buttonTwo = querySelector('.testlagi');
buttonTwo.onClick.listen((_) {
window.history.back();
});
window.onPopState.listen((_) {
window.alert(window.location.pathname);
});
}
My conclusion is that onPopState only triggers when we click on browser's back or forward button, or using window.history.forward() or window.history.back(). So this is like, we render a template, then change its url using pushState, not update template based on url changes. Is this true or not?
Edit:
So maybe I'm not clear enough. Let's say I have something like this:
void main() {
InputElement input = querySelector('.input')
ButtonElement changeUrl = querySelector('.change-url');
changeUrl.onClick.listen((event) {
window.history.pushState(null, 'test tile', input.value);
});
Map urls = {
'/' : showRoot,
'/user/:id' : showUserProfile
};
window.onPopState.listen((_) {
var location = window.location.pathname;
urls[location]();
});
}
I can get input's value by clicking on changeUrl, and then by adding a listener to changeUrl, I can use pushState to update url on browser. What I'm expecting is, when I do pushState, the window.onPopState will triggered and invoke the callback when in reality it doesn't.
tldr, what I'm trying to achieve is:
listen on url changes -> get current url -> use current url to invoke a handler stored in a map. Using onHashChange also doesn't work when updating url using pushState prefixed by #.
edit
set the hash using
window.location.hash = input.value;
this triggers the PopState and HashChange event
as does a click on such a link
abc
original
I don't have time to take a close look what you'r trying to achive..
But I think you should add an event handler for 'window.onHashChange' this way ordinary links work too for navigation, not only buttons with onclick-handlers modifying browser history.
When i init nicEdit with this script :
script 1
<script type="text/javascript">
bkLib.onDomLoaded(function() { nicEditors.allTextAreas() });
My textarea still default , and then i add this script:
script 2
$(document).ready(function() {
nicEditors.allTextAreas();
It's work but what the function of the first script ?
bcause while i just used the second script its work
this is my problem :D
After i'm success init nicEdit , i create new textarea again in new div (i write again script 1 and 2) and then firebug speak " A.removeInstance is not a function "
help me Master
I'm just newbie
In niceEdit.js replace the checkReplace function with the following:
checkReplace : function(e) {
var r;
var editors = nicEditors.editors;
for(var i=0;i<editors.length;i++) {
if(editors[i].instanceById(e)) {
r = editors[i]; // r is an instance of nicEditorInstance therefore it does not have removeInstance or removePanel methods
break;
}
}
if(r) {
r.removeInstance(e);
r.removePanel();
}
return e;
}
Try the newer version from https://github.com/danishkhan/NicEdit/downloads - it's from September 2010. The version from his website is created on April.
On his github-website you can see a comment on the top: "fixed removeInstance bug: r is an instance of nicEditorInstance( or nicE"
Maybe this will help you - I lost the whole morning by fixing an already fixed bug, because I didn't know this =)