Skip to content
This repository has been archived by the owner on Apr 9, 2023. It is now read-only.

Commit

Permalink
feature: add highlights to user description (#55)
Browse files Browse the repository at this point in the history
* feature: add highlight atom with customisable decorators

* feature: implement highlight in user description for hashtags and mentions

* fix: add support for hashtags in github mentions

* refactor: rename github mentions to keywords

This matches the purpose a bit more, since its being used for page keywords.
  • Loading branch information
byCedric authored Oct 17, 2018
1 parent 34686cd commit f72a007
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 8 deletions.
51 changes: 51 additions & 0 deletions src/atoms/highlight/decorator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Create a decorator pattern based on starting characters.
*
* @param {string[]}
* @return {RegExp}
*/
export const createPattern = (chars) => (
new RegExp(`([${chars.join('')}][a-z0-9-_]+)`, 'gi')
);

/**
* Create a list of segments relevant to the decorators.
*
* @param {string} text
* @param {Object} decorators
* @return {string[]}
*/
export const createSegments = (text, decorators) => (
text.split(createPattern(Object.keys(decorators)))
);

/**
* Apply one of the decorators to a single segment.
* It will invoke a decorator with the matched segment, inner text, and an optional key.
*
* @param {string} segment
* @param {Object} decorators
* @param {string|number} [key]
* @return {string|ReactNode}
*/
export const applyDecorator = (segment, decorators, key = undefined) => {
const decorator = decorators[segment.charAt(0)];

return decorator
? decorator(segment, segment.substr(1), key)
: segment;
};

/**
* Decorate a string and return a list of text or components.
* These can be rendered directly with React.
*
* @param {string} text
* @param {Object} decorators
* @return {(string|ReactNode)[]}
*/
export default (text, decorators) => (
createSegments(text, decorators).map(
(segment, key) => applyDecorator(segment, decorators, key)
)
);
9 changes: 9 additions & 0 deletions src/atoms/highlight/elements.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import styled from 'styled-components';

/**
* The highlight container groups the content using a paragraph.
* It is used to mark the content as readable text only.
*/
export const HighlightContainer = styled.p`
margin: 0;
`;
19 changes: 19 additions & 0 deletions src/atoms/highlight/highlight.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';
import decorator from './decorator';
import { HighlightContainer } from './elements';

export default function HighlightAtom(props) {
return (
<HighlightContainer>
{decorator(props.children, props.decorators)}
</HighlightContainer>
);
}

HighlightAtom.propTypes = {
/** The text to highlight with the decorators. */
children: PropTypes.string.isRequired,
/** All decorators with their starting character. */
decorators: PropTypes.object.isRequired,
};
1 change: 1 addition & 0 deletions src/atoms/highlight/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './highlight';
10 changes: 9 additions & 1 deletion src/molecules/user/elements.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const UserName = styled.h1`
* Because of this flexbox positioning, the size of the text has no impact on the alignment.
* This technique is also applied for the avatar to center the name correctly.
*/
export const UserDescription = styled.p`
export const UserDescription = styled.div`
display: flex;
flex: 1;
align-items: flex-start;
Expand All @@ -54,3 +54,11 @@ export const UserDescription = styled.p`
text-align: center;
line-height: 1.5;
`;

/**
* The user description highlight styles a single word within this description.
*/
export const UserDescriptionHighlight = styled.strong`
color: #24292e;
font-weight: 600;
`;
4 changes: 2 additions & 2 deletions src/molecules/user/user-meta.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import Helmet from 'react-helmet';
import { findMentions } from 'src/providers/github';
import { findKeywords } from 'src/providers/github';
import { propTypes, defaultProps } from './prop-type';

export default function UserMoleculeMeta(props) {
Expand All @@ -9,7 +9,7 @@ export default function UserMoleculeMeta(props) {
: `${props.name} (${props.username})`;

const keywords = props.description
? [props.name, props.username].concat(findMentions(props.description))
? [props.name, props.username].concat(findKeywords(props.description))
: [props.name, props.username];

const nameSegments = props.name.split(' ');
Expand Down
11 changes: 10 additions & 1 deletion src/molecules/user/user.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import React from 'react';
import Avatar from 'src/atoms/avatar';
import Highlight from 'src/atoms/highlight';
import { propTypes, defaultProps } from './prop-type';
import UserMeta from './user-meta';
import {
UserContainer,
UserAvatar,
UserName,
UserDescription,
UserDescriptionHighlight,
} from './elements';

const decorators = {
'@': (segment, match, key) => <UserDescriptionHighlight key={key}>{segment}</UserDescriptionHighlight>,
'#': (segment, match, key) => <UserDescriptionHighlight key={key}>{match}</UserDescriptionHighlight>,
};

export default function UserMolecule(props) {
const identifier = `${props.name} (${props.username})`;

Expand All @@ -26,7 +33,9 @@ export default function UserMolecule(props) {
{props.name}
</UserName>
<UserDescription>
{props.description}
<Highlight decorators={decorators}>
{props.description}
</Highlight>
</UserDescription>
</UserContainer>
);
Expand Down
8 changes: 4 additions & 4 deletions src/providers/github.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ export const fetchUser = () => (
export const AsyncUser = createInstance({ promiseFn: fetchUser });

/**
* Get all GitHub mentions from a text.
* This will try and find all words starting with `@` and return them as array, without extra character.
* Get all (GitHub) keywords from a text.
* It will find all words starting with `@` or `#` and return then without this character.
*
* @param {string?} text
* @return {string[]}
*/
export const findMentions = (text) => (
(text || '').match(/@([a-z0-9]+)/gi).map(value => value.substr(1))
export const findKeywords = (text) => (
(text || '').match(/[@#]([a-z0-9]+)/gi).map(value => value.substr(1))
);

0 comments on commit f72a007

Please sign in to comment.