diff options
-rw-r--r-- | .meteor/packages | 1 | ||||
-rw-r--r-- | .meteor/versions | 2 | ||||
-rw-r--r-- | client/components/activities/comments.js | 2 | ||||
-rw-r--r-- | client/components/cards/cardDetails.styl | 2 | ||||
-rwxr-xr-x | client/components/main/editor.js | 166 |
5 files changed, 165 insertions, 8 deletions
diff --git a/.meteor/packages b/.meteor/packages index 1c2cac57..f777ea66 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -94,4 +94,5 @@ lamhieu:unblock meteorhacks:aggregate@1.3.0 wekan-markdown konecty:mongo-counter +summernote:summernote percolate:synced-cron diff --git a/.meteor/versions b/.meteor/versions index e5fa94f0..0d9f93dd 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -169,6 +169,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 @@ -176,6 +177,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 diff --git a/client/components/activities/comments.js b/client/components/activities/comments.js index 3fc5770c..8289b628 100644 --- a/client/components/activities/comments.js +++ b/client/components/activities/comments.js @@ -54,7 +54,7 @@ BlazeComponent.extendComponent({ // XXX This should be a static method of the `commentForm` component function resetCommentInput(input) { - input.val(''); + input.val('').trigger('input'); // without manually trigger, input event won't be fired input.blur(); commentFormIsOpen.set(false); } diff --git a/client/components/cards/cardDetails.styl b/client/components/cards/cardDetails.styl index da0fe9f8..4bba2d4d 100644 --- a/client/components/cards/cardDetails.styl +++ b/client/components/cards/cardDetails.styl @@ -126,7 +126,7 @@ input[type="submit"].attachment-add-link-submit @media screen and (max-width: 800px) .card-details - width: calc(100% - 40px) + width: calc(100% - 1px) padding: 0px 20px 0px 20px margin: 0px transition: none diff --git a/client/components/main/editor.js b/client/components/main/editor.js index 2824723d..9c1ad7a8 100755 --- a/client/components/main/editor.js +++ b/client/components/main/editor.js @@ -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,163 @@ Template.editor.onRendered(() => { }, index: 1, }, - ]); + ]; + const enableTextarea = function() { + const $textarea = this.$(textareaSelector); + autosize($textarea); + $textarea.escapeableTextComplete(mentions); + }; + if (!disableRicherEditor) { + const isSmall = Utils.isMiniScreen(); + const toolbar = isSmall + ? [ + ['view', ['fullscreen']], + ['table', ['table']], + ['font', ['bold', 'underline']], + //['fontsize', ['fontsize']], + ['color', ['color']], + ] + : [ + ['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']], // modal popup has issue somehow :( + ['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 + const inputs = $(selectors); + if (inputs.length === 0) { + // only enable richereditor to new comment or edit comment no others + enableTextarea(); + } else { + const placeholder = inputs.attr('placeholder') || ''; + const mSummernotes = []; + const getSummernote = function(input) { + const idx = inputs.index(input); + if (idx > -1) { + return mSummernotes[idx]; + } + return undefined; + }; + inputs.each(function(idx, input) { + mSummernotes[idx] = $(input).summernote({ + placeholder, + callbacks: { + onInit(object) { + const originalInput = this; + $(originalInput).on('input', function() { + // when comment is submitted, the original textarea will be set to '', so shall we + if (!this.value) { + const sn = getSummernote(this); + sn && sn.summernote('reset'); + object && object.editingArea.find('.note-placeholder').show(); + } + }); + const jEditor = object && object.editable; + const toolbar = object && object.toolbar; + if (jEditor !== undefined) { + jEditor.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(object) { + const someNote = getSummernote(object); + 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('reset'); //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 { + enableTextarea(); + } }); import sanitizeXss from 'xss'; |