summaryrefslogtreecommitdiffstats
path: root/client/components
diff options
context:
space:
mode:
Diffstat (limited to 'client/components')
-rw-r--r--client/components/activities/activities.jade5
-rw-r--r--client/components/activities/activities.js2
-rw-r--r--client/components/activities/comments.js3
-rw-r--r--client/components/boards/boardBody.jade4
-rw-r--r--client/components/boards/boardColors.styl1
-rw-r--r--client/components/boards/boardsList.jade28
-rw-r--r--client/components/boards/boardsList.js4
-rw-r--r--client/components/cards/attachments.js82
-rw-r--r--client/components/cards/cardDetails.js31
-rw-r--r--client/components/cards/cardDetails.styl4
-rw-r--r--client/components/cards/checklists.styl3
-rw-r--r--client/components/cards/labels.styl3
-rw-r--r--client/components/cards/minicard.styl12
-rw-r--r--client/components/import/import.js22
-rw-r--r--client/components/lists/listBody.js31
-rwxr-xr-xclient/components/main/editor.js235
-rw-r--r--client/components/main/layouts.styl6
-rw-r--r--client/components/settings/informationBody.jade12
-rw-r--r--client/components/settings/settingBody.styl8
-rw-r--r--client/components/sidebar/sidebar.js7
-rw-r--r--client/components/sidebar/sidebarArchives.js2
-rw-r--r--client/components/sidebar/sidebarFilters.jade16
-rw-r--r--client/components/sidebar/sidebarFilters.js19
-rw-r--r--client/components/swimlanes/swimlanes.jade3
-rw-r--r--client/components/swimlanes/swimlanes.js18
25 files changed, 390 insertions, 171 deletions
diff --git a/client/components/activities/activities.jade b/client/components/activities/activities.jade
index 5be953b6..deb73072 100644
--- a/client/components/activities/activities.jade
+++ b/client/components/activities/activities.jade
@@ -201,6 +201,7 @@ template(name="cardActivities")
.activity-checklist(href="{{ card.absoluteUrl }}")
+viewer
= checklistItem.title
+
if(currentData.timeKey)
| {{{_ activityType }}}
= ' '
@@ -215,6 +216,10 @@ template(name="cardActivities")
| {{{_ activityType currentData.timeValue}}}
+ if($eq activityType 'deleteComment')
+ | {{{_ 'activity-deleteComment' currentData.commentId}}}.
+ if($eq activityType 'editComment')
+ | {{{_ 'activity-editComment' currentData.commentId}}}.
if($eq activityType 'addComment')
+inlinedForm(classNames='js-edit-comment')
+editor(autofocus=true)
diff --git a/client/components/activities/activities.js b/client/components/activities/activities.js
index 05149826..b082273a 100644
--- a/client/components/activities/activities.js
+++ b/client/components/activities/activities.js
@@ -85,7 +85,7 @@ BlazeComponent.extendComponent({
const lastLabel = Boards.findOne(Session.get('currentBoard')).getLabelById(
lastLabelId,
);
- if (lastLabel.name === undefined || lastLabel.name === '') {
+ if (lastLabel && (lastLabel.name === undefined || lastLabel.name === '')) {
return lastLabel.color;
} else {
return lastLabel.name;
diff --git a/client/components/activities/comments.js b/client/components/activities/comments.js
index 8289b628..95084646 100644
--- a/client/components/activities/comments.js
+++ b/client/components/activities/comments.js
@@ -38,6 +38,7 @@ BlazeComponent.extendComponent({
resetCommentInput(input);
Tracker.flush();
autosize.update(input);
+ input.trigger('submitted');
}
evt.preventDefault();
},
@@ -54,7 +55,7 @@ BlazeComponent.extendComponent({
// XXX This should be a static method of the `commentForm` component
function resetCommentInput(input) {
- input.val('').trigger('input'); // without manually trigger, input event won't be fired
+ input.val(''); // without manually trigger, input event won't be fired
input.blur();
commentFormIsOpen.set(false);
}
diff --git a/client/components/boards/boardBody.jade b/client/components/boards/boardBody.jade
index fd094a93..76a85d87 100644
--- a/client/components/boards/boardBody.jade
+++ b/client/components/boards/boardBody.jade
@@ -7,8 +7,8 @@ template(name="board")
+boardBody
else
//-- XXX We need a better error message in case the board has been archived
- //-- +message(label="board-not-found")
- | {{goHome}}
+ +message(label="board-not-found")
+ //-- | {{goHome}}
else
+spinner
diff --git a/client/components/boards/boardColors.styl b/client/components/boards/boardColors.styl
index efd4367e..3be9c0c3 100644
--- a/client/components/boards/boardColors.styl
+++ b/client/components/boards/boardColors.styl
@@ -241,6 +241,7 @@ setBoardColor(color)
background-color #ffffff !important
padding 15px !important
border 1px solid #000000 !important
+ word-wrap: break-word
// When card has comment, emphasis on minicard:
// bigger red comment icon and number of comments,
diff --git a/client/components/boards/boardsList.jade b/client/components/boards/boardsList.jade
index 85f47963..79bae502 100644
--- a/client/components/boards/boardsList.jade
+++ b/client/components/boards/boardsList.jade
@@ -31,12 +31,28 @@ template(name="boardList")
i.fa.js-has-spenttime-cards(
class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}"
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
- i.fa.js-clone-board(
- class="fa-clone"
- title="{{_ 'duplicate-board'}}")
- i.fa.js-archive-board(
- class="fa-archive"
- title="{{_ 'archive-board'}}")
+ unless isMiniScreen
+ if isSandstorm
+ i.fa.js-clone-board(
+ class="fa-clone"
+ title="{{_ 'duplicate-board'}}")
+ i.fa.js-archive-board(
+ class="fa-archive"
+ title="{{_ 'archive-board'}}")
+ else if currentUser.isBoardAdmin
+ i.fa.js-clone-board(
+ class="fa-clone"
+ title="{{_ 'duplicate-board'}}")
+ i.fa.js-archive-board(
+ class="fa-archive"
+ title="{{_ 'archive-board'}}")
+ else if currentUser.isAdmin
+ i.fa.js-clone-board(
+ class="fa-clone"
+ title="{{_ 'duplicate-board'}}")
+ i.fa.js-archive-board(
+ class="fa-archive"
+ title="{{_ 'archive-board'}}")
template(name="boardListHeaderBar")
h1 {{_ 'my-boards'}}
diff --git a/client/components/boards/boardsList.js b/client/components/boards/boardsList.js
index b1371747..3918af82 100644
--- a/client/components/boards/boardsList.js
+++ b/client/components/boards/boardsList.js
@@ -8,10 +8,10 @@ Template.boardListHeaderBar.events({
Template.boardListHeaderBar.helpers({
templatesBoardId() {
- return Meteor.user().getTemplatesBoardId();
+ return Meteor.user() && Meteor.user().getTemplatesBoardId();
},
templatesBoardSlug() {
- return Meteor.user().getTemplatesBoardSlug();
+ return Meteor.user() && Meteor.user().getTemplatesBoardSlug();
},
});
diff --git a/client/components/cards/attachments.js b/client/components/cards/attachments.js
index f536a655..843f1eb7 100644
--- a/client/components/cards/attachments.js
+++ b/client/components/cards/attachments.js
@@ -55,24 +55,12 @@ Template.cardAttachmentsPopup.events({
'change .js-attach-file'(event) {
const card = this;
const processFile = f => {
- const file = new FS.File(f);
- if (card.isLinkedCard()) {
- file.boardId = Cards.findOne(card.linkedId).boardId;
- file.cardId = card.linkedId;
- } else {
- file.boardId = card.boardId;
- file.swimlaneId = card.swimlaneId;
- file.listId = card.listId;
- file.cardId = card._id;
- }
- file.userId = Meteor.userId();
- const attachment = Attachments.insert(file);
-
- if (attachment && attachment._id && attachment.isImage()) {
- card.setCover(attachment._id);
- }
-
- Popup.close();
+ Utils.processUploadedAttachment(card, f, attachment => {
+ if (attachment && attachment._id && attachment.isImage()) {
+ card.setCover(attachment._id);
+ }
+ Popup.close();
+ });
};
FS.Utility.eachFile(event, f => {
@@ -86,7 +74,7 @@ Template.cardAttachmentsPopup.events({
reader.onload = function(e) {
const dataurl = e && e.target && e.target.result;
if (dataurl !== undefined) {
- shrinkImage({
+ Utils.shrinkImage({
dataurl,
maxSize: MAX_IMAGE_PIXEL,
ratio: COMPRESS_RATIO,
@@ -118,59 +106,9 @@ Template.cardAttachmentsPopup.events({
'click .js-upload-clipboard-image': Popup.open('previewClipboardImage'),
});
-const MAX_IMAGE_PIXEL = Meteor.settings.public.MAX_IMAGE_PIXEL;
-const COMPRESS_RATIO = Meteor.settings.public.IMAGE_COMPRESS_RATIO;
+const MAX_IMAGE_PIXEL = Utils.MAX_IMAGE_PIXEL;
+const COMPRESS_RATIO = Utils.IMAGE_COMPRESS_RATIO;
let pastedResults = null;
-const shrinkImage = function(options) {
- // shrink image to certain size
- const dataurl = options.dataurl,
- callback = options.callback,
- toBlob = options.toBlob;
- let canvas = document.createElement('canvas'),
- image = document.createElement('img');
- const maxSize = options.maxSize || 1024;
- const ratio = options.ratio || 1.0;
- const next = function(result) {
- image = null;
- canvas = null;
- if (typeof callback === 'function') {
- callback(result);
- }
- };
- image.onload = function() {
- let width = this.width,
- height = this.height;
- let changed = false;
- if (width > height) {
- if (width > maxSize) {
- height *= maxSize / width;
- width = maxSize;
- changed = true;
- }
- } else if (height > maxSize) {
- width *= maxSize / height;
- height = maxSize;
- changed = true;
- }
- canvas.width = width;
- canvas.height = height;
- canvas.getContext('2d').drawImage(this, 0, 0, width, height);
- if (changed === true) {
- const type = 'image/jpeg';
- if (toBlob) {
- canvas.toBlob(next, type, ratio);
- } else {
- next(canvas.toDataURL(type, ratio));
- }
- } else {
- next(changed);
- }
- };
- image.onerror = function() {
- next(false);
- };
- image.src = dataurl;
-};
Template.previewClipboardImagePopup.onRendered(() => {
// we can paste image from clipboard
@@ -182,7 +120,7 @@ Template.previewClipboardImagePopup.onRendered(() => {
};
if (MAX_IMAGE_PIXEL) {
// if has size limitation on image we shrink it before uploading
- shrinkImage({
+ Utils.shrinkImage({
dataurl: results.dataURL,
maxSize: MAX_IMAGE_PIXEL,
ratio: COMPRESS_RATIO,
diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js
index 781967ae..cd8813f5 100644
--- a/client/components/cards/cardDetails.js
+++ b/client/components/cards/cardDetails.js
@@ -117,6 +117,37 @@ BlazeComponent.extendComponent({
},
onRendered() {
+ if (Meteor.settings.public.CARD_OPENED_WEBHOOK_ENABLED) {
+ // Send Webhook but not create Activities records ---
+ const card = this.currentData();
+ const userId = Meteor.userId();
+ //console.log(`userId: ${userId}`);
+ //console.log(`cardId: ${card._id}`);
+ //console.log(`boardId: ${card.boardId}`);
+ //console.log(`listId: ${card.listId}`);
+ //console.log(`swimlaneId: ${card.swimlaneId}`);
+ const params = {
+ userId,
+ cardId: card._id,
+ boardId: card.boardId,
+ listId: card.listId,
+ user: Meteor.user().username,
+ url: '',
+ };
+ //console.log('looking for integrations...');
+ const integrations = Integrations.find({
+ boardId: card.boardId,
+ type: 'outgoing-webhooks',
+ enabled: true,
+ activities: { $in: ['CardDetailsRendered', 'all'] },
+ }).fetch();
+ //console.log(`Investigation length: ${integrations.length}`);
+ if (integrations.length > 0) {
+ Meteor.call('outgoingWebhooks', integrations, 'CardSelected', params);
+ }
+ //-------------
+ }
+
if (!Utils.isMiniScreen()) {
Meteor.setTimeout(() => {
$('.card-details').mCustomScrollbar({
diff --git a/client/components/cards/cardDetails.styl b/client/components/cards/cardDetails.styl
index 4bba2d4d..cd475072 100644
--- a/client/components/cards/cardDetails.styl
+++ b/client/components/cards/cardDetails.styl
@@ -99,7 +99,9 @@
&.card-details-item-end,
&.card-details-item-customfield,
&.card-details-item-name
- max-width: 50%
+ display: block
+ word-wrap: break-word
+ max-width: 48%
flex-grow: 1
.card-details-item-title
diff --git a/client/components/cards/checklists.styl b/client/components/cards/checklists.styl
index d48c1851..8ac37a15 100644
--- a/client/components/cards/checklists.styl
+++ b/client/components/cards/checklists.styl
@@ -128,6 +128,9 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item
& .viewer
p
margin-bottom: 2px
+ display: block
+ word-wrap: break-word
+ max-width: 420px
.js-delete-checklist-item
margin: 0 0 0.5em 1.33em
diff --git a/client/components/cards/labels.styl b/client/components/cards/labels.styl
index 3b481d93..9d7c7553 100644
--- a/client/components/cards/labels.styl
+++ b/client/components/cards/labels.styl
@@ -10,9 +10,10 @@
margin-right: 4px
margin-bottom: 5px
padding: 3px 8px
- max-width: 100%
+ max-width: 210px
min-width: 8px
overflow: ellipsis
+ word-wrap: break-word
height: 18px
vertical-align: bottom
diff --git a/client/components/cards/minicard.styl b/client/components/cards/minicard.styl
index 242367b4..c4172572 100644
--- a/client/components/cards/minicard.styl
+++ b/client/components/cards/minicard.styl
@@ -81,6 +81,7 @@
.minicard-labels
float: right
display: flex
+ flex-wrap: wrap
.minicard-label
width: 11px
@@ -92,8 +93,11 @@
.minicard-custom-field
display:flex;
.minicard-custom-field-item
- max-width:50%;
- flex-grow:1;
+ flex-grow: 1
+ display: block
+ word-wrap: break-word
+ max-width: 100px
+ margin-right: 4px
.handle
width: 20px;
height: 20px;
@@ -111,7 +115,9 @@
p:last-child
margin-bottom: 0
.viewer
- display: inline-block
+ display: block
+ word-wrap: break-word
+ max-width: 230px
.dates
display: flex;
flex-direction: row;
diff --git a/client/components/import/import.js b/client/components/import/import.js
index 62c7e525..6368885b 100644
--- a/client/components/import/import.js
+++ b/client/components/import/import.js
@@ -211,22 +211,20 @@ BlazeComponent.extendComponent({
this.parentComponent().nextStep();
},
- onMapMember(evt) {
- const memberToMap = this.currentData();
- if (memberToMap.wekan) {
- // todo xxx ask for confirmation?
- this.unmapMember(memberToMap.id);
- } else {
- this.setSelectedMember(memberToMap.id);
- Popup.open('importMapMembersAdd')(evt);
- }
- },
-
events() {
return [
{
submit: this.onSubmit,
- 'click .js-select-member': this.onMapMember,
+ 'click .js-select-member'(evt) {
+ const memberToMap = this.currentData();
+ if (memberToMap.wekan) {
+ // todo xxx ask for confirmation?
+ this.unmapMember(memberToMap.id);
+ } else {
+ this.setSelectedMember(memberToMap.id);
+ Popup.open('importMapMembersAdd')(evt);
+ }
+ },
},
];
},
diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js
index 7d9e358b..c8e41a0b 100644
--- a/client/components/lists/listBody.js
+++ b/client/components/lists/listBody.js
@@ -701,12 +701,31 @@ BlazeComponent.extendComponent({
this.listId = this.parentComponent().data()._id;
this.swimlaneId = '';
- const boardView = (Meteor.user().profile || {}).boardView;
- if (boardView === 'board-view-swimlanes')
- this.swimlaneId = this.parentComponent()
- .parentComponent()
- .parentComponent()
- .data()._id;
+ const isSandstorm =
+ Meteor.settings &&
+ Meteor.settings.public &&
+ Meteor.settings.public.sandstorm;
+
+ if (isSandstorm) {
+ const user = Meteor.user();
+ if (user) {
+ const boardView = (Meteor.user().profile || {}).boardView;
+ if (boardView === 'board-view-swimlanes') {
+ this.swimlaneId = this.parentComponent()
+ .parentComponent()
+ .parentComponent()
+ .data()._id;
+ }
+ }
+ } else {
+ const boardView = (Meteor.user().profile || {}).boardView;
+ if (boardView === 'board-view-swimlanes') {
+ this.swimlaneId = this.parentComponent()
+ .parentComponent()
+ .parentComponent()
+ .data()._id;
+ }
+ }
},
onRendered() {
diff --git a/client/components/main/editor.js b/client/components/main/editor.js
index 98461c4f..91403086 100755
--- a/client/components/main/editor.js
+++ b/client/components/main/editor.js
@@ -1,7 +1,89 @@
+import _sanitizeXss from 'xss';
+const ASIS = 'asis';
+const sanitizeXss = (input, options) => {
+ const defaultAllowedIframeSrc = /^(https:){0,1}\/\/.*?(youtube|vimeo|dailymotion|youku)/i;
+ const allowedIframeSrcRegex = (function() {
+ let reg = defaultAllowedIframeSrc;
+ const SAFE_IFRAME_SRC_PATTERN =
+ Meteor.settings.public.SAFE_IFRAME_SRC_PATTERN;
+ try {
+ if (SAFE_IFRAME_SRC_PATTERN !== undefined) {
+ reg = new RegExp(SAFE_IFRAME_SRC_PATTERN, 'i');
+ }
+ } catch (e) {
+ /*eslint no-console: ["error", { allow: ["warn", "error"] }] */
+
+ console.error('Wrong pattern specified', SAFE_IFRAM_SRC_PATTERN, e);
+ }
+ return reg;
+ })();
+ const targetWindow = '_blank';
+ const getHtmlDOM = html => {
+ const i = document.createElement('i');
+ i.innerHTML = html;
+ return i.firstChild;
+ };
+ options = {
+ onTag(tag, html, options) {
+ const htmlDOM = getHtmlDOM(html);
+ const getAttr = attr => {
+ return htmlDOM && attr && htmlDOM.getAttribute(attr);
+ };
+ if (tag === 'iframe') {
+ const clipCls = 'note-vide-clip';
+ if (!options.isClosing) {
+ const iframeCls = getAttr('class');
+ let safe = iframeCls.indexOf(clipCls) > -1;
+ const src = getAttr('src');
+ if (allowedIframeSrcRegex.exec(src)) {
+ safe = true;
+ }
+ if (safe)
+ return `<iframe src='${src}' class="${clipCls}" width=100% height=auto allowfullscreen></iframe>`;
+ } else {
+ // remove </iframe> tag
+ return '';
+ }
+ } else if (tag === 'a') {
+ if (!options.isClosing) {
+ if (getAttr(ASIS) === 'true') {
+ // 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}\/\/|\/)/)) {
+ // a valid url
+ return `<a href=${href} target=${targetWindow}>`;
+ }
+ }
+ }
+ } else if (tag === 'img') {
+ if (!options.isClosing) {
+ const src = getAttr('src');
+ if (src) {
+ return `<a href='${src}' class='swipebox'><img src='${src}' class="attachment-image-preview mCS_img_loaded"></a>`;
+ }
+ }
+ }
+ return undefined;
+ },
+ onTagAttr(tag, name, value) {
+ if (tag === 'img' && name === 'src') {
+ if (value && value.substr(0, 5) === 'data:') {
+ // allow image with dataURI src
+ return `${name}='${value}'`;
+ }
+ } else if (tag === 'a' && name === 'target') {
+ return `${name}='${targetWindow}'`; // always change a href target to a new window
+ }
+ return undefined;
+ },
+ ...options,
+ };
+ return _sanitizeXss(input, options);
+};
Template.editor.onRendered(() => {
const textareaSelector = 'textarea';
- const enableRicherEditor =
- Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR || true;
const mentions = [
// User mentions
{
@@ -32,7 +114,7 @@ Template.editor.onRendered(() => {
autosize($textarea);
$textarea.escapeableTextComplete(mentions);
};
- if (enableRicherEditor) {
+ if (Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR !== false) {
const isSmall = Utils.isMiniScreen();
const toolbar = isSmall
? [
@@ -50,47 +132,11 @@ Template.editor.onRendered(() => {
['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', '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 cleanPastedHTML = sanitizeXss;
const editor = '.editor';
const selectors = [
`.js-new-comment-form ${editor}`,
@@ -116,8 +162,8 @@ Template.editor.onRendered(() => {
callbacks: {
onInit(object) {
const originalInput = this;
- $(originalInput).on('input', function() {
- // when comment is submitted, the original textarea will be set to '', so shall we
+ $(originalInput).on('submitted', function() {
+ // resetCommentInput has been called
if (!this.value) {
const sn = getSummernote(this);
sn && sn.summernote('reset');
@@ -134,10 +180,83 @@ Template.editor.onRendered(() => {
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
+ $('.minicards,#header-quick-access').toggle(!isActive); // mini card is still showing when editor is in fullscreen mode, we hide here manually
});
}
},
+ onImageUpload(files) {
+ const $summernote = getSummernote(this);
+ if (files && files.length > 0) {
+ const image = files[0];
+ const currentCard = Cards.findOne(Session.get('currentCard'));
+ const MAX_IMAGE_PIXEL = Utils.MAX_IMAGE_PIXEL;
+ const COMPRESS_RATIO = Utils.IMAGE_COMPRESS_RATIO;
+ const insertImage = src => {
+ const img = document.createElement('img');
+ img.src = src;
+ img.setAttribute('width', '100%');
+ $summernote.summernote('insertNode', img);
+ };
+ const processData = function(fileObj) {
+ Utils.processUploadedAttachment(
+ currentCard,
+ fileObj,
+ attachment => {
+ if (
+ attachment &&
+ attachment._id &&
+ attachment.isImage()
+ ) {
+ attachment.one('uploaded', function() {
+ const maxTry = 3;
+ const checkItvl = 500;
+ let retry = 0;
+ const checkUrl = function() {
+ // even though uploaded event fired, attachment.url() is still null somehow //TODO
+ const url = attachment.url();
+ if (url) {
+ insertImage(
+ `${location.protocol}//${location.host}${url}`,
+ );
+ } else {
+ retry++;
+ if (retry < maxTry) {
+ setTimeout(checkUrl, checkItvl);
+ }
+ }
+ };
+ checkUrl();
+ });
+ }
+ },
+ );
+ };
+ if (MAX_IMAGE_PIXEL) {
+ const reader = new FileReader();
+ reader.onload = function(e) {
+ const dataurl = e && e.target && e.target.result;
+ if (dataurl !== undefined) {
+ // need to shrink image
+ Utils.shrinkImage({
+ dataurl,
+ maxSize: MAX_IMAGE_PIXEL,
+ ratio: COMPRESS_RATIO,
+ toBlob: true,
+ callback(blob) {
+ if (blob !== false) {
+ blob.name = image.name;
+ processData(blob);
+ }
+ },
+ });
+ }
+ };
+ reader.readAsDataURL(image);
+ } else {
+ processData(image);
+ }
+ }
+ },
onPaste() {
// clear up unwanted tag info when user pasted in text
const thisNote = this;
@@ -185,8 +304,6 @@ Template.editor.onRendered(() => {
}
});
-import sanitizeXss from 'xss';
-
// XXX I believe we should compute a HTML rendered field on the server that
// would handle markdown and user mentions. We can simply have two
// fields, one source, and one compiled version (in HTML) and send only the
@@ -231,38 +348,42 @@ Blaze.Template.registerHelper(
// `userId` to the popup as usual, and we need to store it in the DOM
// using a data attribute.
'data-userId': knowedUser.userId,
+ [ASIS]: 'true',
},
linkValue,
);
content = content.replace(fullMention, Blaze.toHTML(link));
}
-
return HTML.Raw(sanitizeXss(content));
}),
);
-
Template.viewer.events({
// Viewer sometimes have click-able wrapper around them (for instance to edit
// the corresponding text). Clicking a link shouldn't fire these actions, stop
// we stop these event at the viewer component level.
'click a'(event, templateInstance) {
- event.stopPropagation();
-
- // XXX We hijack the build-in browser action because we currently don't have
- // `_blank` attributes in viewer links, and the transformer function is
- // handled by a third party package that we can't configure easily. Fix that
- // by using directly `_blank` attribute in the rendered HTML.
- event.preventDefault();
-
+ let prevent = true;
const userId = event.currentTarget.dataset.userid;
if (userId) {
Popup.open('member').call({ userId }, event, templateInstance);
} else {
const href = event.currentTarget.href;
- if (href) {
+ const child = event.currentTarget.firstElementChild;
+ if (child && child.tagName === 'IMG') {
+ prevent = false;
+ } else if (href) {
window.open(href, '_blank');
}
}
+ if (prevent) {
+ event.stopPropagation();
+
+ // XXX We hijack the build-in browser action because we currently don't have
+ // `_blank` attributes in viewer links, and the transformer function is
+ // handled by a third party package that we can't configure easily. Fix that
+ // by using directly `_blank` attribute in the rendered HTML.
+ event.preventDefault();
+ }
},
});
diff --git a/client/components/main/layouts.styl b/client/components/main/layouts.styl
index 672e4520..56c35284 100644
--- a/client/components/main/layouts.styl
+++ b/client/components/main/layouts.styl
@@ -232,6 +232,7 @@ kbd
background: darken(white, 2%)
border-radius: 3px
border: 1px solid darken(white, 10%)
+ color: unset
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.15)
.clear
@@ -377,6 +378,8 @@ a
.viewer
min-height: 18px
+ display: block
+ word-wrap: break-word
ol
list-style-type: decimal
@@ -424,6 +427,9 @@ a
height: 100%
margin: 0px
+ .panel-default
+ width: 83vw
+
.inline-input
height: 37px
margin: 8px 10px 0 0
diff --git a/client/components/settings/informationBody.jade b/client/components/settings/informationBody.jade
index feb7c0dc..2c615ffd 100644
--- a/client/components/settings/informationBody.jade
+++ b/client/components/settings/informationBody.jade
@@ -20,9 +20,21 @@ template(name='statistics')
th Wekan {{_ 'info'}}
td {{statistics.version}}
tr
+ th {{_ 'Meteor_version'}}
+ td {{statistics.meteor.meteorVersion}}
+ tr
th {{_ 'Node_version'}}
td {{statistics.process.nodeVersion}}
tr
+ th {{_ 'MongoDB_version'}}
+ td {{statistics.mongo.mongoVersion}}
+ tr
+ th {{_ 'MongoDB_storage_engine'}}
+ td {{statistics.mongo.mongoStorageEngine}}
+ tr
+ th {{_ 'MongoDB_Oplog_enabled'}}
+ td {{statistics.mongo.mongoOplogEnabled}}
+ tr
th {{_ 'OS_Type'}}
td {{statistics.os.type}}
tr
diff --git a/client/components/settings/settingBody.styl b/client/components/settings/settingBody.styl
index b9300782..bcbd2ea1 100644
--- a/client/components/settings/settingBody.styl
+++ b/client/components/settings/settingBody.styl
@@ -52,10 +52,10 @@
.main-body
padding: 0.1em 1em
- -webkit-user-select: auto // Safari 3.1+
- -moz-user-select: auto // Firefox 2+
- -ms-user-select: auto // IE 10+
- user-select: auto // Standard syntax
+ -webkit-user-select: text // Safari 3.1+
+ -moz-user-select: text // Firefox 2+
+ -ms-user-select: text // IE 10+
+ user-select: text // Standard syntax
ul
li
diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js
index 8b98fd7e..f7efb1e8 100644
--- a/client/components/sidebar/sidebar.js
+++ b/client/components/sidebar/sidebar.js
@@ -47,8 +47,11 @@ BlazeComponent.extendComponent({
},
calculateNextPeak() {
- const altitude = this.find('.js-board-sidebar-content').scrollHeight;
- this.callFirstWith(this, 'setNextPeak', altitude);
+ const sidebarElement = this.find('.js-board-sidebar-content');
+ if (sidebarElement) {
+ const altitude = sidebarElement.scrollHeight;
+ this.callFirstWith(this, 'setNextPeak', altitude);
+ }
},
reachNextPeak() {
diff --git a/client/components/sidebar/sidebarArchives.js b/client/components/sidebar/sidebarArchives.js
index 53fc29b9..a4846561 100644
--- a/client/components/sidebar/sidebarArchives.js
+++ b/client/components/sidebar/sidebarArchives.js
@@ -1,3 +1,4 @@
+archivedRequested = false;
const subManager = new SubsManager();
BlazeComponent.extendComponent({
@@ -12,6 +13,7 @@ BlazeComponent.extendComponent({
const currentBoardId = Session.get('currentBoard');
if (!currentBoardId) return;
const handle = subManager.subscribe('board', currentBoardId, true);
+ archivedRequested = true;
Tracker.nonreactive(() => {
Tracker.autorun(() => {
this.isArchiveReady.set(handle.ready());
diff --git a/client/components/sidebar/sidebarFilters.jade b/client/components/sidebar/sidebarFilters.jade
index f11528b1..55ab213a 100644
--- a/client/components/sidebar/sidebarFilters.jade
+++ b/client/components/sidebar/sidebarFilters.jade
@@ -56,6 +56,22 @@ template(name="filterSidebar")
if Filter.customFields.isSelected _id
i.fa.fa-check
hr
+ ul.sidebar-list
+ li(class="{{#if Filter.archive.isSelected _id}}active{{/if}}")
+ a.name.js-toggle-archive-filter
+ span.sidebar-list-item-description
+ | {{_ 'filter-show-archive'}}
+ if Filter.archive.isSelected _id
+ i.fa.fa-check
+ hr
+ ul.sidebar-list
+ li(class="{{#if Filter.hideEmpty.isSelected _id}}active{{/if}}")
+ a.name.js-toggle-hideEmpty-filter
+ span.sidebar-list-item-description
+ | {{_ 'filter-hide-empty'}}
+ if Filter.hideEmpty.isSelected _id
+ i.fa.fa-check
+ hr
span {{_ 'advanced-filter-label'}}
input.js-field-advanced-filter(type="text")
span {{_ 'advanced-filter-description'}}
diff --git a/client/components/sidebar/sidebarFilters.js b/client/components/sidebar/sidebarFilters.js
index 88438a7a..3483d00c 100644
--- a/client/components/sidebar/sidebarFilters.js
+++ b/client/components/sidebar/sidebarFilters.js
@@ -1,3 +1,5 @@
+const subManager = new SubsManager();
+
BlazeComponent.extendComponent({
events() {
return [
@@ -12,6 +14,23 @@ BlazeComponent.extendComponent({
Filter.members.toggle(this.currentData()._id);
Filter.resetExceptions();
},
+ 'click .js-toggle-archive-filter'(evt) {
+ evt.preventDefault();
+ Filter.archive.toggle(this.currentData()._id);
+ Filter.resetExceptions();
+ const currentBoardId = Session.get('currentBoard');
+ if (!currentBoardId) return;
+ subManager.subscribe(
+ 'board',
+ currentBoardId,
+ Filter.archive.isSelected(),
+ );
+ },
+ 'click .js-toggle-hideEmpty-filter'(evt) {
+ evt.preventDefault();
+ Filter.hideEmpty.toggle(this.currentData()._id);
+ Filter.resetExceptions();
+ },
'click .js-toggle-custom-fields-filter'(evt) {
evt.preventDefault();
Filter.customFields.toggle(this.currentData()._id);
diff --git a/client/components/swimlanes/swimlanes.jade b/client/components/swimlanes/swimlanes.jade
index 485b2ffc..3ad43777 100644
--- a/client/components/swimlanes/swimlanes.jade
+++ b/client/components/swimlanes/swimlanes.jade
@@ -33,7 +33,8 @@ template(name="listsGroup")
+addListForm
else
each lists
- +list(this)
+ if visible this
+ +list(this)
if currentCardIsInThisList _id null
+cardDetails(currentCard)
if currentUser.isBoardMember
diff --git a/client/components/swimlanes/swimlanes.js b/client/components/swimlanes/swimlanes.js
index 568c0bbe..e0857003 100644
--- a/client/components/swimlanes/swimlanes.js
+++ b/client/components/swimlanes/swimlanes.js
@@ -246,6 +246,24 @@ BlazeComponent.extendComponent({
currentCardIsInThisList(listId, swimlaneId) {
return currentCardIsInThisList(listId, swimlaneId);
},
+ visible(list) {
+ if (list.archived) {
+ // Show archived list only when filter archive is on or archive is selected
+ if (!(Filter.archive.isSelected() || archivedRequested)) {
+ return false;
+ }
+ }
+ if (Filter.hideEmpty.isSelected()) {
+ const swimlaneId = this.parentComponent()
+ .parentComponent()
+ .data()._id;
+ const cards = list.cards(swimlaneId);
+ if (cards.count() === 0) {
+ return false;
+ }
+ }
+ return true;
+ },
onRendered() {
const boardComponent = this.parentComponent();
const $listsDom = this.$('.js-lists');