Merge pull request #16 from Nona-Creative/feat/multiple-search-answers

Feat/multiple search answers
This commit is contained in:
Wisani 2018-07-18 12:49:35 +02:00 committed by GitHub
commit b784297a85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 122 additions and 36 deletions

View File

@ -17,12 +17,13 @@ class BrowseArchives extends React.Component {
this.state = {
term: '',
debounceTerm: '',
searchResults: [null],
searchResults: [],
isSingleInterviewModalOpen: false,
isInterviewsListModalOpen: false,
activeSingleInterviewId: 1,
isSearchActive: false,
interviewData: this.transformInterviews(InterviewsData),
matchedCount: 0,
};
this.onSearchInputChange = this.onSearchInputChange.bind(this);
@ -39,7 +40,7 @@ class BrowseArchives extends React.Component {
this.setState({
term: event.target.value,
isSearchActive: true,
searchResults: [null],
searchResults: [],
});
if (event.target.value.length === 0) {
@ -56,17 +57,28 @@ class BrowseArchives extends React.Component {
const searchResults = interviewData.reduce((filtered, interview) => {
const findTerm = this.termIsInInterview(term, interview);
const matchedIndex = findTerm.foundIndex;
const { matchingQuestionAnswerPositions } = findTerm;
const { matchCount } = findTerm;
if (findTerm.found) {
filtered.push({ ...interview, matchedIndex });
filtered.push({
...interview,
matchedIndex,
matchingQuestionAnswerPositions,
matchCount,
});
}
return filtered;
}, []);
const matchedCount = searchResults
.reduce((accumulator, match) => accumulator + match.matchCount, 0);
this.setState({
searchResults,
debounceTerm: term,
matchedCount,
});
}
@ -95,12 +107,13 @@ class BrowseArchives extends React.Component {
this.setState({
isSearchActive: false,
term: '',
matchedCount: 0,
searchResults: [],
});
}
transformInterviews = (interviews) => {
// eslint-disable-next-line
const length = Object.keys(interviews).length;
const { length } = Object.keys(interviews);
const betterInterviews = [];
for (let i = 0; i < length; i++) {
@ -157,13 +170,14 @@ class BrowseArchives extends React.Component {
termIsInInterview = (term, interview) => {
const lcTerm = term.toLowerCase();
const matchesName = interview.name.toLowerCase().includes(lcTerm);
const { interviewData } = this.state;
let foundIndex = 0;
let positionInAnswer = -1;
if (matchesName) {
return {
found: true,
foundIndex: 0,
matchCount: 0,
};
}
@ -173,9 +187,12 @@ class BrowseArchives extends React.Component {
return {
found: true,
foundIndex: 0,
matchCount: 0,
};
}
const matchingQuestionAnswerPositions = [];
const matchingQuestions = interview.interview
.filter((question, questionIndex) => {
if (question.answer === null) {
@ -185,20 +202,34 @@ class BrowseArchives extends React.Component {
const index = question.answer.toLowerCase().indexOf(lcTerm);
if (index !== -1 && interview.activeIndex !== -1) {
const cleanTerm = term.replace(/[^a-zA-Z 0-9]+/g, '');
const regex = new RegExp(cleanTerm, 'ig');
const count = question.answer.match(regex).length;
foundIndex = questionIndex;
positionInAnswer = index;
matchingQuestionAnswerPositions.push({
id: question.question,
strpos: index,
answer: question.answer,
index: questionIndex,
count,
});
}
return index !== -1;
});
this.setState({
interviewData,
});
const matchCount = matchingQuestionAnswerPositions
.reduce((accumulator, match) => accumulator + match.count, 0);
if (matchingQuestions.length > 0) {
return {
found: true,
foundIndex,
positionInAnswer,
matchingQuestionAnswerPositions,
matchCount,
};
}
@ -215,6 +246,7 @@ class BrowseArchives extends React.Component {
searchResults,
interviewData,
debounceTerm,
matchedCount,
} = this.state;
return (
@ -224,6 +256,8 @@ class BrowseArchives extends React.Component {
clearSearchInput={this.clearSearchInput}
isSearchActive={isSearchActive}
term={term}
numResults={searchResults.length}
numMatchedTerms={matchedCount}
/>
<div className="browse-content-wrap container">
<div className="browse-content-left">

View File

@ -30,6 +30,9 @@ const SearchBar = props => (
)
}
</form>
{props.numResults > 0 && props.numResults[0] !== null ?
<p className="search-count">{ props.numMatchedTerms } search term matches | { props.numResults } interview matches</p> :
''}
</div>
</div>
);
@ -39,6 +42,8 @@ SearchBar.propTypes = {
isSearchActive: PropTypes.bool.isRequired,
onSearchInputChange: PropTypes.func.isRequired,
clearSearchInput: PropTypes.func.isRequired,
numResults: PropTypes.number.isRequired,
numMatchedTerms: PropTypes.number.isRequired,
};
export default SearchBar;

View File

@ -71,3 +71,8 @@
outline: none;
}
}
.search-count {
margin: calculateRem(36) 0;
font-size: calculateRem(13);
}

View File

@ -16,39 +16,75 @@ const SearchResults = (props) => {
// sort array alphabetically
const sortedInterviews = props.data.sort((a, b) => a.name.localeCompare(b.name));
const trimText = (text, length) => {
const getStartOffset = (text) => {
if (text.indexOf('>') === 0) {
return -2;
} else if (text.indexOf('/p>') === 0) {
return 3;
} else if (text.indexOf('p>') === 0) {
return -1;
} else if (text.indexOf('<p>') !== 0) {
return false;
}
return 0;
};
const getEndOffset = (text) => {
if (text.substr(text.length - 1, 1) === '<') {
return -1;
} else if (text.substr(text.length - 2, 2) === '<p') {
return -2;
} else if (text.substr(text.length - 3, 3) === '<p>') {
return -3;
}
return 0;
};
const trimText = (text, strpos, length) => {
let offset = 0;
let firstEllipses = '';
let lastEllipses = '';
let startOffset = 0;
let endOffset = 0;
if (text === null) {
return '';
}
return text.length <= length ? text : `${text.substr(0, length)}...`;
if (strpos > length && strpos !== -1 && length > 50) {
offset = strpos - length;
firstEllipses = '<p>...</p>';
}
const offsetText = text.substr(offset, length + offset);
startOffset = getStartOffset(offsetText);
endOffset = getEndOffset(offsetText);
const newOffsetText = startOffset ?
text.substr(offset + startOffset, length + offset + endOffset) :
`<p>${text.substr(offset + 0, length + offset + endOffset)}`;
if (newOffsetText.substr(newOffsetText.length - 1, 1) !== '.' && newOffsetText.substr(newOffsetText.length - 1, 1) !== '>') {
lastEllipses = '...';
}
return text.length <= length ? text : `${firstEllipses}${newOffsetText}${lastEllipses}`;
};
const highlightTerm = (text) => {
const cleanTerm = props.term.replace(/[^a-zA-Z 0-9]+/g, '').toLowerCase();
const cleanTerm = props.term.replace(/[^a-zA-Z 0-9]+/g, '');
const regex = new RegExp(cleanTerm, 'ig');
return text.replace(regex, `<span>${cleanTerm}</span>`);
return text.replace(regex, match => `<span>${match}</span>`);
};
const processText = (text, length = 500) => highlightTerm(trimText(text, length));
const findFirstQuestion = (interview) => {
let { answer } = interview.interview[interview.matchedIndex];
let id = interview.interview[interview.matchedIndex].question;
if (answer === null) {
const firstNonNullAnswer = interview.interview.find(question => question.answer !== null);
id = firstNonNullAnswer.question;
// eslint-disable-next-line
answer = firstNonNullAnswer.answer;
}
const processText = (text, strpos, length = 500) => highlightTerm(trimText(text, strpos, length));
const findQuestion = (answer) => {
const { id } = answer;
const { text } = props.questions.find(question => question.id === id);
return {
question: text,
answer: processText(answer),
};
return text;
};
const interviewNameContainsTerm = (name, searchTerm) =>
@ -69,12 +105,17 @@ const SearchResults = (props) => {
<img src={`${publicRuntimeConfig.subDirPath}/static/img/right-chevron-icon.svg`} alt="right chevron icon" />
</div>
</div>
<h5>{interview.matchedIndex + 1})&nbsp;
{ findFirstQuestion(interview).question }
</h5>
<div>
{ Parser(findFirstQuestion(interview).answer) }
</div>
{interview.matchingQuestionAnswerPositions ?
interview.matchingQuestionAnswerPositions.map(match => (
<div key={match.index + 1}>
<h5>{match.index + 1})&nbsp;
{ findQuestion(match) }
</h5>
<div>
{ Parser(processText(match.answer, match.strpos)) }
</div>
</div>
)) : ''}
</button>
</li>
))

View File

@ -1,6 +1,7 @@
@import './assets/styles/global.scss';
.search-results {
margin-top: calculateRem(32);
li {
margin-bottom: calculateRem(32);