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

refactor(TextFormatter): create TextFormatter component TASK-996 #5257

Closed
wants to merge 1 commit into from
Closed
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
56 changes: 56 additions & 0 deletions jsapp/js/components/textFormatter/textFormatter.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type {ReactNode} from 'react';

const regExpMatchParts = /[^*[\]]+|(\*\*[^*]*(?:\*[^*]+)*\*\*)|(\*[^*]+\*)|(\[.+?\]\(.+?\)({:.+})?)/g;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've used react-markdown before and suggest it. I like most the ability to override all components with custom implementation, that's useful especially with Link-s.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the need here was a simple subset of what markdown does I opted not to use a full library, not at first at least, so we wouldn't have to worry about unwanted markdown styles being accidentally inserted in string and also implementing it simply would avoid having another library linked to the project, but after talking to @Akuukis about this I ended up agreeing that using a library would simplify things here, mainly because there are libraries where we can opt for which marks we want to implement.


const processText = (text: string): ReactNode[] => {
const parts = text.match(regExpMatchParts);

if (!parts) {return [text];}

const formattedParts: ReactNode[] = [];

for (const part of parts) {
if (part.startsWith('**')) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a style issue, but we generally like to avoid if/else chains in favor of multiple simple if statements:

if (x) {
    return a
}
if (y) {
    return b
}
return c

formattedParts.push(<strong>{processText(part.slice(2, -2))}</strong>);
} else if (part.startsWith('*')) {
formattedParts.push(<em>{processText(part.slice(1, -1))}</em>);
} else if (part.startsWith('[')) {
const [label, link, target] = part
.slice(1, -1)
.split(/\]\(|\)\{:target=/);
formattedParts.push(
<a href={link} target={target ? target.slice(1, -1) : '_self'}>
{processText(label)}
</a>
);
} else {
formattedParts.push(part);
}
}

return formattedParts;
};

interface TextFormatterProps {
/**
* Text will be processed and formatted with markdown-like syntax. It accepts the following:
* - *<text>* for italic
* - **<text>** for bold
* - [text](link) for links with target _self
* - [text](link){:target="_blank"} for links with target _blank or other target
* - Formatting can be nested to be combined
*/
text: string;
}

/**
* This component process text and applies simple formatting rules with markdown-like syntax.
* This is meant to be used with long sentences that need formatting and cannot be broken down
* into smaller components duo to translation issues that broken down components would cause.
* The formatting options mimics markdown syntax for italic, bold and links.
*
* @returns ReactNode[]
*/
export default function TextFormatter({text}: TextFormatterProps) {
return processText(text);
}
22 changes: 22 additions & 0 deletions jsapp/js/components/textFormatter/textFormatter.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type {Meta, StoryFn} from '@storybook/react';
import TextFormatter from './textFormatter.component';

export default {
title: 'misc/TextFormatter',
component: TextFormatter,
argTypes: {
text: {
description: 'Text containing markdown-like syntax for formatting. Accepts italic, bold and links with and without target indication. Formatting can be nested to be combined.',
control: 'text',
},
},
} as Meta<typeof TextFormatter>;

const Template: StoryFn<typeof TextFormatter> = (args) => (
<TextFormatter {...args} />
);

export const Primary = Template.bind({});
Primary.args = {
text: 'Formatted text with **bold** and *italic* and [link](https://www.kobotoolbox.org/pricing/){:target="_blank"}. Formatting can be **bold and *italic* combined**, [also **on** *links*](http://kobotoolbox.org).',
};
Loading