Say if the string returns:
str = `
https://www.google.com
http://google.com
https://www.youtube.com/live/gNIQWYgf-0
https://www.youtube.com/embed/3ul2LYG6j14?si=fgxYHjyt6zBmoYEr
https://youtu.be/75Dhfjf6hfjfj
this also has to take into account the variations of different youtube urls
`
so when this string comes back I want to wrap the anchor tags for:
<a href="https://www.google.com">https://www.google.com</a>
<a href="https://google.com">https://www.google.com</a>
and wrap the YouTube with iframe tags:
<iframe width="420" height="315" src="https://youtu.be/75Dhfjf6hfjfj"></iframe>
<iframe width="420" height="315" src="https://www.youtube.com/embed/3ul2LYG6j14?si=fgxYHjyt6zBmoYEr"></iframe>
How can I achieve this, what I have tried:
const redExpYoutube = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|\?v=|watch\?v=|&v=)([^#&?]*).*/;
const regExpUrl = /(www+\.)?(?!youtube?!youtu.be)([\w-]+s{0,3})[/\.,;:!]{1,3}\s{0,3}(r[o0]|n[e3]t|lt|c[o0]m|[i!]nf[o0]|[o0]rg|b[i!][z2]|ru|[e3]du)(\/)?/g;
embeddedString = str.replace(regExpUrl, "<a style='text-decoration:underline;color:red;' href='$1' target='_blank'>$1</a>");
console.log("1: " + embeddedString);
embeddedString = embeddedString.replace(redExpYoutube, "<iframe src='https://$1$2/$3'></iframe>");
embeddedString = embeddedString.replace(/<[\/]{0,1}(div)[^><]*>/g,"");
console.log("2: " + embeddedString);
UPADTE
Update after Brett Donald answered but did not include the full scenario:
str = `
<div>https://www.google.com</div>
<div><span>http://google.com</span></div>
<div>https://www.youtube.com/live/gNIQWYgf-0</div>
<div>https://www.youtube.com/embed/3ul2LYG6j14?si=fgxYHjyt6zBmoYEr</div>
<div>https://youtu.be/75Dhfjf6hfjfj</div>
this also has to take into account the variations of different youtube urls
`
So the URLs will be wrapped around other elements as well as the YouTube URLs will have other elements wrapped around them.
<KeyboardAvoidingView behavior={Platform.OS === "ios" ? "padding" : "height"} style={{ flex: 1, }}>
<RichEditor style={{ flex: 1, }}
placeholder="Enter Comment..."
ref={richText}
initialContentHTML={editorHtml }
onChange ={ (txt)=> {editorOnChange(txt)}}
/>
</KeyboardAvoidingView>
const editorOnChange = (txt) => {
embeddedString = linkify(txt);
console.log("2: " + embeddedString);
}
UPDATE
I am using a WebView now to send data from WebView to react native call-back and do the manipulating there.
Im using the function in React native now and using a webview to pass the data back to the React native app and do the url manipulation there using window.ReactNativeWebView.postMessage(content);
<WebView
onMessage={(event) => {
editorOnChange(event.nativeEvent.data);
}}
source={{
html: `
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>
<body>
<style>
[contentEditable=true]:empty:not(:focus):before {
content: attr(data-text);
cursor: text;
color: red;
pointer-events:none;
}
#placeholder{
position:absolute;
color:#BDBDBD;font-size:46px;outline:none;
}
#contentHtml{
position:absolute;
color:black;font-size:46px;outline:none;width:100%;
}
</style>
<div id="contentHtml" contenteditable=true>
</div>
<div id="placeholder">
Enter Comment...
</div>
</body>
</html>
<script>
document.addEventListener("DOMContentLoaded", function() {
$("#placeholder").on("click", function(){
$("#contentHtml").focus();
$(this).hide();
})
$("#contentHtml").on("input", function(){
var content = $(this).text();
window.ReactNativeWebView.postMessage(content);
if($(this).html().length === 0){
$("#placeholder").show();
} else {
$("#placeholder").hide();
}
});
});
</script>
`,
}}
style={{
width: '100%',
height: '100%',
fontSize:46,
}}>
</WebView>
This call-back in react native
const editorOnChange = (txt) => {
convertLinks(txt);
//console.log(convertLinks(txt));
}
This is the function that Brett Donald suggested to look at:
const convertLinks = ( input ) => {
let text = input;
const linksFound = text.match( /(?:www|http?|https?)[^\s]+/g );
const aLink = [];
console.log(linksFound);
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;
}
}
but when the linkify function is called the matches are concatenating here ["https://youtu.be/75fDo2yk6RM?si=QxkLmcT_TiIgHiTfhttp://www.google.comhGhy"], i cant seem to understand how to split the two matches. this line const linksFound = text.match( /(?:www|http?|https?)[^\s]+/g );
UPDATE
I found how to split the urls using text.match( /(?:www|http?|https?)[^\s]+/img )
But now the urls are wrapping correctly, but the result is leaving empty spaces like this:
["http://www.google.com", "https://youtu.be/75fDo2yk6RM?si=QxkLmcT_TiIgHiTf"]
<a href="http://www.google.com" target="_blank">google.com</a>
<div class="video-wrapper"><iframe src="https://www.youtube.com/em
---all this empty space---
mbed/75fDo2yk6RM?si=QxkLmcT_TiIgHiTf" frameborder="0"
allow="accelerometer; autoplay; encrypted-media; gyroscope;
picture-in-picture" allowfullscreen></iframe></div>
You need to start with some good linkify code, and build up a solution from there. I’ve written a basic formatYouTube()
function here, which you can extend to cater for more scenarios.
const linkify = t => {
const isValidHttpUrl = s => {
let u
try {u = new URL(s)}
catch (_) {return false}
return u.protocol.startsWith("http")
}
const formatYouTube = u => {
if (!u.includes('youtu')) return null
if (u.includes('youtu.be'))
return `https://www.youtube.com/embed/${u.match(/[^\/]*$/)[0]}`
return u
}
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)) {
const z = formatYouTube(y)
if (z)
a.push('<iframe src="' + z + '"></iframe>')
else
a.push('<a href="' + y + '" target="_blank">' + y.split('/')[2] + '</a>')
}
else
a.push(x)
})
a.push(t)
return a.join('')
}
const b = document.querySelector('button')
const ta = document.querySelector('textarea')
const o = document.querySelector('.output')
b.addEventListener('click', evt => {
o.innerHTML = linkify(ta.innerHTML).replaceAll('\n','<br>')
})
body, textarea, button {
font-family: monospace;
}
body {
margin: 1em;
background: silver;
}
textarea {
display: block;
height: 9em;
width: 100%;
padding: 1em;
box-sizing: border-box;
}
button {
margin: 1em 0;
padding: 0.5em 1em;
border: 1px solid black;
background: black;
color: white;
cursor: pointer;
}
.output {
border: 1px solid black;
width: 100%;
padding: 1em;
box-sizing: border-box;
background: white;
}
iframe {
margin: 1em; 0;
border: 1px solid black;
}
<textarea>
one two three microsoft.com four five six
https://www.google.com or http://google.com
https://www.youtube.com/live/gNIQWYgf-0
https://www.youtube.com/embed/3ul2LYG6j14?si=fgxYHjyt6zBmoYEr
https://youtu.be/75Dhfjf6hfjfj
</textarea>
<button>Linkify</button>
<div class="output"></div>
After running this snippet, use the full page link to see things properly.