Explorar o código

Merge branch 'whowillcare-master'

Lauri Ojansivu %!s(int64=5) %!d(string=hai) anos
pai
achega
6218da4c53

+ 1 - 0
.meteor/packages

@@ -95,3 +95,4 @@ meteorhacks:aggregate@1.3.0
 wekan-markdown
 wekan-markdown
 konecty:mongo-counter
 konecty:mongo-counter
 percolate:synced-cron
 percolate:synced-cron
+easylogic:summernote

+ 2 - 0
.meteor/versions

@@ -55,6 +55,7 @@ ddp-server@2.3.0
 deps@1.0.12
 deps@1.0.12
 diff-sequence@1.1.1
 diff-sequence@1.1.1
 dynamic-import@0.5.1
 dynamic-import@0.5.1
+easylogic:summernote@0.8.8
 ecmascript@0.12.7
 ecmascript@0.12.7
 ecmascript-runtime@0.7.0
 ecmascript-runtime@0.7.0
 ecmascript-runtime-client@0.8.0
 ecmascript-runtime-client@0.8.0
@@ -176,6 +177,7 @@ templating-compiler@1.3.3
 templating-runtime@1.3.2
 templating-runtime@1.3.2
 templating-tools@1.1.2
 templating-tools@1.1.2
 tracker@1.2.0
 tracker@1.2.0
+twbs:bootstrap@3.3.6
 ui@1.0.13
 ui@1.0.13
 underscore@1.0.10
 underscore@1.0.10
 url@1.2.0
 url@1.2.0

+ 1 - 1
client/components/activities/comments.js

