From 38ba1601e5614d102827c1536f3a2d51907fd1e4 Mon Sep 17 00:00:00 2001
From: saihaj <44710980+saihaj@users.noreply.github.com>
Date: Thu, 16 Jul 2020 17:57:27 -0500
Subject: [PATCH 01/10] refactor(frontend/controller): split up the search
component
Split search component so that results are their own component
---
app/frontend/src/Controller/Search.js | 408 ------------------
app/frontend/src/Controller/Search/Results.js | 233 ++++++++++
.../{Search.css => Search/index.css} | 0
app/frontend/src/Controller/Search/index.js | 227 ++++++++++
4 files changed, 460 insertions(+), 408 deletions(-)
delete mode 100644 app/frontend/src/Controller/Search.js
create mode 100644 app/frontend/src/Controller/Search/Results.js
rename app/frontend/src/Controller/{Search.css => Search/index.css} (100%)
create mode 100644 app/frontend/src/Controller/Search/index.js
diff --git a/app/frontend/src/Controller/Search.js b/app/frontend/src/Controller/Search.js
deleted file mode 100644
index 6c753b0e..00000000
--- a/app/frontend/src/Controller/Search.js
+++ /dev/null
@@ -1,408 +0,0 @@
-import React, { useRef, useState, useEffect, useCallback, useContext } from 'react'
-import { func, string, oneOfType, number, instanceOf, shape } from 'prop-types'
-import { useLocation, useHistory } from 'react-router-dom'
-import classNames from 'classnames'
-
-import {
- Input,
- InputAdornment,
- List,
- ListItem,
- IconButton,
-} from '@material-ui/core'
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
-import { faTimes } from '@fortawesome/free-solid-svg-icons'
-
-import { stringify } from 'querystring'
-import { firstLetters, stripVishraams, stripAccents, toUnicode, toAscii } from 'gurmukhi-utils'
-
-import {
- SEARCH_TYPES,
- SEARCH_CHARS,
- LANGUAGE_NAMES,
- SEARCH_ANCHORS,
- MIN_SEARCH_CHARS,
- SOURCE_ABBREVIATIONS,
-} from '../lib/consts'
-import {
- getUrlState,
- getTranslation,
- getTransliteration,
- customiseLine,
-} from '../lib/utils'
-import { WritersContext, RecommendedSourcesContext, SettingsContext } from '../lib/contexts'
-import controller from '../lib/controller'
-
-import { withNavigationHotkeys } from '../shared/NavigationHotkeys'
-
-import './Search.css'
-
-// Generate the regex for capturing anchor chars, optionally
-const searchRegex = new RegExp( `^([${Object.keys( SEARCH_ANCHORS ).map( anchor => `\\${anchor}` ).join( '' )}])?(.*)` )
-
-const getSearchParams = searchQuery => {
- // Extract anchors and search query
- const [ , anchor, query ] = searchQuery.match( searchRegex )
-
- const inputValue = query
-
- // Get search type from anchor char, if any
- const type = SEARCH_ANCHORS[ anchor ] || SEARCH_TYPES.firstLetter
-
- const value = type === SEARCH_TYPES.firstLetter
- ? inputValue.slice().replace( new RegExp( SEARCH_CHARS.wildcard, 'g' ), '_' )
- : inputValue
-
- return { anchor, value, type }
-}
-
-const highlightFullWordMatches = ( line, query ) => {
- const sanitisedQuery = query.trim()
-
- const foundPosition = line.search( sanitisedQuery )
- const matchStartPosition = line.lastIndexOf( ' ', foundPosition )
-
- const wordEndPosition = line.indexOf( ' ', foundPosition + sanitisedQuery.length )
- // If the match finishes in the last word, no space will be deteced, and wordEndPosition
- // will be -1. In this case, we want to end at the last position in the line.
- const matchEndPosition = wordEndPosition === -1 ? line.length - 1 : wordEndPosition
-
- return [
- line.substring( 0, matchStartPosition ),
- line.substring( matchStartPosition, matchEndPosition ),
- line.substring( matchEndPosition ),
- ]
-}
-
-const highlightFirstLetterMatches = ( line, query ) => {
- const baseLine = stripVishraams( line )
-
- const letters = toAscii( firstLetters( stripAccents( toUnicode( baseLine ) ) ) )
- const words = baseLine.split( ' ' )
-
- const startPosition = letters.search( stripAccents( query ) )
- const endPosition = startPosition + query.length
-
- return [
- `${words.slice( 0, startPosition ).join( ' ' )} `,
- `${words.slice( startPosition, endPosition ).join( ' ' )} `,
- `${words.slice( endPosition ).join( ' ' )} `,
- ]
-}
-
-/**
- * Separates the line into words before the first match, the first match, and after the match.
- * @param value The full line.
- * @param input The string inputted by the user.
- * @param mode The type of search being performed, either first word or full word.
- * @return An array of [ beforeMatch, match, afterMatch ],
- * with `match` being the highlighted section.`.
- */
-const highlightMatches = gurmukhi => ( value, input, mode ) => {
- if ( !value ) return [ '', '', '' ]
-
- // Account for wildcard characters
- const sanitizedInput = input.replace( new RegExp( '_', 'g' ), '.' )
-
- return mode === SEARCH_TYPES.fullWord
- ? highlightFullWordMatches( gurmukhi, sanitizedInput )
- : highlightFirstLetterMatches( value, sanitizedInput )
-}
-
-/**
- * Search Component.
- * Converts ASCII to unicode on input.
- * Displays results.
- */
-const Search = ( { updateFocus, register, focused } ) => {
- const { local: {
- sources,
- search: {
- showResultCitations,
- resultTransliterationLanguage,
- resultTranslationLanguage,
- lineEnding,
- },
- } = {} } = useContext( SettingsContext )
-
- // Set the initial search query from URL
- const history = useHistory()
- const { search } = useLocation()
- const { query = '' } = getUrlState( search )
-
- const [ searchedValue, setSearchedValue ] = useState( '' )
-
- const { anchor: initialAnchor, value: initialInputValue } = getSearchParams( query )
- const inputValue = useRef( initialInputValue )
- const [ anchor, setAnchor ] = useState( initialAnchor )
-
- const [ results, setResults ] = useState( [] )
-
- const [ isInputFocused, setInputFocused ] = useState( false )
-
- const inputRef = useRef( null )
-
- /**
- * Set the received results and update the searched vale.
- * @param {Object[]} results An array of the returned results.
- */
- const onResults = useCallback( results => {
- setSearchedValue( inputValue.current )
- setResults( results )
-
- updateFocus( 0 )
- }, [ updateFocus ] )
- /**
- * Run on change of value in the search box.
- * Converts ascii to unicode if need be.
- * Sends the search through to the controller.
- * @param {string} value The new value of the search box.
- */
- const onChange = useCallback( ( { target: { value } } ) => {
- const { anchor, type: searchType, value: searchValue } = getSearchParams( value )
-
- // Search if enough letters
- const doSearch = searchValue.length >= MIN_SEARCH_CHARS
-
- if ( doSearch ) {
- controller.search( searchValue, searchType, {
- translations: !!resultTranslationLanguage,
- transliterations: !!resultTransliterationLanguage,
- citations: !!showResultCitations,
- } )
- } else setResults( [] )
-
- inputValue.current = searchValue
- setAnchor( anchor )
-
- // Update URL with search
- history.push( { search: `?${stringify( {
- ...getUrlState( search ),
- query: value,
- } )}` } )
- }, [
- history,
- search,
- resultTranslationLanguage,
- resultTransliterationLanguage,
- showResultCitations,
- ] )
-
- const writers = useContext( WritersContext )
- const recommendedSources = useContext( RecommendedSourcesContext )
-
- /**
- * Renders a single result, highlighting the match.
- * @param {string} gurmukhi The shabad line to display.
- * @param {int} typeId The type id of line.
- * @param {string} lineId The id of the line.
- * @param {string} shabadId The id of the shabad.
- * @param {Component} ref The ref to the component.
- * @param {int} sourceId The id of source.
- * @param {Object} shabad The object containng section information and other metadata.
- * @param {int} sourcePage The page number of shabad in source.
- * @param {string} translations The translations of shabad line to display.
- * @param {string} transliterations The transliterations of shabad line to display.
- */
- const Result = ( {
- gurmukhi,
- typeId,
- id: lineId,
- shabadId,
- ref,
- focused,
- sourceId,
- shabad,
- sourcePage,
- translations,
- transliterations,
- } ) => {
- const transliteration = resultTransliterationLanguage && transliterations && customiseLine(
- getTransliteration(
- { transliterations },
- resultTransliterationLanguage,
- ),
- { lineEnding, typeId },
- )
-
- const translation = resultTranslationLanguage && translations && customiseLine(
- getTranslation( {
- line: { translations },
- shabad: { sourceId },
- recommendedSources,
- sources,
- languageId: resultTranslationLanguage,
- } ),
- { lineEnding, typeId },
- )
-
- // Grab the search mode or assume it's first letter
- const mode = SEARCH_ANCHORS[ anchor ] || SEARCH_TYPES.firstLetter
-
- // Separate the line into words before the match, the match, and after the match
- const getMatches = highlightMatches( gurmukhi )
-
- const [ beforeMatch, match, afterMatch ] = getMatches(
- gurmukhi,
- searchedValue,
- mode,
- )
- const [ translitBeforeMatch, translitMatch, translitAfterMatch ] = getMatches(
- transliteration,
- searchedValue,
- mode,
- )
-
- // Send the shabad id and line id to the server on click
- const onClick = () => controller.shabad( { shabadId, lineId } )
-
- // Helper render functions for citation
- const showCitation = showResultCitations && shabad && shabad.section
- const getEnglish = ( { nameEnglish } ) => nameEnglish
- const getWriterName = () => getEnglish( writers[ shabad.writerId ] )
- const getPageName = () => recommendedSources[ shabad.sourceId ].pageNameEnglish
-
- return (
-
-
-
- {beforeMatch ? {beforeMatch} : null}
- {match ? {match} : null}
- {afterMatch ? {afterMatch} : null}
-
-
-
-
- {translation && (
-
- {translation}
-
- )}
-
- {transliteration && (
-
- {translitBeforeMatch ? {translitBeforeMatch} : null}
- {translitMatch ? {translitMatch} : null}
- {translitAfterMatch ? {translitAfterMatch} : null}
-
- )}
-
-
-
- {showCitation && (
-
- {[
- getWriterName(),
- SOURCE_ABBREVIATIONS[ sourceId ],
- `${getPageName()} ${sourcePage}`,
- ].reduce( ( prev, curr ) => [ prev, ' - ', curr ] )}
-
- )}
-
-
-
- )
- }
-
- Result.propTypes = {
- gurmukhi: string.isRequired,
- id: string.isRequired,
- typeId: string.isRequired,
- shabadId: string.isRequired,
- ref: instanceOf( Result ).isRequired,
- sourceId: number.isRequired,
- shabad: shape( { } ).isRequired,
- sourcePage: number.isRequired,
- translations: string.isRequired,
- transliterations: string.isRequired,
- }
-
- const filterInputKeys = event => {
- const ignoreKeys = [ 'ArrowUp', 'ArrowDown' ]
-
- if ( ignoreKeys.includes( event.key ) ) event.preventDefault()
- }
-
- const refocus = ( { target } ) => {
- setInputFocused( false )
- target.focus()
- }
-
- const highlightSearch = () => inputRef.current.select()
-
- useEffect( () => {
- controller.on( 'results', onResults )
- return () => controller.off( 'results', onResults )
- }, [ onResults ] )
-
- useEffect( () => {
- if ( inputValue.current ) onChange( { target: { value: `${anchor || ''}${inputValue.current}` } } )
- }, [
- onChange,
- anchor,
- resultTransliterationLanguage,
- resultTranslationLanguage,
- showResultCitations,
- ] )
-
- useEffect( () => { highlightSearch() }, [] )
-
- return (
-
- setInputFocused( true )}
- onChange={onChange}
- value={`${anchor || ''}${inputValue.current}`}
- placeholder="Koj"
- disableUnderline
- autoFocus
- endAdornment={inputValue.current && (
-
- onChange( { target: { value: '' } } )}>
-
-
-
- )}
- inputProps={{
- spellCheck: false,
- autoCapitalize: 'off',
- autoCorrect: 'off',
- autoComplete: 'off',
- }}
- />
-
- {results
- ? results
- .map( ( props, i ) => Result( {
- ...props,
- ref: c => register( i, c ),
- focused: focused === i,
- } ) )
- : ''}
-
-
- )
-}
-
-Search.propTypes = {
- focused: oneOfType( [ string, number ] ),
- register: func.isRequired,
- updateFocus: func.isRequired,
-}
-
-Search.defaultProps = {
- focused: undefined,
-}
-
-export default withNavigationHotkeys( {
- keymap: {
- next: [ 'down', 'tab' ],
- previous: [ 'up', 'shift+tab' ],
- first: null,
- last: null,
- },
-} )( Search )
diff --git a/app/frontend/src/Controller/Search/Results.js b/app/frontend/src/Controller/Search/Results.js
new file mode 100644
index 00000000..9b674281
--- /dev/null
+++ b/app/frontend/src/Controller/Search/Results.js
@@ -0,0 +1,233 @@
+import React from 'react'
+import classNames from 'classnames'
+import { ListItem } from '@material-ui/core'
+import { string, oneOfType, number, instanceOf, shape, bool } from 'prop-types'
+import { firstLetters, stripVishraams, stripAccents, toUnicode, toAscii } from 'gurmukhi-utils'
+
+import controller from '../../lib/controller'
+import {
+ SEARCH_TYPES,
+ LANGUAGE_NAMES,
+ SEARCH_ANCHORS,
+ SOURCE_ABBREVIATIONS,
+} from '../../lib/consts'
+import {
+ getTranslation,
+ getTransliteration,
+ customiseLine,
+} from '../../lib/utils'
+
+const highlightFullWordMatches = ( line, query ) => {
+ const sanitisedQuery = query.trim()
+
+ const foundPosition = line.search( sanitisedQuery )
+ const matchStartPosition = line.lastIndexOf( ' ', foundPosition )
+
+ const wordEndPosition = line.indexOf( ' ', foundPosition + sanitisedQuery.length )
+ // If the match finishes in the last word, no space will be deteced, and wordEndPosition
+ // will be -1. In this case, we want to end at the last position in the line.
+ const matchEndPosition = wordEndPosition === -1 ? line.length - 1 : wordEndPosition
+
+ return [
+ line.substring( 0, matchStartPosition ),
+ line.substring( matchStartPosition, matchEndPosition ),
+ line.substring( matchEndPosition ),
+ ]
+}
+
+const highlightFirstLetterMatches = ( line, query ) => {
+ const baseLine = stripVishraams( line )
+
+ const letters = toAscii( firstLetters( stripAccents( toUnicode( baseLine ) ) ) )
+ const words = baseLine.split( ' ' )
+
+ const startPosition = letters.search( stripAccents( query ) )
+ const endPosition = startPosition + query.length
+
+ return [
+ `${words.slice( 0, startPosition ).join( ' ' )} `,
+ `${words.slice( startPosition, endPosition ).join( ' ' )} `,
+ `${words.slice( endPosition ).join( ' ' )} `,
+ ]
+}
+
+/**
+ * Separates the line into words before the first match, the first match, and after the match.
+ * @param value The full line.
+ * @param input The string inputted by the user.
+ * @param mode The type of search being performed, either first word or full word.
+ * @return An array of [ beforeMatch, match, afterMatch ],
+ * with `match` being the highlighted section.`.
+ */
+const highlightMatches = gurmukhi => ( value, input, mode ) => {
+ if ( !value ) return [ '', '', '' ]
+
+ // Account for wildcard characters
+ const sanitizedInput = input.replace( new RegExp( '_', 'g' ), '.' )
+
+ return mode === SEARCH_TYPES.fullWord
+ ? highlightFullWordMatches( gurmukhi, sanitizedInput )
+ : highlightFirstLetterMatches( value, sanitizedInput )
+}
+
+/**
+ * Renders a single result, highlighting the match.
+ * @param {string} gurmukhi The shabad line to display.
+ * @param {int} typeId The type id of line.
+ * @param {string} lineId The id of the line.
+ * @param {string} shabadId The id of the shabad.
+ * @param {Component} ref The ref to the component.
+ * @param {string|int} focused
+ * @param {int} sourceId The id of source.
+ * @param {Object} shabad The object containng section information and other metadata.
+ * @param {int} sourcePage The page number of shabad in source.
+ * @param {string} translations The translations of shabad line to display.
+ * @param {string} transliterations The transliterations of shabad line to display.
+ * @param {string} searchedValue The input to search.
+ * @param {string} anchor Anchor for search mode.
+ * @param {Object} writers From the SettingsContext.
+ * @param {Object} sources From the ContentContext.
+ * @param {Object} recommendedSources From the RecommendedSourcesContext.
+ * @param {int|bool} resultTransliterationLanguage Language code for translits (SettingsContext).
+ * @param {int|bool} resultTranslationLanguage Language code for translations (SettingsContext).
+ * @param {bool} showResultCitations To show citations or not (SettingsContext).
+ * @param {bool} lineEnding To strip line endings or not (SettingsContext).
+ */
+const Result = ( {
+ gurmukhi,
+ typeId,
+ id: lineId,
+ shabadId,
+ ref,
+ focused,
+ sourceId,
+ shabad,
+ sourcePage,
+ translations,
+ transliterations,
+ searchedValue,
+ anchor,
+ writers,
+ sources,
+ recommendedSources,
+ resultTransliterationLanguage,
+ resultTranslationLanguage,
+ showResultCitations,
+ lineEnding,
+} ) => {
+ const transliteration = resultTransliterationLanguage && transliterations && customiseLine(
+ getTransliteration(
+ { transliterations },
+ resultTransliterationLanguage,
+ ),
+ { lineEnding, typeId },
+ )
+
+ const translation = resultTranslationLanguage && translations && customiseLine(
+ getTranslation( {
+ line: { translations },
+ shabad: { sourceId },
+ recommendedSources,
+ sources,
+ languageId: resultTranslationLanguage,
+ } ),
+ { lineEnding, typeId },
+ )
+
+ // Grab the search mode or assume it's first letter
+ const mode = SEARCH_ANCHORS[ anchor ] || SEARCH_TYPES.firstLetter
+
+ // Separate the line into words before the match, the match, and after the match
+ const getMatches = highlightMatches( gurmukhi )
+
+ const [ beforeMatch, match, afterMatch ] = getMatches(
+ gurmukhi,
+ searchedValue,
+ mode,
+ )
+ const [ translitBeforeMatch, translitMatch, translitAfterMatch ] = getMatches(
+ transliteration,
+ searchedValue,
+ mode,
+ )
+
+ // Send the shabad id and line id to the server on click
+ const onClick = () => controller.shabad( { shabadId, lineId } )
+
+ // Helper render functions for citation
+ const showCitation = showResultCitations && shabad && shabad.section
+ const getEnglish = ( { nameEnglish } ) => nameEnglish
+ const getWriterName = () => getEnglish( writers[ shabad.writerId ] )
+ const getPageName = () => recommendedSources[ shabad.sourceId ].pageNameEnglish
+
+ return (
+
+
+
+
+ {beforeMatch ? {beforeMatch} : null}
+ {match ? {match} : null}
+ {afterMatch ? {afterMatch} : null}
+
+
+
+
+ {translation && (
+
+ {translation}
+
+ )}
+
+ {transliteration && (
+
+ {translitBeforeMatch ? {translitBeforeMatch} : null}
+ {translitMatch ? {translitMatch} : null}
+ {translitAfterMatch ? {translitAfterMatch} : null}
+
+ )}
+
+
+
+ {showCitation && (
+
+ {[
+ getWriterName(),
+ SOURCE_ABBREVIATIONS[ sourceId ],
+ `${getPageName()} ${sourcePage}`,
+ ].reduce( ( prev, curr ) => [ prev, ' - ', curr ] )}
+
+ )}
+
+
+
+ )
+}
+
+Result.propTypes = {
+ gurmukhi: string.isRequired,
+ id: string.isRequired,
+ typeId: string.isRequired,
+ shabadId: string.isRequired,
+ ref: instanceOf( Result ).isRequired,
+ sourceId: number.isRequired,
+ shabad: shape( { } ).isRequired,
+ sourcePage: number.isRequired,
+ translations: string.isRequired,
+ transliterations: string.isRequired,
+ focused: oneOfType( [ string, number ] ),
+ searchedValue: string.isRequired,
+ anchor: string.isRequired,
+ writers: shape( {} ).isRequired,
+ sources: shape( {} ).isRequired,
+ recommendedSources: shape( {} ).isRequired,
+ resultTransliterationLanguage: oneOfType( [ bool, number ] ).isRequired,
+ resultTranslationLanguage: oneOfType( [ bool, number ] ).isRequired,
+ showResultCitations: bool.isRequired,
+ lineEnding: bool.isRequired,
+}
+
+Result.defaultProps = {
+ focused: undefined,
+}
+
+export default Result
diff --git a/app/frontend/src/Controller/Search.css b/app/frontend/src/Controller/Search/index.css
similarity index 100%
rename from app/frontend/src/Controller/Search.css
rename to app/frontend/src/Controller/Search/index.css
diff --git a/app/frontend/src/Controller/Search/index.js b/app/frontend/src/Controller/Search/index.js
new file mode 100644
index 00000000..556a11f0
--- /dev/null
+++ b/app/frontend/src/Controller/Search/index.js
@@ -0,0 +1,227 @@
+import React, { useRef, useState, useEffect, useCallback, useContext } from 'react'
+import classNames from 'classnames'
+import { func, string, oneOfType, number } from 'prop-types'
+import { useLocation, useHistory } from 'react-router-dom'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { faTimes } from '@fortawesome/free-solid-svg-icons'
+import { stringify } from 'querystring'
+import {
+ Input,
+ InputAdornment,
+ List,
+ IconButton,
+} from '@material-ui/core'
+
+import { getUrlState } from '../../lib/utils'
+import { WritersContext, RecommendedSourcesContext, SettingsContext } from '../../lib/contexts'
+import controller from '../../lib/controller'
+import { withNavigationHotkeys } from '../../shared/NavigationHotkeys'
+import {
+ SEARCH_TYPES,
+ SEARCH_CHARS,
+ SEARCH_ANCHORS,
+ MIN_SEARCH_CHARS,
+} from '../../lib/consts'
+
+import Result from './Results'
+import './index.css'
+
+// Generate the regex for capturing anchor chars, optionally
+const searchRegex = new RegExp( `^([${Object.keys( SEARCH_ANCHORS ).map( anchor => `\\${anchor}` ).join( '' )}])?(.*)` )
+
+const getSearchParams = searchQuery => {
+ // Extract anchors and search query
+ const [ , anchor, query ] = searchQuery.match( searchRegex )
+
+ const inputValue = query
+
+ // Get search type from anchor char, if any
+ const type = SEARCH_ANCHORS[ anchor ] || SEARCH_TYPES.firstLetter
+
+ const value = type === SEARCH_TYPES.firstLetter
+ ? inputValue.slice().replace( new RegExp( SEARCH_CHARS.wildcard, 'g' ), '_' )
+ : inputValue
+
+ return { anchor, value, type }
+}
+
+/**
+ * Search Component.
+ * Converts ASCII to unicode on input.
+ * Displays results.
+ */
+const Search = ( { updateFocus, register, focused } ) => {
+ const { local: {
+ sources,
+ search: {
+ showResultCitations,
+ resultTransliterationLanguage,
+ resultTranslationLanguage,
+ lineEnding,
+ },
+ } = {} } = useContext( SettingsContext )
+
+ const writers = useContext( WritersContext )
+ const recommendedSources = useContext( RecommendedSourcesContext )
+
+ // Set the initial search query from URL
+ const history = useHistory()
+ const { search } = useLocation()
+ const { query = '' } = getUrlState( search )
+
+ const [ searchedValue, setSearchedValue ] = useState( '' )
+
+ const { anchor: initialAnchor, value: initialInputValue } = getSearchParams( query )
+ const inputValue = useRef( initialInputValue )
+ const [ anchor, setAnchor ] = useState( initialAnchor )
+
+ const [ results, setResults ] = useState( [] )
+
+ const [ isInputFocused, setInputFocused ] = useState( false )
+
+ const inputRef = useRef( null )
+
+ /**
+ * Set the received results and update the searched vale.
+ * @param {Object[]} results An array of the returned results.
+ */
+ const onResults = useCallback( results => {
+ setSearchedValue( inputValue.current )
+ setResults( results )
+
+ updateFocus( 0 )
+ }, [ updateFocus ] )
+ /**
+ * Run on change of value in the search box.
+ * Converts ascii to unicode if need be.
+ * Sends the search through to the controller.
+ * @param {string} value The new value of the search box.
+ */
+ const onChange = useCallback( ( { target: { value } } ) => {
+ const { anchor, type: searchType, value: searchValue } = getSearchParams( value )
+
+ // Search if enough letters
+ const doSearch = searchValue.length >= MIN_SEARCH_CHARS
+
+ if ( doSearch ) {
+ controller.search( searchValue, searchType, {
+ translations: !!resultTranslationLanguage,
+ transliterations: !!resultTransliterationLanguage,
+ citations: !!showResultCitations,
+ } )
+ } else setResults( [] )
+
+ inputValue.current = searchValue
+ setAnchor( anchor )
+
+ // Update URL with search
+ history.push( { search: `?${stringify( {
+ ...getUrlState( search ),
+ query: value,
+ } )}` } )
+ }, [
+ history,
+ search,
+ resultTranslationLanguage,
+ resultTransliterationLanguage,
+ showResultCitations,
+ ] )
+
+ const filterInputKeys = event => {
+ const ignoreKeys = [ 'ArrowUp', 'ArrowDown' ]
+
+ if ( ignoreKeys.includes( event.key ) ) event.preventDefault()
+ }
+
+ const refocus = ( { target } ) => {
+ setInputFocused( false )
+ target.focus()
+ }
+
+ const highlightSearch = () => inputRef.current.select()
+
+ useEffect( () => {
+ controller.on( 'results', onResults )
+ return () => controller.off( 'results', onResults )
+ }, [ onResults ] )
+
+ useEffect( () => {
+ if ( inputValue.current ) onChange( { target: { value: `${anchor || ''}${inputValue.current}` } } )
+ }, [
+ onChange,
+ anchor,
+ resultTransliterationLanguage,
+ resultTranslationLanguage,
+ showResultCitations,
+ ] )
+
+ useEffect( () => { highlightSearch() }, [] )
+
+ return (
+
+ setInputFocused( true )}
+ onChange={onChange}
+ value={`${anchor || ''}${inputValue.current}`}
+ placeholder="Koj"
+ disableUnderline
+ autoFocus
+ endAdornment={inputValue.current && (
+
+ onChange( { target: { value: '' } } )}>
+
+
+
+ )}
+ inputProps={{
+ spellCheck: false,
+ autoCapitalize: 'off',
+ autoCorrect: 'off',
+ autoComplete: 'off',
+ }}
+ />
+
+ {results
+ ? results
+ .map( ( props, i ) => Result( {
+ ...props,
+ searchedValue,
+ anchor,
+ sources,
+ writers,
+ recommendedSources,
+ resultTransliterationLanguage,
+ resultTranslationLanguage,
+ showResultCitations,
+ lineEnding,
+ ref: c => register( i, c ),
+ focused: focused === i,
+ } ) )
+ : ''}
+
+
+ )
+}
+
+Search.propTypes = {
+ focused: oneOfType( [ string, number ] ),
+ register: func.isRequired,
+ updateFocus: func.isRequired,
+}
+
+Search.defaultProps = {
+ focused: undefined,
+}
+
+export default withNavigationHotkeys( {
+ keymap: {
+ next: [ 'down', 'tab' ],
+ previous: [ 'up', 'shift+tab' ],
+ first: null,
+ last: null,
+ },
+} )( Search )
From 3156102c213e37bb6ebd80c88b269d40fad6db53 Mon Sep 17 00:00:00 2001
From: saihaj <44710980+saihaj@users.noreply.github.com>
Date: Fri, 17 Jul 2020 10:35:10 -0500
Subject: [PATCH 02/10] refactor(frontend/controller): move down the results
list mapping to result component
---
app/frontend/src/Controller/Search/Results.js | 76 +++++++++++++++----
app/frontend/src/Controller/Search/index.js | 40 +++-------
2 files changed, 69 insertions(+), 47 deletions(-)
diff --git a/app/frontend/src/Controller/Search/Results.js b/app/frontend/src/Controller/Search/Results.js
index 9b674281..c66852f6 100644
--- a/app/frontend/src/Controller/Search/Results.js
+++ b/app/frontend/src/Controller/Search/Results.js
@@ -1,21 +1,14 @@
-import React from 'react'
+import React, { useContext } from 'react'
import classNames from 'classnames'
-import { ListItem } from '@material-ui/core'
-import { string, oneOfType, number, instanceOf, shape, bool } from 'prop-types'
+import { ListItem, List } from '@material-ui/core'
+import { string, oneOfType, number, instanceOf, shape, bool, arrayOf, func } from 'prop-types'
import { firstLetters, stripVishraams, stripAccents, toUnicode, toAscii } from 'gurmukhi-utils'
+
import controller from '../../lib/controller'
-import {
- SEARCH_TYPES,
- LANGUAGE_NAMES,
- SEARCH_ANCHORS,
- SOURCE_ABBREVIATIONS,
-} from '../../lib/consts'
-import {
- getTranslation,
- getTransliteration,
- customiseLine,
-} from '../../lib/utils'
+import { getTranslation, getTransliteration, customiseLine } from '../../lib/utils'
+import { WritersContext, RecommendedSourcesContext, SettingsContext } from '../../lib/contexts'
+import { SEARCH_TYPES, LANGUAGE_NAMES, SEARCH_ANCHORS, SOURCE_ABBREVIATIONS } from '../../lib/consts'
const highlightFullWordMatches = ( line, query ) => {
const sanitisedQuery = query.trim()
@@ -93,7 +86,7 @@ const highlightMatches = gurmukhi => ( value, input, mode ) => {
* @param {bool} showResultCitations To show citations or not (SettingsContext).
* @param {bool} lineEnding To strip line endings or not (SettingsContext).
*/
-const Result = ( {
+const ResultList = ( {
gurmukhi,
typeId,
id: lineId,
@@ -203,7 +196,58 @@ const Result = ( {
)
}
+const Result = ( { results, searchedValue, anchor, register, focused } ) => {
+ const { local: {
+ sources,
+ search: {
+ showResultCitations,
+ resultTransliterationLanguage,
+ resultTranslationLanguage,
+ lineEnding,
+ },
+ } = {} } = useContext( SettingsContext )
+
+ const writers = useContext( WritersContext )
+ const recommendedSources = useContext( RecommendedSourcesContext )
+
+ return (
+
+ {results
+ ? results.map( ( props, i ) => ResultList( {
+ ...props,
+ searchedValue,
+ anchor,
+ sources,
+ writers,
+ recommendedSources,
+ resultTransliterationLanguage,
+ resultTranslationLanguage,
+ showResultCitations,
+ lineEnding,
+ ref: c => register( i, c ),
+ focused: focused === i,
+ } ) )
+ : ''}
+
+ )
+}
+
Result.propTypes = {
+ results: arrayOf( shape( {} ) ),
+ searchedValue: string,
+ anchor: string,
+ register: func.isRequired,
+ focused: oneOfType( [ string, number ] ),
+}
+
+Result.defaultProps = {
+ results: [],
+ searchedValue: '',
+ anchor: '',
+ focused: undefined,
+}
+
+ResultList.propTypes = {
gurmukhi: string.isRequired,
id: string.isRequired,
typeId: string.isRequired,
@@ -226,7 +270,7 @@ Result.propTypes = {
lineEnding: bool.isRequired,
}
-Result.defaultProps = {
+ResultList.defaultProps = {
focused: undefined,
}
diff --git a/app/frontend/src/Controller/Search/index.js b/app/frontend/src/Controller/Search/index.js
index 556a11f0..80485a9f 100644
--- a/app/frontend/src/Controller/Search/index.js
+++ b/app/frontend/src/Controller/Search/index.js
@@ -5,15 +5,10 @@ import { useLocation, useHistory } from 'react-router-dom'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faTimes } from '@fortawesome/free-solid-svg-icons'
import { stringify } from 'querystring'
-import {
- Input,
- InputAdornment,
- List,
- IconButton,
-} from '@material-ui/core'
+import { Input, InputAdornment, IconButton } from '@material-ui/core'
import { getUrlState } from '../../lib/utils'
-import { WritersContext, RecommendedSourcesContext, SettingsContext } from '../../lib/contexts'
+import { SettingsContext } from '../../lib/contexts'
import controller from '../../lib/controller'
import { withNavigationHotkeys } from '../../shared/NavigationHotkeys'
import {
@@ -52,18 +47,13 @@ const getSearchParams = searchQuery => {
*/
const Search = ( { updateFocus, register, focused } ) => {
const { local: {
- sources,
search: {
showResultCitations,
resultTransliterationLanguage,
resultTranslationLanguage,
- lineEnding,
},
} = {} } = useContext( SettingsContext )
- const writers = useContext( WritersContext )
- const recommendedSources = useContext( RecommendedSourcesContext )
-
// Set the initial search query from URL
const history = useHistory()
const { search } = useLocation()
@@ -184,25 +174,13 @@ const Search = ( { updateFocus, register, focused } ) => {
autoComplete: 'off',
}}
/>
-
- {results
- ? results
- .map( ( props, i ) => Result( {
- ...props,
- searchedValue,
- anchor,
- sources,
- writers,
- recommendedSources,
- resultTransliterationLanguage,
- resultTranslationLanguage,
- showResultCitations,
- lineEnding,
- ref: c => register( i, c ),
- focused: focused === i,
- } ) )
- : ''}
-
+
)
}
From 3b03075e13313fbab3f33878537710cc6e042057 Mon Sep 17 00:00:00 2001
From: saihaj <44710980+saihaj@users.noreply.github.com>
Date: Fri, 17 Jul 2020 10:55:58 -0500
Subject: [PATCH 03/10] refactor(frontend/controller): bring resultsList down
to results scope
this allows to use variables in scope rather than having to pass them down
---
app/frontend/src/Controller/Search/Results.js | 298 ++++++++----------
1 file changed, 127 insertions(+), 171 deletions(-)
diff --git a/app/frontend/src/Controller/Search/Results.js b/app/frontend/src/Controller/Search/Results.js
index c66852f6..cc9697c7 100644
--- a/app/frontend/src/Controller/Search/Results.js
+++ b/app/frontend/src/Controller/Search/Results.js
@@ -1,10 +1,9 @@
import React, { useContext } from 'react'
import classNames from 'classnames'
import { ListItem, List } from '@material-ui/core'
-import { string, oneOfType, number, instanceOf, shape, bool, arrayOf, func } from 'prop-types'
+import { string, oneOfType, number, instanceOf, shape, arrayOf, func } from 'prop-types'
import { firstLetters, stripVishraams, stripAccents, toUnicode, toAscii } from 'gurmukhi-utils'
-
import controller from '../../lib/controller'
import { getTranslation, getTransliteration, customiseLine } from '../../lib/utils'
import { WritersContext, RecommendedSourcesContext, SettingsContext } from '../../lib/contexts'
@@ -63,167 +62,151 @@ const highlightMatches = gurmukhi => ( value, input, mode ) => {
: highlightFirstLetterMatches( value, sanitizedInput )
}
-/**
+const Result = ( { results, searchedValue, anchor, register, focused } ) => {
+ const { local: {
+ sources,
+ search: {
+ showResultCitations,
+ resultTransliterationLanguage,
+ resultTranslationLanguage,
+ lineEnding,
+ },
+ } = {} } = useContext( SettingsContext )
+
+ const writers = useContext( WritersContext )
+ const recommendedSources = useContext( RecommendedSourcesContext )
+
+ /**
* Renders a single result, highlighting the match.
* @param {string} gurmukhi The shabad line to display.
* @param {int} typeId The type id of line.
* @param {string} lineId The id of the line.
* @param {string} shabadId The id of the shabad.
* @param {Component} ref The ref to the component.
- * @param {string|int} focused
* @param {int} sourceId The id of source.
* @param {Object} shabad The object containng section information and other metadata.
* @param {int} sourcePage The page number of shabad in source.
* @param {string} translations The translations of shabad line to display.
* @param {string} transliterations The transliterations of shabad line to display.
- * @param {string} searchedValue The input to search.
- * @param {string} anchor Anchor for search mode.
- * @param {Object} writers From the SettingsContext.
- * @param {Object} sources From the ContentContext.
- * @param {Object} recommendedSources From the RecommendedSourcesContext.
- * @param {int|bool} resultTransliterationLanguage Language code for translits (SettingsContext).
- * @param {int|bool} resultTranslationLanguage Language code for translations (SettingsContext).
- * @param {bool} showResultCitations To show citations or not (SettingsContext).
- * @param {bool} lineEnding To strip line endings or not (SettingsContext).
*/
-const ResultList = ( {
- gurmukhi,
- typeId,
- id: lineId,
- shabadId,
- ref,
- focused,
- sourceId,
- shabad,
- sourcePage,
- translations,
- transliterations,
- searchedValue,
- anchor,
- writers,
- sources,
- recommendedSources,
- resultTransliterationLanguage,
- resultTranslationLanguage,
- showResultCitations,
- lineEnding,
-} ) => {
- const transliteration = resultTransliterationLanguage && transliterations && customiseLine(
- getTransliteration(
- { transliterations },
- resultTransliterationLanguage,
- ),
- { lineEnding, typeId },
- )
-
- const translation = resultTranslationLanguage && translations && customiseLine(
- getTranslation( {
- line: { translations },
- shabad: { sourceId },
- recommendedSources,
- sources,
- languageId: resultTranslationLanguage,
- } ),
- { lineEnding, typeId },
- )
-
- // Grab the search mode or assume it's first letter
- const mode = SEARCH_ANCHORS[ anchor ] || SEARCH_TYPES.firstLetter
-
- // Separate the line into words before the match, the match, and after the match
- const getMatches = highlightMatches( gurmukhi )
-
- const [ beforeMatch, match, afterMatch ] = getMatches(
+ const ResultList = ( {
gurmukhi,
- searchedValue,
- mode,
- )
- const [ translitBeforeMatch, translitMatch, translitAfterMatch ] = getMatches(
- transliteration,
- searchedValue,
- mode,
- )
-
- // Send the shabad id and line id to the server on click
- const onClick = () => controller.shabad( { shabadId, lineId } )
-
- // Helper render functions for citation
- const showCitation = showResultCitations && shabad && shabad.section
- const getEnglish = ( { nameEnglish } ) => nameEnglish
- const getWriterName = () => getEnglish( writers[ shabad.writerId ] )
- const getPageName = () => recommendedSources[ shabad.sourceId ].pageNameEnglish
-
- return (
-
-
-
-
- {beforeMatch ? {beforeMatch} : null}
- {match ? {match} : null}
- {afterMatch ? {afterMatch} : null}
-
-
-
-
- {translation && (
-
- {translation}
-
- )}
-
- {transliteration && (
-
- {translitBeforeMatch ? {translitBeforeMatch} : null}
- {translitMatch ? {translitMatch} : null}
- {translitAfterMatch ? {translitAfterMatch} : null}
-
+ typeId,
+ id: lineId,
+ shabadId,
+ ref,
+ sourceId,
+ shabad,
+ sourcePage,
+ translations,
+ transliterations,
+ } ) => {
+ const transliteration = resultTransliterationLanguage && transliterations && customiseLine(
+ getTransliteration(
+ { transliterations },
+ resultTransliterationLanguage,
+ ),
+ { lineEnding, typeId },
+ )
+
+ const translation = resultTranslationLanguage && translations && customiseLine(
+ getTranslation( {
+ line: { translations },
+ shabad: { sourceId },
+ recommendedSources,
+ sources,
+ languageId: resultTranslationLanguage,
+ } ),
+ { lineEnding, typeId },
+ )
+
+ // Grab the search mode or assume it's first letter
+ const mode = SEARCH_ANCHORS[ anchor ] || SEARCH_TYPES.firstLetter
+
+ // Separate the line into words before the match, the match, and after the match
+ const getMatches = highlightMatches( gurmukhi )
+
+ const [ beforeMatch, match, afterMatch ] = getMatches(
+ gurmukhi,
+ searchedValue,
+ mode,
+ )
+ const [ translitBeforeMatch, translitMatch, translitAfterMatch ] = getMatches(
+ transliteration,
+ searchedValue,
+ mode,
+ )
+
+ // Send the shabad id and line id to the server on click
+ const onClick = () => controller.shabad( { shabadId, lineId } )
+
+ // Helper render functions for citation
+ const showCitation = showResultCitations && shabad && shabad.section
+ const getEnglish = ( { nameEnglish } ) => nameEnglish
+ const getWriterName = () => getEnglish( writers[ shabad.writerId ] )
+ const getPageName = () => recommendedSources[ shabad.sourceId ].pageNameEnglish
+
+ return (
+
+
+
+
+ {beforeMatch ? {beforeMatch} : null}
+ {match ? {match} : null}
+ {afterMatch ? {afterMatch} : null}
+
+
+
+
+ {translation && (
+
+ {translation}
+
+ )}
+
+ {transliteration && (
+
+ {translitBeforeMatch ? {translitBeforeMatch} : null}
+ {translitMatch ? {translitMatch} : null}
+ {translitAfterMatch ? {translitAfterMatch} : null}
+
+ )}
+
+
+
+ {showCitation && (
+
+ {[
+ getWriterName(),
+ SOURCE_ABBREVIATIONS[ sourceId ],
+ `${getPageName()} ${sourcePage}`,
+ ].reduce( ( prev, curr ) => [ prev, ' - ', curr ] )}
+
)}
-
-
- {showCitation && (
-
- {[
- getWriterName(),
- SOURCE_ABBREVIATIONS[ sourceId ],
- `${getPageName()} ${sourcePage}`,
- ].reduce( ( prev, curr ) => [ prev, ' - ', curr ] )}
-
- )}
-
-
-
- )
-}
-
-const Result = ( { results, searchedValue, anchor, register, focused } ) => {
- const { local: {
- sources,
- search: {
- showResultCitations,
- resultTransliterationLanguage,
- resultTranslationLanguage,
- lineEnding,
- },
- } = {} } = useContext( SettingsContext )
-
- const writers = useContext( WritersContext )
- const recommendedSources = useContext( RecommendedSourcesContext )
+
+
+ )
+ }
+
+ ResultList.propTypes = {
+ gurmukhi: string.isRequired,
+ id: string.isRequired,
+ typeId: string.isRequired,
+ shabadId: string.isRequired,
+ ref: instanceOf( Result ).isRequired,
+ sourceId: number.isRequired,
+ shabad: shape( { } ).isRequired,
+ sourcePage: number.isRequired,
+ translations: string.isRequired,
+ transliterations: string.isRequired,
+ }
return (
{results
? results.map( ( props, i ) => ResultList( {
...props,
- searchedValue,
- anchor,
- sources,
- writers,
- recommendedSources,
- resultTransliterationLanguage,
- resultTranslationLanguage,
- showResultCitations,
- lineEnding,
ref: c => register( i, c ),
focused: focused === i,
} ) )
@@ -242,35 +225,8 @@ Result.propTypes = {
Result.defaultProps = {
results: [],
- searchedValue: '',
- anchor: '',
- focused: undefined,
-}
-
-ResultList.propTypes = {
- gurmukhi: string.isRequired,
- id: string.isRequired,
- typeId: string.isRequired,
- shabadId: string.isRequired,
- ref: instanceOf( Result ).isRequired,
- sourceId: number.isRequired,
- shabad: shape( { } ).isRequired,
- sourcePage: number.isRequired,
- translations: string.isRequired,
- transliterations: string.isRequired,
- focused: oneOfType( [ string, number ] ),
- searchedValue: string.isRequired,
- anchor: string.isRequired,
- writers: shape( {} ).isRequired,
- sources: shape( {} ).isRequired,
- recommendedSources: shape( {} ).isRequired,
- resultTransliterationLanguage: oneOfType( [ bool, number ] ).isRequired,
- resultTranslationLanguage: oneOfType( [ bool, number ] ).isRequired,
- showResultCitations: bool.isRequired,
- lineEnding: bool.isRequired,
-}
-
-ResultList.defaultProps = {
+ searchedValue: undefined,
+ anchor: undefined,
focused: undefined,
}
From 8573b3e412accf1ebe95a78d337f00e15b4aecbd Mon Sep 17 00:00:00 2001
From: Harjot Singh
Date: Sun, 25 Oct 2020 20:26:07 +0000
Subject: [PATCH 04/10] refactor(frontend/controller): move search highlighter
into seperate file
---
app/frontend/src/Controller/Search/Results.js | 94 ++++---------------
.../Controller/Search/highlight-matches.js | 57 +++++++++++
2 files changed, 76 insertions(+), 75 deletions(-)
create mode 100644 app/frontend/src/Controller/Search/highlight-matches.js
diff --git a/app/frontend/src/Controller/Search/Results.js b/app/frontend/src/Controller/Search/Results.js
index cc9697c7..b5b0b9cb 100644
--- a/app/frontend/src/Controller/Search/Results.js
+++ b/app/frontend/src/Controller/Search/Results.js
@@ -2,65 +2,13 @@ import React, { useContext } from 'react'
import classNames from 'classnames'
import { ListItem, List } from '@material-ui/core'
import { string, oneOfType, number, instanceOf, shape, arrayOf, func } from 'prop-types'
-import { firstLetters, stripVishraams, stripAccents, toUnicode, toAscii } from 'gurmukhi-utils'
import controller from '../../lib/controller'
import { getTranslation, getTransliteration, customiseLine } from '../../lib/utils'
import { WritersContext, RecommendedSourcesContext, SettingsContext } from '../../lib/contexts'
import { SEARCH_TYPES, LANGUAGE_NAMES, SEARCH_ANCHORS, SOURCE_ABBREVIATIONS } from '../../lib/consts'
-const highlightFullWordMatches = ( line, query ) => {
- const sanitisedQuery = query.trim()
-
- const foundPosition = line.search( sanitisedQuery )
- const matchStartPosition = line.lastIndexOf( ' ', foundPosition )
-
- const wordEndPosition = line.indexOf( ' ', foundPosition + sanitisedQuery.length )
- // If the match finishes in the last word, no space will be deteced, and wordEndPosition
- // will be -1. In this case, we want to end at the last position in the line.
- const matchEndPosition = wordEndPosition === -1 ? line.length - 1 : wordEndPosition
-
- return [
- line.substring( 0, matchStartPosition ),
- line.substring( matchStartPosition, matchEndPosition ),
- line.substring( matchEndPosition ),
- ]
-}
-
-const highlightFirstLetterMatches = ( line, query ) => {
- const baseLine = stripVishraams( line )
-
- const letters = toAscii( firstLetters( stripAccents( toUnicode( baseLine ) ) ) )
- const words = baseLine.split( ' ' )
-
- const startPosition = letters.search( stripAccents( query ) )
- const endPosition = startPosition + query.length
-
- return [
- `${words.slice( 0, startPosition ).join( ' ' )} `,
- `${words.slice( startPosition, endPosition ).join( ' ' )} `,
- `${words.slice( endPosition ).join( ' ' )} `,
- ]
-}
-
-/**
- * Separates the line into words before the first match, the first match, and after the match.
- * @param value The full line.
- * @param input The string inputted by the user.
- * @param mode The type of search being performed, either first word or full word.
- * @return An array of [ beforeMatch, match, afterMatch ],
- * with `match` being the highlighted section.`.
- */
-const highlightMatches = gurmukhi => ( value, input, mode ) => {
- if ( !value ) return [ '', '', '' ]
-
- // Account for wildcard characters
- const sanitizedInput = input.replace( new RegExp( '_', 'g' ), '.' )
-
- return mode === SEARCH_TYPES.fullWord
- ? highlightFullWordMatches( gurmukhi, sanitizedInput )
- : highlightFirstLetterMatches( value, sanitizedInput )
-}
+import highlightMatches from './highlight-matches'
const Result = ( { results, searchedValue, anchor, register, focused } ) => {
const { local: {
@@ -149,41 +97,37 @@ const Result = ( { results, searchedValue, anchor, register, focused } ) => {
return (
-
- {beforeMatch ? {beforeMatch} : null}
- {match ? {match} : null}
- {afterMatch ? {afterMatch} : null}
+ {beforeMatch && {beforeMatch} }
+ {match && {match} }
+ {afterMatch && {afterMatch} }
-
{translation && (
-
- {translation}
-
+
+ {translation}
+
)}
{transliteration && (
-
- {translitBeforeMatch ? {translitBeforeMatch} : null}
- {translitMatch ? {translitMatch} : null}
- {translitAfterMatch ? {translitAfterMatch} : null}
-
+
+ {translitBeforeMatch && {translitBeforeMatch} }
+ {translitMatch && {translitMatch} }
+ {translitAfterMatch && {translitAfterMatch} }
+
)}
-
{showCitation && (
-
- {[
- getWriterName(),
- SOURCE_ABBREVIATIONS[ sourceId ],
- `${getPageName()} ${sourcePage}`,
- ].reduce( ( prev, curr ) => [ prev, ' - ', curr ] )}
-
+
+ {[
+ getWriterName(),
+ SOURCE_ABBREVIATIONS[ sourceId ],
+ `${getPageName()} ${sourcePage}`,
+ ].reduce( ( prev, curr ) => [ prev, ' - ', curr ] )}
+
)}
-
)
diff --git a/app/frontend/src/Controller/Search/highlight-matches.js b/app/frontend/src/Controller/Search/highlight-matches.js
new file mode 100644
index 00000000..b5146471
--- /dev/null
+++ b/app/frontend/src/Controller/Search/highlight-matches.js
@@ -0,0 +1,57 @@
+const { stripVishraams, toAscii, firstLetters, stripAccents, toUnicode } = require( 'gurmukhi-utils' )
+const { SEARCH_TYPES } = require( '../../lib/consts' )
+
+const fullWordMatches = ( line, query ) => {
+ const sanitisedQuery = query.trim()
+
+ const foundPosition = line.search( sanitisedQuery )
+ const matchStartPosition = line.lastIndexOf( ' ', foundPosition )
+
+ const wordEndPosition = line.indexOf( ' ', foundPosition + sanitisedQuery.length )
+ // If the match finishes in the last word, no space will be deteced, and wordEndPosition
+ // will be -1. In this case, we want to end at the last position in the line.
+ const matchEndPosition = wordEndPosition === -1 ? line.length - 1 : wordEndPosition
+
+ return [
+ line.substring( 0, matchStartPosition ),
+ line.substring( matchStartPosition, matchEndPosition ),
+ line.substring( matchEndPosition ),
+ ]
+}
+
+const firstLetterMatches = ( line, query ) => {
+ const baseLine = stripVishraams( line )
+
+ const letters = toAscii( firstLetters( stripAccents( toUnicode( baseLine ) ) ) )
+ const words = baseLine.split( ' ' )
+
+ const startPosition = letters.search( stripAccents( query ) )
+ const endPosition = startPosition + query.length
+
+ return [
+ `${words.slice( 0, startPosition ).join( ' ' )} `,
+ `${words.slice( startPosition, endPosition ).join( ' ' )} `,
+ `${words.slice( endPosition ).join( ' ' )} `,
+ ]
+}
+
+/**
+ * Separates the line into words before the first match, the first match, and after the match.
+ * @param value The full line.
+ * @param input The string inputted by the user.
+ * @param mode The type of search being performed, either first word or full word.
+ * @return An array of [ beforeMatch, match, afterMatch ],
+ * with `match` being the highlighted section.`.
+ */
+const highlightMatches = gurmukhi => ( value, input, mode ) => {
+ if ( !value ) return [ '', '', '' ]
+
+ // Account for wildcard characters
+ const sanitizedInput = input.replace( new RegExp( '_', 'g' ), '.' )
+
+ return mode === SEARCH_TYPES.fullWord
+ ? fullWordMatches( gurmukhi, sanitizedInput )
+ : firstLetterMatches( value, sanitizedInput )
+}
+
+export default highlightMatches
From cb24bfcdd1ebcc44bd279d3495657211a7ac4f9f Mon Sep 17 00:00:00 2001
From: Harjot Singh
Date: Mon, 26 Oct 2020 02:00:02 +0000
Subject: [PATCH 05/10] fix(frontend/controller): fix search highlighting
transliterations
Refactors Results into single result component
---
app/frontend/src/Controller/Search/Result.js | 136 ++++++++++++++
app/frontend/src/Controller/Search/Results.js | 177 ------------------
.../Controller/Search/highlight-matches.js | 57 ------
app/frontend/src/Controller/Search/index.js | 28 ++-
.../Controller/Search/match-highlighter.js | 77 ++++++++
5 files changed, 232 insertions(+), 243 deletions(-)
create mode 100644 app/frontend/src/Controller/Search/Result.js
delete mode 100644 app/frontend/src/Controller/Search/Results.js
delete mode 100644 app/frontend/src/Controller/Search/highlight-matches.js
create mode 100644 app/frontend/src/Controller/Search/match-highlighter.js
diff --git a/app/frontend/src/Controller/Search/Result.js b/app/frontend/src/Controller/Search/Result.js
new file mode 100644
index 00000000..869c1327
--- /dev/null
+++ b/app/frontend/src/Controller/Search/Result.js
@@ -0,0 +1,136 @@
+import React, { forwardRef, useContext } from 'react'
+import { string, number, shape, bool, func } from 'prop-types'
+import classNames from 'classnames'
+import { ListItem } from '@material-ui/core'
+
+import controller from '../../lib/controller'
+import { getTranslation, getTransliteration, customiseLine } from '../../lib/utils'
+import { WritersContext, RecommendedSourcesContext, SettingsContext } from '../../lib/contexts'
+import { LANGUAGE_NAMES, SOURCE_ABBREVIATIONS } from '../../lib/consts'
+
+/**
+ * Renders a single result, highlighting the match.
+ * @param {string} gurmukhi The shabad line to display.
+ * @param {int} typeId The type id of line.
+ * @param {string} lineId The id of the line.
+ * @param {string} shabadId The id of the shabad.
+ * @param {Component} ref The ref to the component.
+ * @param {int} sourceId The id of source.
+ * @param {Object} shabad The object containng section information and other metadata.
+ * @param {int} sourcePage The page number of shabad in source.
+ * @param {string} translations The translations of shabad line to display.
+ * @param {string} transliterations The transliterations of shabad line to display.
+ */
+const Result = forwardRef( ( {
+ gurmukhi,
+ typeId,
+ id: lineId,
+ shabadId,
+ sourceId,
+ shabad,
+ focused,
+ highlighter,
+ sourcePage,
+ translations,
+ transliterations,
+}, ref ) => {
+ const { local: {
+ sources,
+ search: {
+ showResultCitations,
+ resultTransliterationLanguage,
+ resultTranslationLanguage,
+ lineEnding,
+ },
+ } = {} } = useContext( SettingsContext )
+
+ const writers = useContext( WritersContext )
+ const recommendedSources = useContext( RecommendedSourcesContext )
+
+ const transliteration = resultTransliterationLanguage && transliterations && customiseLine(
+ getTransliteration(
+ { transliterations },
+ resultTransliterationLanguage,
+ ),
+ { lineEnding, typeId },
+ )
+
+ const translation = resultTranslationLanguage && translations && customiseLine(
+ getTranslation( {
+ line: { translations },
+ shabad: { sourceId },
+ recommendedSources,
+ sources,
+ languageId: resultTranslationLanguage,
+ } ),
+ { lineEnding, typeId },
+ )
+
+ // Separate the line into words before the match, the match, and after the match
+ const highlight = highlighter( { gurmukhi } )
+ const [ beforeMatch, match, afterMatch ] = highlight( gurmukhi )
+ const [ translitBeforeMatch, translitMatch, translitAfterMatch ] = highlight( transliteration )
+
+ // Send the shabad id and line id to the server on click
+ const onClick = () => controller.shabad( { shabadId, lineId } )
+
+ // Helper render functions for citation
+ const showCitation = showResultCitations && shabad && shabad.section
+ const getEnglish = ( { nameEnglish } ) => nameEnglish
+ const getWriterName = () => getEnglish( writers[ shabad.writerId ] )
+ const getPageName = () => recommendedSources[ shabad.sourceId ].pageNameEnglish
+
+ return (
+
+
+
+ {beforeMatch && {beforeMatch} }
+ {match && {match} }
+ {afterMatch && {afterMatch} }
+
+
+
+ {translation && (
+
+ {translation}
+
+ )}
+
+ {transliteration && (
+
+ {translitBeforeMatch && {translitBeforeMatch} }
+ {translitMatch && {translitMatch} }
+ {translitAfterMatch && {translitAfterMatch} }
+
+ )}
+
+
+ {showCitation && (
+
+ {[
+ getWriterName(),
+ SOURCE_ABBREVIATIONS[ sourceId ],
+ `${getPageName()} ${sourcePage}`,
+ ].reduce( ( prev, curr ) => [ prev, ' - ', curr ] )}
+
+ )}
+
+
+ )
+} )
+
+Result.propTypes = {
+ gurmukhi: string.isRequired,
+ id: string.isRequired,
+ typeId: string.isRequired,
+ shabadId: string.isRequired,
+ focused: bool.isRequired,
+ highlighter: func.isRequired,
+ sourceId: number.isRequired,
+ shabad: shape( { } ).isRequired,
+ sourcePage: number.isRequired,
+ translations: string.isRequired,
+ transliterations: string.isRequired,
+}
+
+export default Result
diff --git a/app/frontend/src/Controller/Search/Results.js b/app/frontend/src/Controller/Search/Results.js
deleted file mode 100644
index b5b0b9cb..00000000
--- a/app/frontend/src/Controller/Search/Results.js
+++ /dev/null
@@ -1,177 +0,0 @@
-import React, { useContext } from 'react'
-import classNames from 'classnames'
-import { ListItem, List } from '@material-ui/core'
-import { string, oneOfType, number, instanceOf, shape, arrayOf, func } from 'prop-types'
-
-import controller from '../../lib/controller'
-import { getTranslation, getTransliteration, customiseLine } from '../../lib/utils'
-import { WritersContext, RecommendedSourcesContext, SettingsContext } from '../../lib/contexts'
-import { SEARCH_TYPES, LANGUAGE_NAMES, SEARCH_ANCHORS, SOURCE_ABBREVIATIONS } from '../../lib/consts'
-
-import highlightMatches from './highlight-matches'
-
-const Result = ( { results, searchedValue, anchor, register, focused } ) => {
- const { local: {
- sources,
- search: {
- showResultCitations,
- resultTransliterationLanguage,
- resultTranslationLanguage,
- lineEnding,
- },
- } = {} } = useContext( SettingsContext )
-
- const writers = useContext( WritersContext )
- const recommendedSources = useContext( RecommendedSourcesContext )
-
- /**
- * Renders a single result, highlighting the match.
- * @param {string} gurmukhi The shabad line to display.
- * @param {int} typeId The type id of line.
- * @param {string} lineId The id of the line.
- * @param {string} shabadId The id of the shabad.
- * @param {Component} ref The ref to the component.
- * @param {int} sourceId The id of source.
- * @param {Object} shabad The object containng section information and other metadata.
- * @param {int} sourcePage The page number of shabad in source.
- * @param {string} translations The translations of shabad line to display.
- * @param {string} transliterations The transliterations of shabad line to display.
- */
- const ResultList = ( {
- gurmukhi,
- typeId,
- id: lineId,
- shabadId,
- ref,
- sourceId,
- shabad,
- sourcePage,
- translations,
- transliterations,
- } ) => {
- const transliteration = resultTransliterationLanguage && transliterations && customiseLine(
- getTransliteration(
- { transliterations },
- resultTransliterationLanguage,
- ),
- { lineEnding, typeId },
- )
-
- const translation = resultTranslationLanguage && translations && customiseLine(
- getTranslation( {
- line: { translations },
- shabad: { sourceId },
- recommendedSources,
- sources,
- languageId: resultTranslationLanguage,
- } ),
- { lineEnding, typeId },
- )
-
- // Grab the search mode or assume it's first letter
- const mode = SEARCH_ANCHORS[ anchor ] || SEARCH_TYPES.firstLetter
-
- // Separate the line into words before the match, the match, and after the match
- const getMatches = highlightMatches( gurmukhi )
-
- const [ beforeMatch, match, afterMatch ] = getMatches(
- gurmukhi,
- searchedValue,
- mode,
- )
- const [ translitBeforeMatch, translitMatch, translitAfterMatch ] = getMatches(
- transliteration,
- searchedValue,
- mode,
- )
-
- // Send the shabad id and line id to the server on click
- const onClick = () => controller.shabad( { shabadId, lineId } )
-
- // Helper render functions for citation
- const showCitation = showResultCitations && shabad && shabad.section
- const getEnglish = ( { nameEnglish } ) => nameEnglish
- const getWriterName = () => getEnglish( writers[ shabad.writerId ] )
- const getPageName = () => recommendedSources[ shabad.sourceId ].pageNameEnglish
-
- return (
-
-
-
- {beforeMatch && {beforeMatch} }
- {match && {match} }
- {afterMatch && {afterMatch} }
-
-
-
- {translation && (
-
- {translation}
-
- )}
-
- {transliteration && (
-
- {translitBeforeMatch && {translitBeforeMatch} }
- {translitMatch && {translitMatch} }
- {translitAfterMatch && {translitAfterMatch} }
-
- )}
-
-
- {showCitation && (
-
- {[
- getWriterName(),
- SOURCE_ABBREVIATIONS[ sourceId ],
- `${getPageName()} ${sourcePage}`,
- ].reduce( ( prev, curr ) => [ prev, ' - ', curr ] )}
-
- )}
-
-
- )
- }
-
- ResultList.propTypes = {
- gurmukhi: string.isRequired,
- id: string.isRequired,
- typeId: string.isRequired,
- shabadId: string.isRequired,
- ref: instanceOf( Result ).isRequired,
- sourceId: number.isRequired,
- shabad: shape( { } ).isRequired,
- sourcePage: number.isRequired,
- translations: string.isRequired,
- transliterations: string.isRequired,
- }
-
- return (
-
- {results
- ? results.map( ( props, i ) => ResultList( {
- ...props,
- ref: c => register( i, c ),
- focused: focused === i,
- } ) )
- : ''}
-
- )
-}
-
-Result.propTypes = {
- results: arrayOf( shape( {} ) ),
- searchedValue: string,
- anchor: string,
- register: func.isRequired,
- focused: oneOfType( [ string, number ] ),
-}
-
-Result.defaultProps = {
- results: [],
- searchedValue: undefined,
- anchor: undefined,
- focused: undefined,
-}
-
-export default Result
diff --git a/app/frontend/src/Controller/Search/highlight-matches.js b/app/frontend/src/Controller/Search/highlight-matches.js
deleted file mode 100644
index b5146471..00000000
--- a/app/frontend/src/Controller/Search/highlight-matches.js
+++ /dev/null
@@ -1,57 +0,0 @@
-const { stripVishraams, toAscii, firstLetters, stripAccents, toUnicode } = require( 'gurmukhi-utils' )
-const { SEARCH_TYPES } = require( '../../lib/consts' )
-
-const fullWordMatches = ( line, query ) => {
- const sanitisedQuery = query.trim()
-
- const foundPosition = line.search( sanitisedQuery )
- const matchStartPosition = line.lastIndexOf( ' ', foundPosition )
-
- const wordEndPosition = line.indexOf( ' ', foundPosition + sanitisedQuery.length )
- // If the match finishes in the last word, no space will be deteced, and wordEndPosition
- // will be -1. In this case, we want to end at the last position in the line.
- const matchEndPosition = wordEndPosition === -1 ? line.length - 1 : wordEndPosition
-
- return [
- line.substring( 0, matchStartPosition ),
- line.substring( matchStartPosition, matchEndPosition ),
- line.substring( matchEndPosition ),
- ]
-}
-
-const firstLetterMatches = ( line, query ) => {
- const baseLine = stripVishraams( line )
-
- const letters = toAscii( firstLetters( stripAccents( toUnicode( baseLine ) ) ) )
- const words = baseLine.split( ' ' )
-
- const startPosition = letters.search( stripAccents( query ) )
- const endPosition = startPosition + query.length
-
- return [
- `${words.slice( 0, startPosition ).join( ' ' )} `,
- `${words.slice( startPosition, endPosition ).join( ' ' )} `,
- `${words.slice( endPosition ).join( ' ' )} `,
- ]
-}
-
-/**
- * Separates the line into words before the first match, the first match, and after the match.
- * @param value The full line.
- * @param input The string inputted by the user.
- * @param mode The type of search being performed, either first word or full word.
- * @return An array of [ beforeMatch, match, afterMatch ],
- * with `match` being the highlighted section.`.
- */
-const highlightMatches = gurmukhi => ( value, input, mode ) => {
- if ( !value ) return [ '', '', '' ]
-
- // Account for wildcard characters
- const sanitizedInput = input.replace( new RegExp( '_', 'g' ), '.' )
-
- return mode === SEARCH_TYPES.fullWord
- ? fullWordMatches( gurmukhi, sanitizedInput )
- : firstLetterMatches( value, sanitizedInput )
-}
-
-export default highlightMatches
diff --git a/app/frontend/src/Controller/Search/index.js b/app/frontend/src/Controller/Search/index.js
index 80485a9f..935f5573 100644
--- a/app/frontend/src/Controller/Search/index.js
+++ b/app/frontend/src/Controller/Search/index.js
@@ -5,7 +5,7 @@ import { useLocation, useHistory } from 'react-router-dom'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faTimes } from '@fortawesome/free-solid-svg-icons'
import { stringify } from 'querystring'
-import { Input, InputAdornment, IconButton } from '@material-ui/core'
+import { Input, InputAdornment, IconButton, List } from '@material-ui/core'
import { getUrlState } from '../../lib/utils'
import { SettingsContext } from '../../lib/contexts'
@@ -18,7 +18,8 @@ import {
MIN_SEARCH_CHARS,
} from '../../lib/consts'
-import Result from './Results'
+import Result from './Result'
+import getHighlighter from './match-highlighter'
import './index.css'
// Generate the regex for capturing anchor chars, optionally
@@ -147,6 +148,10 @@ const Search = ( { updateFocus, register, focused } ) => {
useEffect( () => { highlightSearch() }, [] )
+ // Get match highlighter for the current search mode
+ const searchMode = SEARCH_ANCHORS[ anchor ] || SEARCH_TYPES.firstLetter
+ const highlighter = getHighlighter( searchedValue, searchMode )
+
return (
{
autoComplete: 'off',
}}
/>
-
+
+
+ {results && results.map( ( result, index ) => (
+ register( index, ref )}
+ focused={focused === index}
+ highlighter={highlighter}
+ />
+ ) )}
+
)
}
diff --git a/app/frontend/src/Controller/Search/match-highlighter.js b/app/frontend/src/Controller/Search/match-highlighter.js
new file mode 100644
index 00000000..1fab8181
--- /dev/null
+++ b/app/frontend/src/Controller/Search/match-highlighter.js
@@ -0,0 +1,77 @@
+import { stripVishraams, toAscii, firstLetters, stripAccents, toUnicode } from 'gurmukhi-utils'
+
+import { SEARCH_TYPES } from '../../lib/consts'
+
+const fullWordMatches = query => ( { target, gurmukhi } ) => {
+ const baseGurmukhi = stripVishraams( gurmukhi )
+ const baseTarget = stripVishraams( target )
+
+ const sanitisedQuery = query.trim()
+
+ const foundPosition = baseGurmukhi.search( sanitisedQuery )
+ const matchStartPosition = baseGurmukhi.lastIndexOf( ' ', foundPosition )
+
+ const wordEndPosition = baseGurmukhi.indexOf( ' ', foundPosition + sanitisedQuery.length )
+ // If the match finishes in the last word, no space will be deteced, and wordEndPosition
+ // will be -1. In this case, we want to end at the last position in the line.
+ const matchEndPosition = wordEndPosition === -1 ? baseGurmukhi.length - 1 : wordEndPosition
+
+ // Grab the word indexes in gurmukhi
+ const [ wordMatchStart, wordMatchLength ] = [
+ gurmukhi.substring( 0, matchStartPosition ).trim().split( ' ' ).length - 1,
+ gurmukhi.substring( matchStartPosition, matchEndPosition ).trim().split( ' ' ).length,
+ ]
+
+ const words = baseTarget.split( ' ' )
+
+ return [
+ `${words.slice( 0, wordMatchStart ).join( ' ' )} `,
+ `${words.slice( wordMatchStart, wordMatchStart + wordMatchLength ).join( ' ' )} `,
+ `${words.slice( wordMatchStart + wordMatchLength ).join( ' ' )} `,
+ ]
+}
+
+const firstLetterMatches = query => ( { target, gurmukhi } ) => {
+ const baseGurmukhi = stripVishraams( gurmukhi )
+ const baseLine = stripVishraams( target )
+
+ const letters = toAscii( firstLetters( stripAccents( toUnicode( baseGurmukhi ) ) ) )
+ const words = baseLine.split( ' ' )
+
+ const startPosition = letters.search( stripAccents( query ) )
+ const endPosition = startPosition + query.length
+
+ return [
+ `${words.slice( 0, startPosition ).join( ' ' )} `,
+ `${words.slice( startPosition, endPosition ).join( ' ' )} `,
+ `${words.slice( endPosition ).join( ' ' )} `,
+ ]
+}
+
+const highlighters = {
+ [ SEARCH_TYPES.fullWord ]: fullWordMatches,
+ [ SEARCH_TYPES.firstLetter ]: firstLetterMatches,
+}
+
+/**
+ * Separates the line into words before the first match, the first match, and after the match.
+ * @param target The text to highlight.
+ * @param context Contains gurmukhi and other contextual information required by all highlighters.
+ * @param searchQuery The string inputted by the user.
+ * @param searchMode The type of search being performed, either first word or full word.
+ * @return An array of [ beforeMatch, match, afterMatch ],
+ * with `match` being the highlighted section.`.
+ */
+const getHighlighter = ( searchQuery, searchMode ) => context => target => {
+ if ( !target ) return [ '', '', '' ]
+
+ // Account for wildcard characters
+ const sanitizedQuery = searchQuery.replace( new RegExp( '_', 'g' ), '.' )
+
+ // Select the right higlighter
+ const highlight = highlighters[ searchMode ]
+
+ return highlight( sanitizedQuery )( { target, ...context } )
+}
+
+export default getHighlighter
From f8fdffb41b19e71fb7be0fc3d3bb682c3b1dfd75 Mon Sep 17 00:00:00 2001
From: Harjot Singh
Date: Mon, 26 Oct 2020 18:40:46 +0000
Subject: [PATCH 06/10] refactor(frontend/controller): fix spelling in comment
Co-authored-by: Saihajpreet Singh
---
app/frontend/src/Controller/Search/match-highlighter.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/frontend/src/Controller/Search/match-highlighter.js b/app/frontend/src/Controller/Search/match-highlighter.js
index 1fab8181..bf3fafb7 100644
--- a/app/frontend/src/Controller/Search/match-highlighter.js
+++ b/app/frontend/src/Controller/Search/match-highlighter.js
@@ -68,7 +68,7 @@ const getHighlighter = ( searchQuery, searchMode ) => context => target => {
// Account for wildcard characters
const sanitizedQuery = searchQuery.replace( new RegExp( '_', 'g' ), '.' )
- // Select the right higlighter
+ // Select the right highlighter
const highlight = highlighters[ searchMode ]
return highlight( sanitizedQuery )( { target, ...context } )
From 264a7d52a58f65ca3737c436546c66e78df23bfd Mon Sep 17 00:00:00 2001
From: Harjot Singh
Date: Mon, 26 Oct 2020 18:40:58 +0000
Subject: [PATCH 07/10] refactor(frontend/controller): fix spelling in comment
Co-authored-by: Saihajpreet Singh
---
app/frontend/src/Controller/Search/Result.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/frontend/src/Controller/Search/Result.js b/app/frontend/src/Controller/Search/Result.js
index 869c1327..216d18ee 100644
--- a/app/frontend/src/Controller/Search/Result.js
+++ b/app/frontend/src/Controller/Search/Result.js
@@ -16,7 +16,7 @@ import { LANGUAGE_NAMES, SOURCE_ABBREVIATIONS } from '../../lib/consts'
* @param {string} shabadId The id of the shabad.
* @param {Component} ref The ref to the component.
* @param {int} sourceId The id of source.
- * @param {Object} shabad The object containng section information and other metadata.
+ * @param {Object} shabad The object containing section information and other metadata.
* @param {int} sourcePage The page number of shabad in source.
* @param {string} translations The translations of shabad line to display.
* @param {string} transliterations The transliterations of shabad line to display.
From 8a8fd11636bf61ef6a98dfefc3afbacd2806ccad Mon Sep 17 00:00:00 2001
From: Harjot Singh
Date: Mon, 26 Oct 2020 18:41:07 +0000
Subject: [PATCH 08/10] refactor(frontend/controller): fix spelling in comment
Co-authored-by: Saihajpreet Singh
---
app/frontend/src/Controller/Search/match-highlighter.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/frontend/src/Controller/Search/match-highlighter.js b/app/frontend/src/Controller/Search/match-highlighter.js
index bf3fafb7..6620f010 100644
--- a/app/frontend/src/Controller/Search/match-highlighter.js
+++ b/app/frontend/src/Controller/Search/match-highlighter.js
@@ -12,7 +12,7 @@ const fullWordMatches = query => ( { target, gurmukhi } ) => {
const matchStartPosition = baseGurmukhi.lastIndexOf( ' ', foundPosition )
const wordEndPosition = baseGurmukhi.indexOf( ' ', foundPosition + sanitisedQuery.length )
- // If the match finishes in the last word, no space will be deteced, and wordEndPosition
+ // If the match finishes in the last word, no space will be detected, and wordEndPosition
// will be -1. In this case, we want to end at the last position in the line.
const matchEndPosition = wordEndPosition === -1 ? baseGurmukhi.length - 1 : wordEndPosition
From abec67a2fb82d1beee0f1b55ac19f6cf9ec2d9ae Mon Sep 17 00:00:00 2001
From: Harjot Singh
Date: Mon, 26 Oct 2020 21:51:31 +0000
Subject: [PATCH 09/10] docs(frontend/controller): add more information about
match highlighting
---
.../Controller/Search/match-highlighter.js | 28 ++++++++++++++++++-
1 file changed, 27 insertions(+), 1 deletion(-)
diff --git a/app/frontend/src/Controller/Search/match-highlighter.js b/app/frontend/src/Controller/Search/match-highlighter.js
index 6620f010..da038762 100644
--- a/app/frontend/src/Controller/Search/match-highlighter.js
+++ b/app/frontend/src/Controller/Search/match-highlighter.js
@@ -2,21 +2,32 @@ import { stripVishraams, toAscii, firstLetters, stripAccents, toUnicode } from '
import { SEARCH_TYPES } from '../../lib/consts'
+/**
+ * Highlights a full word query against a matched line.
+ * Finds the position to highlight in the target string using the Gurmukhi matched line,
+ * and using the position of the highlighted words, highlights the same words in the target
+ * string.
+ */
const fullWordMatches = query => ( { target, gurmukhi } ) => {
+ // Remove vishraams to prevent query from not matching
const baseGurmukhi = stripVishraams( gurmukhi )
+ // Remove vishraams from target to prevent vishraams in output
const baseTarget = stripVishraams( target )
+ // Trailing spaces can cause mismatches
const sanitisedQuery = query.trim()
+ // Find the match position, and then backtrack to find the beginning of the word
const foundPosition = baseGurmukhi.search( sanitisedQuery )
const matchStartPosition = baseGurmukhi.lastIndexOf( ' ', foundPosition )
+ // Search forward to find the end of the match
const wordEndPosition = baseGurmukhi.indexOf( ' ', foundPosition + sanitisedQuery.length )
// If the match finishes in the last word, no space will be detected, and wordEndPosition
// will be -1. In this case, we want to end at the last position in the line.
const matchEndPosition = wordEndPosition === -1 ? baseGurmukhi.length - 1 : wordEndPosition
- // Grab the word indexes in gurmukhi
+ // Grab the start index and length of the entire matching words
const [ wordMatchStart, wordMatchLength ] = [
gurmukhi.substring( 0, matchStartPosition ).trim().split( ' ' ).length - 1,
gurmukhi.substring( matchStartPosition, matchEndPosition ).trim().split( ' ' ).length,
@@ -31,13 +42,22 @@ const fullWordMatches = query => ( { target, gurmukhi } ) => {
]
}
+/**
+ * Highlights a first letter query against a matched line.
+ * Finds the words to match in the Gurmukhi string, and highlights
+ * the corresponding target string.
+ */
const firstLetterMatches = query => ( { target, gurmukhi } ) => {
+ // Remove vishraams to prevent query from not matching
const baseGurmukhi = stripVishraams( gurmukhi )
+ // Remove vishraams from target to prevent vishraams in output
const baseLine = stripVishraams( target )
+ // Get only letters, so that simple first letters can be matched
const letters = toAscii( firstLetters( stripAccents( toUnicode( baseGurmukhi ) ) ) )
const words = baseLine.split( ' ' )
+ // Find the start and end positions of the match, including the entire end word
const startPosition = letters.search( stripAccents( query ) )
const endPosition = startPosition + query.length
@@ -48,6 +68,12 @@ const firstLetterMatches = query => ( { target, gurmukhi } ) => {
]
}
+/**
+ * Supported search mode match highlighters.
+ * Highlighters must support highlighting against a 1-1 transliteration or gurmukhi string.
+ * Highlighters all receive the same parameters.
+ * Highlights must return a tuple of [ beforeMatch, match, afterMatch ]
+ */
const highlighters = {
[ SEARCH_TYPES.fullWord ]: fullWordMatches,
[ SEARCH_TYPES.firstLetter ]: firstLetterMatches,
From 2edeaccd5d09aef27c324d1ddf9de9325a934f44 Mon Sep 17 00:00:00 2001
From: Harjot Singh
Date: Mon, 26 Oct 2020 21:54:44 +0000
Subject: [PATCH 10/10] docs(frontend/controller): update JSDoc of Result
component
---
app/frontend/src/Controller/Search/Result.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/app/frontend/src/Controller/Search/Result.js b/app/frontend/src/Controller/Search/Result.js
index 216d18ee..5ee2e7e3 100644
--- a/app/frontend/src/Controller/Search/Result.js
+++ b/app/frontend/src/Controller/Search/Result.js
@@ -14,9 +14,10 @@ import { LANGUAGE_NAMES, SOURCE_ABBREVIATIONS } from '../../lib/consts'
* @param {int} typeId The type id of line.
* @param {string} lineId The id of the line.
* @param {string} shabadId The id of the shabad.
- * @param {Component} ref The ref to the component.
* @param {int} sourceId The id of source.
* @param {Object} shabad The object containing section information and other metadata.
+ * @param {Boolean} focused Whether the line is focused or not.
+ * @param {Function} highlighter The match highlighter.
* @param {int} sourcePage The page number of shabad in source.
* @param {string} translations The translations of shabad line to display.
* @param {string} transliterations The transliterations of shabad line to display.