contenteditable elements with markup for links on the fly (JS, jQuery) - javascript

I am building a lean & clean html/js-based text editor similar to Medium's writing tool. I use contenteditable to make divs editable.
I'd like to add inline markup for text links (e.g. typing [[Google|https://www.google.com]] instantly converts into <a href='https:www.google.com'>Google</a>, i.e. Google). All this should happen on the fly, i.e. while typing. That is, when the user types the second closing ] of the link markup or the cursor focus is set outside of a [[text|url]] element, JS recognizes this event and instantly converts the [[text|url]] into a html link. On the other hand, if the focus is set on an existing text link (i.e., inside an <a>...</a> html tag within the editable div, (1) the link's default opening behavior is blocked and (2) the text link is instantly converted back into the markup version for editing (e.g., Google becomes [[Google|https://www.google.com]]).
I am not very familiar with regex in JS. Any idea how this can be done?
Thanks very much for your help.
PS: I use jQuery 1.11.0
PSS: The disired behavior is somewhat similar to this text editor's when backspacing into a link (deleting ] converts the link into the markup version and typing the closing ] converts the markup version into a link). The difference is that I am not separating the writing field from the shown text, everything happens inline.

Check out this fiddle.
It is just a sample.
It converts [[Google|https://www.google.com]] to <a href='https://www.google.com'>Google</a>, i.e. Google.
I used this Regular Expression to detect the inline markup.
It has some bugs, but I think it will help you to implement what you need.
Here is the snippet.
$('.editor').keyup(function(e) {
if (e.keyCode === 221) {
var text = $('.editor').html();
var match = /\[\[(.+)\|(https?:\/\/.*\..+)\]\]/g.exec(text);
if (match !== null) {
text = text.replace(match[0], "<span><a href='" + match[2] + "'>" + match[1] + "</a></span>");
$('.editor').html(text);
}
}
});
.editor {
position: absolute;
border: black dashed 2px;
left: 5%;
right: 5%;
top: 10%;
bottom: 10%;
padding: 15px;
font-family: 'Lucida Console';
font-weight: bold;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="editor" contenteditable='true'>This is the Editor. write something here.</div>

Related

Is it possible to edit only a portion of the text content or HTML inside of an element?

The code below correctly changes the text inside of an HTML element by rewriting the entire line of text. This is accomplished using textContent but innerHTML can also do this.
document.querySelectorAll(".box")[0].textContent = 'The number of dollars in my bank account is $1';
body {
margin: 0;
padding: 0;
}
.box {
padding: 10px;
font-size: 2rem;
color: white;
font-family: sans-serif;
background-color: blue;
}
<div class="box">The number of dollars in my bank account is ?</div>
However is there a way to change just a portion of the text without having to rewrite the entire line? This probably isn't correct but something like .innerHTML[5, 10] that would change just those specifically targeted characters.
Put simply when Javascript is used to change text or tags inside of an element does it always rewrite the entire section? This might not be visible to the user but is that what always happens behind the scene? Thanks so much!
Far better approach would be to change your HTML to something like
<div class="box">The number of dollars in my bank account is <span id='amount'></span>.</div>
Then you can easily change just the amount with js:
document.getElementById('amount').innerHTML = '5$';
So your website displays:
The number of dollars in my bank account is 5$.
If you want to stick to your approach, only way to do that would be to save the entire content into a string and then perform some search on it, either by substring, filter or regex. Then you would replace the elements you want and put the string back in the HTML.
Use a nested span tag to display the dollar amount. Then you only have to update the span's text. And when you need the entire sentence you can just access the div.box 's textContent. It returns the textContent of all nested elements concatenated.

CKEditor break <div>

CKEditor works great. There is just minor thing which I would like to improve
We have message system which uses CKEditor. The original (old) text is in a with a vertical blue line. See the screenshot:
I would like to break the <div>.
Up to now I found no way to break it.
In my case a RETURN key should break the <div>.
You can config the enter key with the following modes:
ENTER_P – new <p> paragraphs are created;
ENTER_BR – lines are broken with <br> elements;
ENTER_DIV – new <div> blocks are created.
So in your case, to break a div:
CKEDITOR.replace( 'textarea_id', {
enterMode: CKEDITOR.ENTER_DIV
});
Here are the docs for ENTER_BR
If setting enterMode = CKEDITOR.ENTER_DIV doesn't work for you, another solution is to capture the enter keypress and manually break the div by:
1) inserting a closing </div> tag to split the blue line div, creating the upper half.
2) inserting a new tag <div class="novertline"> which opens a div without a vertical line - This is the break.
3) insert a closing </div>
4) insert another which makes the bottom half of the split text retain its vertical line.
Because insertHtml doesn't allow splitting a div, I used insertText with a flag to subsequently replace with my html.
CKEDITOR.instances.textarea.on( 'key', function (evt) {
if (evt.data.keyCode == 13){
CKEDITOR.instances.textarea.insertText('SOME FLAG');
var data = CKEDITOR.instances.textarea.getData();
data = data.replace('SOME FLAG', '</div><div class="novertline"></div><div class="vertline">');
CKEDITOR.instances.textarea.setData(data);
}
});
In my ckeditor.css file, I added my two classes - but you obviously already have your equivalent div class which is creating the blue line:
.vertline {
margin-left: 10px;
border-left: 1px solid blue;
height: 100%;
}
.novertline {
margin-left: 10px;
border-left: none;
height: 100%;
}
This will need to be customized to your situation. However, it does break the div as you require.

How do widgets that put HTML in a HTML textbox (tags, Google+ Invite, etc) work?

I find it hard to get myself started in this topic so I can write my own widgets for my own needs. How can I combine plain text and HTML elements (links, images), like seen everywhere on the web (Google, Facebook, etc), in a HTML textbox in a way that it still behaves all together like simple text (i. e. deleteable with backspace)?
How does this work? What is the underlying "trick"?
Ok, your question indicates that you need a starting point,
lets then start with a basic HTML such as a div, a ul, and an input
<div class="myTags">
<ul id="tags">
<li class="nd"><input type="text" id="tagInput" placeholder="add some tags..."/></li>
</ul>
</div>
now lets write some jquery to handle the tagging:
$('#tagInput').keypress(function(e){
if (e.keyCode === 13) {
tag = $(this).val();
if(tag.length > 0){
var newLi = $('<li></li>').text(tag);
}
$('#tags').append(newLi);
$(this).val('');
}
});
this jquery snippet listens to the keypress event on the provided input which I called tagInput
the enter key goes with keyCode 13 hence, if it is been hit you take the value of the textbox and and create a new li element then you go and append it to your ul.
what is missing here is how to make the ul looks like horizontal, this is a starting css to be used:
#tags{
float: left;
min-height: 10px;
min-width: 100px;
margin:0px;
padding: 0px;
list-style-type: none;
text-align: center;
background-color: #fff;
border: 1px solid #ccc;
}
#tags li { display: inline-block; padding: 10px;}
#tagInput{background: none;
border: none;}
which will make the ul horizontal, and it will delete the background from the input and adds the border and the background to the ul, which is a lovely trick specially with the placeholder being available, Now for the backspace deleting process it is simple too, take the previous jquery snippet and add the following code to it:
$('#tagInput').keyup(function(e){
if(e.keyCode === 8 && $(this).val().length<=0){
$('#tags li:last').not('.nd').remove();
}
});
which what it does is simply check for keyCode 8 which is a backspace, Note: some people would recommend to listen to keyCode 46 which is delete, it is up to you.
and I also check for the input value so it should be empty to delete the last inserted tag.
Now by wrapping it up you have the following Fiddle to check.
which is a good start point so you can now do whatever you want with the tag styles and many other fancy stuff.
Hope that I helped.
Disclaimer: the previous code is not to be copy pasted, and it is there just for point clarification.
Update
also, adding outline:0 to the input will make more real, see Fiddle
#tagInput{background: none;
border: none; outline:0}
This is all done through javascript (mostly). Look into jquery. All the heavy lifting is done and provides you with a "easy-to-use" javascript library for client-side scripting that can make all these things possible. Obliviously the more complicated you get, the more custom scripting will be needed.
If I understand you correctly, you want to display both text and any other content (images) in one editable component (text area) that looks like native component. I think you can easily achieve that with CSS. The whole trick is to clear the default styling of textarea and wrap it with <div> with custom CSS.
See an example here
You can further enhcance the solution with Javascript and CSS. For instance, you can make the textarea to auto expand as you type
Hope that helps!