@@ -54,7 +54,7 @@ BlazeComponent.extendComponent({
 
 
 // XXX This should be a static method of the `commentForm` component
 // XXX This should be a static method of the `commentForm` component
 function resetCommentInput(input) {
 function resetCommentInput(input) {
-  input.val('');
+  input.val('').trigger('input'); // without manually trigger, input event won't be fired
   input.blur();
   input.blur();
   commentFormIsOpen.set(false);
   commentFormIsOpen.set(false);
 }
 }

+ 1 - 1
client/components/cards/cardDetails.styl

@@ -126,7 +126,7 @@ input[type="submit"].attachment-add-link-submit
 
 
 @media screen and (max-width: 800px)
 @media screen and (max-width: 800px)
   .card-details
   .card-details
-    width: calc(100% - 40px)
+    width: calc(100% - 1px)
     padding: 0px 20px 0px 20px
     padding: 0px 20px 0px 20px
     margin: 0px
     margin: 0px
     transition: none
     transition: none

+ 161 - 6
client/components/main/editor.js

@@ -1,9 +1,8 @@
 Template.editor.onRendered(() => {
 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
     // User mentions
     {
     {
       match: /\B@([\w.]*)$/,
       match: /\B@([\w.]*)$/,
@@ -27,7 +26,163 @@ Template.editor.onRendered(() => {
       },
       },
       index: 1,
       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';
 import sanitizeXss from 'xss';

+ 29 - 0
client/components/main/layouts.styl

@@ -2,6 +2,35 @@
 
 
 global-reset()
 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
 html, body, input, select, textarea, button
   font: 14px Roboto, "Helvetica Neue", Arial, Helvetica, sans-serif
   font: 14px Roboto, "Helvetica Neue", Arial, Helvetica, sans-serif
   line-height: 18px
   line-height: 18px

+ 18 - 18
client/components/settings/settingBody.jade

@@ -44,7 +44,7 @@ template(name="general")
         ul
         ul
           li
           li
             .title {{_ 'invite-people'}}
             .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
           li
             .title {{_ 'to-boards'}}
             .title {{_ 'to-boards'}}
             .bg-white
             .bg-white
@@ -63,20 +63,20 @@ template(name='email')
       .title {{_ 'smtp-host'}}
       .title {{_ 'smtp-host'}}
       .description {{_ 'smtp-host-description'}}
       .description {{_ 'smtp-host-description'}}
       .form-group
       .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
     li.smtp-form
       .title {{_ 'smtp-port'}}
       .title {{_ 'smtp-port'}}
       .description {{_ 'smtp-port-description'}}
       .description {{_ 'smtp-port-description'}}
       .form-group
       .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
     li.smtp-form
       .title {{_ 'smtp-username'}}
       .title {{_ 'smtp-username'}}
       .form-group
       .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
     li.smtp-form
       .title {{_ 'smtp-password'}}
       .title {{_ 'smtp-password'}}
       .form-group
       .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
     li.smtp-form
       .title {{_ 'smtp-tls'}}
       .title {{_ 'smtp-tls'}}
       .form-group
       .form-group
@@ -88,7 +88,7 @@ template(name='email')
     li.smtp-form
     li.smtp-form
       .title {{_ 'send-from'}}
       .title {{_ 'send-from'}}
       .form-group
       .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
     li
       button.js-save.primary {{_ 'save'}}
       button.js-save.primary {{_ 'save'}}
@@ -101,17 +101,17 @@ template(name='accountSettings')
     li.accounts-form
     li.accounts-form
       .title {{_ 'accounts-allowEmailChange'}}
       .title {{_ 'accounts-allowEmailChange'}}
       .form-group.flex
       .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'}}
         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'}}
         span {{_ 'no'}}
     li
     li
     li.accounts-form
     li.accounts-form
       .title {{_ 'accounts-allowUserNameChange'}}
       .title {{_ 'accounts-allowUserNameChange'}}
       .form-group.flex
       .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'}}
         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'}}
         span {{_ 'no'}}
     li
     li
       button.js-accounts-save.primary {{_ 'save'}}
       button.js-accounts-save.primary {{_ 'save'}}
@@ -128,7 +128,7 @@ template(name='announcementSettings')
         ul
         ul
           li
           li
             .title {{_ 'admin-announcement-title'}}
             .title {{_ 'admin-announcement-title'}}
-            textarea#admin-announcement.form-control= currentSetting.body
+            textarea#admin-announcement.wekan-form-control= currentSetting.body
           li
           li
             button.js-announcement-save.primary {{_ 'save'}}
             button.js-announcement-save.primary {{_ 'save'}}
 
 
@@ -137,16 +137,16 @@ template(name='layoutSettings')
     //li.layout-form
     //li.layout-form
       .title {{_ 'hide-logo'}}
       .title {{_ 'hide-logo'}}
       .form-group.flex
       .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'}}
         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'}}
         span {{_ 'no'}}
     li.layout-form
     li.layout-form
       .title {{_ 'display-authentication-method'}}
       .title {{_ 'display-authentication-method'}}
       .form-group.flex
       .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'}}
         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'}}
         span {{_ 'no'}}
     li.layout-form
     li.layout-form
       .title {{_ 'default-authentication-method'}}
       .title {{_ 'default-authentication-method'}}
@@ -154,13 +154,13 @@ template(name='layoutSettings')
     li.layout-form
     li.layout-form
       .title {{_ 'custom-product-name'}}
       .title {{_ 'custom-product-name'}}
       .form-group
       .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
     li.layout-form
       .title {{_ 'add-custom-html-after-body-start'}}
       .title {{_ 'add-custom-html-after-body-start'}}
-      textarea#customHTMLafterBodyStart.form-control= currentSetting.customHTMLafterBodyStart
+      textarea#customHTMLafterBodyStart.wekan-form-control= currentSetting.customHTMLafterBodyStart
     li.layout-form
     li.layout-form
       .title {{_ 'add-custom-html-before-body-end'}}
       .title {{_ 'add-custom-html-before-body-end'}}
-      textarea#customHTMLbeforeBodyEnd.form-control= currentSetting.customHTMLbeforeBodyEnd
+      textarea#customHTMLbeforeBodyEnd.wekan-form-control= currentSetting.customHTMLbeforeBodyEnd
     li
     li
       button.js-save-layout.primary {{_ 'save'}}
       button.js-save-layout.primary {{_ 'save'}}
 
 

+ 2 - 2
client/components/settings/settingBody.styl

@@ -105,14 +105,14 @@
 .bg-white
 .bg-white
   background #f9fbfc;
   background #f9fbfc;
 
 
-.form-control.has-error
+.wekan-form-control.has-error
   border-color: #a94442;
   border-color: #a94442;
   box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
   box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
 
 
 li.has-error
 li.has-error
   color #a94442
   color #a94442
   .form-group
   .form-group
-    .form-control
+    .wekan-form-control
       border-color: #a94442;
       border-color: #a94442;
       box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
       box-shadow: inset 0 1px 1px rgba(0,0,0,.075);