Modified Tag and Bookmark list pages to highlight text that matches searchstring

This commit is contained in:
2023-02-22 19:54:10 -06:00
committed by Carl Tibule
parent 657000ddd6
commit 51ffab6015
6 changed files with 40 additions and 31 deletions

View File

@ -1,24 +1,28 @@
import React from "react"; import React from "react";
import { Row, Col, Form, Button } from "../external"; import { Row, Col, Form, Button } from "../external";
import DateTimeHelper from "../../utils/dateTimeHelper"; import DateTimeHelper from "../../utils/dateTimeHelper";
import "../../styles/component/bookmark.css"; import { getHighlightedText } from "../../utils";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { Tag } from "../../components"; import { Tag } from "../../components";
export function Bookmark(props) { export function Bookmark(props) {
const navigate = useNavigate(); const navigate = useNavigate();
const getTruncatedDescription = () => `${props.bookmark.description.substring(0, 97)}${props.bookmark.description.length >= 97 ? "..." : ""}`;
const getHighlightedTitle = () => getHighlightedText(props.bookmark.title, props.searchString);
const getHighlightedDescription = () => getHighlightedText(getTruncatedDescription(), props.searchString)
return <div className="mb-3"> return <div className="mb-3">
<Row> <Row>
<Col xs={1} className="d-flex justify-content-center align-items-center"> <Col xs={1} className="d-flex justify-content-center align-items-center">
<Form.Check onChange={(e) => {props.onBookmarkSelected(e.target.checked)}} checked={props.bookmark.isSelected} /> <Form.Check onChange={(e) => {props.onBookmarkSelected(e.target.checked)}} checked={props.bookmark.isSelected} />
</Col> </Col>
<Col xs={11}> <Col xs={11}>
<a href="#" style={{textDecoration: "none"}}>{props.bookmark.title}</a> <a href={props.bookmark.url} target="_blank" style={{textDecoration: "none"}}>{ getHighlightedTitle() }</a>
<div className="font-weight-normal">{`${props.bookmark.description.substring(0, 97)}${props.bookmark.description.length >= 97 ? "..." : ""}`}</div> <div className="font-weight-normal">{ getHighlightedDescription()}</div>
<div> <div>
{ {
props.bookmark.tags.map((tag) => <Tag key={props.bookmark.id-tag.id} tag={tag} />) props.bookmark.tags.map((tag) => <Tag key={props.bookmark.id-tag.id} tag={tag} searchString={props.searchString}/>)
} }
</div> </div>
<div> <div>

View File

@ -1,13 +1,16 @@
import React from "react"; import React from "react";
import { Button } from "../external"; import { Button } from "../external";
import { getHighlightedText } from "../../utils";
export function Tag(props) { export function Tag(props) {
const getHighlightedTagName = () => props.searchString ? getHighlightedText(props.tag.name, props.searchString) : props.tag.name
return ( return (
<Button <Button
variant="link" variant="link"
style={{textDecoration: "none"}} className={`ms-0 me-2 p-0 ${props.tag.isHidden ? "text-danger" : null}`} style={{textDecoration: "none"}} className={`ms-0 me-2 p-0 ${props.tag.isHidden ? "text-danger" : null}`}
onClick={props.onClick}> onClick={props.onClick}>
#{props.tag.name} #{getHighlightedTagName()}
</Button> </Button>
); );
}; };

View File

@ -2,11 +2,13 @@ import { isDev } from "./isDevHelper";
import { getTagGroups, flattenTagArrays } from "./tagsHelper"; import { getTagGroups, flattenTagArrays } from "./tagsHelper";
import { isSubset } from "./arrayHelper"; import { isSubset } from "./arrayHelper";
import { containsSubstring } from "./bookmarkHelper"; import { containsSubstring } from "./bookmarkHelper";
import { getHighlightedText } from "./stringsHelper";
export { export {
isDev, isDev,
getTagGroups, getTagGroups,
isSubset, isSubset,
containsSubstring, containsSubstring,
flattenTagArrays flattenTagArrays,
getHighlightedText,
} }

View File

@ -0,0 +1,10 @@
// https://stackoverflow.com/questions/29652862/highlight-text-using-reactjs
export const getHighlightedText = (text, highlight) => {
// Split on highlight term and include term into parts, ignore case
const parts = text.split(new RegExp(`(${highlight})`, 'gi'));
return <span> { parts.map((part, i) =>
<span key={i} className={part.toLowerCase() === highlight.toLowerCase() ? "fw-bold" : ""}>
{ part }
</span>)
} </span>;
}

View File

@ -323,6 +323,7 @@ export function BookmarksListView(props) {
onBookmarkSelected={(selected) => onBookmarkSelected(selected, bookmark)} onBookmarkSelected={(selected) => onBookmarkSelected(selected, bookmark)}
onDeleteClicked={() => handleDeleteBookmark(bookmark.id)} onDeleteClicked={() => handleDeleteBookmark(bookmark.id)}
onHideClicked={() => handleHideBookmarks([bookmark.id])} onHideClicked={() => handleHideBookmarks([bookmark.id])}
searchString={searchString}
/> />
}) })
} }

