mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 14:47:53 +01:00
Issue 12033 - Use version of habitica-markdown that includes mentions (#12089)
* Issue 12033 - Use version of habitica-markdown that includes mention plugin Also fixes frontend parts of 11504 and 10924 * Issue 12033 - Reduce duplication in chatCard & messageCard * Issue 12033 - Use habitica-markdown version 2.0.0 * Issue 12033 - Use new entry point and fix tests * Issue 12033 - Rename renderMarkdown to renderWithMentions
This commit is contained in:
6
package-lock.json
generated
6
package-lock.json
generated
@@ -7379,9 +7379,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"habitica-markdown": {
|
"habitica-markdown": {
|
||||||
"version": "1.4.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/habitica-markdown/-/habitica-markdown-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/habitica-markdown/-/habitica-markdown-2.0.0.tgz",
|
||||||
"integrity": "sha512-hklG3eBILNbx/VxGeRxuk+/RiWWllcd5QLNv7Kvm2wGBRTeK9c3my2eusGuHXkwStEFGxjJD5e0iMO47cGPxYw==",
|
"integrity": "sha512-70Kl/d7v1d2Rz6TFkQQ9hYcBYGAHnIPbRgS3PrW/dD/GGpN42q6gT3sCLsIpLqEXbN0EWjVscGs2qKWYLc6BMQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"habitica-markdown-emoji": "1.2.4",
|
"habitica-markdown-emoji": "1.2.4",
|
||||||
"markdown-it": "10.0.0",
|
"markdown-it": "10.0.0",
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
"gulp-imagemin": "^6.2.0",
|
"gulp-imagemin": "^6.2.0",
|
||||||
"gulp-nodemon": "^2.5.0",
|
"gulp-nodemon": "^2.5.0",
|
||||||
"gulp.spritesmith": "^6.9.0",
|
"gulp.spritesmith": "^6.9.0",
|
||||||
"habitica-markdown": "^1.4.0",
|
"habitica-markdown": "^2.0.0",
|
||||||
"helmet": "^3.22.0",
|
"helmet": "^3.22.0",
|
||||||
"image-size": "^0.8.3",
|
"image-size": "^0.8.3",
|
||||||
"in-app-purchase": "^1.11.3",
|
"in-app-purchase": "^1.11.3",
|
||||||
|
|||||||
6
website/client/package-lock.json
generated
6
website/client/package-lock.json
generated
@@ -11676,9 +11676,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"habitica-markdown": {
|
"habitica-markdown": {
|
||||||
"version": "1.4.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/habitica-markdown/-/habitica-markdown-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/habitica-markdown/-/habitica-markdown-2.0.0.tgz",
|
||||||
"integrity": "sha512-hklG3eBILNbx/VxGeRxuk+/RiWWllcd5QLNv7Kvm2wGBRTeK9c3my2eusGuHXkwStEFGxjJD5e0iMO47cGPxYw==",
|
"integrity": "sha512-70Kl/d7v1d2Rz6TFkQQ9hYcBYGAHnIPbRgS3PrW/dD/GGpN42q6gT3sCLsIpLqEXbN0EWjVscGs2qKWYLc6BMQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"habitica-markdown-emoji": "1.2.4",
|
"habitica-markdown-emoji": "1.2.4",
|
||||||
"markdown-it": "10.0.0",
|
"markdown-it": "10.0.0",
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
"eslint-config-habitrpg": "^6.2.0",
|
"eslint-config-habitrpg": "^6.2.0",
|
||||||
"eslint-plugin-mocha": "^5.3.0",
|
"eslint-plugin-mocha": "^5.3.0",
|
||||||
"eslint-plugin-vue": "^6.2.2",
|
"eslint-plugin-vue": "^6.2.2",
|
||||||
"habitica-markdown": "^1.4.0",
|
"habitica-markdown": "^2.0.0",
|
||||||
"hellojs": "^1.18.4",
|
"hellojs": "^1.18.4",
|
||||||
"inspectpack": "^4.4.0",
|
"inspectpack": "^4.4.0",
|
||||||
"intro.js": "^2.9.3",
|
"intro.js": "^2.9.3",
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
<div
|
<div
|
||||||
ref="markdownContainer"
|
ref="markdownContainer"
|
||||||
class="text"
|
class="text"
|
||||||
v-html="atHighlight(parseMarkdown(msg.text))"
|
v-html="parseMarkdown(msg.text)"
|
||||||
></div>
|
></div>
|
||||||
<hr>
|
<hr>
|
||||||
<div
|
<div
|
||||||
@@ -201,7 +201,7 @@ import moment from 'moment';
|
|||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import escapeRegExp from 'lodash/escapeRegExp';
|
import escapeRegExp from 'lodash/escapeRegExp';
|
||||||
|
|
||||||
import habiticaMarkdown from 'habitica-markdown';
|
import renderWithMentions from '@/libs/renderWithMentions';
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import userLink from '../userLink';
|
import userLink from '../userLink';
|
||||||
|
|
||||||
@@ -210,7 +210,6 @@ import copyIcon from '@/assets/svg/copy.svg';
|
|||||||
import likeIcon from '@/assets/svg/like.svg';
|
import likeIcon from '@/assets/svg/like.svg';
|
||||||
import likedIcon from '@/assets/svg/liked.svg';
|
import likedIcon from '@/assets/svg/liked.svg';
|
||||||
import reportIcon from '@/assets/svg/report.svg';
|
import reportIcon from '@/assets/svg/report.svg';
|
||||||
import { highlightUsers } from '../../libs/highlightUsers';
|
|
||||||
import { CHAT_FLAG_LIMIT_FOR_HIDING, CHAT_FLAG_FROM_SHADOW_MUTE } from '@/../../common/script/constants';
|
import { CHAT_FLAG_LIMIT_FOR_HIDING, CHAT_FLAG_FROM_SHADOW_MUTE } from '@/../../common/script/constants';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -341,12 +340,8 @@ export default {
|
|||||||
chatId: message.id,
|
chatId: message.id,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
atHighlight (text) {
|
|
||||||
return highlightUsers(text, this.user.auth.local.username, this.user.profile.name);
|
|
||||||
},
|
|
||||||
parseMarkdown (text) {
|
parseMarkdown (text) {
|
||||||
if (!text) return null;
|
return renderWithMentions(text, this.user);
|
||||||
return habiticaMarkdown.render(String(text));
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<div
|
<div
|
||||||
class="text"
|
class="text"
|
||||||
v-html="atHighlight(parseMarkdown(msg.text))"
|
v-html="parseMarkdown(msg.text)"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
v-if="isMessageReported"
|
v-if="isMessageReported"
|
||||||
@@ -139,13 +139,12 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
|
||||||
import habiticaMarkdown from 'habitica-markdown';
|
import renderWithMentions from '@/libs/renderWithMentions';
|
||||||
import { mapState } from '@/libs/store';
|
import { mapState } from '@/libs/store';
|
||||||
import userLink from '../userLink';
|
import userLink from '../userLink';
|
||||||
|
|
||||||
import deleteIcon from '@/assets/svg/delete.svg';
|
import deleteIcon from '@/assets/svg/delete.svg';
|
||||||
import reportIcon from '@/assets/svg/report.svg';
|
import reportIcon from '@/assets/svg/report.svg';
|
||||||
import { highlightUsers } from '../../libs/highlightUsers';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -204,12 +203,8 @@ export default {
|
|||||||
|
|
||||||
await axios.delete(`/api/v4/inbox/messages/${message.id}`);
|
await axios.delete(`/api/v4/inbox/messages/${message.id}`);
|
||||||
},
|
},
|
||||||
atHighlight (text) {
|
|
||||||
return highlightUsers(text, this.user.auth.local.username, this.user.profile.name);
|
|
||||||
},
|
|
||||||
parseMarkdown (text) {
|
parseMarkdown (text) {
|
||||||
if (!text) return null;
|
return renderWithMentions(text, this.user);
|
||||||
return habiticaMarkdown.render(String(text));
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import escapeRegExp from 'lodash/escapeRegExp';
|
|
||||||
|
|
||||||
const optionalAnchorTagRegExStr = '(<\\w[^>]*)?'; // everything including the anchor tag is recognized
|
|
||||||
const mentionRegExStr = '(@(?:<(?:em|strong)>)?[\\w-]+(?:<\\/(?:em|strong)>)?)';
|
|
||||||
const optionalPostMentionRegExStr = '(\\.\\w+)?'; // like dot-TLD
|
|
||||||
|
|
||||||
const finalMentionRegEx = new RegExp(`${optionalAnchorTagRegExStr}${mentionRegExStr}${optionalPostMentionRegExStr}`, 'gi');
|
|
||||||
|
|
||||||
export function highlightUsers (text, userName, displayName) { // eslint-disable-line import/prefer-default-export, max-len
|
|
||||||
const currentUser = [`@${userName}`, `@${displayName}`].map(escapeRegExp);
|
|
||||||
|
|
||||||
text = text.replace(finalMentionRegEx, (fullMatched, preMention, mentionStr, postMention) => { // eslint-disable-line no-param-reassign, max-len
|
|
||||||
if ((preMention && preMention.includes('<a')) || Boolean(postMention)) {
|
|
||||||
return fullMatched;
|
|
||||||
}
|
|
||||||
|
|
||||||
let fixedStr = mentionStr.replace(/<\/?em>/g, '_');
|
|
||||||
fixedStr = fixedStr.replace(/<\/?strong>/g, '__');
|
|
||||||
|
|
||||||
const isUserMention = currentUser.includes(fixedStr) ? 'at-highlight' : '';
|
|
||||||
|
|
||||||
return fullMatched.replace(mentionStr, `<span class="at-text ${isUserMention}">${fixedStr}</span>`);
|
|
||||||
});
|
|
||||||
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
7
website/client/src/libs/renderWithMentions.js
Normal file
7
website/client/src/libs/renderWithMentions.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import habiticaMarkdown from 'habitica-markdown/withMentions';
|
||||||
|
|
||||||
|
export default function renderWithMentions (text, user) {
|
||||||
|
if (!text) return null;
|
||||||
|
const env = { userName: user.auth.local.username, displayName: user.profile.name };
|
||||||
|
return habiticaMarkdown.render(String(text), env);
|
||||||
|
}
|
||||||
@@ -1,11 +1,20 @@
|
|||||||
import habiticaMarkdown from 'habitica-markdown';
|
import renderMarkdown from '@/libs/renderWithMentions';
|
||||||
import { highlightUsers } from '@/libs/highlightUsers';
|
|
||||||
|
describe('renderWithMentions', () => {
|
||||||
|
function user (name, displayName) {
|
||||||
|
return { auth: { local: { username: name } }, profile: { name: displayName } };
|
||||||
|
}
|
||||||
|
|
||||||
|
it('returns null if no text supplied', () => {
|
||||||
|
const result = renderMarkdown('', user('a', 'b'));
|
||||||
|
|
||||||
|
expect(result).to.be.null;
|
||||||
|
});
|
||||||
|
|
||||||
describe('highlightUserAndEmail', () => {
|
|
||||||
it('highlights displayname', () => {
|
it('highlights displayname', () => {
|
||||||
const text = 'hello @displayedUser with text after';
|
const text = 'hello @displayedUser with text after';
|
||||||
|
|
||||||
const result = highlightUsers(text, 'user', 'displayedUser');
|
const result = renderMarkdown(text, user('user', 'displayedUser'));
|
||||||
|
|
||||||
expect(result).to.contain('<span class="at-text at-highlight">@displayedUser</span>');
|
expect(result).to.contain('<span class="at-text at-highlight">@displayedUser</span>');
|
||||||
});
|
});
|
||||||
@@ -13,45 +22,42 @@ describe('highlightUserAndEmail', () => {
|
|||||||
it('highlights username', () => {
|
it('highlights username', () => {
|
||||||
const text = 'hello @user';
|
const text = 'hello @user';
|
||||||
|
|
||||||
const result = highlightUsers(text, 'user', 'displayedUser');
|
const result = renderMarkdown(text, user('user', 'displayedUser'));
|
||||||
expect(result).to.contain('<span class="at-text at-highlight">@user</span>');
|
expect(result).to.contain('<span class="at-text at-highlight">@user</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('highlights username sandwiched with underscores', () => {
|
it('highlights username sandwiched with underscores', () => {
|
||||||
const text = 'hello @<em>user</em>';
|
const text = 'hello @_user_';
|
||||||
|
|
||||||
const result = highlightUsers(text, '_user_', 'displayedUser');
|
const result = renderMarkdown(text, user('_user_', 'displayedUser'));
|
||||||
expect(result).to.contain('<span class="at-text at-highlight">@_user_</span>');
|
expect(result).to.contain('<span class="at-text at-highlight">@_user_</span>');
|
||||||
expect(result).to.not.contain('<em>');
|
expect(result).to.not.contain('<em>');
|
||||||
expect(result).to.not.contain('</em>');
|
expect(result).to.not.contain('</em>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('highlights username sandwiched with double underscores', () => {
|
it('highlights username sandwiched with double underscores', () => {
|
||||||
const text = 'hello @<strong>user</strong>';
|
const text = 'hello @__user__';
|
||||||
|
|
||||||
const result = highlightUsers(text, 'diffUser', 'displayDiffUser');
|
const result = renderMarkdown(text, user('diffUser', 'displayDiffUser'));
|
||||||
expect(result).to.contain('<span class="at-text ">@__user__</span>');
|
expect(result).to.contain('<span class="at-text">@__user__</span>');
|
||||||
expect(result).to.not.contain('<strong>');
|
expect(result).to.not.contain('<strong>');
|
||||||
expect(result).to.not.contain('</strong>');
|
expect(result).to.not.contain('</strong>');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('not highlights any email', () => {
|
it('not highlights any email', () => {
|
||||||
const text = habiticaMarkdown.render('hello@example.com');
|
const result = renderMarkdown('hello@example.com', user('example', 'displayedUser'));
|
||||||
|
|
||||||
const result = highlightUsers(text, 'example', 'displayedUser');
|
|
||||||
expect(result).to.not.contain('<span class="at-highlight">@example</span>');
|
expect(result).to.not.contain('<span class="at-highlight">@example</span>');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('complex highlight', () => {
|
it('complex highlight', () => {
|
||||||
const plainText = 'a bit more @mentions to @use my@mentions.com broken.@mail.com';
|
const plainText = 'a bit more @mentions to @use my@mentions.com broken @mail.com';
|
||||||
|
|
||||||
const text = habiticaMarkdown.render(plainText);
|
const result = renderMarkdown(plainText, user('use', 'mentions'));
|
||||||
|
|
||||||
const result = highlightUsers(text, 'use', 'mentions');
|
|
||||||
|
|
||||||
expect(result).to.contain('<span class="at-text at-highlight">@mentions</span>');
|
expect(result).to.contain('<span class="at-text at-highlight">@mentions</span>');
|
||||||
expect(result).to.contain('<span class="at-text at-highlight">@use</span>');
|
expect(result).to.contain('<span class="at-text at-highlight">@use</span>');
|
||||||
|
expect(result).to.contain('<span class="at-text">@mail</span>');
|
||||||
expect(result).to.not.contain('<span class="at-text at-highlight">@mentions</span>.com');
|
expect(result).to.not.contain('<span class="at-text at-highlight">@mentions</span>.com');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
Reference in New Issue
Block a user