summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLauri Ojansivu <x@xet7.org>2019-07-26 01:44:04 +0300
committerLauri Ojansivu <x@xet7.org>2019-07-26 01:44:04 +0300
commit6218da4c53698bd26407142151529f84c0fd6019 (patch)
tree37b1c085324123f52af16f82abebe4ad9a00070f
parentbdeb941682cb9afee707397a457f9942914c011d (diff)
parent2c042e7f119ac404a1060d10d368d1bbd9d15e9c (diff)
downloadwekan-6218da4c53698bd26407142151529f84c0fd6019.tar.gz
wekan-6218da4c53698bd26407142151529f84c0fd6019.tar.bz2
wekan-6218da4c53698bd26407142151529f84c0fd6019.zip
Merge branch 'whowillcare-master'
-rw-r--r--.meteor/packages1
-rw-r--r--.meteor/versions2
-rw-r--r--client/components/activities/comments.js2
-rw-r--r--client/components/cards/cardDetails.styl2
-rwxr-xr-xclient/components/main/editor.js167
-rw-r--r--client/components/main/layouts.styl29
-rw-r--r--client/components/settings/settingBody.jade36
-rw-r--r--client/components/settings/settingBody.styl4
8 files changed, 215 insertions, 28 deletions
diff --git a/.meteor/packages b/.meteor/packages
index 1c2cac57..63f1f7eb 100644
--- a/.meteor/packages
+++ b/.meteor/packages
@@ -95,3 +95,4 @@ meteorhacks:aggregate@1.3.0
wekan-markdown
konecty:mongo-counter
percolate:synced-cron
+easylogic:summernote
diff --git a/.meteor/versions b/.meteor/versions
index e5fa94f0..20f1d924 100644
--- a/.meteor/versions
+++ b/.meteor/versions
@@ -55,6 +55,7 @@ ddp-server@2.3.0
deps@1.0.12
diff-sequence@1.1.1
dynamic-import@0.5.1
+easylogic:summernote@0.8.8
ecmascript@0.12.7
ecmascript-runtime@0.7.0
ecmascript-runtime-client@0.8.0
@@ -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..98461c4f 100755
--- a/client/components/main/editor.js
+++ b/client/components/main/editor.js
@@ -1,9 +1,8 @@
Template.editor.onRendered(() => {
- const $textarea = this.$('textarea');
-
- autosize($textarea);
-
- $textarea.escapeableTextComplete([
+ const textareaSelector = 'textarea';
+ const enableRicherEditor =
+ Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR || true;
+ const mentions = [
// User mentions
{
match: /\B@([\w.]*)$/,
@@ -27,7 +26,163 @@ Template.editor.onRendered(() => {
},
index: 1,
},
- ]);
+ ];
+ const enableTextarea = function() {
+ const $textarea = this.$(textareaSelector);
+ autosize($textarea);
+ $textarea.escapeableTextComplete(mentions);
+ };
+ if (enableRicherEditor) {
+ 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';
diff --git a/client/components/main/layouts.styl b/client/components/main/layouts.styl
index 46ee720c..672e4520 100644
--- a/client/components/main/layouts.styl
+++ b/client/components/main/layouts.styl
@@ -2,6 +2,35 @@
global-reset()
+*
+ -webkit-box-sizing: unset
+ box-sizing: unset
+
+.note-popover .popover-content .note-color-palette div .note-color-btn, .panel-heading.note-toolbar .note-color-palette div .note-color-btn
+ background: none
+
+a:focus
+ outline: unset
+ outline-offset: unset
+
+a:hover,a:focus
+ color: unset
+ text-decoration: unset
+
+.badge
+ display: unset
+ min-width: unset
+ padding: unset
+ font-size: unset
+ font-weight: unset
+ line-height: unset
+ color: unset
+ text-align: unset
+ white-space: unset
+ vertical-align: unset
+ background-color: unset
+ border-radius: unset
+
html, body, input, select, textarea, button
font: 14px Roboto, "Helvetica Neue", Arial, Helvetica, sans-serif
line-height: 18px
diff --git a/client/components/settings/settingBody.jade b/client/components/settings/settingBody.jade
index 89911e09..43836b2b 100644
--- a/client/components/settings/settingBody.jade
+++ b/client/components/settings/settingBody.jade
@@ -44,7 +44,7 @@ template(name="general")
ul
li
.title {{_ 'invite-people'}}
- textarea#email-to-invite.form-control(rows='5', placeholder="{{_ 'email-addresses'}}")
+ textarea#email-to-invite.wekan-form-control(rows='5', placeholder="{{_ 'email-addresses'}}")
li
.title {{_ 'to-boards'}}
.bg-white
@@ -63,20 +63,20 @@ template(name='email')
.title {{_ 'smtp-host'}}
.description {{_ 'smtp-host-description'}}
.form-group
- input.form-control#mail-server-host(type="text", placeholder="smtp.domain.com" value="{{currentSetting.mailServer.host}}")
+ input.wekan-form-control#mail-server-host(type="text", placeholder="smtp.domain.com" value="{{currentSetting.mailServer.host}}")
li.smtp-form
.title {{_ 'smtp-port'}}
.description {{_ 'smtp-port-description'}}
.form-group
- input.form-control#mail-server-port(type="text", placeholder="25" value="{{currentSetting.mailServer.port}}")
+ input.wekan-form-control#mail-server-port(type="text", placeholder="25" value="{{currentSetting.mailServer.port}}")
li.smtp-form
.title {{_ 'smtp-username'}}
.form-group
- input.form-control#mail-server-username(type="text", placeholder="{{_ 'username'}}" value="{{currentSetting.mailServer.username}}")
+ input.wekan-form-control#mail-server-username(type="text", placeholder="{{_ 'username'}}" value="{{currentSetting.mailServer.username}}")
li.smtp-form
.title {{_ 'smtp-password'}}
.form-group
- input.form-control#mail-server-password(type="password", placeholder="{{_ 'password'}}" value="{{currentSetting.mailServer.password}}")
+ input.wekan-form-control#mail-server-password(type="password", placeholder="{{_ 'password'}}" value="{{currentSetting.mailServer.password}}")
li.smtp-form
.title {{_ 'smtp-tls'}}
.form-group
@@ -88,7 +88,7 @@ template(name='email')
li.smtp-form
.title {{_ 'send-from'}}
.form-group
- input.form-control#mail-server-from(type="email", placeholder="no-reply@domain.com" value="{{currentSetting.mailServer.from}}")
+ input.wekan-form-control#mail-server-from(type="email", placeholder="no-reply@domain.com" value="{{currentSetting.mailServer.from}}")
li
button.js-save.primary {{_ 'save'}}
@@ -101,17 +101,17 @@ template(name='accountSettings')
li.accounts-form
.title {{_ 'accounts-allowEmailChange'}}
.form-group.flex
- input.form-control#accounts-allowEmailChange(type="radio" name="allowEmailChange" value="true" checked="{{#if allowEmailChange}}checked{{/if}}")
+ input.wekan-form-control#accounts-allowEmailChange(type="radio" name="allowEmailChange" value="true" checked="{{#if allowEmailChange}}checked{{/if}}")
span {{_ 'yes'}}
- input.form-control#accounts-allowEmailChange(type="radio" name="allowEmailChange" value="false" checked="{{#unless allowEmailChange}}checked{{/unless}}")
+ input.wekan-form-control#accounts-allowEmailChange(type="radio" name="allowEmailChange" value="false" checked="{{#unless allowEmailChange}}checked{{/unless}}")
span {{_ 'no'}}
li
li.accounts-form
.title {{_ 'accounts-allowUserNameChange'}}
.form-group.flex
- input.form-control#accounts-allowUserNameChange(type="radio" name="allowUserNameChange" value="true" checked="{{#if allowUserNameChange}}checked{{/if}}")
+ input.wekan-form-control#accounts-allowUserNameChange(type="radio" name="allowUserNameChange" value="true" checked="{{#if allowUserNameChange}}checked{{/if}}")
span {{_ 'yes'}}
- input.form-control#accounts-allowUserNameChange(type="radio" name="allowUserNameChange" value="false" checked="{{#unless allowUserNameChange}}checked{{/unless}}")
+ input.wekan-form-control#accounts-allowUserNameChange(type="radio" name="allowUserNameChange" value="false" checked="{{#unless allowUserNameChange}}checked{{/unless}}")
span {{_ 'no'}}
li
button.js-accounts-save.primary {{_ 'save'}}
@@ -128,7 +128,7 @@ template(name='announcementSettings')
ul
li
.title {{_ 'admin-announcement-title'}}
- textarea#admin-announcement.form-control= currentSetting.body
+ textarea#admin-announcement.wekan-form-control= currentSetting.body
li
button.js-announcement-save.primary {{_ 'save'}}
@@ -137,16 +137,16 @@ template(name='layoutSettings')
//li.layout-form
.title {{_ 'hide-logo'}}
.form-group.flex
- input.form-control#hide-logo(type="radio" name="hideLogo" value="true" checked="{{#if currentSetting.hideLogo}}checked{{/if}}")
+ input.wekan-form-control#hide-logo(type="radio" name="hideLogo" value="true" checked="{{#if currentSetting.hideLogo}}checked{{/if}}")
span {{_ 'yes'}}
- input.form-control#hide-logo(type="radio" name="hideLogo" value="false" checked="{{#unless currentSetting.hideLogo}}checked{{/unless}}")
+ input.wekan-form-control#hide-logo(type="radio" name="hideLogo" value="false" checked="{{#unless currentSetting.hideLogo}}checked{{/unless}}")
span {{_ 'no'}}
li.layout-form
.title {{_ 'display-authentication-method'}}
.form-group.flex
- input.form-control#display-authentication-method(type="radio" name="displayAuthenticationMethod" value="true" checked="{{#if currentSetting.displayAuthenticationMethod}}checked{{/if}}")
+ input.wekan-form-control#display-authentication-method(type="radio" name="displayAuthenticationMethod" value="true" checked="{{#if currentSetting.displayAuthenticationMethod}}checked{{/if}}")
span {{_ 'yes'}}
- input.form-control#display-authentication-method(type="radio" name="displayAuthenticationMethod" value="false" checked="{{#unless currentSetting.displayAuthenticationMethod}}checked{{/unless}}")
+ input.wekan-form-control#display-authentication-method(type="radio" name="displayAuthenticationMethod" value="false" checked="{{#unless currentSetting.displayAuthenticationMethod}}checked{{/unless}}")
span {{_ 'no'}}
li.layout-form
.title {{_ 'default-authentication-method'}}
@@ -154,13 +154,13 @@ template(name='layoutSettings')
li.layout-form
.title {{_ 'custom-product-name'}}
.form-group
- input.form-control#product-name(type="text", placeholder="" value="{{currentSetting.productName}}")
+ input.wekan-form-control#product-name(type="text", placeholder="" value="{{currentSetting.productName}}")
li.layout-form
.title {{_ 'add-custom-html-after-body-start'}}
- textarea#customHTMLafterBodyStart.form-control= currentSetting.customHTMLafterBodyStart
+ textarea#customHTMLafterBodyStart.wekan-form-control= currentSetting.customHTMLafterBodyStart
li.layout-form
.title {{_ 'add-custom-html-before-body-end'}}
- textarea#customHTMLbeforeBodyEnd.form-control= currentSetting.customHTMLbeforeBodyEnd
+ textarea#customHTMLbeforeBodyEnd.wekan-form-control= currentSetting.customHTMLbeforeBodyEnd
li
button.js-save-layout.primary {{_ 'save'}}
diff --git a/client/components/settings/settingBody.styl b/client/components/settings/settingBody.styl
index dbf91a6c..b9300782 100644
--- a/client/components/settings/settingBody.styl
+++ b/client/components/settings/settingBody.styl
@@ -105,14 +105,14 @@
.bg-white
background #f9fbfc;
-.form-control.has-error
+.wekan-form-control.has-error
border-color: #a94442;
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
li.has-error
color #a94442
.form-group
- .form-control
+ .wekan-form-control
border-color: #a94442;
box-shadow: inset 0 1px 1px rgba(0,0,0,.075);