Highlight text by keywords which are separated by comma

I would like to split up text by comma - i.e. keywords are as the following:
keyword1, keyword2, keyword3, keyword4
How can I have a the black style element for each and every keyword that is separated by a comma. Is there an easy way to do this? The text is always dynamic, so I never know exactly what those keywords will be and how many. So each keyword should have a box elements around it.
<span class="keyword-option-black">keyword1, keyword2, keyword3, keyword4</span>
.keyword-option-black {
color:white;
background-size:contain;
margin:10px;
padding:5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
background:black;
}
CSS is unable to select text.
In order to highlight the keywords on a page, you need to use programming languages such as JavaScript (for client-side) or PHP (or whatever else for server-side) to wrap the keywords by a wrapper element which has a special CSS style to distinguish the keys.
Here, I implemented the above approach by using jQuery (just to demonstrate):
Working Fiddle
JS part:
// Insert the keywords here,
// you can also get the keys automatically from the DOM if needed
var keywords = ['Google', 'Facebook', 'Social'];
$.each(keywords, function(index, key) {
var $content = $('#content'),
text = $content.html();
$content.html(
text.replace(
new RegExp("("+ key +")", 'ig'), "<span class='highlight'>$1</span>"
)
);
});
And here is the highlight class:
.highlight {
color: white;
background-size: contain;
margin:10px;
padding:5px;
border-radius: 5px;
background: black;
}
HTML and CSS are static. Since you don't know how many keywords are gonna be there, you need something dynamic. Depending on implementation, you can use JavaScript or any Back-end language you are using.
Basically, you want to add a span tag around each keyword and style that span (span in one with class keyword-option-black). So, when you enter keywords to HTML use <span>keywordX</span> in loop that adds elements or, alternatively, add those tags when document loads using JavaScript.
Hope this helps.
Using Django, I did it the following way.
Create a simple CSS class to perform the highlighting.
Create a custom tag that splits up data in template view into <span class="keyword">A</span><span class="keyword">B</span>. It takes the original view output and parses this into a format that already has the spans included.

