Caret behavior inside contenteditable true and next to contenteditable false elements - javascript

I'm trying to create a textarea that listens to URLs typed, pasted or dropped and converts them to links. These links are not editable and must be treated as blocks. Delete and backspace must delete them. Finally, it would be nice if they show full selection when the caret is moved through them.
Problems: after spending too much time and trying some of the suggested solutions found here on stackoverflow (Tim Down), I still couldn't get it to work correctly. I'm finding it very hard to have the caret in the correct place all the time and after pasting a link and the automatic conversion is done, the selection is locked and no input can be done. I tried to insert and keep a zero width character before and after each link but then other selection problems started to occurr.
Can someone please have a look at the following fiddle which represents a simplified version focusing the problem and point me to the right direction for the correct keyboard navigation through these non-editable links inside a contenteditable div?
jsFiddle here
$("#customtextarea").on("input propertychange drop paste", function (e) {
var $this = $(this), savedSelection = saveSelection($this.get(0));
var parsedHtml = getParsedHtml($this.html()); // This gets the innerhtml with the urls turned to links
$this.html(parsedHtml).focus();
restoreSelection($this.get(0), savedSelection);
});
EDIT: I've taken the approach suggested by XuoriG and I'm still facing more or less the same issues as before: caret gets 'stuck' after the link and the links can't be deleted. Also keyboard navigation (left, right) is not working.
New jsFiddle here

When using $this.html you're replacing the entire DOM and losing your selection.
The way I was able to do this is by creating and removing DOM nodes on the fly on every key up. I can't post code for you right now but basically you go through every node on key up and create a new anchor tag with the content matched by your url regex and remove that from text node. Repeat until you dont have any more matches in the node. You'll be able to save and restore the selection this way.
The hard part after is being able to append to an existing url. The way I did it is by detecting if a text node is right next to an anchor tag, and if the anchor text + the text node matches an url too you append the text node to the anchor tag and remove the text node.

Related

Vue: how to know what text is selected / highlighted by user and display multiple instances

