mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-30 02:56:20 -05:00
Decorate comment text to add @username links and scratch-domain links
This commit is contained in:
parent
ecb497b30f
commit
9066686c2b
3 changed files with 98 additions and 30 deletions
|
@ -5,26 +5,71 @@ const reactStringReplace = require('react-string-replace');
|
||||||
* Helper method that replaces @mentions and #hashtags in plain text
|
* Helper method that replaces @mentions and #hashtags in plain text
|
||||||
*
|
*
|
||||||
* @param {string} text string to convert
|
* @param {string} text string to convert
|
||||||
* @return {string} string with links for @mentions and #hashtags
|
* @param {?object} opts options object of boolean flags, defaults to all true
|
||||||
|
* @property {boolean} opts.hashtag If #hashtags should be converted to search links
|
||||||
|
* @property {boolean} opts.usernames If @usernames should be converted to /users/username links
|
||||||
|
* @property {boolean} opts.scratchLinks If scratch-domain links should be converted to <a> links
|
||||||
|
* @return {Array} Array with strings and react components for links
|
||||||
*/
|
*/
|
||||||
module.exports = text => {
|
module.exports = (text, opts) => {
|
||||||
let replacedText;
|
opts = opts || {
|
||||||
|
usernames: true,
|
||||||
|
hashtags: true,
|
||||||
|
scratchLinks: true
|
||||||
|
};
|
||||||
|
|
||||||
|
let replacedText = [text];
|
||||||
|
|
||||||
// Match @-mentions (username is alphanumeric, underscore and dash)
|
// Match @-mentions (username is alphanumeric, underscore and dash)
|
||||||
replacedText = reactStringReplace(text, /@([\w-]+)/g, (match, i) => (
|
if (opts.usernames) {
|
||||||
|
replacedText = reactStringReplace(replacedText, /@([\w-]+)/g, (match, i) => (
|
||||||
<a
|
<a
|
||||||
href={`/users/${match}`}
|
href={`/users/${match}`}
|
||||||
key={match + i}
|
key={match + i}
|
||||||
>@{match}</a>
|
>@{match}</a>
|
||||||
));
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// Match hashtags
|
// Match hashtags
|
||||||
|
if (opts.hashtags) {
|
||||||
replacedText = reactStringReplace(replacedText, /(#[\w-]+)/g, (match, i) => (
|
replacedText = reactStringReplace(replacedText, /(#[\w-]+)/g, (match, i) => (
|
||||||
<a
|
<a
|
||||||
href={`/search/projects?q=${match}`}
|
href={`/search/projects?q=${match}`}
|
||||||
key={match + i}
|
key={match + i}
|
||||||
>{match}</a>
|
>{match}</a>
|
||||||
));
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match scratch links
|
||||||
|
/*
|
||||||
|
Ported from the python...
|
||||||
|
"Oh boy a giant regex!" Said nobody ever.
|
||||||
|
(^|\s)(https?://(?:[\w-]+\.)*scratch\.mit\.edu(?:/(?:\S*[\w:/#[\]@\$&\'()*+=])?)?(?![^?!,:;\w\s]\S))
|
||||||
|
(^|\s)
|
||||||
|
Only begin capturing after a space, or at the beginning of a word
|
||||||
|
https?
|
||||||
|
URLs beginning with http or https
|
||||||
|
://(?:[\w-]+\.)*scratch\.mit\.edu
|
||||||
|
allow *.scratch.mit.edu urls
|
||||||
|
(?:/...)?
|
||||||
|
optionally followed by a slash
|
||||||
|
(?:\S*[\w:/#[\]@\$&\'()*+=])?
|
||||||
|
optionally that slash is followed by anything that's not a space, until
|
||||||
|
that string is followed by URL-valid characters that aren't punctuation
|
||||||
|
(?![^?!,:;\w\s]\S))
|
||||||
|
Don't capture if this string is embedded in another string (e.g., the
|
||||||
|
beginning of a non-scratch URL), but allow punctuation
|
||||||
|
*/
|
||||||
|
if (opts.scratchLinks) {
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
const linkRegexp = /((?:^|\s)https?:\/\/(?:[\w-]+\.)*(?:scratch\.mit\.edu|scratch-wiki\.info)(?:\/(?:\S*[\w:/#[\]@$&'()*+=])?)?(?![^?!,:;\w\s]\S))/g;
|
||||||
|
replacedText = reactStringReplace(replacedText, linkRegexp, (match, i) => (
|
||||||
|
<a
|
||||||
|
href={match}
|
||||||
|
key={match + i}
|
||||||
|
>{match}</a>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
return replacedText;
|
return replacedText;
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,6 +11,7 @@ const FormattedMessage = require('react-intl').FormattedMessage;
|
||||||
const ComposeComment = require('./compose-comment.jsx');
|
const ComposeComment = require('./compose-comment.jsx');
|
||||||
const DeleteCommentModal = require('../../../components/modal/comments/delete-comment.jsx');
|
const DeleteCommentModal = require('../../../components/modal/comments/delete-comment.jsx');
|
||||||
const ReportCommentModal = require('../../../components/modal/comments/report-comment.jsx');
|
const ReportCommentModal = require('../../../components/modal/comments/report-comment.jsx');
|
||||||
|
const decorateText = require('../../../lib/decorate-text.jsx');
|
||||||
|
|
||||||
require('./comment.scss');
|
require('./comment.scss');
|
||||||
|
|
||||||
|
@ -101,6 +102,16 @@ class Comment extends React.Component {
|
||||||
|
|
||||||
const visible = visibility === 'visible';
|
const visible = visibility === 'visible';
|
||||||
|
|
||||||
|
let commentText = content;
|
||||||
|
if (replyUsername) {
|
||||||
|
commentText = `@${replyUsername} ${commentText}`;
|
||||||
|
}
|
||||||
|
commentText = decorateText(commentText, {
|
||||||
|
scratchLinks: true,
|
||||||
|
usernames: true,
|
||||||
|
hashtags: false
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex-row comment"
|
className="flex-row comment"
|
||||||
|
@ -167,13 +178,17 @@ class Comment extends React.Component {
|
||||||
*/}
|
*/}
|
||||||
|
|
||||||
<span className="comment-content">
|
<span className="comment-content">
|
||||||
{replyUsername && (
|
{commentText.map(fragment => {
|
||||||
<a href={`/users/${replyUsername}`}>@{replyUsername} </a>
|
if (typeof fragment === 'string') {
|
||||||
)}
|
return (
|
||||||
<EmojiText
|
<EmojiText
|
||||||
as="span"
|
as="span"
|
||||||
text={content}
|
text={fragment}
|
||||||
/>
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return fragment;
|
||||||
|
})}
|
||||||
</span>
|
</span>
|
||||||
<FlexRow className="comment-bottom-row">
|
<FlexRow className="comment-bottom-row">
|
||||||
<span className="comment-time">
|
<span className="comment-time">
|
||||||
|
|
|
@ -238,7 +238,11 @@ const PreviewPresentation = ({
|
||||||
value={projectInfo.instructions}
|
value={projectInfo.instructions}
|
||||||
/> :
|
/> :
|
||||||
<div className="project-description">
|
<div className="project-description">
|
||||||
{decorateText(projectInfo.instructions)}
|
{decorateText(projectInfo.instructions, {
|
||||||
|
usernames: true,
|
||||||
|
hashtags: true,
|
||||||
|
scratchLinks: false
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</FlexRow>
|
</FlexRow>
|
||||||
|
@ -270,7 +274,11 @@ const PreviewPresentation = ({
|
||||||
value={projectInfo.description}
|
value={projectInfo.description}
|
||||||
/> :
|
/> :
|
||||||
<div className="project-description last">
|
<div className="project-description last">
|
||||||
{decorateText(projectInfo.description)}
|
{decorateText(projectInfo.description, {
|
||||||
|
usernames: true,
|
||||||
|
hashtags: true,
|
||||||
|
scratchLinks: false
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</FlexRow>
|
</FlexRow>
|
||||||
|
|
Loading…
Reference in a new issue