What I am trying to achieve
I am building input-like content editable div. You are supposed to click some tags outside the div to add them inside the div while also being able to type around said tags.
The problem and how to reproduce it
I am using user-select: none (normal and webkit) to keep tag buttons from being selected, therefore losing my caret's position. It works in Firefox and Chrome but not in Safari (I aware of the -webkit- prefix and using it).
Here is a fiddle where you can reproduce the problem.
What I've tried
The root of my problem was maintaining the caret's position while leaving the content editable div.
I have previously tried to use rangy but got stuck in some limitations regarding Firefox. These limitations where quite annoying from an UX standpoint. You can check my previous question and how it got me here, to this user-select: none solution -Caret disappears in Firefox when saving its position with Rangy
That's how I got to this solution featuring user-select: none.
My components/JS:
new Vue({
el: "#app",
data(){
return {
filters_toggled: false,
fake_input_content: '',
input_length: 0,
typed: false,
boolean_buttons: [{
type: '1',
label: 'ȘI',
tag: 'ȘI',
img: 'https://i.imgur.com/feHin0S.png'
}, {
type: '2',
label: 'SAU',
tag: 'SAU',
img: 'https://i.imgur.com/vWJeJwb.png'
}, {
type: '3',
label: 'NU',
tag: 'NU',
img: 'https://i.imgur.com/NNg1spZ.png'
}],
saved_sel: 0,
value: null,
options: ['list', 'of', 'options']
}
},
name: 'boolean-input',
methods: {
inputLength($event){
this.input_length = $event.target.innerText.length;
if(this.input_length == 0)
this.typed = false;
},
addPlaceholder(){
if(this.input_length == 0 && this.typed == false){
this.$refs.divInput.innerHTML = 'Cuvinte cheie, cautare booleana..'
}
},
clearPlaceholder(){
if(this.input_length == 0 && this.typed == false){
this.$refs.divInput.innerHTML = '';
}
},
updateBooleanInput($event){
this.typed = true;
this.inputLength($event);
},
saveCursorLocation($event){
/*
if($event.which != 8){
if(this.saved_sel)
rangy.removeMarkers(this.saved_sel)
this.saved_sel = rangy.saveSelection();
}
*/
// if(this.input_length == 0 && this.typed == false){
// var div = this.$refs.divInput;
// var sel = rangy.getSelection();
// sel.collapse(div, 0);
// }
},
insertNode: function(node){
var selection = rangy.getSelection();
var range = selection.getRangeAt(0);
range.insertNode(node);
range.setStartAfter(node);
range.setEndAfter(node);
selection.removeAllRanges();
selection.addRange(range);
},
addBooleanTag($event){
// return this.$refs.ChatInput.insertEmoji($event.img);
if (!this.$refs.divInput.contains(document.activeElement)) {
this.$refs.divInput.focus();
}
console.log(this.input_length);
if(this.typed == false & this.input_length == 0){
this.$refs.divInput.innerHTML = ''
var space = '';
this.typed = true
//this.saveCursorLocation($event);
}
//rangy.restoreSelection(this.saved_sel);
console.log(getSelection().anchorNode, getSelection().anchorOffset, getSelection().focusNode, getSelection().focusOffset)
var node = document.createElement('img');
node.src = $event.img;
node.className = "boolean-button--img boolean-button--no-margin";
node.addEventListener('click', (event) => {
// event.currentTarget.node.setAttribute('contenteditable','false');
this.$refs.divInput.removeChild(node);
})
this.insertNode(node);
this.saveCursorLocation($event);
},
clearHtmlElem($event){
var i = 0;
var temp = $event.target.querySelectorAll("span, br");
if(temp.length > 0){
for(i = 0; i < temp.length; i++){
if(!temp[i].classList.contains('rangySelectionBoundary')){
if (temp[i].tagName == "br"){
temp[i].parentNode.removeChild(temp[i]);
} else {
temp[i].outerHTML = temp[i].innerHTML;
}
}
}
}
},
pasted($event){
$event.preventDefault();
var text = $event.clipboardData.getData('text/plain');
this.insert(document.createTextNode(text));
this.inputLength($event);
this.typed == true;
},
insert(node){
this.$refs.divInput.focus();
this.insertNode(node);
this.saveCursorLocation($event);
},
fixDelete(){
}
},
props: [ 'first'],
mounted() {
this.addPlaceholder()
}
})
My HTML
<div id="app">
<div class="input__label-wrap">
<span class="input__label">Cauta</span>
<div style="user-select: none; -webkit-user-select: none">
<span readonly v-on:click="addBooleanTag(b_button)" v-for="b_button in boolean_buttons" class="boolean-buttons">{{b_button.label}}</span>
</div>
</div>
<div class="input__boolean input__boolean--no-focus">
<div
#keydown.enter.prevent
#blur="addPlaceholder"
#keyup="saveCursorLocation($event); fixDelete(); clearHtmlElem($event);"
#input="updateBooleanInput($event); clearHtmlElem($event);"
#paste="pasted"
v-on:click="clearPlaceholder(); saveCursorLocation($event);"
class="input__boolean-content"
ref="divInput"
contenteditable="true">Cuvinte cheie, cautare booleana..</div>
</div>
</div>
My CSS
.filters__toggler
{
cursor: pointer;
padding: 2px;
transition: all 0.2s ease-in-out;
margin-left: 10px;
}
.filters__toggler path
{
fill: #314964;
}
.filters__toggler-collapsed
{
transform: rotate(-180deg);
}
.input__label
{
font-family: $roboto;
font-size: 14px;
color: #314964;
letter-spacing: -0.13px;
text-align: justify;
}
.input__boolean
{
width: 100%;
background: #FFFFFF;
border: 1px solid #AFB0C3;
border-radius: 5px;
padding: 7px 15px 7px;
font-family: $opensans;
font-size: 14px;
color: #082341;
min-height: 40px;
box-sizing: border-box;
margin-top: 15px;
display: flex;
flex-direction: row;
align-items: center;
line-height: 22px;
overflow: hidden;
}
.input__boolean-content
{
width: 100%;
height: 100%;
outline: none;
border: none;
position: relative;
padding: 0px;
word-break: break-word;
}
.input__boolean img
{
cursor: pointer;
margin-bottom: -6px;
}
.input__boolean--no-focus
{
color: #9A9AA6
}
.input__label-wrap
{
display: flex;
justify-content: space-between;
width: 100%;
position: relative;
}
.boolean-buttons
{
background-color: #007AFF;
padding: 3px 15px;
border-radius: 50px;
color: #fff;
font-family: $roboto;
font-size: 14px;
font-weight: 300;
cursor: pointer;
margin-left: 10px;
}
.boolean-button--img
{
height: 22px;
margin-left: 10px;
}
.boolean-button--no-margin
{
margin: 0;
}
.popper
{
background-color: $darkbg;
font-family: $opensans;
font-size: 12px;
line-height: 14px;
color: #fff;
padding: 4px 12px;
border-color: $darkbg;
box-shadow: 0 5px 12px 0 rgba(49,73,100,0.14);
border-radius: 8px;
}
.filters__helper
{
cursor: pointer;
margin-left: 10px;
margin-bottom: -3px;
}
.popper[x-placement^="top"] .popper__arrow
{
border-color: #082341 transparent transparent transparent;
}
Note: ignore the new vue, it's pasted from the Fiddle. I would suggest using the fiddle to inspect the code, reproduce the problem.
Expected behaviour vs actual results
In Safari (latest version), if I type a word and then click somewhere in that word or move the caret in that word through the keyboard arrows then click one of the tags on the right side of the input, the tag should be added in the middle of clicked word (where was the selection made) but it is added at the beginning of the word.
tl;dr: Safari does not respect the caret's position when clicking one of the tags. It adds the tag at the beginning of the content editable div, not where the caret previously was.
Edit 1: Based on these logs, getSelection() teaches us that the offset is always 0 because in Safari, the div loses focus.
It seems you basically found the answer yourself already. It is a timing issue.
If you change the event to mousedown, the caret position isn't lost and the tag gets inserted at the correct position.
<div id="app">
<div class="input__label-wrap">
<span class="input__label">Cauta</span>
<div style="user-select: none; -webkit-user-select: none">
<span readonly v-on:mousedown="addBooleanTag(b_button)" v-for="b_button in boolean_buttons" class="boolean-buttons">{{b_button.label}}</span>
</div>
</div>
<div class="input__boolean input__boolean--no-focus">
<div
#keydown.enter.prevent
#blur="addPlaceholder"
#keyup="saveCursorLocation($event); fixDelete(); clearHtmlElem($event);"
#input="updateBooleanInput($event); clearHtmlElem($event);"
#paste="pasted"
v-on:click="clearPlaceholder(); saveCursorLocation($event);"
class="input__boolean-content"
ref="divInput"
contenteditable="true">Cuvinte cheie, cautare booleana..</div>
</div>
</div>
https://jsfiddle.net/xmuzp20o/
If you don't want to add the actual tag on mousedown, then you could save the caret position at least in that event, so that you still have the correct position in the click event.
Related
Id like to make a component in react that allows me to have a textarea with tags that can be inserted when clicked from a dropdown. Id also like this textarea to be able to mix text aswell. I have currently been trying to use tagify with react but I cant seem to figure out a way to the tagify's function that adds the tag to be accessed by the onClick that is connected to the dropdown.
Any ideas?
I believe you can get your answer in this URL of other question asked on StackOverflow https://stackoverflow.com/a/38119725/15405352
var $container = $('.container');
var $backdrop = $('.backdrop');
var $highlights = $('.highlights');
var $textarea = $('textarea');
var $toggle = $('button');
// yeah, browser sniffing sucks, but there are browser-specific quirks to handle that are not a matter of feature detection
var ua = window.navigator.userAgent.toLowerCase();
var isIE = !!ua.match(/msie|trident\/7|edge/);
var isWinPhone = ua.indexOf('windows phone') !== -1;
var isIOS = !isWinPhone && !!ua.match(/ipad|iphone|ipod/);
function applyHighlights(text) {
text = text
.replace(/\n$/g, '\n\n')
.replace(/[A-Z].*?\b/g, '<mark>$&</mark>');
if (isIE) {
// IE wraps whitespace differently in a div vs textarea, this fixes it
text = text.replace(/ /g, ' <wbr>');
}
return text;
}
function handleInput() {
var text = $textarea.val();
var highlightedText = applyHighlights(text);
$highlights.html(highlightedText);
}
function handleScroll() {
var scrollTop = $textarea.scrollTop();
$backdrop.scrollTop(scrollTop);
var scrollLeft = $textarea.scrollLeft();
$backdrop.scrollLeft(scrollLeft);
}
function fixIOS() {
// iOS adds 3px of (unremovable) padding to the left and right of a textarea, so adjust highlights div to match
$highlights.css({
'padding-left': '+=3px',
'padding-right': '+=3px'
});
}
function bindEvents() {
$textarea.on({
'input': handleInput,
'scroll': handleScroll
});
$toggle.on('click', function() {
$container.toggleClass('perspective');
});
}
if (isIOS) {
fixIOS();
}
bindEvents();
handleInput();
#import url(https://fonts.googleapis.com/css?family=Open+Sans);
*, *::before, *::after {
box-sizing: border-box;
}
body {
margin: 30px;
background-color: #f0f0f0;
}
.container, .backdrop, textarea {
width: 460px;
height: 180px;
}
.highlights, textarea {
padding: 10px;
font: 20px/28px 'Open Sans', sans-serif;
letter-spacing: 1px;
}
.container {
display: block;
margin: 0 auto;
transform: translateZ(0);
-webkit-text-size-adjust: none;
}
.backdrop {
position: absolute;
z-index: 1;
border: 2px solid #685972;
background-color: #fff;
overflow: auto;
pointer-events: none;
transition: transform 1s;
}
.highlights {
white-space: pre-wrap;
word-wrap: break-word;
color: transparent;
}
textarea {
display: block;
position: absolute;
z-index: 2;
margin: 0;
border: 2px solid #74637f;
border-radius: 0;
color: #444;
background-color: transparent;
overflow: auto;
resize: none;
transition: transform 1s;
}
mark {
border-radius: 3px;
color: transparent;
background-color: #b1d5e5;
}
button {
display: block;
width: 300px;
margin: 30px auto 0;
padding: 10px;
border: none;
border-radius: 6px;
color: #fff;
background-color: #74637f;
font: 18px 'Opens Sans', sans-serif;
letter-spacing: 1px;
appearance: none;
cursor: pointer;
}
.perspective .backdrop {
transform:
perspective(1500px)
translateX(-125px)
rotateY(45deg)
scale(.9);
}
.perspective textarea {
transform:
perspective(1500px)
translateX(155px)
rotateY(45deg)
scale(1.1);
}
textarea:focus, button:focus {
outline: none;
box-shadow: 0 0 0 2px #c6aada;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">
<div class="backdrop">
<div class="highlights"></div>
</div>
<textarea>This demo shows how to highlight bits of text within a textarea. Alright, that's a lie. You can't actually render markup inside a textarea. However, you can fake it by carefully positioning a div behind the textarea and adding your highlight markup there. JavaScript takes care of syncing the content and scroll position from the textarea to the div, so everything lines up nicely. Hit the toggle button to peek behind the curtain. And feel free to edit this text. All capitalized words will be highlighted.</textarea>
</div>
<button>Toggle Perspective</button>
Reference- https://codepen.io/lonekorean/pen/gaLEMR for example
var cancelBtn = document.querySelector(".btn--cancel");
var acceptBtn = document.querySelector(".btn--accept");
var thankYouPopup = document.querySelector(".thank-you-popup");
var comeBackSoonPopup = document.querySelector(".come-back-soon-popup");
document.addEventListener(
"click",
function(e) {
if (
((e.target.className !== "btn btn--cancel" ||
e.target.className !== "btn btn--accept") &&
e.target.className == "btn-container") ||
(e.target.className !== "thank-you-popup" &&
e.target.className == "thank-you-container") ||
(e.target.className !== "come-back-soon-popup" &&
e.target.className == "come-back-soon-container")
) {
console.log("foo");
var els = document.querySelectorAll("*");
for (let i = 0; i < els.length; i++) {
console.log("els[i]", els[i]);
if (
els[i].left === "0" &&
(els[i].className === "thank-you-popup" ||
els[i].className === "come-back-soon-popup")
) {
setTimeout(function() {
els[i].style.left = "10000px";
}, 0);
}
}
}
},
false
);
function myEventHandler(el) {
if (getComputedStyle(el).left == "10000px") {
el.style.left = "0";
setTimeout(function() {
el.style.left = "10000px";
}, 8000);
} else {
el.style.left = "10000px";
}
}
cancelBtn.addEventListener(
"click",
function() {
myEventHandler(comeBackSoonPopup);
},
false
);
cancelBtn.removeEventListener(
"click",
function() {
myEventHandler(comeBackSoonPopup);
},
false
);
acceptBtn.addEventListener(
"click",
function() {
myEventHandler(thankYouPopup);
},
false
);
acceptBtn.removeEventListener(
"click",
function() {
myEventHandler(thankYouPopup);
},
false
);
* {
box-sizing: border-box;
}
body {
overflow-x: hidden;
}
main {
height: auto;
margin-top: 0%;
font: 1rem system-ui;
}
.thank-you-container,
.come-back-soon-container {
width: 100%;
display: flex;
justify-content: flex-end;
}
.thank-you-popup,
.come-back-soon-popup {
text-align: center;
padding: 5%;
height: 38px;
background: lightgrey;
border: 3px solid #bbb;
border-radius: 4px;
width: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 3em;
text-shadow: 0px 1px 0px;
position: relative;
left: 10000px;
transition-property: left;
transition-duration: 1s;
}
p {
text-shadow: 0px 1px 0px rgba(255, 255, 255, 1);
}
.btn-container {
margin: 5% 0;
display: flex;
justify-content: center;
}
.btn {
display: inline-block;
margin: 0 1%;
height: 38px;
padding: 0 30px;
line-height: 38px;
color: #ffffff;
text-align: center;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.1rem;
text-transform: uppercase;
text-decoration: none;
white-space: nowrap;
background-color: transparent;
border-radius: 4px;
border: 1px solid #bbb;
cursor: pointer;
-o-transition: background-color 0.225s ease-in;
transition: background-color 0.225s ease-in;
}
.btn--cancel {
background-color: #ff4136;
}
.btn--cancel:hover {
background-color: #e2392f;
}
.btn--accept {
background: #01ff70;
}
.btn--accept:hover {
background-color: #06da63;
}
<main>
<div class="thank-you-container">
<div class="thank-you-popup">
<p>Thank you!</p>
</div>
</div>
<div class="btn-container">
<button class="btn btn--cancel">cancel</button>
<button class="btn btn--accept">accept</button>
</div>
<div class="come-back-soon-container">
<div class="come-back-soon-popup">
<p>Come back soon!</p>
</div>
</div>
</main>
<!--
In this exercise, you are asked to create a couple of buttons that, when clicked, trigger the display of popups. We want to not only see your coding skills, but also your eye for design and experience. Follow the instructions below and feel free to explain what you're doing or ask questions as you go.
1. Create two buttons centered on the page, next to each other. One should be for canceling and the other for accepting, so use appropriate background colors. The buttons should have rounded borders and should brighten up a bit with easing on hover.
2. Create two popups, one positioned a bit off from the bottom right corner of the page and the other from the top right. The popups should have rounded corners, a slightly thick border, and some padding. One popup should contain the text "Thank you!" while the other should contain the text "Come back soon."
3. Now, position the two popups off screen to the right using CSS.
4. We will now complete the exercise. Add a click handler to each button. For the first button, when clicked we want to slide the "Come back soon" popup from the right into view. For the second button, when clicked we want to slide the "Thank you!" popup from the right into view. The popups should slide back out of view 8 seconds after coming into view or when clicking anywhere on the page except the buttons and the popups.
5. BONUS: Make this work for touch devices!
-->
I'm close to fulfilling this requirement in an exercise:
We will now complete the exercise. Add a click handler to each button.
For the first button, when clicked we want to slide the "Come back
soon" popup from the right into view. For the second button, when
clicked we want to slide the "Thank you!" popup from the right into
view. The popups should slide back out of view 8 seconds after coming
into view or when clicking anywhere on the page except the buttons and
the popups.
I decided to isolate this eventListener on the document to loop only elements in the DOM but when I console.log it's spitting out every possible node i.e. blank spaces, css, script tags.
Can anyone help me just isolate the elements in the dom?
I have included a working example, HTML, CSS and JavaScript
Thanks in advance!
Is this what you want?
var els = document.querySelectorAll("body *");
i want to no repeat the tags that i will writing in field i try but the console appears the span value repeat like this in image how can i fix that to not repeat the tags. i used JQuery
s://i.stack.imgur.com/dIVP1.jpg
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
<input class="add-tags" type="text" placeholder="Type Your Tags">
<div class="tags"></div>
</div>
<script>
$('.add-tags').on('keyup', function(e) {
var tagsKey = e.keyCode || e.which;
if (tagsKey === 188) {
var thisValue = $(this).val().slice(0, -1); //remove last letter
$('.tags').append('<span class="tags-span"><i class="fa fa-times"></i>' + thisValue + '</span>');
var spanvalue = $('.tags-span').text();
console.log(spanvalue);
if (thisValue === spanvalue) {
console.log('good');
} else {
console.log('bad');
}
$(this).val('');
}
$('.tags').on('click', '.tags-span i', function() {
$(this).parent('.tags-span').remove();
});
});
</script>
Voila!
I have a gift for you, but first I would like to point out, that next time you should invest more time into the preparation of your question. Don't cry, don't beg, start from doing your homework first and get as much information as you can. Stackoverflow is not a place were people will do you job for you.
Right now, one can only guess what you are really trying to achieve.
After some harsh treatment let's go to the good parts:
In the following example (https://jsfiddle.net/mkbctrlll/xb6ar2n1/95/)
I have made a simple input that creates a tag on a SPACE key hit. Each tag could be easily removable if X is clicked.
HTML:
<form class="wrapper">
<label for="test">
<span id="tags">
Tags:
</span>
<input id="test" type="text" >
</label>
</form>
JS:
var tagsCollection = []
document.body.onkeyup = function(e){
if(e.keyCode == 32){
var content = getContent('#test')
console.log(tagsCollection.indexOf(content))
if(tagsCollection.indexOf(content) === -1) {
console.log('Spacebar hit! Creating tag')
createTag(content)
tagsCollection.push(content)
console.log(tagsCollection)
} else {
console.log('We already have this one sir!')
displayError('Whoops! Looks like this tag already exists... )')
}
}
}
$('.wrapper').on('click', function(event) {
$(event.target).closest('.tag').remove()
})
function displayError(content) {
var error = document.createElement('p')
error.classList.add('error')
error.innerHTML = content
document.querySelector('.wrapper').append(error)
}
function getContent(target) {
var value = $(target).val().replace(/\W/g, '')
$(target).val("")
return value
}
function createTag(content) {
var $tags = $('#tags')
var tag = document.createElement('span')
var closeIcon = '×'
var iconHTML = document.createElement('span')
iconHTML.classList.add('remove')
iconHTML.innerHTML = closeIcon
tag.classList.add('tag')
tag.append(iconHTML)
tag.append(content)
$tags.append(tag)
}
function removeTag(target) {
target.remove()
}
CSS:
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
.wrapper {
background: #fff;
border-radius: 4px;
padding: 20px;
font-size: 25px;
transition: all 0.2s;
margin: 0 auto;
width: 300px;
}
#tags {
display: block;
margin-bottom: 10px;
font-size: 14px;
}
#test {
display: block;
width: 100%;
}
.tag {
border-radius: 16px;
background-color: #ccc;
color: white;
font-size: 12px;
padding: 4px 6px 4px 16px;
position: relative;
}
.tag:not(:last-child) {
margin-right: 4px;
}
.remove {
font-weight: 600;
position: absolute;
top: 50%;
left: 6px;
cursor: pointer;
transform: translateY(-50%);
}
.remove:hover {
color: red;
}
It is just a quick and dirty example, not a production lvl code.
So far I managed to make this working fiddle. My problem now is that after I press enter to send the data to the server, i need to disable the edit on the current input and pass the focus to the next.
Also does anyone have any idea how do I make that text bliking thing in the project? https://bootsnipp.com/snippets/yNgQ1
PS: you need to press enter to start the console
var terminal = $('#terminal');
$(document).keypress(function(e) {
if (e.which === 13) {
e.preventDefault();
var stdin = $('.stdin').last().text();
console.log(stdin);
consoleInteration(stdin);
}
});
function consoleInteration(stdin) {
//RESULT FROM AJAX POST
result = "This is the output from the shell";
terminal.append('<br><div class="static">' + result + '</div><br>');
terminal.append('<div class="static"><span class="fa fa-arrow-right console-arrow"></span> ~ </div>');
terminal.append('<div class="stdin" id="stdin" contenteditable="true"></div>');
}
.terminal {
width: 100%;
padding: 4px;
background-color: black;
opacity: 0.7;
height: 650px;
color: #fff;
font-family: 'Source Code Pro', monospace;
font-weight: 200;
font-size: 14px;
white-space: pre-wrap;
white-space: -moz-pre-wrap;
white-space: -pre-wrap;
white-space: -o-pre-wrap;
word-wrap: break-word;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
overflow-y: auto;
}
.terminal div {
display: inline-block;
}
.terminal .static {
color: #5ed7ff;
font-weight: bold;
}
.console-arrow {
color: #bde371;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="terminal" class="terminal">
</div>
You can disable edition by doing :
$('.stdin').last().removeAttr("contenteditable")
Then append the next line :
terminal.append('<div class="stdin" id="stdin" contenteditable="true"></div>')
Then select the last (newly added) line and set focus on it :
$('.stdin').last().focus()
What you need
First, .attr(): this allow you to change the contenteditable attribute (true/false).
Secondly .focus(): focus the desired element (just get the last .stdin with .last()).
Handling the cursor
In your div (the one that works like an input), you will make the text color as transparent with color: transparent, this way you will hide the cursor.But you need the text to show, so you will add text-shadow to help: text-shadow: 0 0 0 black.
To create the cursor, you will need one <div> after the other with editable content.
With everything set, you make use of .setInterval() with .css() to change the visibility and, at every change, .remove() the last cursor <div>.
var terminal = $('#terminal');
window.setInterval(function () {
if ($('#cursor').css('visibility') === 'visible') {
$('#cursor').css({
visibility: 'hidden'
});
} else {
$('#cursor').css({
visibility: 'visible'
});
}
}, 500);
$(document).keypress(function(e) {
if (e.which === 13) {
e.preventDefault();
var stdin = $('.stdin').last().text();
console.log(stdin);
consoleInteration(stdin);
}
});
function consoleInteration(stdin) {
$("#cursor").remove();
$(".stdin").last().attr("contenteditable", "false");
//RESULT FROM AJAX POST
result = "This is the output from the shell";
terminal.append('<br><div class="static">' + result + '</div><br>');
terminal.append('<div class="static"><span class="fa fa-arrow-right console-arrow"></span> ~ </div>');
terminal.append('<div class="stdin" id="stdin" contenteditable="true"></div>');
terminal.append('<div id="cursor"></div>');
$(".stdin").last().focus();
}
.terminal {
width: 100%;
padding: 4px;
background-color: black;
opacity: 0.7;
height: 650px;
color: #fff;
font-family: 'Source Code Pro', monospace;
font-weight: 200;
font-size: 14px;
white-space: pre-wrap;
white-space: -moz-pre-wrap;
white-space: -pre-wrap;
white-space: -o-pre-wrap;
word-wrap: break-word;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
overflow-y: auto;
}
.terminal div {
display: inline-block;
}
.terminal .static {
color: #5ed7ff;
font-weight: bold;
}
.console-arrow {
color: #bde371;
}
.stdin{
color: transparent;
text-shadow: 0 0 0 white;
}
#cursor {
top: 10px;
width: 7px;
height: 15px;
margin-bottom: 0;
background: #5ed7ff;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="terminal" class="terminal">
</div>
I'm having quite a bit of trouble while trying to use CKEditor's drag and drop integration.
At first, dragging and dropping into the editor works allright with dataTransfer and all. But whenever I have to destroy and recreate an instance of the editor, dragging and dropping content stops working as expected.
I have modified the sample code directly from CKEditor's SDK page about DnD integration, where you can see the issue being reproduced.
(I just reduced the example as to make it more succint and added the "Destroy and recreate" button at the bottom of the list.)
Could not get it to work in JSFiddle, so sorry about that, but here's the code:
'use strict';
var CONTACTS = [{
name: 'Huckleberry Finn',
tel: '+48 1345 234 235',
email: 'h.finn#example.com',
avatar: 'hfin'
}, {
name: 'D\'Artagnan',
tel: '+45 2345 234 235',
email: 'dartagnan#example.com',
avatar: 'dartagnan'
}];
CKEDITOR.disableAutoInline = true;
CKEDITOR.plugins.add('drag_list', {
requires: 'widget',
init: function(editor) {
editor.widgets.add('drag_list', {
allowedContent: true,
pathName: 'drag_list',
upcast: function(el) {
return el.name == 'table' && el.hasClass('product_widget');
}
});
editor.addFeature(editor.widgets.registered.drag_list);
editor.on('paste', function(evt) {
var contact = evt.data.dataTransfer.getData('contact');
if (!contact) {
return;
}
evt.data.dataValue =
'<span class="h-card">' +
'' + contact.name + '' +
' ' +
'<span class="p-tel">' + contact.tel + '</span>' +
'</span>';
});
}
});
CKEDITOR.document.getById('contactList').on('dragstart', function(evt) {
var target = evt.data.getTarget().getAscendant('div', true);
CKEDITOR.plugins.clipboard.initDragDataTransfer(evt);
var dataTransfer = evt.data.dataTransfer;
dataTransfer.setData('contact', CONTACTS[target.data('contact')]);
dataTransfer.setData('text/html', target.getText());
if (dataTransfer.$.setDragImage) {
dataTransfer.$.setDragImage(target.findOne('img').$, 0, 0);
}
});
CKEDITOR.inline('editor1', {
extraPlugins: 'drag_list,sourcedialog,justify'
});
function destroy_recreate() {
for (var instance in CKEDITOR.instances) {
console.log(CKEDITOR.instances[instance])
CKEDITOR.instances[instance].destroy();
}
CKEDITOR.inline('editor1', {
extraPlugins: 'drag_list,sourcedialog,justify'
});
}
.columns {
background: #fff;
padding: 20px;
border: 1px solid #E7E7E7;
}
.columns:after {
content: "";
clear: both;
display: block;
}
.columns > .editor {
float: left;
width: 65%;
position: relative;
z-index: 1;
}
.columns > .contacts {
float: right;
width: 35%;
box-sizing: border-box;
padding: 0 0 0 20px;
}
#contactList {
list-style-type: none;
margin: 0 !important;
padding: 0;
}
#contactList li {
background: #FAFAFA;
margin-bottom: 1px;
height: 56px;
line-height: 56px;
cursor: pointer;
}
#contactList li:nth-child(2n) {
background: #F3F3F3;
}
#contactList li:hover {
background: #FFFDE3;
border-left: 5px solid #DCDAC1;
margin-left: -5px;
}
.contact {
padding: 0 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.contact .u-photo {
display: inline-block;
vertical-align: middle;
margin-right: 10px;
}
#editor1 .h-card {
background: #FFFDE3;
padding: 3px 6px;
border-bottom: 1px dashed #ccc;
}
#editor1 {
border: 1px solid #E7E7E7;
padding: 0 20px;
background: #fff;
position: relative;
}
#editor1 .h-card .p-tel {
font-style: italic;
}
#editor1 .h-card .p-tel::before,
#editor1 .h-card .p-tel::after {
font-style: normal;
}
#editor1 .h-card .p-tel::before {
content: "(☎ ";
}
#editor1 .h-card .p-tel::after {
content: ")";
}
#editor1 h1 {
text-align: center;
}
#editor1 hr {
border-style: dotted;
border-color: #DCDCDC;
border-width: 1px 0 0;
}
<script src="http://cdn.ckeditor.com/4.5.5/standard-all/ckeditor.js"></script>
<div class="columns">
<div class="editor">
<div cols="10" id="editor1" name="editor1" rows="10" contenteditable="true">
<h3>Drop stuff here then press the Destroy/Recreate button and try again.</h3>
</div>
</div>
<div class="contacts">
<h3>List of Droppable Contacts</h3>
<ul id="contactList">
<li>
<div class="contact h-card" data-contact="0" draggable="true" tabindex="0">
<img src="http://sdk.ckeditor.com/samples/assets/draganddrop/img/hfin.png" alt="avatar" class="u-photo">Huckleberry Finn
</div>
</li>
<li>
<div class="contact h-card" data-contact="1" draggable="true" tabindex="0">
<img src="http://sdk.ckeditor.com/samples/assets/draganddrop/img/dartagnan.png" alt="avatar" class="u-photo">D'Artagnan
</div>
</li>
</ul>
<button class='destroy_recreate' onclick='destroy_recreate()'>Destroy and recreate editors</button>
</div>
</div>
Other plugins like sourcedialog and justify seem to keep working well, but drag_list does not.
Does anyone know why this is happening? What do I have to do to be able to drag and drop content such as the example's hcards in a newly created CKEditor instance?
Thanks in advance.
It looks like a nasty bug in the editor core. I checked it and reported a ticket: https://dev.ckeditor.com/ticket/14339 Until the ticket will be fixed, all I can suggest is to reattach dragstart event on the editor recreation. You can put: CKEDITOR.document.getById('contactList').on('dragstart', ... ); inside the plugin init method. After such change drag and drop should work, but dragstart will be fired multiple times. You can detach the dragstart event, before you attach it again end everything should work fine.