mirror of
https://github.com/wekan/wekan.git
synced 2025-12-16 23:40:13 +01:00
Merge pull request #2611 from whowillcare/master
Addfeature: Enable HTML email content for richer comment
This commit is contained in:
commit
b9a7bd3503
3 changed files with 37 additions and 18 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
import _sanitizeXss from 'xss';
|
import _sanitizeXss from 'xss';
|
||||||
|
const ASIS = 'asis';
|
||||||
const sanitizeXss = (input, options) => {
|
const sanitizeXss = (input, options) => {
|
||||||
const defaultAllowedIframeSrc = /^(https:){0,1}\/\/.*?(youtube|vimeo|dailymotion|youku)/i;
|
const defaultAllowedIframeSrc = /^(https:){0,1}\/\/.*?(youtube|vimeo|dailymotion|youku)/i;
|
||||||
const allowedIframeSrcRegex = (function() {
|
const allowedIframeSrcRegex = (function() {
|
||||||
|
|
@ -17,28 +18,39 @@ const sanitizeXss = (input, options) => {
|
||||||
return reg;
|
return reg;
|
||||||
})();
|
})();
|
||||||
const targetWindow = '_blank';
|
const targetWindow = '_blank';
|
||||||
|
const getHtmlDOM = html => {
|
||||||
|
const i = document.createElement('i');
|
||||||
|
i.innerHTML = html;
|
||||||
|
return i.firstChild;
|
||||||
|
};
|
||||||
options = {
|
options = {
|
||||||
onTag(tag, html, options) {
|
onTag(tag, html, options) {
|
||||||
|
const htmlDOM = getHtmlDOM(html);
|
||||||
|
const getAttr = attr => {
|
||||||
|
return htmlDOM && attr && htmlDOM.getAttribute(attr);
|
||||||
|
};
|
||||||
if (tag === 'iframe') {
|
if (tag === 'iframe') {
|
||||||
const clipCls = 'note-vide-clip';
|
const clipCls = 'note-vide-clip';
|
||||||
if (!options.isClosing) {
|
if (!options.isClosing) {
|
||||||
const srcp = /src=(['"]{0,1})(\S*)(\1)/;
|
const iframeCls = getAttr('class');
|
||||||
let safe = html.indexOf(`class="${clipCls}"`) > -1;
|
let safe = iframeCls.indexOf(clipCls) > -1;
|
||||||
if (srcp.exec(html)) {
|
const src = getAttr('src');
|
||||||
const src = RegExp.$2;
|
if (allowedIframeSrcRegex.exec(src)) {
|
||||||
if (allowedIframeSrcRegex.exec(src)) {
|
safe = true;
|
||||||
safe = true;
|
|
||||||
}
|
|
||||||
if (safe)
|
|
||||||
return `<iframe src='${src}' class="${clipCls}" width=100% height=auto allowfullscreen></iframe>`;
|
|
||||||
}
|
}
|
||||||
|
if (safe)
|
||||||
|
return `<iframe src='${src}' class="${clipCls}" width=100% height=auto allowfullscreen></iframe>`;
|
||||||
} else {
|
} else {
|
||||||
|
// remove </iframe> tag
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
} else if (tag === 'a') {
|
} else if (tag === 'a') {
|
||||||
if (!options.isClosing) {
|
if (!options.isClosing) {
|
||||||
if (/href=(['"]{0,1})(\S*)(\1)/.exec(html)) {
|
if (getAttr(ASIS) === 'true') {
|
||||||
const href = RegExp.$2;
|
// if has a ASIS attribute, don't do anything, it's a member id
|
||||||
|
return html;
|
||||||
|
} else {
|
||||||
|
const href = getAttr('href');
|
||||||
if (href.match(/^((http(s){0,1}:){0,1}\/\/|\/)/)) {
|
if (href.match(/^((http(s){0,1}:){0,1}\/\/|\/)/)) {
|
||||||
// a valid url
|
// a valid url
|
||||||
return `<a href=${href} target=${targetWindow}>`;
|
return `<a href=${href} target=${targetWindow}>`;
|
||||||
|
|
@ -47,8 +59,8 @@ const sanitizeXss = (input, options) => {
|
||||||
}
|
}
|
||||||
} else if (tag === 'img') {
|
} else if (tag === 'img') {
|
||||||
if (!options.isClosing) {
|
if (!options.isClosing) {
|
||||||
if (new RegExp('src=([\'"]{0,1})(\\S*)(\\1)').exec(html)) {
|
const src = getAttr('src');
|
||||||
const src = RegExp.$2;
|
if (src) {
|
||||||
return `<a href='${src}' class='swipebox'><img src='${src}' class="attachment-image-preview mCS_img_loaded"></a>`;
|
return `<a href='${src}' class='swipebox'><img src='${src}' class="attachment-image-preview mCS_img_loaded"></a>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -203,7 +215,9 @@ Template.editor.onRendered(() => {
|
||||||
// even though uploaded event fired, attachment.url() is still null somehow //TODO
|
// even though uploaded event fired, attachment.url() is still null somehow //TODO
|
||||||
const url = attachment.url();
|
const url = attachment.url();
|
||||||
if (url) {
|
if (url) {
|
||||||
insertImage(url);
|
insertImage(
|
||||||
|
`${location.protocol}//${location.host}${url}`,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
retry++;
|
retry++;
|
||||||
if (retry < maxTry) {
|
if (retry < maxTry) {
|
||||||
|
|
@ -334,6 +348,7 @@ Blaze.Template.registerHelper(
|
||||||
// `userId` to the popup as usual, and we need to store it in the DOM
|
// `userId` to the popup as usual, and we need to store it in the DOM
|
||||||
// using a data attribute.
|
// using a data attribute.
|
||||||
'data-userId': knowedUser.userId,
|
'data-userId': knowedUser.userId,
|
||||||
|
[ASIS]: 'true',
|
||||||
},
|
},
|
||||||
linkValue,
|
linkValue,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,9 @@ if (Meteor.isServer) {
|
||||||
if (activity.userId) {
|
if (activity.userId) {
|
||||||
// No need send notification to user of activity
|
// No need send notification to user of activity
|
||||||
// participants = _.union(participants, [activity.userId]);
|
// participants = _.union(participants, [activity.userId]);
|
||||||
params.user = activity.user().getName();
|
const user = activity.user();
|
||||||
|
params.user = user.getName();
|
||||||
|
params.userEmails = user.emails;
|
||||||
params.userId = activity.userId;
|
params.userId = activity.userId;
|
||||||
}
|
}
|
||||||
if (activity.boardId) {
|
if (activity.boardId) {
|
||||||
|
|
@ -172,7 +174,7 @@ if (Meteor.isServer) {
|
||||||
const comment = activity.comment();
|
const comment = activity.comment();
|
||||||
params.comment = comment.text;
|
params.comment = comment.text;
|
||||||
if (board) {
|
if (board) {
|
||||||
const atUser = /(?:^|\s+)@(\S+)(?:\s+|$)/g;
|
const atUser = /(?:^|>|\b|\s)@(\S+)(?:\s|$|<|\b)/g;
|
||||||
const comment = params.comment;
|
const comment = params.comment;
|
||||||
if (comment.match(atUser)) {
|
if (comment.match(atUser)) {
|
||||||
const commenter = params.user;
|
const commenter = params.user;
|
||||||
|
|
@ -184,6 +186,8 @@ if (Meteor.isServer) {
|
||||||
}
|
}
|
||||||
const user = Users.findOne(username) || Users.findOne({ username });
|
const user = Users.findOne(username) || Users.findOne({ username });
|
||||||
const uid = user && user._id;
|
const uid = user && user._id;
|
||||||
|
params.atUsername = username;
|
||||||
|
params.atEmails = user.emails;
|
||||||
if (board.hasMember(uid)) {
|
if (board.hasMember(uid)) {
|
||||||
title = 'act-atUserComment';
|
title = 'act-atUserComment';
|
||||||
watchers = _.union(watchers, [uid]);
|
watchers = _.union(watchers, [uid]);
|
||||||
|
|
|
||||||
|
|
@ -32,14 +32,14 @@ Meteor.startup(() => {
|
||||||
if (texts.length === 0) return;
|
if (texts.length === 0) return;
|
||||||
|
|
||||||
// merge the cached content into single email and flush
|
// merge the cached content into single email and flush
|
||||||
const text = texts.join('\n\n');
|
const html = texts.join('<br/>\n\n');
|
||||||
user.clearEmailBuffer();
|
user.clearEmailBuffer();
|
||||||
try {
|
try {
|
||||||
Email.send({
|
Email.send({
|
||||||
to: user.emails[0].address.toLowerCase(),
|
to: user.emails[0].address.toLowerCase(),
|
||||||
from: Accounts.emailTemplates.from,
|
from: Accounts.emailTemplates.from,
|
||||||
subject,
|
subject,
|
||||||
text,
|
html,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue