3

I am trying to insert a link into selected text, as is common with front-end editors.

I can add a link to the user's text selection like this:

var sel = window.getSelection(); var e = document.createElement("a"); e.innerHTML = sel.toString(); e.type = "link"; e.href = "www.the_link_to_open.com" e.target = "_blank"; var range = sel.getRangeAt(0); range.deleteContents(); range.insertNode(e) 

This successfully adds an <a> tag around the selected word, with the properties needed for the added link, like this:

<a type="link" href="www.the_link_to_open.com" target="_blank">highlighted text</a> 

However, the flow a user would go through in the editor is to select the word/s, then open an input where they can add the link. But, as soon as the user clicks (focuses) on the input field the window.getSelection() registers the input as the selection, which obviously makes adding the link impossible (since the selected word needs to be the selection).

I tried storing the result of window.getSelection() to use later, but this seems to dynamically change the stored value regardless. I even tried a hard(?) copy to try and store the window.getSelection() permanently using const selection = JSON.stringify(window.getSelection()) but this doesn't capture the output.

How can one keep the selection object stored when the user focuses away from the selected text?

5
  • can you provide the rest of the relevant code? Commented Nov 28, 2021 at 22:37
  • @yochanansheinberger that's all of it. You can literally paste it into your browser console and try it yourself. Select some text on the screen with your cursor and run this code. Then try the same experiment while focusing on a different element. Commented Nov 28, 2021 at 22:41
  • but it doesn't work like this in an actual project. in that case you will have to seperat the code to two functions. the first one triggered when the user selects some text, and stores the selected text. the second one will be triggered when the user will add the link through the input, and will execute the rest of the code. Commented Nov 28, 2021 at 23:02
  • This is exactly how it works. See editorjs.io ..... highlight a word and click the link icon. Commented Nov 28, 2021 at 23:28
  • Apprently Google uses iframes to achieves this, so there's probably no other way to maintain the selection while focusing on a different element stackoverflow.com/a/37301159/1639594 Commented Nov 28, 2021 at 23:47

4 Answers 4

2

here you have a working solution with two function as described in my comment above.

let selectedText, range; function getSelectedText() { const selectObj = window.getSelection(); selectedText = selectObj.toString(); range = selectObj.getRangeAt(0) } function createLink(e) { var a = document.createElement("a"); a.innerHTML = selectedText a.type = "link"; a.href = e.target.value a.target = "_blank"; range.deleteContents(); range.insertNode(a); } document.querySelector('.text').addEventListener('mouseup', getSelectedText) document.querySelector('.link').addEventListener('change', (e) => createLink(e))
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>Document</title> </head> <body> <div class="text"> Lorem ipsum dolor sit amet consectetur adipisicing elit. Minima illum, quod assumenda nisi illo hic quo minus excepturi quasi labore debitis nemo molestiae nesciunt, neque laboriosam repellendus necessitatibus vero corporis. </div> <br /> <div> <label>Add url</label> <input class="link" type="text" /> </div> </body> </html>

Sign up to request clarification or add additional context in comments.

Comments

1

@yochanan was very close, but not quite what I needed. His solution added the link to a random area on the page. Likely because I am using a dynamic modal for entering a link, where as he used a static one.

For the solution to work as intended, I had to distinguish between mouseup and long-press, in addition to handling the window selection.

HTML

<div id="hold_text" contenteditable=false>This is some text. Select one or more words in here, by highlighting the word/s with your cursor.<br><br>Then click on the LINK button, add your link, and hit ENTER.</div> <button id="butt">LINK</button> <div id='modal'><a id='close'>X<a><input id="input" placeholder='paste url, then hit enter'></input></div> 

CSS

