Cybernetic
Cybernetic

Reputation: 13344

Insert a link into selected text using JS (losing window.getSelection() value when user focuses on input to enter URL)

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?

Upvotes: 3

Views: 1078

Answers (4)

Nick
Nick

Reputation: 69

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!!!

Upvotes: 0

Cybernetic
Cybernetic

Reputation: 13344

@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

Upvotes: 1

yochanan sheinberger
yochanan sheinberger

Reputation: 735

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>

Upvotes: 2

IchEben
IchEben

Reputation: 35

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.

Upvotes: -1

Related Questions