My input is
const text = 'Hello #kevin12 How are you?'
How I render it
<span>{text}</span>
How I want to render it
<span>Hello <em>#kevin12</em> How are you?</span>
My parsing function (incomplete)
const parseText = value => {
const mention = value.substring(value.lastIndexOf('#')+1)
const words = value.split(mention)
return words.map(word => word === mention ? React.createElement('em', {}, word) : word)
}
...
<span>{parseText(text)}</span>
Please help me complete this rendering function.
const parseText = value => {
const words = value.split(' ')
return words.map((word, index) => {
if (index !== words.length - 1) {
word += " "
}
return word[0] === '#' ? <em>{word}</em> : word;
})
}
Split by words, iterate over the array and find the item that start with #
export default function App() {
const text = "Hello #kevin12 How are you?";
const parseText = value => {
const mention = value.split(" ");
return mention.map(w => (w[0] === "#" ? <em> {w} </em> : <> {w}</>));
};
return (
<span>
<span>{parseText(text)}</span>
</span>
);
}
I will suggest going through the regex, here is the sample code
const text = 'Hello #kevin12 How are you?';
let finalResult = test;
let re =/(?:^|\W)#(\w+)(?!\w)/g;
let match,matches =[];
while(match =re.exec(test)){
let found ='#'+match[1];
let replace ='<em>'+found+'</em>';
finalresult =finalResult.replace(found,replace);
}
console.log(finalResult) ;
Related
I made a search bar that takes everything typed into it and checks if the json file has the name, author or category that matches to it.
How could I make it so that everything typed into the search bar gets highlighted in the part where it displays matched results?
Here is a picture of the search bar for example. I typed in FPS and it found a category with FPS in it. I would want the fps part to be highlighted.
How I made the search bar work:
const search = document.getElementById("search");
const matchList = document.getElementById("match-list");
const searchStates = async searchText => {
const res = await fetch("firebase link");
const states = await res.json();
const listaInformacija2 = Object.values(states)
let matches = listaInformacija2.filter(state => {
const regex = RegExp(`^${searchText}`, "gi");
return state.autor.match(regex) || state.naziv.match(regex) || state.kategorija.match(regex);
});
if(searchText.length === 0) {
matches = []
matchList.innerHTML = "";
}
outputHtml(matches);
};
const outputHtml = matches => {
if(matches.length > 0){
const html = matches.map(match => `
<a href=kurs.html?id=${match.id} id="searchedLink">
<div class="course-col" id="allCourses">
<h4>${match.naziv}</h4>
<p>Kategorija: ${match.kategorija}</p>
<p>Autor: ${match.autor}</p>
</div>
</a>
`).join("");
matchList.innerHTML = html;
}
}
search.addEventListener("input", () => searchStates(search.value));
You can add a specific span element to the highlighted text dynamically, I would also avoid making a Firebase call each search, as it would cost a lot of money if the app scale, here is what I got:
const FIREBASE_URL = '<your firebase url>';
const $search = document.getElementById('search');
const $matchList = document.getElementById('match-list');
const data = {};
function escape(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function search(query) {
if (!query || !data.statesList) return [];
const matches = data.statesList.filter(state => query.test(state.autor) || query.test(state.naziv) || query.test(state.kategorija));
return matches;
}
function render(matches, originalValue, query) {
if (!matches.length) {
$matchList.innerHTML = '';
return;
}
const html = matches
.map(match => {
const replacer = `<span class="search-match">${originalValue}</span>`;
return `
<a href=kurs.html?id=${match.id} class="searchedLink">
<div class="course-col" class="allCourses">
<h4>${match.naziv.replace(query, replacer)}</h4>
<p>Kategorija: ${match.kategorija.replace(query, replacer)}</p>
<p>Autor: ${match.autor.replace(query, replacer)}</p>
</div>
</a>`;
})
.join('');
$matchList.innerHTML = html;
}
function inputHandler(event) {
const { value } = event.target;
let query;
if (!value) query = null;
else query = new RegExp(escape(value), 'i');
const matches = search(query);
render(matches, value, query);
}
async function init() {
const response = await fetch(FIREBASE_URL);
data.states = await response.json();
data.statesList = Object.values(data.states);
$search.addEventListener('input', inputHandler);
}
init().catch(console.error);
And your CSS could look like this:
.search-match {
background-color: #e0d392f0;
}
I am leaving this answer here because it took me about an hour+ to solve and I am hoping to help anyone else looking for a similar answer.
The question has a few parts that are all answered in separate SO posts:
Read a text file in javascript
Parse the text to get a space delimited string, excluding special characters, tabs, new lines, etc.
Count the number of each word
Display a list in descending order
Starting with an input that accepts a file and a function that will eventually sort our string, ezString:
return (
<div>
<input type="file" onChange={e => showFile(e)}/>
{ezString ? getSortedArr() : null}
</div>
);
and a function to turn that file into text (with a useState var ezString)
const [ezString, setEzString] = useState(null)
const showFile = async (e) => {
e.preventDefault()
const reader = new FileReader()
reader.onload = async (e) => {
const file = e.target.result
const goodString = file.replaceAll(/\s\s+/g, ' ')
.replaceAll(/(\r\n|\r|\n)/g, ' ')
.replaceAll(/[^a-zA-Z ]/g, "").toLowerCase()
setEzString(goodString);
};
reader.readAsText(e.target.files[0])
}
and a sorting function
const getSortedArr = () => {
let wordArray = ezString.split(' ').filter(n => n)
let wordCount = {};
for (let word of wordArray) {
if (wordCount[word]) {
wordCount[word] = wordCount[word] + 1
} else {
wordCount[word] = 1
}
}
let sortedArr = Object.entries(wordCount).sort((a, b) => b[1] - a[1])
return sortedArr ? sortedArr.map(arr => {
return (
<div key={arr[0]}>
<p style={{fontSize: 16}}>{arr[0]}: {arr[1]}</p>
</div>)
}) : null
}
With these parts we have the full component:
import React, {useState} from 'react'
const WordCounter = () => {
const [ezString, setEzString] = useState(null)
const showFile = async (e) => {
e.preventDefault()
const reader = new FileReader()
reader.onload = async (e) => {
const file = e.target.result
const goodString = file.replaceAll(/\s\s+/g, ' ')
.replaceAll(/(\r\n|\r|\n)/g, ' ')
.replaceAll(/[^a-zA-Z ]/g, "").toLowerCase()
setEzString(goodString);
};
reader.readAsText(e.target.files[0])
}
const getSortedArr = () => {
let wordArray = ezString.split(' ').filter(n => n)
let wordCount = {};
for (let word of wordArray) {
if (wordCount[word]) {
wordCount[word] = wordCount[word] + 1
} else {
wordCount[word] = 1
}
}
let sortedArr = Object.entries(wordCount).sort((a, b) => b[1] - a[1])
return sortedArr ? sortedArr.map(arr => {
return (
<div key={arr[0]}>
<p style={{fontSize: 16}}>{arr[0]}: {arr[1]}</p>
</div>)
}) : null
}
return (
<div className="App">
<input type="file" onChange={e => showFile(e)}/>
{ezString ? getSortedArr() : null}
</div>
);
}
export default WordCounter;
Areas for improvement:
I'm terrible with regex, thus the terrible regex and the need to filter empty strings out of the wordArray
I'm trying to write a regex that match a fixed alphanumeric string that may contain dots.
This regex will have to match all the characters but the dots.
I would need to use this in Javascript search() for comparing two strings.
Word to find: 30A10Z20
All of those are a correct match:
30A1.0Z2.0
30A.10Z20
3.0.A10.Z20
3.0.A.1.0.Z.2.0.
I've written these but with no success:
^30A10Z20\\.{0,1}?$
^30A10Z20\\.?$
^30A10Z20(?=\\.)
Any leads or help will be very much appreciated.
I'm not sure a RegExp will be the best way to do what I suppose you want to do (based on yur comments: try reformulating your question). Would this snippet be an idea?
const findTerm = (word, searchTerm) =>
word.replace(/[^a-z0-9]/gi, "") === searchTerm;
const aFewStrings = [
`something 30A1.0Z2.0 etc`,
`30A.10Z20 hithere`,
`Hello 3.0.A10.Z20`,
`The value 3.0.....A.1.0....Z.2.0. may be valid`,
`As may be the value 3.0###A.1.0#&!'Z.2.0.`,
`Bye 3.0.A.1.0.Z.2.0. ended`,
];
aFewStrings.forEach(s => {
const words = s.split(" ").map( w => findTerm(w, "30A10Z20") ? `<b>${w}</b>` : w );
console.log(words.join(" "));
});
If you want parts of the string (cf your comment), you have to do a bit of parsing. Something like:
const findTerm = (word, searchTerm) =>
RegExp(`(${searchTerm})`, "i").test(word.replace(/[^a-z0-9]/gi, ""));
const toBold = (word, searchTerm) => {
const word2Parse = [...word];
const wordPreserved = word2Parse.slice(0);
const len = searchTerm.length;
let foundIndices = [];
let i = 0;
while (word2Parse.length) {
const noDots = word2Parse.slice(1).filter(v => !/[^a-z0-9]/i.test(v));
const next = searchTerm.length > 1 && noDots[0] === searchTerm[1];
const found = searchTerm.length > 1
? word2Parse[0] === searchTerm[0] && next
: word2Parse[0] === searchTerm[0];
searchTerm = found ? searchTerm.slice(1) : searchTerm;
found && foundIndices.push(i);
i += 1;
word2Parse.shift();
}
wordPreserved[foundIndices[0]] = `<b>${wordPreserved[foundIndices[0]]}`;
wordPreserved[foundIndices.slice(-1)] = `${
wordPreserved[foundIndices.slice(-1)]}</b>`;
return wordPreserved.join("");
}
const aFewStrings = [
`something 30A1.0Z2.0 etc`,
`30A.10Z20 hithere`,
`Hello 3.0.A10.Z20`,
`The value 3.0.....A.1.0....Z.2.0. may be valid`,
`As may be the value 3.0###A.1.0#&!'Z.2.0.`,
`Bye 3.0.A.1.0.Z.2.0. ended`,
`3.0.A.1.0.Z.2.....0`,
];
const result = document.querySelector("#result");
let term = `30A1`;
result.appendChild(
Object.assign(
document.createElement("p"), {
innerHTML: `[1 String, search '30A1']: ${
(aFewStrings[3].split(" ")
.map(w =>
findTerm(w, term) ? toBold(w, term) : w)
.join(" "))}`
})
);
term = `notfound`;
result.appendChild(
Object.assign(
document.createElement("p"), {
innerHTML: `[1 String, search 'notfound']: ${
(aFewStrings[1].split(" ")
.map(w =>
findTerm(w, term) ? toBold(w, term) : w)
.join(" "))}`
})
);
term = `0Z20`;
aFewStrings.forEach(s => {
const words = s.split(" ").map(w =>
findTerm(w, term) ? toBold(w, term) : w);
result.appendChild(
Object.assign(
document.createElement("div"), {
innerHTML: words.join(" ")
})
);
});
body {
margin: 2rem;
font: normal 12px/15px verdana, arial;
}
b {
color: red;
}
<div id="result"></div>
I'm trying to highlight a certain body string. So if there is an # #username highlight part of the string, and leave the rest of the content unhighlighted. Very similar to how discord mentions a user.
So im trying to implement that logic
The issue is that it's highlighting the wrong parts of the string. For example
const regex = /^#/i;
const words = comment.comment_body.split(" ");
let highLight;
let username;
for (let i = 0; i < words.length; i++) {
const word = words[i];
console.log("checking for word", word);
if (regex.test(word)) {
username = word
console.log("test passed");
highLight = <ReactMarkdown className="highlightMention" source={word} />;
} else {
highLight = <ReactMarkdown className="markdownStyle" source={comment.comment_body} />;
}
}
.....
{comment.gifUrl === "" && highLight}
Map the splitted workds and if that words starts with # return it inside the highlighter else return the normal word :
const Component = () => {
...
return <div>{ comment.comment_body.split(' ').map(word=>{
if(word.startsWith('#')){
return <ReactMarkdown className="highlightMention" source={body} />;
}else{
return " "+word ;
}
})}
</div>;
};
const regex = /^#/i;
const words = comment.comment_body.split(" ");
let highLight = [];
let username;
for (let i = 0; i < words.length; i++) {
const word = words[i];
console.log("checking for word", word);
if (regex.test(word)) {
username = word
console.log("test passed");
// `highLight = <ReactMarkdown className="highlightMention" source={word} />;` this line will only do `highlight` for the last `word`
highLight.push(<ReactMarkdown className="highlightMention" source={word} />);
} else {
highLight.push(<ReactMarkdown className="markdownStyle" source={comment.comment_body} />);
}
}
.....
{comment.gifUrl === "" && highLight}
I was trying to build a hanging man game and in order to make this game, the word has been replaced by underscores like for instance (hello) ==> _ _ _ _ _. So every time when the player clicks on the keyButton it should replace the underscore by the matched letter.
But one important thing to keep in mind is that it should not replace the underscore for instance like when a user clicks on e --> _ e _ _ _ and then clicks on l --> _ _ l l _. You see the letter e that matched was being replaced by underscore which is the case in my code. The magic happens in the showMatchedLetter function
import React, { useState, useEffect } from 'react';
import { fetchButton } from '../actions';
import axios from 'axios';
import 'babel-polyfill';
const App = () => {
const [word, setWord] = useState([]);
const [underscore, setUnderscore] = useState([]);
const [data, setData] = useState([]);
useEffect(() => {
const runEffect = async () => {
const result = await axios('src/api/api.js');
setData(result.data)
}
runEffect();
}, []);
const randomWord = () => {
const chosenWord = data[Math.floor(Math.random() * data.length)];
replaceLetter(chosenWord.word);
}
const replaceLetter = (string) => {
let getString = string;
setWord(getString);
let stringToUnderScore = getString.replace(/[a-z]/gi, '_');
setUnderscore(stringToUnderScore);
}
useEffect(() => {
const checkLetter = (event) => {
if(event.keyCode >= 65 && event.keyCode <= 90) {
checkMatchLetter(word, String.fromCharCode(event.keyCode).toLowerCase());
}
};
document.addEventListener('keydown', checkLetter);
return () => {
document.removeEventListener('keydown', checkLetter);
}
}, [word]);
const checkMatchLetter = (word, keyButton) => {
if(word == undefined) {
return;
} else {
for(let i = 0; i < word.length; i++) {
if(word[i] == keyButton) {
let index = i;
showMatchedLetter(word[i], index);
}
}
}
}
const showMatchedLetter = (letter, index) => {
console.log(letter, index);
let string = word;
string = setCharAt(string, index, letter);
console.log(string)
}
const setCharAt = (string, index, letter) => {
if(index > string.length - 1) return string;
console.log(string)
return underscore.substr(0, index) + letter + underscore.substr(index + 1);
}
return (
<div>
<p>{word}</p>
<p>{underscore}</p>
<button onClick={randomWord}></button>
</div>
)
}
export default App;
A way to approach this would be to just keep a running list of the letters that have already been guessed.
Then simply replace any letter that wasn't guessed with an underscore to get your "hidden" word each time a new letter is guessed.
The revealOnly function below can be implemented in any way you want, but what's important is that it takes the full word string and a list of letters to replace and then returns a new string that omits any non-guessed letters.
const phrase = 'Hello World'
function revealOnly(str, guessed=[]){
const regExpr = new RegExp(`[^${guessed.join("")}\\s]`, 'ig')
return str.replace(regExpr, '_')
}
console.log(revealOnly(phrase, [])) // no letters guessed
console.log(revealOnly(phrase, ['e', 'l'])) // some letters guessed
The following is a working demo in react to demonstrate how you might use this:
const { useState } = React
const App = () => {
const [phrase, setPhrase] = useState('Hello World')
const [guessed, setGuessed] = useState([])
function onGuessLetter(letter){
if(guessed.includes(letter)) return
// add letter to guessed array
setGuessed(prev => [...prev, letter])
}
function revealOnly(str, guessed=[]){
const regExpr = new RegExp(`[^${guessed.join("")}\\s]`, 'ig')
return str.replace(regExpr, '_')
}
return (
<div>
<div> {revealOnly(phrase, guessed)} </div>
<LetterPicker onPick={onGuessLetter} />
</div>
)
}
const LetterPicker = (props)=>{
const letters = []
for(let i = 0; i < 26; i++){
letters.push(String.fromCharCode(97+i))
}
return (
<div className={'letter-picker'}>
{letters.map(l => <div onClick={()=>props.onPick(l)}>{l}</div>)}
</div>
)
}
ReactDOM.render(<App/>, document.querySelector("#root"))
.letter-picker > div {
display : inline-block;
cursor : pointer;
padding : 3px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Keeping a running list of letters guessed could also be useful for checking if a word has been fully revealed (although you could just check for the existence of any _ as well). Similarly in traditional hangman fashion you can show the previously letters somewhere as well.
EDIT:
How do I check if the entire word is guessed.
Just check if the revealed word is equal to the original phrase.
const curr = revealOnly(phrase, ['h','e','l','w','o','r', 'd']
const isGuessed = curr === phrase
const { useState } = React
const App = () => {
const [phrase, setPhrase] = useState('Hello World')
const [guessed, setGuessed] = useState([])
function onGuessLetter(letter){
if(guessed.includes(letter)) return
// add letter to guessed array
setGuessed(prev => [...prev, letter])
}
function revealOnly(str, guessed=[]){
const regExpr = new RegExp(`[^${guessed.join("")}\\s]`, 'ig')
return str.replace(regExpr, '_')
}
const curr = revealOnly(phrase, guessed)
const isGuessed = curr === phrase
return (
<div>
<div> {curr} </div>
<div>Gussed: {isGuessed.toString()} </div>
<LetterPicker onPick={onGuessLetter} />
</div>
)
}
const LetterPicker = (props)=>{
const letters = []
for(let i = 0; i < 26; i++){
letters.push(String.fromCharCode(97+i))
}
return (
<div className={'letter-picker'}>
{letters.map(l => <div onClick={()=>props.onPick(l)}>{l}</div>)}
</div>
)
}
ReactDOM.render(<App/>, document.querySelector("#root"))
.letter-picker > div {
display : inline-block;
cursor : pointer;
padding : 3px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>