View File

@ -1,6 +1,6 @@
import React, { useEffect, useReducer, useState } from "react"; import React, { useEffect, useReducer, useState } from "react";
import { useAuth0 } from "@auth0/auth0-react"; import { useAuth0 } from "@auth0/auth0-react";
import { containsSubstring } from "../utils"; import { containsSubstring, getHighlightedText } from "../utils";
import { SplashScreen, SearchForm, UpsertTagModal, InterceptModal } from "../components"; import { SplashScreen, SearchForm, UpsertTagModal, InterceptModal } from "../components";
import { Alert, Button, Col, Container, Dropdown, DropdownButton, Form, Row, Table } from "../components/external"; import { Alert, Button, Col, Container, Dropdown, DropdownButton, Form, Row, Table } from "../components/external";
import { getAllTags, createNewTag, updateTag, deleteTags, hideTags } from "../api"; import { getAllTags, createNewTag, updateTag, deleteTags, hideTags } from "../api";
@ -9,24 +9,6 @@ export function TagsView(props) {
const { getAccessTokenSilently } = useAuth0(); const { getAccessTokenSilently } = useAuth0();
const [searchString, setSearchString] = useState(""); const [searchString, setSearchString] = useState("");
const handleSearch = (e) => {
e.preventDefault();
if(!searchString) {
dispatchTagsState({type: "DISPLAY_ALL"});
} else {
dispatchTagsState({type: "SEARCH"});
}
};
const handleSearchStringChange = (e) => {
if(!e.target.value) {
dispatchTagsState({type: "DISPLAY_ALL"});
} else {
setSearchString(e.target.value);
}
};
const [isAllTagsSelected, setAllTagsSelected] = useState(false); const [isAllTagsSelected, setAllTagsSelected] = useState(false);
const initialSplashScreenState = { show: false, message: null }; const initialSplashScreenState = { show: false, message: null };
@ -127,11 +109,7 @@ export function TagsView(props) {
newState = state.map(x => ({...x, isDisplayed: true})); newState = state.map(x => ({...x, isDisplayed: true}));
break; break;
case "SEARCH": case "SEARCH":
if(!searchString) { newState = state.map(x => ({...x, isDisplayed: !searchString || containsSubstring(x, searchString)}));
dispatchTagsState({type: "DISPLAY_ALL"});
}
newState = state.map(x => ({...x, isDisplayed: containsSubstring(x, searchString)}));
break; break;
case "TAG_UPDATE": case "TAG_UPDATE":
newState = [...state]; newState = [...state];
@ -314,6 +292,17 @@ export function TagsView(props) {
} }
}; };
const handleSearch = (e) => {
e.preventDefault();
setSearchString(e.target[0].value);
dispatchTagsState({type: "SEARCH"});
};
const handleSearchStringChange = (e) => {
setSearchString(e.target.value);
dispatchTagsState({type: "SEARCH"});
};
useEffect(() => { useEffect(() => {
dispatchSplashScreenState({type: "SHOW_SPLASH_SCREEN", payload: {message: "Retrieving Tags..."}}); dispatchSplashScreenState({type: "SHOW_SPLASH_SCREEN", payload: {message: "Retrieving Tags..."}});
fetchTags(); fetchTags();
@ -409,7 +398,7 @@ export function TagsView(props) {
checked={tag.isSelected} checked={tag.isSelected}
/> />
</td> </td>
<td>{tag.name}</td> <td>{getHighlightedText(tag.name, searchString)}</td>
<td> <td>
<span className={tag.isHidden ? "text-danger" : "text-dark"}>{ tag.isHidden ? "Yes" : "No" }</span> <span className={tag.isHidden ? "text-danger" : "text-dark"}>{ tag.isHidden ? "Yes" : "No" }</span>
</td> </td>