Modified Tag and Bookmark list pages to highlight text that matches searchstring
This commit is contained in:
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -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,
|
||||||
}
|
}
|
||||||
10
yaba-web/src/utils/stringsHelper.js
Normal file
10
yaba-web/src/utils/stringsHelper.js
Normal 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>;
|
||||||
|
}
|
||||||
@ -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}
|
||||||
/>
|
/>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user