* { font-family: arial; } body { background: #218c74; } #hold_text { height: 200px; width: 500px; background: #f7f1e3; border-radius: 4px; padding: 10px; font-size: 18px; } button { height : auto; width : auto; background : #ff5252; border-radius : 4px; padding: 8px; font-size: 18px; border: none; margin-top: 10px; cursor: pointer; color: white; } #modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); height: auto; width: auto; box-shadow: 2px 2px 30px black; display: none; border-radius: 4px; border : 2px solid #ffda79; } #close { cursor: pointer; color: white; margin: 5px; } input { width: 300px; height: 30px; font-size: 18px; border: none; outline: 0; } 

JS

text = document.getElementById("hold_text"); button = document.getElementById("butt"); modal = document.getElementById("modal"); close = document.getElementById("close"); input = document.getElementById("input"); button.addEventListener("click", function() { modal.style.display = "block"; input.focus(); close.addEventListener("click", function(e) { modal.style.display = "none"; }); input.addEventListener("keypress", function(e) { if(e.key === "Enter") { createLink(e); modal.style.display = "none"; input.value = ""; } }) }); cnt = 0; text.addEventListener("mouseup", function() { cnt++; if(cnt === 2) { getSelectedText(); } setTimeout(function() { cnt = 0; }, 200) if(long_press) { getSelectedText(); long_press = false; } }) call_on_longpress(); long_press = false; function call_on_longpress() { var delay; var longpress = 400; text.addEventListener('mousedown', function(e) { var _this = this; delay = setTimeout(check, longpress); function check() { long_press = true; } }, true); text.addEventListener('mouseup', function(e) { clearTimeout(delay); }); text.addEventListener('mouseout', function(e) { clearTimeout(delay); }); } let selectedText, range; function getSelectedText() { const selectObj = window.getSelection(); selectedText = selectObj.toString(); range = selectObj.getRangeAt(0) } function createLink(e) { var a = document.createElement("a"); a.innerHTML = selectedText a.type = "link"; a.href = e.target.value a.target = "_blank"; range.deleteContents(); range.insertNode(a); } 

RESULT

enter image description here

CodePen

Comments

0

I recommend you to put a button called "Select text" so that when you click it, you can select the text you want. I made an app for you, if it solves the problem, you can use it freely:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Inserting Links</title> <script> "use strict" function Onload() { var CanSelect = false; var Link = ""; var sel = ""; var LinkInput = document.getElementById("link"); LinkInput.addEventListener("change", () => { Link = LinkInput.value; }); var SelectButton = document.getElementById("button"); SelectButton.addEventListener("click", () => { CanSelect = true; }); function Select() { if (CanSelect) { var selection = window.getSelection(); CanSelect = false; sel = selection; } } function Insert(selection) { var e = document.createElement("a"); e.innerHTML = selection.toString(); e.type = "link"; e.href = Link; e.target = "_blank"; var range = selection.getRangeAt(0); range.deleteContents(); range.insertNode(e); } window.addEventListener("pointerup", () => { Select(); }); var InsertButton = document.getElementById("insert"); InsertButton.addEventListener("click", () => { Insert(sel); }); } window.addEventListener("load", Onload); </script> </head> <body> <input type="text" placeholder="Link 🔗" id="link"> <button id="button">Select Text 🔼</button> <button id="insert">Insert ✅</button> <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Esse earum magnam ratione unde maiores illum minus accusantium iste! Accusamus sit quibusdam aut aperiam nemo. Soluta vitae ullam facilis illum tempora.</p> </body> </html>

I hope this will help you!!!

3 Comments

Nice solution, however not quite the user flow I'm going for. But great work. See my provided answer for the final result. Cheers.
There's no need to wave at people when you answer, and in fact that kind of thing is likely to get edited out of your contributions. We like to focus on answers here, not fluff. Please read How to Answer.
Also, "you can use it freely" is completely unnecessary. By posting here you are granting a Creative Commons license. It applies to all contributed content.
-1

How about you do it the other way around? You create the new “a” element before you show the input to the user, but you add an id attribute. This way you can later, after the user confirms the input prompt, find it by its id, change the href to the user input and remove the id again.

var sel = window.getSelection(); var e = document.createElement("a"); e.innerHTML = sel.toString(); e.type = "link"; e.href = "www.willBeOverwritten.com" e.target = "_blank"; e.id = "newLinkWaitingForUserInput" var range = sel.getRangeAt(0); range.deleteContents(); range.insertNode(e) //Show input popup //in the callback of the popup: var userInput = "www.userInput.com"; //Search for the newly created link var link = document.getElementById("newLinkWaitingForUserInput"); //Set the href to the userinput link.href = userInput; //remove the id link.removeAttribute("id");

Of course you also have to remove the “a” element if the user cancels the input prompt.

2 Comments

I would have written this as comment, but I don’t have enough points yet. Anyway, hope this helps.
your code snippet doesn't run

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.