I have this HTML code with pre-written message. My goal is to highlight text between [quote] [/quote] in a yellow background once I focus/click on the text area.
<textarea>
This is a test message.
[quote]Wise man said he is wise.[/quote] There could be more quotes too:
[quote]this is second quote [/quote]
He is correct.
</textarea>
Is it possible to do it with pure Javascript? I think it should be something like:
textarea onfocus="function()">
find text between [quote][/quote]
apply yellow background to found text: background Color='#ffc'
....
(and if there is no [quote] [/quote] found then it should do nothing, ie. no warnings).
Since you cannot do that using <textatea> i'd suggest to take a look at
<div contenteditable>
</div>
here's an example:
var area = document.getElementById("area");
var text = area.innerHTML;
area.innerHTML = text.replace(/\[\s*quote.*\](.*)[^[]*\[\s*\/quote.*\]/ig, "<span>$1</span>");
[contenteditable]{
white-space:pre-wrap;
}
[contenteditable] span{
background:#ffc;
}
<div id="area" contenteditable>
This is a test message.
[quote]Wise man said he is wise.[/quote] There could be more quotes too:
[quote]this is second quote [/quote]
He is correct.
</div>
Otherwise, since you cannot treat HTML elements inside a textarea like actual HTML elements in order to highlight them → you should create an in-memory element with the same size (font-size etc) of your textarea, do the above, calculate the positions of the generated span elements, than apply some higlight overlays over the respective positions over your textarea, take care that they "follow-up" if the window resizes... and the story goes...
Here's a jQuery plugin to achieve the above-mentioned:
http://mistic100.github.io/jquery-highlighttextarea/
Currently I'm investigating two approaches: Highlight Text Inside a Textarea, which describes how this plugin is done: https://github.com/lonekorean/highlight-within-textarea
And syntax higlighter for MediaWiki: source, description of approach.
Both of them use additional element behind textarea with the same font and positioning to show background colors. Textarea background is made transparent. Then on edit and scroll you sync contents and scroll between textarea and element behind.
Here is my simplified code for it: https://codepen.io/bunyk-1472854887/full/RLJbNq/
Core logic of the highlighter is like this (some details skipped):
textarea.addEventListener('keyup', textUpdate);
textarea.addEventListener('scroll', scrollUpdate);
function textUpdate() {
var html = html_escape(textarea.value);
enter code here
html = html.replace(/\[quote\](.*?)\[\/quote\]/g, '[quote]<span class="quote">$1</span>[/quote]');
background.innerHTML = html;
}
function scrollUpdate() {
background.scrollTop = textarea.scrollTop;
};
Related
I'm trying to write a basic text editor using contenteditable. In this MCVE, it only has one function, which is that selected text is given a red highlight.†
The code I'm using is here:
function createSpan() {
let selection = document.getSelection();
let range = selection.getRangeAt(0);
let element = document.createElement("span");
element.className = "inline-equation";
range.surroundContents(element);
let newRange = new Range();
newRange.selectNodeContents(element);
selection.removeAllRanges();
selection.addRange(newRange);
}
$("button").click(createSpan)
.inline-equation {
background-color: red;
display: inline-block;
}
#editor {
width: 100%;
height: 100%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button>
Create Span
</button>
<div id="editor" contenteditable="true">
This is a contenteditable area.
</div>
I'm having trouble with the idea that the user may move out of the highlight area and continue typing in unformatted text. To experience this issue:
Run the Stack Snippet above
Select the text from somewhere in the middle til the end, then click Create Span
Type some new text at the end of the line
This new text has the red highlight too, even if you attempt to move out of the inserted span by pressing the right arrow key.
I'd still like to give the user the option to append new text which is formatted, but then also allow the user to navigate out of the span so that they may continue to type normal text.
In other words, the span should act as a completely separate editable object which may be moved into or out of. This includes the ability to move out of the span even if it's at the end of the document, so that the user can continue typing in non-formatted text.
The best example I am able to give of what I'd like is Microsoft Word's inline equations. Notice how, in the GIF below, the equation acts as a separate object, which I may navigate out of so that I can type normal text to the right of it. The is how I'd like my span to act.
I've tried replacing the span with a div with inline-block formatting to see if that affected the behaviour, but it didn't. How should I achieve the effect I'm looking for?
† In the actual use case, the 'highlight' actually denotes LaTeX-formatted mathematics which are rendered later. I'm writing what is essentially an editor for a proprietary markup language which supports inline LaTeX.
The issue is that you need something editable at end for this to work. There are lot of existing SO thread for the same. You can see below
Why Is My Contenteditable Cursor Jumping to the End in Chrome?
contenteditable put caret outside inserted span
contenteditable with nested span. Who has the focus?
Focusing on nested contenteditable element
Combining knowledge from above thread the simplest thing I could think of was adding below keyup handler
$("#editor").on('keyup',(e) => {
var editor = $("#editor").get(0)
var cn = editor.childNodes;
if (cn[cn.length - 1].nodeType !== Node.TEXT_NODE)
{
empty = document.createTextNode( '\uFEFF' );
editor.appendChild(empty);
}
if (cn[0].nodeType !== Node.TEXT_NODE)
{
empty = document.createTextNode( '\uFEFF' );
editor.prepend(empty);
}
})
Which makes sure there is one text node to step out of the div. You can do the same thing for the starting div if you want. Below is JSFiddle for the same
https://jsfiddle.net/4tcLr0qa/1/
When pasting data from the clipboard into an html textarea, it does some pretty cool parsing to make the paste look close to what was copied as far as newlines go.
For example, I have the following html on a page that I select (everything highlights in blue) and then I copy it:
Hello
<br>
<br>
<br>
<div>more</div>
<div>last</div>
So to be clear, what I am copying is what the output of this jsfiddle looks like.
Now when I paste this magical, copied text into a text area, I see exactly what I would expect: "Hello", empty line, empty line, empty line, "more", "last". Cool! Then when I use jQuery's .val() on the textarea, I get "Hello\n\n\nmore\nlast". Super cool. It took the br's and div's and was able to infer the correct newlines from it.
Now...
What I am trying to do it programmatically take the same data I copied earlier and set it as the textarea's value as if it were pasted.
Here is what I have tried...
So, say the stuff I copied earlier was wrapped in a <div id="parent">...</div>.
var parent = $("#parent");
var textarea = $("#theTextArea");
// Set the value of the text area to be the html of the thing I care about
textarea.val(parent.html());
Now I know this isn't the same as a copy-paste, but I was hoping it would take care of me and do what I wanted. It doesn't. The textarea gets filled with Hello<br><br><br><div>more</div><div>last</div>. The html that was once invisible is now stringified and made part of the text.
Obviously I did this wrong. .html() returns, of course, the string of html. But is there something I could call on parent that would give me the text with all inferred linebreaks?. I have tried calling parent.text(), but this only gives Hellomorelast (no line breaks).
A few notes that could help with an answer: I am using Angular, so I have access to all their goodies and jQuery.
Edit:
Solution
It is not nice but you can try to replace html tags with line breaks '\n' or do some line breaks in the html file and get the content with text().
var parent1 = $("#paren1");
var textarea1 = $("#theTextArea1");
var parent2 = $("#paren2");
var textarea2 = $("#theTextArea2");
// Set the value of the text area to be the html of the thing I care about
var text = parent1.html();
text = text.replace(new RegExp("<br>", 'g'),"\n");
text = text.replace(new RegExp("<div>", 'g'),"");
text = text.replace(new RegExp("</div>", 'g'),"\n");
textarea1.val(text);
textarea2.val(parent2.text());
JSFiddle
I need to extract the text from a div with paragraphs and spans and other things and put it into a textarea. I need to load just the text, not the HTML.
For that, I can use:
loadtext = $('#mydiv').text();
However, I DO need to retain the line breaks.
For that, I'm doing:
loadtext = $('#mydiv').text().replace(/<br>/gm, '\r\n');
But it doesn't seem to be working, because when I load that text into a textarea, it's all flat with no line breaks. Am I doing something wrong?
$('#mydiv').text() has already been stripped of all HTML, including<br> elements, so this will not work. You need to modify the HTML of the #mydiv element and replace all <br/> elements, then retrieve the text.
$('#mydiv').find('br').each(function(){
$(this).after("\n")
.remove();
});
var loadtext = $("#mydiv").text();
An alternate solution is to use an intermediate element that's never added to the document.
var html = $('#mydiv').html(); // e.g. '<p>line 1</p><br><br><p>line 2</p>'
var text = $('<div>').html(html.replace(/<br\/?>/g, '\n')).text();
/* text =
"line 1
line 2"
*/
$('#mytextarea').text(text);
This supports <br> (HTML) and <br/>(XHTML).
Here is what I am trying to accomplish: When a user uses a mouse, keyboard, or touch to select text inside "myDiv" I want to acquire three discreet chunks of HTML: the HTML before the selection (to the "left" of it), the HTML inside the selection, and the HTML after the selection (to the "right" of it). The html should be as it would appear with myDiv.innerHTML.
The selection might start or end inside a tag pair (i.e., the isolated selection isn't necessarily valid HTML). I don't need to deal with special scenarios like absolute-positioned elements within the selection; all of the selections I am concerned with will be constrained to one div that will contain basic tags like strong, em, ul, ol, h1, image, and table.
The closest I've come is using rangy to snag the selection and calling selection.getRangeAt(0).cloneContents() to get the selection HTML. This works well enough until I make a selection that is invalid in isolation, and the browser alters the HTML of the document fragment to make it valid markup.
Extra Information: Here's why I need this:
I am creating a document feedback system, so I need to save the selection information to a database for later retrieval and reconstitution. Normally I would save the selection using the DOM path and the selected text, but the text may change between saving and reconstitution. For example, the author might move entire paragraphs around, delete sections, etc. The DOM path becomes pretty useless then.
So my (imperfect) plan is to store the selection as [offset, length, html_snippet]. That's the "position". I'll also store the html snippets that came directly before and after the selected text. This is the "context".
Using a combination of these data I should be able to relocate the originally selected text most of the time, even if it has moved or partially changed. When that fails, the UI will have a way to address it, but I'd like that to occur as infrequently as possible.
Superthanks!
I have several questions:
1.- When you say 'the html after the selection' - how would that html be any different than the html previous to the selection or viceversa? Is the 'selection' process itself tampering with the html because of your 'script' or whatever?
2.- You said the text selections are not taking place in textareas...what elements are you working with then? paragraphs? divs...? Narrowing it down would help.
3.- Have you thought about using jquery?
http://api.jquery.com/select/
Doing something like
$('#element_with_text_goes_here').select(function() {
//apply grabbing functions here, for example
//copy html 'before' selection:
$pre_html = $('html').clone();
// copy selection...see below:
// copy html 'after' selection'...same as before
});
Copy selection:
As noted here:
Selecting text in an element (akin to highlighting with your mouse)
Jason wrote the following function:
function selectText(element) {
var doc = document;
var text = doc.getElementById(element);
if (doc.body.createTextRange) { // ms
var range = doc.body.createTextRange();
range.moveToElementText(text);
range.select();
} else if (window.getSelection) { // moz, opera, webkit
var selection = window.getSelection();
var range = doc.createRange();
range.selectNodeContents(text);
selection.removeAllRanges();
selection.addRange(range);
}
}
With a live working demo that can be found here:
http://jsfiddle.net/edelman/KcX6A/339/
And a jquery plugin version here:
http://jsfiddle.net/edelman/KcX6A/340/
Which you can use for the obtention of the selected text. You'll just have to tweak it accordingly since he was approaching it from a reversed angle.
The more details you can give us...the better we can help.
Hope this helps
G
This code gets html/text from user's selection, but it works in IE only. The code works with cross-tag selection too. (Globals used to keep the code short.)
<script>
function selected(){
thediv=document.getElementById('div');
res=document.getElementById('htm');
userSelection=document.selection;
userRange=userSelection.createRange();
/* For wider scale of elements */
// rangeParent=userRange.parentElement();
// if(rangeParent!=thediv) userRange.moveToElementText(rangeParent);
rangeText=userRange.htmlText; // OR: rangeText=userRange.text;
res.innerText=rangeText;
return;
}
</script>
</head>
<body onload="document.onselectionchange=selected;">
<div id="div">
<h1>The great testpage</h1>
<p>A paragraph with some text</p>
<p>This paragraph <b>contains</b> a child element.</p>
<p>And this is the last paragraph.</p>
<table>
<tr><td>Cell1-1</td><td>cell1-2</td></tr>
<tr><td>Cell2-1</td><td>cell2-2</td></tr>
</table>
<ol>
<li>item1</li>
<li>item2</li>
<li>item3</li>
</ol>
</div>
<br>
<span id="htm"></span>
</body>
Content before&after selection in the thediv you'll get like this: prepost=thediv.innerHTML/innerText.split(rangeText);
If the page contains any other elements but thediv, they have to be made unselectable.
What I want?
I want a div what works like a textarea, I don't want to have the ability to edit things in the div, and paste images and so on just plain text.
Example
www.facebook.com
- The best example is facebook's news feed.
Why I need this way?
If you check out facebook's news feed, you well see that the area where you can write your post, expands as you write your post or hit a lots of enters.
This is the same reason why I want to use a div with contentEditable, because in textarea I can't do that.
#
PLEASE NO JQUERY only JAVASCRIPT
Resizable Textarea using pure JavaScript without frameworks:
<html>
<head>
<script>
function taOnInput()
{
var dis = this;
setTimeout(
function(){
var span = document.createElement("div");
span.innerHTML = escape(dis.value).replace(/[%]0A/g, "<br/>")+"<br/>."; //Extra BR for padding... TextArea uses %0A, not \n
span.style.width = dis.offsetWidth+"px";
span.style.padding = "0px";
span.style.fontFamily = "Lucida Console";
document.body.appendChild(span); //Offset height doesnt work when not in DOM tree i guess =/? or is it a hack
dis.style.height = span.offsetHeight+"px";
document.body.removeChild(span);
}, 1
); //setTimeout=hack, since oKP is called BEFORE character append.
}
window.onload = function()
{
var resizableTA = document.getElementById("resizableTA");
resizableTA.onkeypress = taOnInput;
}
</script>
<title>ItzWarty - Untitled Document</title>
</head>
<body>
<textarea id="resizableTA">Trololololol</textarea>
</body>
</html>
Very hackish, put it together in less than 10 minutes. Hopefully it'll at least give you an idea.
ONLY tested on Google Chrome 5.0.308.0
Explanation of code, since i fail at commenting
1) before window.onload, the textarea of id "resizableTA" has been created and appended to document.body of DOM tree.
2) window.onload attaches an event handler, taOnInput [textarea on input].
3) textarea on input creates a dummy span, forces its width to the width of the textarea and font style to "Lucida Console", which AFAIK is the default font for textareas, copies the value of the textarea to the span's innerHTML, while replacing %0A [newline that textareas use] with [line break]...
4) span's offsetHeight is the height of the span, which can now be used to force the height of the textarea.
Can anyone confirm that Lucida Console is the default font of textarea? Is it Consola? Courier New? I assumed any fixed-width font would work. I don't use Mac, so I dont know what fonts it shared with windows, though i think Courier New is a better choice...
You don't have to use a DIV. You can still have a textarea and expand it when necessary.
Here is a jQuery plugin that does just that: http://plugins.jquery.com/project/TextAreaResizer
Here is a demo: http://www.itsavesyou.com/TextArea_Resizer_example.htm
It seems to me that the best solution to this is still to use a textarea element, and to use Javascript to resize it as needed in response to content changes.
Textarea elements do allow for manual resizing by default (and this can be controlled by the "resize" CSS property. But sites that implement auto-expanding multi line text fields without rich formatting would primarily do so with textareas combined with styling and Javascript.