align div next to input

I'm using Jorn Zaefferer's Autocomplete query plugin, http://bassistance.de/jquery-plugins/jquery-plugin-autocomplete/ and I wanted to add a button that will load all the elements the same as combobox.
So, I created a method that puts a div with image as background next to an input text:
var createDownArrow = function (input, request, options) {
var defaults = {
downArrowClass: 'autocomplete_DownArrow'
};
options = $.extend({}, defaults, options);
var offset = $(input).offset();
var width = typeof options.width == 'string' || options.width > 0 ? options.width : $(input).width();
var element = $('<div/>').addClass(options.downArrowClass).appendTo(document.body).
css({ position: 'absolute', top: offset.top, left: offset.left + width}).click(function () {
if (request && typeof request == 'function') {
request();
}
});
};
the input text has the following css:
border: 1px solid #888888;
height:15px;
font-weight: normal;
font-size: 12px;
font-family: Arial (Hebrew);
padding:0px;
margin:0xp;
this is the div css:
background-image:url(drop.jpg);
background-position:top right;
height:17px;
width:17px;
cursor:pointer;
and using this function on the input in the html:
<br /><br /><br /><br />
<input type="text" id="test" />
I get the result:
Which you can see is not the desire result.
How can I align the div next to the input? (I am using direction:rtl)
try enclosing in label tag
<label for="in"><img src=" " /><input name="in" /></label>
Write up your example that statically includes the arrow at all times in the html. When that works, replace the arrow's html by javascript that can generate it, and you'll be fine.
In more detail, I think you've got a problematic approach; you should use the right tool for the right job. So while it's possible to do what you want by manual positioning, you really don't want to be getting into this quagmire: Javascript is a poor tool for this. You probably can't get around using some javascript, but your life will be much easier if you do as much as possible in HTML+CSS - tools explicitly made for layout. In particular, that means making your arrow button statically first, and then adding exactly the html that works to the DOM-tree, rather that trying to manually control layout by computing width+height+position of the input; which you're almost certainly going to get wrong (not to mention when things get tricky, e.g. with overflow:scroll and the like).
Oh, and you could get rid of the <br/> tags and simply make the input display:block with a margin-top.
One possible CSS approach would be to follow the button by width-0 span (inline element) containing a position absolute (don't affect layout) block that shows whatever you want. E.g.: http://jsfiddle.net/emn13/nvY2F/
To get this to work as a plugin, you'd write javascript to create the span and div in the html; preferably the css would be in a linked stylesheet, but you could use inline styles too, of course. The CSS transitions are just for fun, of course.
In order to solve this, I canceled the absolute position and added display:inline-block to the div. Then I just inserted in before the input and thats all.

Categories