Add Feature: Comments can be richer (can support some safe HTML tags)

This commit is contained in:
Sam X. Chen 2019-07-22 13:53:37 -04:00
parent f06098f3dd
commit e3e504310a
3 changed files with 129 additions and 6 deletions

View file

@ -94,3 +94,4 @@ lamhieu:unblock
meteorhacks:aggregate@1.3.0
wekan-markdown
konecty:mongo-counter
summernote:summernote

View file

@ -168,6 +168,7 @@ standard-minifier-css@1.5.3
standard-minifier-js@2.4.1
staringatlights:fast-render@3.2.0
staringatlights:inject-data@2.3.0
summernote:summernote@0.8.1
tap:i18n@1.8.2
templates:tabs@2.3.0
templating@1.3.2
@ -175,6 +176,7 @@ templating-compiler@1.3.3
templating-runtime@1.3.2
templating-tools@1.1.2
tracker@1.2.0
twbs:bootstrap@3.3.6
ui@1.0.13
underscore@1.0.10
url@1.2.0

View file

@ -1,9 +1,7 @@
Template.editor.onRendered(() => {
const $textarea = this.$('textarea');
autosize($textarea);
$textarea.escapeableTextComplete([
const textareaSelector = 'textarea';
const disableRicherEditor = Meteor.settings.public.NO_RICHER_EDITOR;
const mentions = [
// User mentions
{
match: /\B@([\w.]*)$/,
@ -27,7 +25,129 @@ Template.editor.onRendered(() => {
},
index: 1,
},
]);
];
if (!disableRicherEditor) {
const isSmall = Utils.isMiniScreen();
const toolbar = isSmall
? [
['font', ['bold', 'underline']],
['fontsize', ['fontsize']],
['color', ['color']],
['table', ['table']],
['view', ['fullscreen']],
]
: [
['style', ['style']],
['font', ['bold', 'underline', 'clear']],
['fontsize', ['fontsize']],
['fontname', ['fontname']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
//['insert', ['link', 'picture', 'video']], // iframe tag will be sanitized TODO if iframe[class=note-video-clip] can be added into safe list, insert video can be enabled
['insert', ['link', 'picture']],
['view', ['fullscreen', 'help']],
];
const cleanPastedHTML = function(input) {
const badTags = [
'style',
'script',
'applet',
'embed',
'noframes',
'noscript',
'meta',
'link',
'button',
'form',
].join('|');
const badPatterns = new RegExp(
`(?:${[
`<(${badTags})s*[^>][\\s\\S]*?<\\/\\1>`,
`<(${badTags})[^>]*?\\/>`,
].join('|')})`,
'gi',
);
let output = input;
// remove bad Tags
output = output.replace(badPatterns, '');
// remove attributes ' style="..."'
const badAttributes = new RegExp(
`(?:${[
'on\\S+=([\'"]?).*?\\1',
'href=([\'"]?)javascript:.*?\\2',
'style=([\'"]?).*?\\3',
'target=\\S+',
].join('|')})`,
'gi',
);
output = output.replace(badAttributes, '');
output = output.replace(/(<a )/gi, '$1target=_ '); // always to new target
return output;
};
const editor = '.editor';
const selectors = [
`.js-new-comment-form ${editor}`,
`.js-edit-comment ${editor}`,
].join(','); // only new comment and edit comment
$(selectors).summernote({
callbacks: {
onInit(object) {
const jEditor = object && object.editor;
const toolbar = object && object.toolbar;
if (jEditor !== undefined) {
jEditor.find('.note-editable').escapeableTextComplete(mentions);
}
if (toolbar !== undefined) {
const fBtn = toolbar.find('.btn-fullscreen');
fBtn.on('click', function() {
const $this = $(this),
isActive = $this.hasClass('active');
$('.minicards').toggle(!isActive); // mini card is still showing when editor is in fullscreen mode, we hide here manually
});
}
},
onPaste() {
// clear up unwanted tag info when user pasted in text
const thisNote = $(this);
const updatePastedText = function(someNote) {
const original = someNote.summernote('code');
const cleaned = cleanPastedHTML(original); //this is where to call whatever clean function you want. I have mine in a different file, called CleanPastedHTML.
someNote.summernote('code', ''); //clear original
someNote.summernote('pasteHTML', cleaned); //this sets the displayed content editor to the cleaned pasted code.
};
setTimeout(function() {
//this kinda sucks, but if you don't do a setTimeout,
//the function is called before the text is really pasted.
updatePastedText(thisNote);
}, 10);
},
},
dialogsInBody: true,
disableDragAndDrop: true,
toolbar,
popover: {
image: [
[
'image',
['resizeFull', 'resizeHalf', 'resizeQuarter', 'resizeNone'],
],
['float', ['floatLeft', 'floatRight', 'floatNone']],
['remove', ['removeMedia']],
],
table: [
['add', ['addRowDown', 'addRowUp', 'addColLeft', 'addColRight']],
['delete', ['deleteRow', 'deleteCol', 'deleteTable']],
],
air: [['color', ['color']], ['font', ['bold', 'underline', 'clear']]],
},
height: 200,
});
} else {
const $textarea = this.$(textareaSelector);
autosize($textarea);
$textarea.escapeableTextComplete(mentions);
}
});
import sanitizeXss from 'xss';