Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: highlight search messages #884

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/markups/src/elements/BoldSpan.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PlainSpan from './PlainSpan';
import ItalicSpan from './ItalicSpan';
import StrikeSpan from './StrikeSpan';
import LinkSpan from './LinkSpan';
import HighlightText from './highlightText';

const BoldSpan = ({ contents }) => (
<strong>
Expand All @@ -17,6 +18,7 @@ const BoldSpan = ({ contents }) => (

case 'ITALIC':
return <ItalicSpan key={index} contents={content.value} />;

case 'LINK':
return (
<LinkSpan
Expand All @@ -30,6 +32,9 @@ const BoldSpan = ({ contents }) => (
/>
);

case 'HIGHLIGHT_TEXT':
return <HighlightText key={index} contents={content.value} />;

default:
return null;
}
Expand Down
29 changes: 27 additions & 2 deletions packages/markups/src/elements/CodeElement.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,44 @@
import React from 'react';
import PropTypes from 'prop-types';
import PlainSpan from './PlainSpan';
import HighlightText from './highlightText';
import { InlineElementsStyles } from './elements.styles';

const CodeElement = ({ contents }) => {
const styles = InlineElementsStyles();

const contentsArray = Array.isArray(contents) ? contents : [contents];
return (
<code css={styles.inlineElement}>
<PlainSpan contents={contents.value} />
{contentsArray.map((content, index) => {
switch (content.type) {
case 'PLAIN_TEXT':
return <PlainSpan key={index} contents={content.value} />;

case 'HIGHLIGHT_TEXT':
return <HighlightText key={index} contents={content.value} />;

default:
return null;
}
})}
</code>
);
};

export default CodeElement;

CodeElement.propTypes = {
contents: PropTypes.any,
contents: PropTypes.oneOfType([
PropTypes.arrayOf(
PropTypes.shape({
type: PropTypes.string.isRequired,
value: PropTypes.any.isRequired,
})
),
PropTypes.shape({
type: PropTypes.string.isRequired,
value: PropTypes.any.isRequired,
}),
]).isRequired,
};
5 changes: 5 additions & 0 deletions packages/markups/src/elements/InlineElements.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import ChannelMention from '../mentions/ChannelMention';
import ColorElement from './ColorElement';
import LinkSpan from './LinkSpan';
import UserMention from '../mentions/UserMention';
import HighlightText from './highlightText';

const InlineElements = ({ contents }) =>
contents.map((content, index) => {
Expand Down Expand Up @@ -53,6 +54,10 @@ const InlineElements = ({ contents }) =>
}
/>
);

case 'HIGHLIGHT_TEXT':
return <HighlightText key={index} contents={content.value} />;

default:
return null;
}
Expand Down
4 changes: 4 additions & 0 deletions packages/markups/src/elements/ItalicSpan.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import PlainSpan from './PlainSpan';
import BoldSpan from './BoldSpan';
import StrikeSpan from './StrikeSpan';
import HighlightText from './highlightText';

const ItalicSpan = ({ contents }) => (
<em>
Expand All @@ -17,6 +18,9 @@ const ItalicSpan = ({ contents }) => (
case 'BOLD':
return <BoldSpan key={index} contents={content.value} />;

case 'HIGHLIGHT_TEXT':
return <HighlightText key={index} contents={content.value} />;

default:
return null;
}
Expand Down
4 changes: 4 additions & 0 deletions packages/markups/src/elements/LinkSpan.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PlainSpan from './PlainSpan';
import StrikeSpan from './StrikeSpan';
import ItalicSpan from './ItalicSpan';
import BoldSpan from './BoldSpan';
import HighlightText from './highlightText';

const getBaseURI = () => {
if (document.baseURI) {
Expand Down Expand Up @@ -44,6 +45,9 @@ const LinkSpan = ({ href, label }) => {
case 'BOLD':
return <BoldSpan key={index} contents={content.value} />;

case 'HIGHLIGHT_TEXT':
return <HighlightText key={index} contents={content.value} />;

default:
return null;
}
Expand Down
4 changes: 4 additions & 0 deletions packages/markups/src/elements/StrikeSpan.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import PlainSpan from './PlainSpan';
import BoldSpan from './BoldSpan';
import ItalicSpan from './ItalicSpan';
import HighlightText from './highlightText';

const StrikeSpan = ({ contents }) => (
<del>
Expand All @@ -17,6 +18,9 @@ const StrikeSpan = ({ contents }) => (
case 'BOLD':
return <BoldSpan key={index} contents={content.value} />;

case 'HIGHLIGHT_TEXT':
return <HighlightText key={index} contents={content.value} />;

default:
return null;
}
Expand Down
15 changes: 14 additions & 1 deletion packages/markups/src/elements/elements.styles.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { css } from '@emotion/react';
import { useTheme, darken } from '@embeddedchat/ui-elements';
import { useTheme, darken, lighten } from '@embeddedchat/ui-elements';

export const InlineElementsStyles = () => {
const { theme } = useTheme();
Expand Down Expand Up @@ -81,6 +81,19 @@ export const EmojiStyles = {
`,
};

export const useHighlightTextStyles = (theme, mode) => {
const styles = {
highlight: css`
background-color: ${mode === 'light'
? lighten(theme.colors.warning, 0.25)
: darken(theme.colors.warningForeground, 0.3)};
font-weight: bold;
`,
};

return styles;
};

const useMentionStyles = (contents, username) => {
const { theme } = useTheme();
const styles = {
Expand Down
22 changes: 22 additions & 0 deletions packages/markups/src/elements/highlightText.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Box, useTheme } from '@embeddedchat/ui-elements';
import { useHighlightTextStyles } from './elements.styles';

const HighlightText = ({ contents }) => {
const { theme } = useTheme();
const { mode } = useTheme();
const styles = useHighlightTextStyles(theme, mode);

return (
<Box is="span" css={styles.highlight}>
{contents}{' '}
</Box>
);
};

HighlightText.propTypes = {
contents: PropTypes.string.isRequired,
};

export default HighlightText;
151 changes: 151 additions & 0 deletions packages/react/src/lib/highlightUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
const highlightText = (text, searchTerm) => {
const parts = text.split(new RegExp(`(${searchTerm})`, 'gi'));
const result = [];

parts.forEach((part) => {
if (part.toLowerCase() === searchTerm.toLowerCase()) {
result.push({ type: 'HIGHLIGHT_TEXT', value: part });
} else {
result.push({ type: 'PLAIN_TEXT', value: part });
}
});

return result;
};

export function highlightSearchTerm(messagesArr, searchedWords) {
const searchTerms = Array.isArray(searchedWords)
? searchedWords
: [searchedWords];

return messagesArr.map((message) => {
if (message.md) {
message.md = message.md.map((paragraphBlock) => {
if (paragraphBlock.type === 'PARAGRAPH') {
const updatedValue = paragraphBlock.value.reduce(
(accumulatedValue, content) => {
if (content.type === 'PLAIN_TEXT') {
let updatedContent = content.value;
searchTerms.forEach((searchTerm) => {
if (searchTerm && updatedContent) {
accumulatedValue.push(
...highlightText(updatedContent, searchTerm)
);
updatedContent = '';
}
});

if (updatedContent) {
accumulatedValue.push({
type: 'PLAIN_TEXT',
value: updatedContent,
});
}
} else if (content.type === 'LINK') {
const updatedLabel = content.value.label.reduce(
(labelAccumulatedValue, labelContent) => {
if (labelContent.type === 'PLAIN_TEXT') {
let updatedLabelContent = labelContent.value;
searchTerms.forEach((searchTerm) => {
if (searchTerm && updatedLabelContent) {
labelAccumulatedValue.push(
...highlightText(updatedLabelContent, searchTerm)
);
updatedLabelContent = '';
}
});

if (updatedLabelContent) {
labelAccumulatedValue.push({
type: 'PLAIN_TEXT',
value: updatedLabelContent,
});
}
} else {
labelAccumulatedValue.push(labelContent);
}

return labelAccumulatedValue;
},
[]
);

accumulatedValue.push({
...content,
value: {
...content.value,
label: updatedLabel,
},
});
} else if (content.type === 'INLINE_CODE') {
let updatedContent = content.value.value;
searchTerms.forEach((searchTerm) => {
if (searchTerm && updatedContent) {
accumulatedValue.push(
...highlightText(updatedContent, searchTerm)
);
updatedContent = '';
}
});

if (updatedContent) {
accumulatedValue.push({
type: 'INLINE_CODE',
value: { type: 'PLAIN_TEXT', value: updatedContent },
});
}
} else if (['STRIKE', 'BOLD', 'ITALIC'].includes(content.type)) {
const updatedContents = content.value.reduce(
(innerAccumulatedValue, innerContent) => {
if (innerContent.type === 'PLAIN_TEXT') {
let updatedContent = innerContent.value;
searchTerms.forEach((searchTerm) => {
if (searchTerm && updatedContent) {
innerAccumulatedValue.push(
...highlightText(updatedContent, searchTerm)
);
updatedContent = '';
}
});

if (updatedContent) {
innerAccumulatedValue.push({
type: 'PLAIN_TEXT',
value: updatedContent,
});
}
} else {
innerAccumulatedValue.push(innerContent);
}

return innerAccumulatedValue;
},
[]
);

accumulatedValue.push({
...content,
value: updatedContents,
});
} else {
accumulatedValue.push(content);
}

return accumulatedValue;
},
[]
);

return {
...paragraphBlock,
value: updatedValue,
};
}

return paragraphBlock;
});
}

return message;
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const SearchMessages = () => {
searchFiltered={messageList}
shouldRender={(msg) => !!msg}
viewType={viewType}
searchedText={text}
/>
);
};
Expand Down
Loading
Loading