I am trying to make component, Highlight, that can:
know what the user selected / highlighted (mousedown and drag over text)
show the highlighted text (color the aforementioned selected text in a color)
can handle multiple instances of this.
see images for idea of what I am aiming for:
From google, StackExchange, Medium posts, etc I have a component which can figure out the text the user highlighted:
codesandbox
This is mostly taken from this medium post by freecodecamp, which uses a slot based approach.
For the actually rendering of highlighted text, most examples I have found do so via regex and replacing the original text with a span or something (e.g. vue-text-highlight).
These two methods are naturally at odd. It might work for highlighting once, but if the original text is altered to now contain a span element, then subsequent or re-selection won't work.
So I was wondering if anyone had any ideas to surmount this.
In the above images, I show what I will be using this highlight component for (linking two different textual instances), but for the moment I think the images clarify what I mean by selecting and highlighting text.
Was pretty fun - and this isn't a great attempt, but a quick one to get you going.
https://codesandbox.io/s/zw2179y2yl
It supports the following:
Highlighting multiple bits of text independently with (relatively) random colours.
Support for selecting text in the right panel (then click on the corresponding already highlighted text in the left box) and it will inherit the same colour.
Pretty easy to extend to set some ids on the arrays, and then hold references between the right and left boxes.
How to use:
Action: Highlight some text on the left panel
Result: You will see the highlighted text under the panels
Action: Highlight some text on the right panel
Result: It will show up with a grey background in the right panel
Action: Click on one of the highlighted text rows under the panels
Result: The text you previously highlighted on the right will inherit the colour of the selected text you clicked.
Edit:
Just noticed a couple of bugs:
If you highlight a space, it totally screws up.
The highlighting is greedy, so if you highlight "it", it will highlight all instances of "it" whether they are in words or not with the same colour throughout the whole text.
I kinda did the same thing (about grabbing user's selection) in my single file component vue-selection-share when imitating medium's selection dropdown menu. This is what I did to grab the user's selection:
const selection = window.getSelection()
and then, I used startNode and endNode to grab the elements that the selection starts and ends in:
const startNode = selectionRange.startContainer.parentNode
const endNode = selectionRange.endContainer.parentNode
after I test whether the selection is valid, I transform it to a string
this.selectedText = selection.toString()
whereafter creating a handleAction method using this.selectedText.
the random color-generation part can be solved simply by this below:
data: {
myColour: '#'+(Math.random()*0xFFFFFF<<0).toString(16)
},
mounted() {
document.body.style.background = this.myColour;
},
methods: {
generator: function(){
// give the selected text an ID before doing this
let highlightedText = document.getElementById(highlightedText)
this.myColour = '#'+(Math.random()*0xFFFFFF<<0).toString(16);
document.highlightedText.style.background = this.myColour;
}
}
you can, if I recall correctly, find more and getElementById and colour changing here on mozilla.
I did not have the time to check the other answer but do follow his when you think his solution is better as my answer is a quick one written on my phone. during lunch.

contentEditable and lastChild IE, Firefox & Chrome LastChild

Apologies for the long question, but I've tried a lot of things and done some research and havent found much of a solution. I have a content editable div.
<div contentEditable=true onkeyup='showResult(this.lastChild.textContent)'></div>
When a user types something into this div, the showResult javascript runs which is basically an ajax request which returns a list of items that match. When a user clicks on one of the suggestions, say the name "John", a span with the suggestion is added into this contentEditable div like so:
<div contentEditable=true onkeyup='showResult(this.lastChild.textContent)'>
<span id='uniqueId1' class='SpanClass' contentEditable='false'>John</span>
</div>
Having selected one Name, the user may want to search for another name. It HTML terms, that means that they would be typing the following:
<div contentEditable=true onkeyup='showResult(this.lastChild.textContent)'>
<span id='uniqueId1' class='SpanClass' contentEditable='false'>John</span>
New User Text Goes Here
</div>
On Chrome, the right behaviour happens when the user continues tries typing in the div - the showResult function runs on the new text that the user types in and ignores the span elements. For Example, if the user types in "Fr" having already selected John, it ignores the first children (John), and sends what the user typed off via ajax and returns suggestions like Fred and Frankie.
However, in IE the span is still content editable and the user can't add any text other than within the span, which seems to make no sense as it is clearly contentEditable=false The ajax request is therefore run on the "John" text plus whatever the user types in next, which is not what I'm trying to achieve.
Finally, in Firefox, the span is not contentEditable BUT the lastChild bit only picks up text within the span, and ignores the text the user puts in.
I've console logged the results of showResult(this.lastChild.textContent) to see what is being sent to the ajax request.
In Chrome, typing in "Fr" in the box after the "John" span sends "Fr" to the ajax and returns the right result.
In IE, typing in "Fr" in the box sends "JohnFr"
In Firefox, typing in "Fr" just sends "John".
As the issue is with this lastChild and the span, I've also included the Javascript that creates the span element. This only activates after a successful result is return and the result is clicked on. (please excuse the very messy Javascript/Jquery)
$('body').on("click", '.TagHints', function(){
//Once you click on the suggestion
var ThisData = $(this).data("id");
var ThisId = $(this).attr("id");
var ThisTag = $(this).data("tag");
//delete the text that the user typed in
elementToRemove = document.getElementById("FakeInput").lastChild;
document.getElementById("FakeInput").removeChild(elementToRemove);
var TagDiv = document.createElement('span');
TagDiv.className = 'SpanClass';
TagDiv.id = ThisId;
TagDiv.innerHTML = ThisTag;
TagDiv.contentEditable=false;
//append the Span to the contentEditable div
document.getElementById("FakeInput").appendChild(TagDiv);
var TagHints = document.getElementsByClassName("TagHints");
while(TagHints.length > 0){
TagHints[0].parentNode.removeChild(TagHints[0]);
}
});
Why are the three browsers behaving completely differently and how do I get them all to behave like Chrome is? Is there a better way of getting the text not in the spans?
I read on another answer that firefox likes inputs and IE likes breaks in this context but both do not seem to work for me. :-(.
One big stopper to good solutions is that jQuery stops working after about line 6, which has also completely baffled me. If anyone can explain why its not working, that would also be cool. Maybe something to do with it being an ajax query and content being created after jquery is loaded?
Thanks for your help!
So, although I haven't had any responses I've come up with a workaround. However, lastChild is still behaving differently between the three browsers so would appreciate any further input.
The answer was to have a function in the document ready section which cloned the original div, stripped it of its children and grabbed the text on key up using Jquery. This then passed the result to the showResult function like so:
$('#FakeInput').keyup(function(){
var ThisClone = $('#FakeInput').clone()
.children()
.remove()
.end()
.text();
showResult(ThisClone);
});
Equally, rather than trying to remove the text that the user typed in from the divs when the user makes a selection I simply cloned the items in the span, using their class, then emptied the contents of the div and reappended these cloned divs.
var TagItems = $('.TagItems').clone();
$('#FakeInput').empty();
$('#FakeInput').append(TagItems);
This solution works across the three browsers.

Auto Update a piece of text depending on position

Right now i am using the awesome flexible-nav
to display the current subtopic i am at in my post.
Additionally to that I was wondering whether I could take this current // string and display it on top of the page in my actual navigation bar. So that the text would change as i scroll and as the flexible-nav changes.
Thanks in Advance
You need to append some code to flexible-nav.js, I suggest you make modification in expanded one and minify it later.
After the line 208 of flexible-nav.js (i.e. closest && closest.node.addClass('current');) add these lines of code.
var doc_title = closest.node.html();
$("title").html(doc_title);
This code changes Title as you keep on moving down the scroll. I wasn't able to figure out call back function in the script (presuming if they have any) but this should work just fine.
You can add any of jQuery selector of your wish instead of $("title") in the code.

Javascript unfocus select menu

I have a big page, with full of (server side) generated information organized into "chapters". To allow an easier overview for the user I put a little element with CSS fixed position to the top right corner of the page.
<div class="selector">Goto section within the table: <select
id="chapterselector"
onChange="goto_section('chapterselector')">%SELECTOR%</select>
</div>
The text "%SELECTOR%" is replaced by the server side component to the correct option elements.
function goto_section ( element ) {
element = document.getElementById(element);
window.location = '#Chapter_' + element.value;
}
This is the JavaScript part for now. This works nicely. However one little issue remains:
Users (including me) can use the select menu to jump inside the document, but then often cursor arrow keys would be used to navigate to scroll the page. The problem: after using the select menu, it has the focus, so cursor keys now "scrolls" the possible choices inside the select menu. What I want: after using the select menu, I want it to lose the focus automatically, so cursor keys scrolls the page.
How can I do this? Thanks for any suggestion.
element.blur()
try that after setting the location

Find a Text and scroll into it with javascript

I have a div that contains hundreds of lines.
I want to create a function that allows me to find a text and scroll into it:
function findAndScroll(text)
So, I enter the wanted text in an input text, I click on the "Go" button that will trigger the "findAndScroll" function then I get scrolled to that text.
Before coding, is there an existant jQuery plugin or a javascript library
that can do this?
Thank you,
Regards.
You can use a function like this to find the text and highlight it. You can then scroll to the highlighted element like this:
var offset = $("#id_of_highlighted_element").offset().top;
window.scrollTo(0,offset);
or you can simply go the id like this
window.location = "#id_of_highlighted_element";
However window.scrollTo is more flexible because you can set the element to wherever you want on the page.

Categories

Resources