12

Long story short, I have a website made under Wix.com editor, and coding was made possible a few months ago. I have set up a custom comment box, so users can post their comments, and read others'.

Now the thing is, the "comment Input" takes plain text, and whenever a link is posted, it is displayed as plain text, no color, no clickability.

I want a code that 'reads' the list of comments, and convert every text that begins with 'https' or 'http' or 'www' ... orange and clickable (opening in a new tab)

Any solution please ?

Thanks !

I have tried many things such as :

$w('#text95').html = 
       (/((http:|https:)[^\s]+[\w])/g, '<a href="$1" target="_blank">$1</a>').replace;

text95 = the displayed comments (it is a text that repeats itself for as many comments as there are)

Ivar
  • 6,138
  • 12
  • 49
  • 61
Tristan
  • 121
  • 1
  • 1
  • 3
  • When you say it is "a text that repeats itself for as many comments as there are", is it a `
      ` or what? That's an ID, so there shouldn't be more than one of them in any given page.
    – samanime Apr 03 '18 at 16:27
  • it's inside a 'repeater' (I'm using Wix) – Tristan Apr 04 '18 at 19:37
  • The [linkifyjs](https://www.npmjs.com/package/linkifyjs) library is the heavyweight solution to this. For a lighter weight solution, see my answer below. – Brett Donald Jan 12 '23 at 06:17

8 Answers8

21

It looks like your replace syntax is wrong. Try something like this. I'm pretty sure this will work.

function linkify(inputText) {
    var replacedText, replacePattern1, replacePattern2, replacePattern3;

    //URLs starting with http://, https://, or ftp://
    replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
    replacedText = inputText.replace(replacePattern1, '<a href="$1" target="_blank">$1</a>');

    //URLs starting with "www." (without // before it, or it'd re-link the ones done above).
    replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
    replacedText = replacedText.replace(replacePattern2, '$1<a href="http://$2" target="_blank">$2</a>');

    //Change email addresses to mailto:: links.
    replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
    replacedText = replacedText.replace(replacePattern3, '<a href="mailto:$1">$1</a>');

    return replacedText;
}

Calling it with:

$w('#text95').innerHTML = linkify($w('#text95').html);
isherwood
  • 58,414
  • 16
  • 114
  • 157
6

Here is my answer (improved Version including video links).

See also this Codepen here.

const convertLinks = ( input ) => {

  let text = input;
  const linksFound = text.match( /(?:www|https?)[^\s]+/g );
  const aLink = [];

  if ( linksFound != null ) {

    for ( let i=0; i<linksFound.length; i++ ) {
      let replace = linksFound[i];
      if ( !( linksFound[i].match( /(http(s?)):\/\// ) ) ) { replace = 'http://' + linksFound[i] }
      let linkText = replace.split( '/' )[2];
      if ( linkText.substring( 0, 3 ) == 'www' ) { linkText = linkText.replace( 'www.', '' ) }
      if ( linkText.match( /youtu/ ) ) {

        let youtubeID = replace.split( '/' ).slice(-1)[0];
        aLink.push( '<div class="video-wrapper"><iframe src="https://www.youtube.com/embed/' + youtubeID + '" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div>' )
      }
      else if ( linkText.match( /vimeo/ ) ) {
        let vimeoID = replace.split( '/' ).slice(-1)[0];
        aLink.push( '<div class="video-wrapper"><iframe src="https://player.vimeo.com/video/' + vimeoID + '" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe></div>' )
      }
      else {
        aLink.push( '<a href="' + replace + '" target="_blank">' + linkText + '</a>' );
      }
      text = text.split( linksFound[i] ).map(item => { return aLink[i].includes('iframe') ? item.trim() : item } ).join( aLink[i] );
    }
    return text;

  }
  else {
    return input;
  }
}

This replaces long and clumsy links within plain texts to short clickable links within that text. (And also wraps videos in responsive iframes)

Example:

This clumsy link https://stackoverflow.com/questions/49634850/javascript-convert-plain-text-links-to-clickable-links/52544985#52544985 is very clumsy and this http://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split is not much better. This one www.apple.com is nice but www can be removed.

Becomes:

This clumsy link <a href="https://stackoverflow.com/questions/49634850/javascript-convert-plain-text-links-to-clickable-links/52544985#52544985" target="_blank">stackoverflow.com</a> is very clumsy and this <a href="http://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split" target="_blank">developer.mozilla.org</a> is not much better. This one <a href="http://www.apple.com" target="_blank">apple.com</a> is nice but www can be removed.

The linkified text then displays as follows:

This clumsy link stackoverflow.com is very clumsy and this developer.mozilla.org is not much better. This one apple.com is nice but www can be removed.

Brett Donald
  • 6,745
  • 4
  • 23
  • 51
philipeachille
  • 185
  • 1
  • 6
  • Awesome! Could you also add in if statements for social links like facebook, twitter, instagram and linked in? That way if it finds a facebook link in the text it converts it to a facebook url icon? So it would render it like so – Aaron Aug 24 '21 at 16:59
  • @Aaron you could try this module: https://github.com/valueinstrument/v-alpha/blob/master/web-interface/app/vcore/src/helper/v-description.js – philipeachille Sep 19 '21 at 20:11
  • 1
    I love it, thanks! However, I made a couple of improvements and posted these as another answer. https://stackoverflow.com/a/71734086/2518285 – Brett Donald Apr 06 '22 at 01:17
3

I'm not sure what $w is or if you can really assign the html like that, but i'm guessing this is jquery since the $ most commonly refers to the jquery object.

Your try was close, it would be..

$('#text95').html($('#text95').html().replace(/((http:|https:)[^\s]+[\w])/g, '<a href="$1" target="_blank">$1</a>'));

try it..

$('#text95').html($('#text95').html().replace(/((http:|https:)[^\s]+[\w])/g, '<a href="$1" target="_blank">$1</a>'));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id=text95>
stuff and stuff and http://ww.stuff.com stuff
</div>
I wrestled a bear once.
  • 22,983
  • 19
  • 69
  • 116
  • I had to tweak it a little, since it showed me error (it should be $w in front of elements) https://www.noelshack.com/2018-14-3-1522864719-capture-d-ecran-46.png – Tristan Apr 04 '18 at 17:56
  • also, the second snippet shows me errors : https://www.noelshack.com/2018-14-3-1522865204-capture-d-ecran-49.png – Tristan Apr 04 '18 at 18:07
  • you're putting markup inside a script which indicates to me that you haven't even made the slightest effort to learn javascript. that's like the first thing you learn. stack overflow is not a free code writing service so, either make some effort to learn on your own or hire a developer. good luckm – I wrestled a bear once. Apr 04 '18 at 18:41
  • What do you mean ? I just did as you said. – Tristan Apr 04 '18 at 19:35
2

I really like the solution by @philipeachille. It’s lightweight and does the essentials. However, it has a couple of issues I needed to address:

  • if a link is followed immediately by a punctuation mark, the punctuation is included in the link
  • if the same link is included more than once, the logic gets confused
  • some links don’t start with either www or http, for example microsoft.com

I derived the following from his code, fixing these issues and omitting the video embedding stuff, which I didn’t want:

const linkify = t => {
  const isValidHttpUrl = s => {
    let u
    try {u = new URL(s)}
    catch (_) {return false}
    return u.protocol.startsWith("http")
  }
  const m = t.match(/(?<=\s|^)[a-zA-Z0-9-:/]+\.[a-zA-Z0-9-].+?(?=[.,;:?!-]?(?:\s|$))/g)
  if (!m) return t
  const a = []
  m.forEach(x => {
    const [t1, ...t2] = t.split(x)
    a.push(t1)
    t = t2.join(x)
    const y = (!(x.match(/:\/\//)) ? 'https://' : '') + x
    if (isNaN(x) && isValidHttpUrl(y)) 
      a.push('<a href="' + y + '" target="_blank">' + y.split('/')[2] + '</a>')
    else
      a.push(x)
  })
  a.push(t)
  return a.join('')
}

To explain the main regular expression:

  • (?<=\s|^) looks behind (before) the link to determine where the link starts, which is either any white space or the beginning of the string
  • [a-zA-Z0-9-:/]+\.[a-zA-Z0-9] matches the start of a link – a pattern like x.x or x://x.x
    • [a-zA-Z0-9-:/]+ a combination of one or more letters, numbers, hyphens, colons or slashes
    • \. followed immediately by a dot
    • [a-zA-Z0-9] followed immediately by another letter or number
  • .+? matches the rest of the link.
  • (?=[.,;:?!-]?(?:\s|$)) looks ahead (after) the link to determine where the link ends
    • ?= positive lookahead
    • (?:\s|$) the link is ended either by any white space or by the end of the string
    • [.,;:?!-]? unless the white space or end of string is immediately preceded by one of these seven punctuation marks, in which case this punctuation mark ends the link.

The potential links matched by this regular expression are then subject to further checks. To make sure the match isn’t just a decimal number like 2.25 we use Javascript’s isNaN() function. Then we attempt to construct a URL object with the match to ensure that it’s a valid URL (thanks @pavlo for the logic), and finally we ensure that it starts with http.

Here is a snippet if you’d like to try some different blocks of text to see how they get linkified:

const linkify = t => {
  const isValidHttpUrl = s => {
    let u
    try {u = new URL(s)}
    catch (_) {return false}
    return u.protocol.startsWith("http")
  }
  const m = t.match(/(?<=\s|^)[a-zA-Z0-9-:/]+\.[a-zA-Z0-9-].+?(?=[.,;:?!-]?(?:\s|$))/g)
  if (!m) return t
  const a = []
  m.forEach(x => {
    const [t1, ...t2] = t.split(x)
    a.push(t1)
    t = t2.join(x)
    const y = (!(x.match(/:\/\//)) ? 'https://' : '') + x
    if (isNaN(x) && isValidHttpUrl(y)) 
      a.push('<a href="' + y + '" target="_blank">' + y.split('/')[2] + '</a>')
    else
      a.push(x)
  })
  a.push(t)
  return a.join('')
}
document.querySelectorAll('.linkify-this').forEach(o => {
  o.innerHTML = linkify(o.innerHTML)
})
<p class="linkify-this">
Any links I put into this paragraph will be linkified, such as apple.com, http://google.com and www.facebook.com.
</p>
<p class="linkify-this">
https://microsoft.com will be matched even at the start of the text.
</p>
<p class="linkify-this">
If I refer to a domain name suffix only, such as .com or .co.uk, it won't be linkified, only complete domain names like https://www.gov.uk will be linkified.
</p>
<p class="linkify-this">
Some links contain numbers, like w3.org, but we don't want straight decimal numbers like 2.25 to be linkified. We also want to ignore non-http URLs like ftp://some.host.com and injection attempts like https://x.com"style="color:red".
</p>
Brett Donald
  • 6,745
  • 4
  • 23
  • 51
1

I correct errors philipeachille's code because youtubeID parameter is not correct. I also correct direct youtube links.

convertLinks = input => {
    let text = input;
    const aLink = [];
    const linksFound = text.match(/(?:www|https?)[^\s]+/g);

    if (linksFound != null) {
        for (let i = 0; i < linksFound.length; i++) {
            let replace = linksFound[i];

            if (!(linksFound[i].match(/(http(s?)):\/\//))) {
                replace = 'http://' + linksFound[i]
            }

            let linkText = replace.split('/')[2];

            if (linkText.substring(0, 3) == 'www') {
                linkText = linkText.replace('www.', '')
            }

            if (linkText.match(/youtu/)) {
                const youtubeID = replace.split('/').slice(-1)[0].split('=')[1];

                if (youtubeID === undefined || youtubeID === '') {
                    aLink.push('<a href="' + replace + '" target="_blank">' + linkText + '</a>');
                } else {
                    aLink.push('<span class="video-wrapper"><iframe src="https://www.youtube.com/embed/' + youtubeID + '" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></span>');
                }
            } else {
                aLink.push('<a href="' + replace + '" target="_blank">' + linkText + '</a>');
            }

            text = text.split(linksFound[i]).map(item => {
                return aLink[i].includes('iframe') ? item.trim() : item
            }).join(aLink[i]);
        }
        return text;
    }
    else {
        return input;
    }
};

Usage:

const text = 'Hello. This is a link https://www.google.com and this is youtube video https://www.youtube.com/watch?v=O-hnSlicxV4';

convertLinks(text);
cenksari
  • 11
  • 3
  • 1
    No one here has accounted for a link being at the end of a sentence, which means there's going to be a period. So, therefore, with all these solutions the period is being included as part of the link and shouldn't be. – scottrod Dec 28 '20 at 18:16
  • 1
    That's true @scottrod, so I have fixed that in my answer. https://stackoverflow.com/a/71734086/2518285 – Brett Donald Apr 06 '22 at 01:18
0

If string contains URL anywhere, convert that string into link. I try above code but this is not working properly for me. After adding some conditions it worked. Thank you for helping me out @user9590073

function convertLink(inputText) {
        var replacedText, replacePattern1, replacePattern2, replacePattern3;
    
    //URLs starting with http://, https://, or ftp://
    replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&#\/%?=~_|!:,.;]*[-A-Z0-9+&#\/%=~_|])/gim;
    if (replacePattern1.test(inputText))
        inputText = inputText.replace(replacePattern1, '<a href="$1" target="_blank" style="color:blue">$1</a>');

    //URLs starting with "www." (without // before it, or it'd re-link the ones done above).
    replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
    if (replacePattern2.test(inputText))
        inputText = inputText.replace(replacePattern2, '$1<a href="http://$2" target="_blank" style="color:blue">$2</a>');

    //Change email addresses to mailto:: links.
    replacePattern3 = /(([a-zA-Z0-9\-\_\.])+[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
    if (replacePattern3.test(inputText))
        replacedText = inputText.replace(replacePattern3, '<a href="mailto:$1">$1</a>');

    return inputText;
}

And then I pass my text into ConverLink Func to open my modal with clickable URL.

$modalBody.find('div.news-content').html('<p>' + convertLink(response.NewsContent) + '</p>');
0

Here is a version (just for http/s and ftp links) that doesn't replace the url with a link if it looks like it's already in a link (or rather that is preceded with a " or ')

function linkifyBareHttp(inputText){
    //URLs starting with http://, https://, or ftp://
    const replacePattern1 = /\b(?<!(\'|\"))(((https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|]))/gim;
    return inputText.replace(replacePattern1, '<a href="$2"></a>');
}

here is a little tester, showing what kind of things it handles:

function testLinkify(){
   console.log('starting test'); 
     test(`https://example.com`, `<a href="https://example.com"></a>`);
   test(`\nhttps://example.com`,`\n<a href="https://example.com"></a>`);
   test(`<a href="https://example.com"></a>`,`<a href="https://example.com"></a>`);
   test(`<a href="https://example.com"></a> https://example.com`,`<a href="https://example.com"></a> <a href="https://example.com"></a>`);
    test(`https://example.com\nhttps://example.net <a href="https://example.org">BAZ</a>`,`<a href="https://example.com"></a>\n<a href="https://example.net"></a> <a href="https://example.org">BAZ</a>`);
}
function test(input,expect){
  const testFunction = linkifyBareHttp;
  const output = testFunction(input);
  console.log (output === expect ? 'PASS':'FAIL');
      console.log(` INPUT: ${input}`);
  if(output !== expect) {
    console.log(`EXPECT: ${expect}`);
    console.log(`OUTPUT: ${output}`)
  }
}
0

 $(".hkt-chat-chatbot-paragraph-msg").map(function() {
      $(this).html(linkify($(this).text().replace(/[\u00A0-\u9999<>\&]/g, function(i) { return '&#'+i.charCodeAt(0)+';';})))
    });


    function linkify(inputText) {
            var replacedText, replacePattern1, replacePattern2, replacePattern3;
        
            //URLs starting with http://, https://, or ftp://
            replacePattern1 = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
            replacedText = inputText.replace(replacePattern1, '<a href="$1" target="_blank">$1</a>');
        
            //URLs starting with "www." (without // before it, or it'd re-link the ones done above).
            replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
            replacedText = replacedText.replace(replacePattern2, '$1<a href="http://$2" target="_blank">$2</a>');
        
            //Change email addresses to mailto:: links.
            replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
            replacedText = replacedText.replace(replacePattern3, '<a href="mailto:$1">$1</a>');
        
            return replacedText;
        }
.hkt-chat-chatbot-paragraph-msg{
border:1px solid green;
padding:5px;
margin-bottom:5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="hkt-chat-chatbot-paragraph-msg" >
    www.google.com
    https://bing.com
    ftp://192.138.1.1:80/home
    normal text
    paragraph click on next link http://example.com
    </div>
    <div class="hkt-chat-chatbot-paragraph-msg" >
    normal text
    </div>
    <div class="hkt-chat-chatbot-paragraph-msg" >
    www.google.com
    https://bing.com
    ftp://192.138.1.1:80/home
    normal text
    paragraph click on next link http://example.com
    </div>

This worked for me. I was having some htmlentity which need to be preserves so first get the text() have converted them first to encoded form and then added hyperlinks and replace the initial text with new html code.