summaryrefslogtreecommitdiffstats
path: root/client
diff options
context:
space:
mode:
authorRomulus Tsai 蔡仲明 <urakagi@gmail.com>2020-05-08 09:35:11 +0800
committerRomulus Tsai 蔡仲明 <urakagi@gmail.com>2020-05-08 09:35:11 +0800
commitcfcc73724fcd394150d1b815d0a7a4c466e216b5 (patch)
treea9648255f14cd8b0e1ad8eee1f8d42337a0668bc /client
parenta3658993128bdddd5d40f792c19281dc5eac51f5 (diff)
parent533bc045d06269dba2f42cdfe61817a1b3407974 (diff)
downloadwekan-cfcc73724fcd394150d1b815d0a7a4c466e216b5.tar.gz
wekan-cfcc73724fcd394150d1b815d0a7a4c466e216b5.tar.bz2
wekan-cfcc73724fcd394150d1b815d0a7a4c466e216b5.zip
Merge branch 'master' of https://github.com/wekan/wekan
Diffstat (limited to 'client')
-rw-r--r--client/00-startup.js6
-rw-r--r--client/components/activities/activities.jade347
-rw-r--r--client/components/activities/activities.js115
-rw-r--r--client/components/activities/activities.styl2
-rw-r--r--client/components/activities/comments.styl20
-rw-r--r--client/components/boards/boardArchive.js2
-rw-r--r--client/components/boards/boardBody.js14
-rw-r--r--client/components/boards/boardHeader.jade14
-rw-r--r--client/components/boards/boardHeader.js21
-rw-r--r--client/components/boards/boardsList.jade8
-rw-r--r--client/components/boards/boardsList.js80
-rw-r--r--client/components/boards/boardsList.styl20
-rw-r--r--client/components/cards/attachments.jade28
-rw-r--r--client/components/cards/cardDate.js3
-rw-r--r--client/components/cards/cardDetails.jade525
-rw-r--r--client/components/cards/cardDetails.js193
-rw-r--r--client/components/cards/cardDetails.styl35
-rw-r--r--client/components/cards/checklists.jade7
-rw-r--r--client/components/cards/checklists.js37
-rw-r--r--client/components/cards/checklists.styl5
-rw-r--r--client/components/cards/labels.styl2
-rw-r--r--client/components/cards/minicard.jade4
-rw-r--r--client/components/cards/minicard.js16
-rw-r--r--client/components/cards/minicard.styl2
-rw-r--r--client/components/cards/subtasks.jade4
-rw-r--r--client/components/cards/subtasks.js26
-rw-r--r--client/components/import/import.jade3
-rw-r--r--client/components/lists/list.js29
-rw-r--r--client/components/lists/list.styl6
-rw-r--r--client/components/lists/listBody.js25
-rw-r--r--client/components/lists/listHeader.jade50
-rw-r--r--client/components/lists/listHeader.js15
-rwxr-xr-xclient/components/main/editor.js192
-rw-r--r--client/components/main/header.jade7
-rw-r--r--client/components/main/header.styl9
-rw-r--r--client/components/main/layouts.jade14
-rw-r--r--client/components/main/layouts.js7
-rw-r--r--client/components/main/popup.styl5
-rw-r--r--client/components/notifications/notification.jade10
-rw-r--r--client/components/notifications/notification.js28
-rw-r--r--client/components/notifications/notification.styl57
-rw-r--r--client/components/notifications/notificationIcon.jade53
-rw-r--r--client/components/notifications/notifications.jade5
-rw-r--r--client/components/notifications/notifications.js32
-rw-r--r--client/components/notifications/notifications.styl17
-rw-r--r--client/components/notifications/notificationsDrawer.jade20
-rw-r--r--client/components/notifications/notificationsDrawer.js53
-rw-r--r--client/components/notifications/notificationsDrawer.styl69
-rw-r--r--client/components/rules/actions/boardActions.jade37
-rw-r--r--client/components/rules/actions/boardActions.js30
-rw-r--r--client/components/settings/informationBody.jade8
-rw-r--r--client/components/settings/peopleBody.jade67
-rw-r--r--client/components/settings/peopleBody.js95
-rw-r--r--client/components/settings/peopleBody.styl2
-rw-r--r--client/components/settings/settingBody.jade31
-rw-r--r--client/components/settings/settingBody.js10
-rw-r--r--client/components/settings/settingBody.styl5
-rw-r--r--client/components/sidebar/sidebar.jade229
-rw-r--r--client/components/sidebar/sidebar.js415
-rw-r--r--client/components/sidebar/sidebar.styl2
-rw-r--r--client/components/sidebar/sidebarArchives.jade54
-rw-r--r--client/components/sidebar/sidebarArchives.js9
-rw-r--r--client/components/sidebar/sidebarFilters.jade33
-rw-r--r--client/components/sidebar/sidebarFilters.js5
-rw-r--r--client/components/swimlanes/swimlaneHeader.js8
-rw-r--r--client/components/swimlanes/swimlanes.jade33
-rw-r--r--client/components/swimlanes/swimlanes.js48
-rw-r--r--client/components/users/userAvatar.jade1
-rw-r--r--client/components/users/userHeader.jade84
-rw-r--r--client/components/users/userHeader.js64
-rw-r--r--client/lib/datepicker.js10
-rw-r--r--client/lib/filter.js10
-rwxr-xr-xclient/lib/keyboard.js52
-rw-r--r--client/lib/textComplete.js11
-rw-r--r--client/lib/utils.js70
75 files changed, 2732 insertions, 933 deletions
diff --git a/client/00-startup.js b/client/00-startup.js
new file mode 100644
index 00000000..4a717b67
--- /dev/null
+++ b/client/00-startup.js
@@ -0,0 +1,6 @@
+// PWA
+if ('serviceWorker' in navigator) {
+ window.addEventListener('load', function() {
+ navigator.serviceWorker.register('/pwa-service-worker.js');
+ });
+}
diff --git a/client/components/activities/activities.jade b/client/components/activities/activities.jade
index deb73072..c86936a0 100644
--- a/client/components/activities/activities.jade
+++ b/client/components/activities/activities.jade
@@ -8,234 +8,201 @@ template(name="activities")
+cardActivities
template(name="boardActivities")
- each currentBoard.activities
- .activity
- +userAvatar(userId=user._id)
- p.activity-desc
- +memberName(user=user)
+ each activityData in currentBoard.activities
+ +activity(activity=activityData card=card mode=mode)
- if($eq activityType 'deleteAttachment')
- | {{{_ 'activity-delete-attach' cardLink}}}.
+template(name="cardActivities")
+ each activityData in currentCard.activities
+ +activity(activity=activityData card=card mode=mode)
+
+template(name="activity")
+ .activity
+ +userAvatar(userId=activity.user._id)
+ p.activity-desc
+ +memberName(user=activity.user)
+
+ //- attachment activity -------------------------------------------------
+ if($eq activity.activityType 'deleteAttachment')
+ | {{{_ 'activity-delete-attach' cardLink}}}.
+
+ if($eq activity.activityType 'addAttachment')
+ | {{{_ 'activity-attached' attachmentLink cardLink}}}.
+ if($neq mode 'board')
+ if activity.attachment.isImage
+ img.attachment-image-preview(src=activity.attachment.url)
+
+ //- board activity ------------------------------------------------------
+ if($eq mode 'board')
+ if($eq activity.activityType 'createBoard')
+ | {{_ 'activity-created' boardLabel}}.
- if($eq activityType 'addAttachment')
- | {{{_ 'activity-attached' attachmentLink cardLink}}}.
+ if($eq activity.activityType 'importBoard')
+ | {{{_ 'activity-imported-board' boardLabel sourceLink}}}.
- if($eq activityType 'addBoardMember')
+ if($eq activity.activityType 'addBoardMember')
| {{{_ 'activity-added' memberLink boardLabel}}}.
- if($eq activityType 'addComment')
- | {{{_ 'activity-on' cardLink}}}
- a.activity-comment(href="{{ card.absoluteUrl }}")
- +viewer
- = comment.text
-
- if($eq activityType 'addChecklist')
- | {{{_ 'activity-checklist-added' cardLink}}}.
- .activity-checklist(href="{{ card.absoluteUrl }}")
- +viewer
- = checklist.title
- if($eq activityType 'removeChecklist')
- | {{{_ 'activity-checklist-removed' cardLink}}}.
-
- if($eq activityType 'checkedItem')
- | {{{_ 'activity-checked-item' checkItem checklist.title cardLink}}}.
-
- if($eq activityType 'uncheckedItem')
- | {{{_ 'activity-unchecked-item' checkItem checklist.title cardLink}}}.
-
- if($eq activityType 'checklistCompleted')
- | {{{_ 'activity-checklist-completed' checklist.title cardLink}}}.
+ if($eq activity.activityType 'removeBoardMember')
+ | {{{_ 'activity-excluded' memberLink boardLabel}}}.
- if($eq activityType 'checklistUncompleted')
- | {{{_ 'activity-checklist-uncompleted' checklist.title cardLink}}}.
+ //- card activity -------------------------------------------------------
+ if($eq activity.activityType 'createCard')
+ if($eq mode 'card')
+ | {{{_ 'activity-added' cardLabel activity.listName}}}.
+ else
+ | {{{_ 'activity-added' cardLabel boardLabel}}}.
- if($eq activityType 'addChecklistItem')
- | {{{_ 'activity-checklist-item-added' checklist.title cardLink}}}.
- .activity-checklist(href="{{ card.absoluteUrl }}")
- +viewer
- = checklistItem.title
- if($eq activityType 'removedChecklistItem')
- | {{{_ 'activity-checklist-item-removed' checklist.title cardLink}}}.
+ if($eq activity.activityType 'importCard')
+ | {{{_ 'activity-imported' cardLink boardLabel sourceLink}}}.
- if($eq activityType 'archivedCard')
- | {{{_ 'activity-archived' cardLink}}}.
+ if($eq activity.activityType 'moveCard')
+ | {{{_ 'activity-moved' cardLabel activity.oldList.title activity.list.title}}}.
- if($eq activityType 'archivedList')
- | {{_ 'activity-archived' list.title}}.
+ if($eq activity.activityType 'moveCardBoard')
+ | {{{_ 'activity-moved' cardLink activity.oldBoardName activity.boardName}}}.
- if($eq activityType 'archivedSwimlane')
- | {{_ 'activity-archived' swimlane.title}}.
+ if($eq activity.activityType 'archivedCard')
+ | {{{_ 'activity-archived' cardLink}}}.
- if($eq activityType 'createBoard')
- | {{_ 'activity-created' boardLabel}}.
+ if($eq activity.activityType 'restoredCard')
+ | {{{_ 'activity-sent' cardLink boardLabel}}}.
- if($eq activityType 'createCard')
- | {{{_ 'activity-added' cardLink boardLabel}}}.
+ //- checklist activity --------------------------------------------------
+ if($eq activity.activityType 'addChecklist')
+ | {{{_ 'activity-checklist-added' cardLink}}}.
+ if($eq mode 'card')
+ .activity-checklist
+ +viewer
+ = activity.checklist.title
+ else
+ a.activity-checklist(href="{{ activity.card.absoluteUrl }}")
+ +viewer
+ = activity.checklist.title
- if($eq activityType 'createCustomField')
- | {{_ 'activity-customfield-created' customField}}.
+ if($eq activity.activityType 'removedChecklist')
+ | {{{_ 'activity-checklist-removed' cardLink}}}.
- if($eq activityType 'createList')
- | {{_ 'activity-added' list.title boardLabel}}.
+ if($eq activity.activityType 'completeChecklist')
+ | {{{_ 'activity-checklist-completed' activity.checklist.title cardLink}}}.
- if($eq activityType 'createSwimlane')
- | {{_ 'activity-added' swimlane.title boardLabel}}.
+ if($eq activity.activityType 'uncompleteChecklist')
+ | {{{_ 'activity-checklist-uncompleted' activity.checklist.title cardLink}}}.
- if($eq activityType 'removeList')
- | {{_ 'activity-removed' title boardLabel}}.
+ if($eq activity.activityType 'checkedItem')
+ | {{{_ 'activity-checked-item' checkItem activity.checklist.title cardLink}}}.
- if($eq activityType 'importBoard')
- | {{{_ 'activity-imported-board' boardLabel sourceLink}}}.
+ if($eq activity.activityType 'uncheckedItem')
+ | {{{_ 'activity-unchecked-item' checkItem activity.checklist.title cardLink}}}.
- if($eq activityType 'importCard')
- | {{{_ 'activity-imported' cardLink boardLabel sourceLink}}}.
+ if($eq activity.activityType 'addChecklistItem')
+ | {{{_ 'activity-checklist-item-added' activity.checklist.title cardLink}}}.
+ .activity-checklist(href="{{ activity.card.absoluteUrl }}")
+ +viewer
+ = activity.checklistItem.title
- if($eq activityType 'importList')
- | {{{_ 'activity-imported' listLabel boardLabel sourceLink}}}.
+ if($eq activity.activityType 'removedChecklistItem')
+ | {{{_ 'activity-checklist-item-removed' activity.checklist.title cardLink}}}.
- if($eq activityType 'joinMember')
- if($eq user._id member._id)
- | {{{_ 'activity-joined' cardLink}}}.
+ //- comment activity ----------------------------------------------------
+ if($eq mode 'card')
+ //- if we are in card mode we display the comment in a way that it
+ //- can be edited by the owner
+ if($eq activity.activityType 'addComment')
+ +inlinedForm(classNames='js-edit-comment')
+ +editor(autofocus=true)
+ = activity.comment.text
+ .edit-controls
+ button.primary(type="submit") {{_ 'edit'}}
else
- | {{{_ 'activity-added' memberLink cardLink}}}.
-
- if($eq activityType 'moveCardBoard')
- | {{{_ 'activity-moved' cardLink oldBoardName boardName}}}.
-
- if($eq activityType 'moveCard')
- | {{{_ 'activity-moved' cardLink oldList.title list.title}}}.
-
- if($eq activityType 'removeBoardMember')
- | {{{_ 'activity-excluded' memberLink boardLabel}}}.
+ .activity-comment
+ +viewer
+ = activity.comment.text
+ span(title=activity.createdAt).activity-meta {{ moment activity.createdAt }}
+ if ($eq currentUser._id activity.comment.userId)
+ = ' - '
+ a.js-open-inlined-form {{_ "edit"}}
+ = ' - '
+ a.js-delete-comment {{_ "delete"}}
- if($eq activityType 'restoredCard')
- | {{{_ 'activity-sent' cardLink boardLabel}}}.
+ if($eq activity.activityType 'deleteComment')
+ | {{{_ 'activity-deleteComment' currentData.commentId}}}.
- if($eq activityType 'addedLabel')
- | {{{_ 'activity-added-label' lastLabel cardLink}}}.
+ if($eq activity.activityType 'editComment')
+ | {{{_ 'activity-editComment' currentData.commentId}}}.
+ else
+ //- if we are not in card mode we only display a summary of the comment
+ if($eq activity.activityType 'addComment')
+ | {{{_ 'activity-on' cardLink}}}
+ a.activity-comment(href="{{ activity.card.absoluteUrl }}")
+ +viewer
+ = activity.comment.text
- if($eq activityType 'removedLabel')
- | {{{_ 'activity-removed-label' lastLabel cardLink}}}.
+ //- customField activity ------------------------------------------------
+ if($eq mode 'board')
+ if($eq activity.activityType 'createCustomField')
+ | {{_ 'activity-customfield-created' customField}}.
- if($eq activityType 'setCustomField')
+ if($eq activity.activityType 'setCustomField')
| {{{_ 'activity-set-customfield' lastCustomField lastCustomFieldValue cardLink}}}.
- if($eq activityType 'unsetCustomField')
+ if($eq activity.activityType 'unsetCustomField')
| {{{_ 'activity-unset-customfield' lastCustomField cardLink}}}.
- if($eq activityType 'unjoinMember')
- if($eq user._id member._id)
- | {{{_ 'activity-unjoined' cardLink}}}.
- else
- | {{{_ 'activity-removed' memberLink cardLink}}}.
+ //- label activity ------------------------------------------------------
+ if($eq activity.activityType 'addedLabel')
+ | {{{_ 'activity-added-label' lastLabel cardLink}}}.
- span(title=createdAt).activity-meta {{ moment createdAt }}
+ if($eq activity.activityType 'removedLabel')
+ | {{{_ 'activity-removed-label' lastLabel cardLink}}}.
-template(name="cardActivities")
- each currentCard.activities
- .activity
- +userAvatar(userId=user._id)
- p.activity-desc
- +memberName(user=user)
- if($eq activityType 'createCard')
- | {{_ 'activity-added' cardLabel listName}}.
- if($eq activityType 'importCard')
- | {{{_ 'activity-imported' cardLabel list.title sourceLink}}}.
- if($eq activityType 'joinMember')
- if($eq user._id member._id)
- | {{_ 'activity-joined' cardLabel}}.
- else
- | {{{_ 'activity-added' memberLink cardLabel}}}.
- if($eq activityType 'unjoinMember')
- if($eq user._id member._id)
- | {{_ 'activity-unjoined' cardLabel}}.
- else
- | {{{_ 'activity-removed' cardLabel memberLink}}}.
- if($eq activityType 'archivedCard')
- | {{_ 'activity-archived' cardLabel}}.
+ //- list activity -------------------------------------------------------
+ if($neq mode 'card')
+ if($eq activity.activityType 'createList')
+ | {{{_ 'activity-added' listLabel boardLabel}}}.
- if($eq activityType 'addedLabel')
- | {{{_ 'activity-added-label-card' lastLabel }}}.
-
- if($eq activityType 'removedLabel')
- | {{{_ 'activity-removed-label-card' lastLabel }}}.
+ if($eq activity.activityType 'importList')
+ | {{{_ 'activity-imported' listLabel boardLabel sourceLink}}}.
- if($eq activityType 'removeChecklist')
- | {{{_ 'activity-checklist-removed' cardLabel}}}.
+ if($eq activity.activityType 'removeList')
+ | {{{_ 'activity-removed' activity.title boardLabel}}}.
- if($eq activityType 'checkedItem')
- | {{{_ 'activity-checked-item-card' checkItem checklist.title }}}.
+ if($eq activity.activityType 'archivedList')
+ | {{_ 'activity-archived' listLabel}}.
- if($eq activityType 'uncheckedItem')
- | {{{_ 'activity-unchecked-item-card' checkItem checklist.title }}}.
+ //- member activity ----------------------------------------------------
+ if($eq activity.activityType 'joinMember')
+ if($eq user._id activity.member._id)
+ | {{{_ 'activity-joined' cardLink}}}.
+ else
+ | {{{_ 'activity-added' memberLink cardLink}}}.
- if($eq activityType 'checklistCompleted')
- | {{{_ 'activity-checklist-completed-card' checklist.title }}}.
+ if($eq activity.activityType 'unjoinMember')
+ if($eq user._id activity.member._id)
+ | {{{_ 'activity-unjoined' cardLink}}}.
+ else
+ | {{{_ 'activity-removed' memberLink cardLink}}}.
- if($eq activityType 'checklistUncompleted')
- | {{{_ 'activity-checklist-uncompleted-card' checklist.title }}}.
+ //- swimlane activity --------------------------------------------------
+ if($neq mode 'card')
+ if($eq activity.activityType 'createSwimlane')
+ | {{{_ 'activity-added' activity.swimlane.title boardLabel}}}.
- if($eq activityType 'restoredCard')
- | {{_ 'activity-sent' cardLabel boardLabel}}.
- if($eq activityType 'moveCard')
- | {{_ 'activity-moved' cardLabel oldList.title list.title}}.
+ if($eq activity.activityType 'archivedSwimlane')
+ | {{_ 'activity-archived' activity.swimlane.title}}.
- if($eq activityType 'moveCardBoard')
- | {{{_ 'activity-moved' cardLink oldBoardName boardName}}}.
- if($eq activityType 'addAttachment')
- | {{{_ 'activity-attached' attachmentLink cardLabel}}}.
- if attachment.isImage
- img.attachment-image-preview(src=attachment.url)
- if($eq activityType 'deleteAttachment')
- | {{{_ 'activity-delete-attach' cardLabel}}}.
- if($eq activityType 'removedChecklist')
- | {{{_ 'activity-checklist-removed' cardLabel}}}.
- if($eq activityType 'addChecklist')
- | {{{_ 'activity-checklist-added' cardLabel}}}.
- .activity-checklist
- +viewer
- = checklist.title
- if($eq activityType 'addChecklistItem')
- | {{{_ 'activity-checklist-item-added' checklist.title cardLink}}}.
- .activity-checklist(href="{{ card.absoluteUrl }}")
- +viewer
- = checklistItem.title
-
- if(currentData.timeKey)
- | {{{_ activityType }}}
+ //- I don't understand this part ----------------------------------------
+ if(currentData.timeKey)
+ | {{{_ activity.activityType }}}
+ = ' '
+ i(title=currentData.timeValue).activity-meta {{ moment currentData.timeValue 'LLL' }}
+ if (currentData.timeOldValue)
= ' '
- i(title=currentData.timeValue).activity-meta {{ moment currentData.timeValue 'LLL' }}
- if (currentData.timeOldValue)
- = ' '
- | {{{_ "previous_as" }}}
- = ' '
- i(title=currentData.timeOldValue).activity-meta {{ moment currentData.timeOldValue 'LLL' }}
- = ' @'
- else if(currentData.timeValue)
- | {{{_ 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)
- = comment.text
- .edit-controls
- button.primary(type="submit") {{_ 'edit'}}
- else
- .activity-comment
- +viewer
- = comment.text
- span(title=createdAt).activity-meta {{ moment createdAt }}
- if ($eq currentUser._id comment.userId)
- = ' - '
- a.js-open-inlined-form {{_ "edit"}}
- = ' - '
- a.js-delete-comment {{_ "delete"}}
-
- else
- span(title=createdAt).activity-meta {{ moment createdAt }}
+ | {{{_ "previous_as" }}}
+ = ' '
+ i(title=currentData.timeOldValue).activity-meta {{ moment currentData.timeOldValue 'LLL' }}
+ = ' @'
+ else if(currentData.timeValue)
+ | {{{_ activity.activityType currentData.timeValue}}}
+
+ span(title=activity.createdAt).activity-meta {{ moment activity.createdAt }}
diff --git a/client/components/activities/activities.js b/client/components/activities/activities.js
index b082273a..5d356f6e 100644
--- a/client/components/activities/activities.js
+++ b/client/components/activities/activities.js
@@ -41,7 +41,9 @@ BlazeComponent.extendComponent({
});
});
},
+}).register('activities');
+BlazeComponent.extendComponent({
loadNextPage() {
if (this.loadNextPageLocked === false) {
this.page.set(this.page.get() + 1);
@@ -50,41 +52,37 @@ BlazeComponent.extendComponent({
},
checkItem() {
- const checkItemId = this.currentData().checklistItemId;
+ const checkItemId = this.currentData().activity.checklistItemId;
const checkItem = ChecklistItems.findOne({ _id: checkItemId });
- return checkItem.title;
+ return checkItem && checkItem.title;
},
boardLabel() {
+ const data = this.currentData();
+ if (data.mode !== 'board') {
+ return createBoardLink(data.activity.board(), data.activity.listName);
+ }
return TAPi18n.__('this-board');
},
cardLabel() {
+ const data = this.currentData();
+ if (data.mode !== 'card') {
+ return createCardLink(this.currentData().activity.card());
+ }
return TAPi18n.__('this-card');
},
cardLink() {
- const card = this.currentData().card();
- return (
- card &&
- Blaze.toHTML(
- HTML.A(
- {
- href: card.absoluteUrl(),
- class: 'action-card',
- },
- card.title,
- ),
- )
- );
+ return createCardLink(this.currentData().activity.card());
},
lastLabel() {
- const lastLabelId = this.currentData().labelId;
+ const lastLabelId = this.currentData().activity.labelId;
if (!lastLabelId) return null;
- const lastLabel = Boards.findOne(Session.get('currentBoard')).getLabelById(
- lastLabelId,
- );
+ const lastLabel = Boards.findOne(
+ this.currentData().activity.boardId,
+ ).getLabelById(lastLabelId);
if (lastLabel && (lastLabel.name === undefined || lastLabel.name === '')) {
return lastLabel.color;
} else {
@@ -94,7 +92,7 @@ BlazeComponent.extendComponent({
lastCustomField() {
const lastCustomField = CustomFields.findOne(
- this.currentData().customFieldId,
+ this.currentData().activity.customFieldId,
);
if (!lastCustomField) return null;
return lastCustomField.name;
@@ -102,10 +100,10 @@ BlazeComponent.extendComponent({
lastCustomFieldValue() {
const lastCustomField = CustomFields.findOne(
- this.currentData().customFieldId,
+ this.currentData().activity.customFieldId,
);
if (!lastCustomField) return null;
- const value = this.currentData().value;
+ const value = this.currentData().activity.value;
if (
lastCustomField.settings.dropdownItems &&
lastCustomField.settings.dropdownItems.length > 0
@@ -122,11 +120,13 @@ BlazeComponent.extendComponent({
},
listLabel() {
- return this.currentData().list().title;
+ const activity = this.currentData().activity;
+ const list = activity.list();
+ return (list && list.title) || activity.title;
},
sourceLink() {
- const source = this.currentData().source;
+ const source = this.currentData().activity.source;
if (source) {
if (source.url) {
return Blaze.toHTML(
@@ -146,30 +146,31 @@ BlazeComponent.extendComponent({
memberLink() {
return Blaze.toHTMLWithData(Template.memberName, {
- user: this.currentData().member(),
+ user: this.currentData().activity.member(),
});
},
attachmentLink() {
- const attachment = this.currentData().attachment();
+ const attachment = this.currentData().activity.attachment();
// trying to display url before file is stored generates js errors
return (
- attachment &&
- attachment.url({ download: true }) &&
- Blaze.toHTML(
- HTML.A(
- {
- href: attachment.url({ download: true }),
- target: '_blank',
- },
- attachment.name(),
- ),
- )
+ (attachment &&
+ attachment.url({ download: true }) &&
+ Blaze.toHTML(
+ HTML.A(
+ {
+ href: attachment.url({ download: true }),
+ target: '_blank',
+ },
+ attachment.name(),
+ ),
+ )) ||
+ this.currentData().activity.attachmentName
);
},
customField() {
- const customField = this.currentData().customField();
+ const customField = this.currentData().activity.customField();
if (!customField) return null;
return customField.name;
},
@@ -179,7 +180,7 @@ BlazeComponent.extendComponent({
{
// XXX We should use Popup.afterConfirmation here
'click .js-delete-comment'() {
- const commentId = this.currentData().commentId;
+ const commentId = this.currentData().activity.commentId;
CardComments.remove(commentId);
},
'submit .js-edit-comment'(evt) {
@@ -187,7 +188,7 @@ BlazeComponent.extendComponent({
const commentText = this.currentComponent()
.getValue()
.trim();
- const commentId = Template.parentData().commentId;
+ const commentId = Template.parentData().activity.commentId;
if (commentText) {
CardComments.update(commentId, {
$set: {
@@ -199,4 +200,36 @@ BlazeComponent.extendComponent({
},
];
},
-}).register('activities');
+}).register('activity');
+
+function createCardLink(card) {
+ return (
+ card &&
+ Blaze.toHTML(
+ HTML.A(
+ {
+ href: card.absoluteUrl(),
+ class: 'action-card',
+ },
+ card.title,
+ ),
+ )
+ );
+}
+
+function createBoardLink(board, list) {
+ let text = board.title;
+ if (list) text += `: ${list}`;
+ return (
+ board &&
+ Blaze.toHTML(
+ HTML.A(
+ {
+ href: board.absoluteUrl(),
+ class: 'action-board',
+ },
+ text,
+ ),
+ )
+ );
+}
diff --git a/client/components/activities/activities.styl b/client/components/activities/activities.styl
index 380e7b40..f3b1acdd 100644
--- a/client/components/activities/activities.styl
+++ b/client/components/activities/activities.styl
@@ -9,7 +9,7 @@
clear: both
.activity
- margin: 10px 0
+ margin: 0.5px 0
display: flex
.member
diff --git a/client/components/activities/comments.styl b/client/components/activities/comments.styl
index 22f9c482..ccf24b72 100644
--- a/client/components/activities/comments.styl
+++ b/client/components/activities/comments.styl
@@ -46,3 +46,23 @@
&:is-open
cursor: auto
+
+.comment-item
+ background-color: #fff
+ border: 0
+ box-shadow: 0 1px 2px rgba(0, 0, 0, .23)
+ color: #8c8c8c
+ height: 36px
+ margin: 4px 4px 6px 0
+ width: 92%
+
+ &:hover
+ background: darken(white, 12%)
+
+ &.add-comment
+ display: flex
+ margin: 5px
+
+ a
+ display: block
+ margin: auto
diff --git a/client/components/boards/boardArchive.js b/client/components/boards/boardArchive.js
index d3e65bd8..5a5cf772 100644
--- a/client/components/boards/boardArchive.js
+++ b/client/components/boards/boardArchive.js
@@ -7,7 +7,7 @@ BlazeComponent.extendComponent({
return Boards.find(
{ archived: true },
{
- sort: ['title'],
+ sort: { sort: 1 /* boards default sorting */ },
},
);
},
diff --git a/client/components/boards/boardBody.js b/client/components/boards/boardBody.js
index 55b8c0a1..4e473f18 100644
--- a/client/components/boards/boardBody.js
+++ b/client/components/boards/boardBody.js
@@ -1,7 +1,7 @@
import { Cookies } from 'meteor/ostrio:cookies';
const cookies = new Cookies();
const subManager = new SubsManager();
-const { calculateIndex, enableClickOnTouch } = Utils;
+const { calculateIndex } = Utils;
const swimlaneWhileSortingHeight = 150;
BlazeComponent.extendComponent({
@@ -191,9 +191,6 @@ BlazeComponent.extendComponent({
},
});
- // ugly touch event hotfix
- enableClickOnTouch('.js-swimlane:not(.placeholder)');
-
this.autorun(() => {
let showDesktopDragHandles = false;
currentUser = Meteor.user();
@@ -205,20 +202,17 @@ BlazeComponent.extendComponent({
} else {
showDesktopDragHandles = false;
}
- if (
- Utils.isMiniScreen() ||
- (!Utils.isMiniScreen() && showDesktopDragHandles)
- ) {
+ if (Utils.isMiniScreen() || showDesktopDragHandles) {
$swimlanesDom.sortable({
handle: '.js-swimlane-header-handle',
});
- } else {
+ } else if (!Utils.isMiniScreen() && !showDesktopDragHandles) {
$swimlanesDom.sortable({
handle: '.swimlane-header',
});
}
- // Disable drag-dropping if the current user is not a board member or is comment only
+ // Disable drag-dropping if the current user is not a board member
$swimlanesDom.sortable('option', 'disabled', !userIsMember());
});
diff --git a/client/components/boards/boardHeader.jade b/client/components/boards/boardHeader.jade
index 53a74f76..4c0edac4 100644
--- a/client/components/boards/boardHeader.jade
+++ b/client/components/boards/boardHeader.jade
@@ -193,20 +193,6 @@ template(name="boardChangeViewPopup")
| {{_ 'board-view-cal'}}
if $eq Utils.boardView "board-view-cal"
i.fa.fa-check
- if currentUser.isAdmin
- hr
- li
- with "board-view-rules"
- a.js-open-rules-view(title="{{_ 'rules'}}")
- i.fa.fa-magic
- | {{_ 'rules'}}
- else if currentUser.isBoardAdmin
- hr
- li
- with "board-view-rules"
- a.js-open-rules-view(title="{{_ 'rules'}}")
- i.fa.fa-magic
- | {{_ 'rules'}}
template(name="createBoard")
form
diff --git a/client/components/boards/boardHeader.js b/client/components/boards/boardHeader.js
index eea43bd3..be0146ec 100644
--- a/client/components/boards/boardHeader.js
+++ b/client/components/boards/boardHeader.js
@@ -30,22 +30,7 @@ Template.boardMenuPopup.events({
'click .js-outgoing-webhooks': Popup.open('outgoingWebhooks'),
'click .js-import-board': Popup.open('chooseBoardSource'),
'click .js-subtask-settings': Popup.open('boardSubtaskSettings'),
-});
-
-Template.boardMenuPopup.helpers({
- exportUrl() {
- const params = {
- boardId: Session.get('currentBoard'),
- };
- const queryParams = {
- authToken: Accounts._storedLoginToken(),
- };
- return FlowRouter.path('/api/boards/:boardId/export', params, queryParams);
- },
- exportFilename() {
- const boardId = Session.get('currentBoard');
- return `wekan-export-board-${boardId}.json`;
- },
+ 'click .js-card-settings': Popup.open('boardCardSettings'),
});
Template.boardChangeTitlePopup.events({
@@ -190,10 +175,6 @@ Template.boardChangeViewPopup.events({
Utils.setBoardView('board-view-cal');
Popup.close();
},
- 'click .js-open-rules-view'() {
- Modal.openWide('rulesMain');
- Popup.close();
- },
});
const CreateBoard = BlazeComponent.extendComponent({
diff --git a/client/components/boards/boardsList.jade b/client/components/boards/boardsList.jade
index 79bae502..bbce1d6f 100644
--- a/client/components/boards/boardsList.jade
+++ b/client/components/boards/boardsList.jade
@@ -1,10 +1,10 @@
template(name="boardList")
.wrapper
- ul.board-list.clearfix
+ ul.board-list.clearfix.js-boards
li.js-add-board
a.board-list-item.label {{_ 'add-board'}}
each boards
- li(class="{{#if isStarred}}starred{{/if}}" class=colorClass)
+ li(class="{{#if isStarred}}starred{{/if}}" class=colorClass).js-board
if isInvited
.board-list-item
span.details
@@ -39,7 +39,7 @@ template(name="boardList")
i.fa.js-archive-board(
class="fa-archive"
title="{{_ 'archive-board'}}")
- else if currentUser.isBoardAdmin
+ else if isAdministrable
i.fa.js-clone-board(
class="fa-clone"
title="{{_ 'duplicate-board'}}")
@@ -55,7 +55,7 @@ template(name="boardList")
title="{{_ 'archive-board'}}")
template(name="boardListHeaderBar")
- h1 {{_ 'my-boards'}}
+ h1 {{_ title }}
.board-header-btns.right
a.board-header-btn.js-open-archived-board
i.fa.fa-archive
diff --git a/client/components/boards/boardsList.js b/client/components/boards/boardsList.js
index 3918af82..9208fdb2 100644
--- a/client/components/boards/boardsList.js
+++ b/client/components/boards/boardsList.js
@@ -1,4 +1,5 @@
const subManager = new SubsManager();
+const { calculateIndex, enableClickOnTouch } = Utils;
Template.boardListHeaderBar.events({
'click .js-open-archived-board'() {
@@ -7,6 +8,9 @@ Template.boardListHeaderBar.events({
});
Template.boardListHeaderBar.helpers({
+ title() {
+ return FlowRouter.getRouteName() === 'home' ? 'my-boards' : 'public';
+ },
templatesBoardId() {
return Meteor.user() && Meteor.user().getTemplatesBoardId();
},
@@ -20,20 +24,80 @@ BlazeComponent.extendComponent({
Meteor.subscribe('setting');
},
- boards() {
- return Boards.find(
- {
- archived: false,
- 'members.userId': Meteor.userId(),
- type: 'board',
+ onRendered() {
+ const self = this;
+ function userIsAllowedToMove() {
+ return Meteor.user();
+ }
+
+ const itemsSelector = '.js-board:not(.placeholder)';
+
+ const $boards = this.$('.js-boards');
+ $boards.sortable({
+ connectWith: '.js-boards',
+ tolerance: 'pointer',
+ appendTo: '.board-list',
+ helper: 'clone',
+ distance: 7,
+ items: itemsSelector,
+ placeholder: 'board-wrapper placeholder',
+ start(evt, ui) {
+ ui.helper.css('z-index', 1000);
+ ui.placeholder.height(ui.helper.height());
+ EscapeActions.executeUpTo('popup-close');
+ },
+ stop(evt, ui) {
+ // To attribute the new index number, we need to get the DOM element
+ // of the previous and the following card -- if any.
+ const prevBoardDom = ui.item.prev('.js-board').get(0);
+ const nextBoardBom = ui.item.next('.js-board').get(0);
+ const sortIndex = calculateIndex(prevBoardDom, nextBoardBom, 1);
+
+ const boardDomElement = ui.item.get(0);
+ const board = Blaze.getData(boardDomElement);
+ // Normally the jquery-ui sortable library moves the dragged DOM element
+ // to its new position, which disrupts Blaze reactive updates mechanism
+ // (especially when we move the last card of a list, or when multiple
+ // users move some cards at the same time). To prevent these UX glitches
+ // we ask sortable to gracefully cancel the move, and to put back the
+ // DOM in its initial state. The card move is then handled reactively by
+ // Blaze with the below query.
+ $boards.sortable('cancel');
+
+ board.move(sortIndex.base);
},
- { sort: ['title'] },
- );
+ });
+
+ // ugly touch event hotfix
+ enableClickOnTouch(itemsSelector);
+
+ // Disable drag-dropping if the current user is not a board member or is comment only
+ this.autorun(() => {
+ $boards.sortable('option', 'disabled', !userIsAllowedToMove());
+ });
+ },
+
+ boards() {
+ let query = {
+ archived: false,
+ type: 'board',
+ };
+ if (FlowRouter.getRouteName() === 'home')
+ query['members.userId'] = Meteor.userId();
+ else query.permission = 'public';
+
+ return Boards.find(query, {
+ sort: { sort: 1 /* boards default sorting */ },
+ });
},
isStarred() {
const user = Meteor.user();
return user && user.hasStarred(this.currentData()._id);
},
+ isAdministrable() {
+ const user = Meteor.user();
+ return user && user.isBoardAdmin(this.currentData()._id);
+ },
hasOvertimeCards() {
subManager.subscribe('board', this.currentData()._id, false);
diff --git a/client/components/boards/boardsList.styl b/client/components/boards/boardsList.styl
index ae366e83..97d4f195 100644
--- a/client/components/boards/boardsList.styl
+++ b/client/components/boards/boardsList.styl
@@ -11,6 +11,19 @@ $spaceBetweenTiles = 16px
box-sizing: border-box
position: relative
+ &.placeholder:after
+ content: '';
+ display: block;
+ background: darken(white, 20%)
+ border-radius: 3px;
+ height: 106px;
+ margin: 8px;
+
+ &.ui-sortable-helper
+ cursor: grabbing
+ transform: rotate(4deg)
+ display: block !important
+
&.starred
.fa-star,
.fa-star-o
@@ -20,7 +33,7 @@ $spaceBetweenTiles = 16px
overflow: hidden;
background-color: #999
color: #f6f6f6
- height: 90px
+ height: auto
font-size: 16px
line-height: 22px
border-radius: 3px
@@ -31,6 +44,7 @@ $spaceBetweenTiles = 16px
margin: ($spaceBetweenTiles/2)
position: relative
text-decoration: none
+ word-wrap: break-word
&.tile
background-size: auto
@@ -55,7 +69,7 @@ $spaceBetweenTiles = 16px
.label
font-weight: normal
- line-height:90px
+ line-height: 56px
:hover
background-color:#939393
@@ -183,7 +197,7 @@ $spaceBetweenTiles = 16px
overflow: scroll
li
- width: 50%
+ width: 50%
.board-list-item
overflow: hidden
diff --git a/client/components/cards/attachments.jade b/client/components/cards/attachments.jade
index 2a96f4f4..61454fa7 100644
--- a/client/components/cards/attachments.jade
+++ b/client/components/cards/attachments.jade
@@ -38,18 +38,22 @@ template(name="attachmentsGalery")
| {{_ 'download'}}
if currentUser.isBoardMember
unless currentUser.isCommentOnly
- if isImage
- a(class="{{#if $eq ../coverId _id}}js-remove-cover{{else}}js-add-cover{{/if}}")
- i.fa.fa-thumb-tack
- if($eq ../coverId _id)
- | {{_ 'remove-cover'}}
- else
- | {{_ 'add-cover'}}
- a.js-confirm-delete
- i.fa.fa-close
- | {{_ 'delete'}}
+ unless currentUser.isWorker
+ if isImage
+ a(class="{{#if $eq ../coverId _id}}js-remove-cover{{else}}js-add-cover{{/if}}")
+ i.fa.fa-thumb-tack
+ if($eq ../coverId _id)
+ | {{_ 'remove-cover'}}
+ else
+ | {{_ 'add-cover'}}
+ a.js-confirm-delete
+ i.fa.fa-close
+ | {{_ 'delete'}}
if currentUser.isBoardMember
unless currentUser.isCommentOnly
- li.attachment-item.add-attachment
- a.js-add-attachment {{_ 'add-attachment' }}
+ unless currentUser.isWorker
+ //li.attachment-item.add-attachment
+ a.js-add-attachment
+ i.fa.fa-plus
+ | {{_ 'add-attachment' }}
diff --git a/client/components/cards/cardDate.js b/client/components/cards/cardDate.js
index cb54b033..c4b5c6d8 100644
--- a/client/components/cards/cardDate.js
+++ b/client/components/cards/cardDate.js
@@ -97,7 +97,8 @@ Template.dateBadge.helpers({
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
- !Meteor.user().isCommentOnly()
+ !Meteor.user().isCommentOnly() &&
+ !Meteor.user().isWorker()
);
},
});
diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade
index 2b4f44b9..ae97e0e9 100644
--- a/client/components/cards/cardDetails.jade
+++ b/client/components/cards/cardDetails.jade
@@ -8,16 +8,23 @@ template(name="cardDetails")
a.fa.fa-times-thin.close-card-details.js-close-card-details
if currentUser.isBoardMember
a.fa.fa-navicon.card-details-menu.js-open-card-details-menu
+ input.inline-input(type="text" id="cardURL_copy" value="{{ absoluteUrl }}")
+ a.fa.fa-link.card-copy-button.js-copy-link(
+ class="fa-link"
+ title="{{_ 'copy-card-link-to-clipboard'}}"
+ value="{{ absoluteUrl }}"
+ )
if isMiniScreen
a.fa.fa-times-thin.close-card-details-mobile-web.js-close-card-details
if currentUser.isBoardMember
a.fa.fa-navicon.card-details-menu-mobile-web.js-open-card-details-menu
+ a.fa.fa-link.card-copy-mobile-button
h2.card-details-title.js-card-title(
class="{{#if canModifyCard}}js-open-inlined-form is-editable{{/if}}")
+viewer
= getTitle
- if isWatching
- i.fa.fa-eye.card-details-watch
+ if isWatching
+ i.card-details-watch.fa.fa-eye
.card-details-path
each parentList
| &nbsp; &gt; &nbsp;
@@ -25,7 +32,7 @@ template(name="cardDetails")
// else
{{_ 'top-level-card'}}
if isLinkedCard
- h3.linked-card-location
+ a.linked-card-location.js-go-to-linked-card
+viewer
| {{getBoardTitle}} > {{getTitle}}
@@ -36,70 +43,105 @@ template(name="cardDetails")
p.warning {{_ 'card-archived'}}
.card-details-items
- .card-details-item.card-details-item-received
- h3.card-details-item-title {{_ 'card-received'}}
- if getReceived
- +cardReceivedDate
- else
- if canModifyCard
- a.js-received-date {{_ 'add'}}
-
- .card-details-item.card-details-item-start
- h3.card-details-item-title {{_ 'card-start'}}
- if getStart
- +cardStartDate
- else
- if canModifyCard
- a.js-start-date {{_ 'add'}}
-
- .card-details-item.card-details-item-due
- h3.card-details-item-title {{_ 'card-due'}}
- if getDue
- +cardDueDate
- else
+ if currentBoard.allowsReceivedDate
+ .card-details-item.card-details-item-received
+ h3
+ i.fa.fa-sign-out
+ card-details-item-title {{_ 'card-received'}}
+ if getReceived
+ +cardReceivedDate
+ else
+ if canModifyCard
+ unless currentUser.isWorker
+ a.card-label.add-label.js-received-date
+ i.fa.fa-plus
+
+ if currentBoard.allowsStartDate
+ .card-details-item.card-details-item-start
+ h3
+ i.fa.fa-hourglass-start
+ card-details-item-title {{_ 'card-start'}}
+ if getStart
+ +cardStartDate
+ else
+ if canModifyCard
+ unless currentUser.isWorker
+ a.card-label.add-label.js-start-date
+ i.fa.fa-plus
+
+ if currentBoard.allowsDueDate
+ .card-details-item.card-details-item-due
+ h3
+ i.fa.fa-sign-in
+ card-details-item-title {{_ 'card-due'}}
+ if getDue
+ +cardDueDate
+ else
+ if canModifyCard
+ unless currentUser.isWorker
+ a.card-label.add-label.js-due-date
+ i.fa.fa-plus
+
+ if currentBoard.allowsEndDate
+ .card-details-item.card-details-item-end
+ h3
+ i.fa.fa-hourglass-end
+ card-details-item-title {{_ 'card-end'}}
+ if getEnd
+ +cardEndDate
+ else
+ if canModifyCard
+ unless currentUser.isWorker
+ a.card-label.add-label.js-end-date
+ i.fa.fa-plus
+
+ //.card-details-items
+ if currentBoard.allowsMembers
+ .card-details-item.card-details-item-members
+ h3
+ i.fa.fa-users
+ card-details-item-title {{_ 'members'}}
+ each getMembers
+ +userAvatar(userId=this cardId=../_id)
+ | {{! XXX Hack to hide syntaxic coloration /// }}
if canModifyCard
- a.js-due-date {{_ 'add'}}
-
- .card-details-item.card-details-item-end
- h3.card-details-item-title {{_ 'card-end'}}
- if getEnd
- +cardEndDate
- else
+ unless currentUser.isWorker
+ a.member.add-member.card-details-item-add-button.js-add-members(title="{{_ 'card-members-title'}}")
+ i.fa.fa-plus
+
+ //if assigneeSelected
+ if currentBoard.allowsAssignee
+ .card-details-item.card-details-item-assignees
+ h3
+ i.fa.fa-user
+ card-details-item-title {{_ 'assignee'}}
+ each getAssignees
+ +userAvatarAssignee(userId=this cardId=../_id)
+ | {{! XXX Hack to hide syntaxic coloration /// }}
if canModifyCard
- a.js-end-date {{_ 'add'}}
-
- .card-details-items
- .card-details-item.card-details-item-members
- h3.card-details-item-title {{_ 'members'}}
- each getMembers
- +userAvatar(userId=this cardId=../_id)
- | {{! XXX Hack to hide syntaxic coloration /// }}
- if canModifyCard
- a.member.add-member.card-details-item-add-button.js-add-members(title="{{_ 'card-members-title'}}")
- i.fa.fa-plus
-
- .card-details-item.card-details-item-assignees
- h3.card-details-item-title {{_ 'assignee'}}
- each getAssignees
- +userAvatarAssignee(userId=this cardId=../_id)
- | {{! XXX Hack to hide syntaxic coloration /// }}
- if canModifyCard
- unless assigneeSelected
a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}")
i.fa.fa-plus
+ if currentUser.isWorker
+ unless assigneeSelected
+ a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}")
+ i.fa.fa-plus
+
+ if currentBoard.allowsLabels
+ .card-details-item.card-details-item-labels
+ h3
+ i.fa.fa-tags
+ card-details-item-title {{_ 'labels'}}
+ a(class="{{#if canModifyCard}}js-add-labels{{else}}is-disabled{{/if}}" title="{{_ 'card-labels-title'}}")
+ each labels
+ span.card-label(class="card-label-{{color}}" title=name)
+ +viewer
+ = name
+ if canModifyCard
+ unless currentUser.isWorker
+ a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}")
+ i.fa.fa-plus
- .card-details-item.card-details-item-labels
- h3.card-details-item-title {{_ 'labels'}}
- a(class="{{#if canModifyCard}}js-add-labels{{else}}is-disabled{{/if}}" title="{{_ 'card-labels-title'}}")
- each labels
- span.card-label(class="card-label-{{color}}" title=name)
- +viewer
- = name
- if canModifyCard
- a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}")
- i.fa.fa-plus
-
- .card-details-items
+ //.card-details-items
each customFieldsWD
.card-details-item.card-details-item-customfield
h3.card-details-item-title
@@ -107,7 +149,7 @@ template(name="cardDetails")
= definition.name
+cardCustomField
- .card-details-items
+ //.card-details-items
if getSpentTime
.card-details-item.card-details-item-spent
if getIsOvertime
@@ -116,84 +158,124 @@ template(name="cardDetails")
h3.card-details-item-title {{_ 'spent-time-hours'}}
+cardSpentTime
- //- XXX We should use "editable" to avoid repetiting ourselves
- if canModifyCard
- h3.card-details-item-title {{_ 'description'}}
- +inlinedCardDescription(classNames="card-description js-card-description")
- +editor(autofocus=true)
- | {{getUnsavedValue 'cardDescription' _id getDescription}}
- .edit-controls.clearfix
- button.primary(type="submit") {{_ 'save'}}
- a.fa.fa-times-thin.js-close-inlined-form
- else
- a.js-open-inlined-form
- if getDescription
+ //.card-details-items
+ if currentBoard.allowsRequestedBy
+ .card-details-item.card-details-item-name
+ h3
+ i.fa.fa-shopping-cart
+ card-details-item-title {{_ 'requested-by'}}
+ if canModifyCard
+ unless currentUser.isWorker
+ +inlinedForm(classNames="js-card-details-requester")
+ +editCardRequesterForm
+ else
+ a.js-open-inlined-form
+ if getRequestedBy
+ +viewer
+ = getRequestedBy
+ else
+ | {{_ 'add'}}
+ else if getRequestedBy
+viewer
- = getDescription
- else
- | {{_ 'edit'}}
- if (hasUnsavedValue 'cardDescription' _id)
- p.quiet
- | {{_ 'unsaved-description'}}
- a.js-open-inlined-form {{_ 'view-it'}}
- = ' - '
- a.js-close-inlined-form {{_ 'discard'}}
- else if getDescription
- h3.card-details-item-title {{_ 'description'}}
- +viewer
- = getDescription
+ = getRequestedBy
- .card-details-items
- .card-details-item.card-details-item-name
- h3.card-details-item-title {{_ 'requested-by'}}
- if canModifyCard
- +inlinedForm(classNames="js-card-details-requester")
- +editCardRequesterForm
- else
- a.js-open-inlined-form
- if getRequestedBy
- +viewer
- = getRequestedBy
- else
- | {{_ 'add'}}
- else if getRequestedBy
- +viewer
- = getRequestedBy
-
- .card-details-item.card-details-item-name
- h3.card-details-item-title {{_ 'assigned-by'}}
- if canModifyCard
- +inlinedForm(classNames="js-card-details-assigner")
- +editCardAssignerForm
- else
- a.js-open-inlined-form
- if getAssignedBy
- +viewer
- = getAssignedBy
+ if currentBoard.allowsAssignedBy
+ .card-details-item.card-details-item-name
+ h3
+ i.fa.fa-user-plus
+ card-details-item-title {{_ 'assigned-by'}}
+ if canModifyCard
+ unless currentUser.isWorker
+ +inlinedForm(classNames="js-card-details-assigner")
+ +editCardAssignerForm
else
- | {{_ 'add'}}
- else if getRequestedBy
- +viewer
- = getAssignedBy
-
- hr
- +checklists(cardId = _id)
+ a.js-open-inlined-form
+ if getAssignedBy
+ +viewer
+ = getAssignedBy
+ else
+ | {{_ 'add'}}
+ else if getRequestedBy
+ +viewer
+ = getAssignedBy
- if currentBoard.allowsSubtasks
+ if getVoteQuestion
hr
- +subtasks(cardId = _id)
-
- hr
- h3
- i.fa.fa-paperclip
- | {{_ 'attachments'}}
+ .vote-title
+ h3
+ i.fa.fa-thumbs-up
+ card-details-item-title {{_ 'vote-question'}}
+ .vote-result
+ if votePublic
+ a.card-label.card-label-green.js-show-positive-votes {{ voteCountPositive }}
+ a.card-label.card-label-red.js-show-negative-votes {{ voteCountNegative }}
+ else
+ .card-label.card-label-green {{ voteCountPositive }}
+ .card-label.card-label-red {{ voteCountNegative }}
+ +viewer
+ = getVoteQuestion
+ button.card-details-green.js-vote.js-vote-positive(class="{{#if voteState}}voted{{/if}}") {{_ 'vote-for-it'}}
+ button.card-details-red.js-vote.js-vote-negative(class="{{#if $eq voteState false}}voted{{/if}}") {{_ 'vote-against'}}
- +attachmentsGalery
+ //- XXX We should use "editable" to avoid repetiting ourselves
+ if canModifyCard
+ unless currentUser.isWorker
+ if currentBoard.allowsDescriptionTitle
+ hr
+ h3
+ i.fa.fa-align-left
+ card-details-item-title {{_ 'description'}}
+ if currentBoard.allowsDescriptionText
+ +inlinedCardDescription(classNames="card-description js-card-description")
+ +editor(autofocus=true)
+ | {{getUnsavedValue 'cardDescription' _id getDescription}}
+ .edit-controls.clearfix
+ button.primary(type="submit") {{_ 'save'}}
+ a.fa.fa-times-thin.js-close-inlined-form
+ else
+ if currentBoard.allowsDescriptionText
+ a.js-open-inlined-form
+ if getDescription
+ +viewer
+ = getDescription
+ else
+ | {{_ 'edit'}}
+ if (hasUnsavedValue 'cardDescription' _id)
+ p.quiet
+ | {{_ 'unsaved-description'}}
+ a.js-open-inlined-form {{_ 'view-it'}}
+ = ' - '
+ a.js-close-inlined-form {{_ 'discard'}}
+ else if getDescription
+ if currentBoard.allowsDescriptionTitle
+ hr
+ h3.card-details-item-title {{_ 'description'}}
+ if currentBoard.allowsDescriptionText
+ +viewer
+ = getDescription
+
+ .card-checklist-attachmentGalerys
+ .card-checklist-attachmentGalery.card-checklists
+ if currentBoard.allowsChecklists
+ hr
+ +checklists(cardId = _id)
+ if currentBoard.allowsSubtasks
+ hr
+ +subtasks(cardId = _id)
+ if currentBoard.allowsAttachments
+ hr
+ h3
+ i.fa.fa-paperclip
+ | {{_ 'attachments'}}
+ .card-checklist-attachmentGalery.card-attachmentGalery
+ +attachmentsGalery
hr
unless currentUser.isNoComments
.activity-title
- h3 {{ _ 'activity'}}
+ h3
+ i.fa.fa-history
+ | {{ _ 'activity'}}
if currentUser.isBoardMember
.material-toggle-switch
span.toggle-switch-title {{_ 'hide-system-messages'}}
@@ -202,9 +284,10 @@ template(name="cardDetails")
else
input.toggle-switch(type="checkbox" id="toggleButton")
label.toggle-label(for="toggleButton")
- if currentUser.isBoardMember
- unless currentUser.isNoComments
- +commentForm
+ if currentBoard.allowsComments
+ if currentUser.isBoardMember
+ unless currentUser.isNoComments
+ +commentForm
unless currentUser.isNoComments
if isLoaded.get
if isLinkedCard
@@ -235,32 +318,89 @@ template(name="editCardAssignerForm")
template(name="cardDetailsActionsPopup")
ul.pop-over-list
- li: a.js-toggle-watch-card {{#if isWatching}}{{_ 'unwatch'}}{{else}}{{_ 'watch'}}{{/if}}
+ li
+ a.js-toggle-watch-card
+ if isWatching
+ i.fa.fa-eye
+ | {{_ 'unwatch'}}
+ else
+ i.fa.fa-eye-slash
+ | {{_ 'watch'}}
if canModifyCard
- hr
- ul.pop-over-list
- //li: a.js-members {{_ 'card-edit-members'}}
- //li: a.js-labels {{_ 'card-edit-labels'}}
- //li: a.js-attachments {{_ 'card-edit-attachments'}}
- li: a.js-custom-fields {{_ 'card-edit-custom-fields'}}
- //li: a.js-received-date {{_ 'editCardReceivedDatePopup-title'}}
- //li: a.js-start-date {{_ 'editCardStartDatePopup-title'}}
- //li: a.js-due-date {{_ 'editCardDueDatePopup-title'}}
- //li: a.js-end-date {{_ 'editCardEndDatePopup-title'}}
- li: a.js-spent-time {{_ 'editCardSpentTimePopup-title'}}
- li: a.js-set-card-color {{_ 'setCardColorPopup-title'}}
- hr
- ul.pop-over-list
- li: a.js-move-card-to-top {{_ 'moveCardToTop-title'}}
- li: a.js-move-card-to-bottom {{_ 'moveCardToBottom-title'}}
- hr
+ unless currentUser.isWorker
+ hr
+ ul.pop-over-list
+ //li: a.js-members {{_ 'card-edit-members'}}
+ //li: a.js-labels {{_ 'card-edit-labels'}}
+ //li: a.js-attachments {{_ 'card-edit-attachments'}}
+ if getVoteQuestion
+ li
+ a.js-cancel-voting
+ i.fa.fa-thumbs-up
+ | {{_ 'card-cancel-voting'}}
+ else
+ li
+ a.js-start-voting
+ i.fa.fa-thumbs-up
+ | {{_ 'card-start-voting'}}
+ li
+ a.js-custom-fields
+ i.fa.fa-list-alt
+ | {{_ 'card-edit-custom-fields'}}
+ //li: a.js-received-date {{_ 'editCardReceivedDatePopup-title'}}
+ //li: a.js-start-date {{_ 'editCardStartDatePopup-title'}}
+ //li: a.js-due-date {{_ 'editCardDueDatePopup-title'}}
+ //li: a.js-end-date {{_ 'editCardEndDatePopup-title'}}
+ li
+ a.js-spent-time
+ i.fa.fa-clock-o
+ | {{_ 'editCardSpentTimePopup-title'}}
+ li
+ a.js-set-card-color
+ i.fa.fa-paint-brush
+ | {{_ 'setCardColorPopup-title'}}
+ hr
ul.pop-over-list
- li: a.js-move-card {{_ 'moveCardPopup-title'}}
- li: a.js-copy-card {{_ 'copyCardPopup-title'}}
- li: a.js-copy-checklist-cards {{_ 'copyChecklistToManyCardsPopup-title'}}
+ li
+ a.js-move-card-to-top
+ i.fa.fa-arrow-up
+ | {{_ 'moveCardToTop-title'}}
+ li
+ a.js-move-card-to-bottom
+ i.fa.fa-arrow-down
+ | {{_ 'moveCardToBottom-title'}}
+ unless currentUser.isWorker
+ hr
+ ul.pop-over-list
+ li
+ a.js-move-card
+ i.fa.fa-arrow-right
+ | {{_ 'moveCardPopup-title'}}
+ li
+ a.js-copy-card
+ i.fa.fa-copy
+ | {{_ 'copyCardPopup-title'}}
+ hr
+ ul.pop-over-list
+ li
+ a.js-copy-checklist-cards
+ i.fa.fa-list
+ i.fa.fa-copy
+ | {{_ 'copyChecklistToManyCardsPopup-title'}}
unless archived
- li: a.js-archive {{_ 'archive-card'}}
- li: a.js-more {{_ 'cardMorePopup-title'}}
+ hr
+ ul.pop-over-list
+ li
+ a.js-archive
+ i.fa.fa-arrow-right
+ i.fa.fa-archive
+ | {{_ 'archive-card'}}
+ hr
+ ul.pop-over-list
+ li
+ a.js-more
+ i.fa.fa-link
+ | {{_ 'cardMorePopup-title'}}
template(name="moveCardPopup")
+boardsAndLists
@@ -312,16 +452,27 @@ template(name="cardMembersPopup")
i.fa.fa-check
template(name="cardAssigneesPopup")
- ul.pop-over-list.js-card-assignee-list
- each board.activeMembers
- li.item(class="{{#if isCardAssignee}}active{{/if}}")
- a.name.js-select-assignee(href="#")
- +userAvatar(userId=user._id)
- span.full-name
- = user.profile.fullname
- | (<span class="username">{{ user.username }}</span>)
- if isCardAssignee
- i.fa.fa-check
+ unless currentUser.isWorker
+ ul.pop-over-list.js-card-assignee-list
+ each board.activeMembers
+ li.item(class="{{#if isCardAssignee}}active{{/if}}")
+ a.name.js-select-assignee(href="#")
+ +userAvatar(userId=user._id)
+ span.full-name
+ = user.profile.fullname
+ | (<span class="username">{{ user.username }}</span>)
+ if isCardAssignee
+ i.fa.fa-check
+ if currentUser.isWorker
+ ul.pop-over-list.js-card-assignee-list
+ li.item(class="{{#if currentUser.isCardAssignee}}active{{/if}}")
+ a.name.js-select-assignee(href="#")
+ +userAvatar(userId=currentUser._id)
+ span.full-name
+ = currentUser.profile.fullname
+ | (<span class="username">{{ currentUser.username }}</span>)
+ if currentUser.isCardAssignee
+ i.fa.fa-check
template(name="userAvatarAssignee")
a.assignee.js-assignee(title="{{userData.profile.fullname}} ({{userData.username}})")
@@ -349,11 +500,13 @@ template(name="cardAssigneePopup")
p.quiet @{{ user.username }}
ul.pop-over-list
if currentUser.isNotCommentOnly
+ unless currentUser.isWorker
li: a.js-remove-assignee {{_ 'remove-member-from-card'}}
- if $eq currentUser._id user._id
- with currentUser
- li: a.js-edit-profile {{_ 'edit-profile'}}
+ unless currentUser.isWorker
+ if $eq currentUser._id user._id
+ with currentUser
+ li: a.js-edit-profile {{_ 'edit-profile'}}
template(name="userAvatarAssigneeInitials")
svg.avatar.avatar-assignee-initials(viewBox="0 0 {{viewPortWidth}} 15")
@@ -413,3 +566,35 @@ template(name="cardDeletePopup")
unless archived
p {{_ "card-delete-suggest-archive"}}
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
+
+template(name="cardStartVotingPopup")
+ form.edit-vote-question
+ .fields
+ label(for="vote") {{_ 'vote-question'}}
+ input.js-vote-field#vote(type="text" name="vote" value="{{card.getVoteQuestion}}" autofocus)
+ label(for="vote-public") {{_ 'vote-public'}}
+ a.js-toggle-vote-public
+ .materialCheckBox#vote-public(name="vote-public")
+
+ button.primary.confirm.js-submit {{_ 'save'}}
+ //- button.js-remove-color.negate.wide.right {{_ 'delete'}}
+
+template(name="positiveVoteMembersPopup")
+ ul.pop-over-list.js-card-member-list
+ each m in voteMemberPositive
+ li.item
+ a.name
+ +userAvatar(userId=m._id)
+ span.full-name
+ = m.profile.fullname
+ | (<span class="username">{{ m.username }}</span>)
+
+template(name="negativeVoteMembersPopup")
+ ul.pop-over-list.js-card-member-list
+ each m in voteMemberNegative
+ li.item
+ a.name
+ +userAvatar(userId=m._id)
+ span.full-name
+ = m.profile.fullname
+ | (<span class="username">{{ m.username }}</span>)
diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js
index 67120043..271fbe2f 100644
--- a/client/components/cards/cardDetails.js
+++ b/client/components/cards/cardDetails.js
@@ -1,5 +1,5 @@
const subManager = new SubsManager();
-const { calculateIndexData, enableClickOnTouch } = Utils;
+const { calculateIndexData } = Utils;
let cardColors;
Meteor.startup(() => {
@@ -38,6 +38,37 @@ BlazeComponent.extendComponent({
Meteor.subscribe('unsaved-edits');
},
+ voteState() {
+ const card = this.currentData();
+ const userId = Meteor.userId();
+ let state;
+ if (card.vote) {
+ if (card.vote.positive) {
+ state = _.contains(card.vote.positive, userId);
+ if (state === true) return true;
+ }
+ if (card.vote.negative) {
+ state = _.contains(card.vote.negative, userId);
+ if (state === true) return false;
+ }
+ }
+ return null;
+ },
+ votePublic() {
+ const card = this.currentData();
+ if (card.vote) return card.vote.public;
+ return null;
+ },
+ voteCountPositive() {
+ const card = this.currentData();
+ if (card.vote && card.vote.positive) return card.vote.positive.length;
+ return null;
+ },
+ voteCountNegative() {
+ const card = this.currentData();
+ if (card.vote && card.vote.negative) return card.vote.negative.length;
+ return null;
+ },
isWatching() {
const card = this.currentData();
return card.findWatcher(Meteor.userId());
@@ -51,7 +82,8 @@ BlazeComponent.extendComponent({
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
- !Meteor.user().isCommentOnly()
+ !Meteor.user().isCommentOnly() &&
+ !Meteor.user().isWorker()
);
},
@@ -199,9 +231,6 @@ BlazeComponent.extendComponent({
},
});
- // ugly touch event hotfix
- enableClickOnTouch('.card-checklist-items .js-checklist');
-
const $subtasksDom = this.$('.card-subtasks-items');
$subtasksDom.sortable({
@@ -237,20 +266,21 @@ BlazeComponent.extendComponent({
},
});
- // ugly touch event hotfix
- enableClickOnTouch('.card-subtasks-items .js-subtasks');
-
function userIsMember() {
return Meteor.user() && Meteor.user().isBoardMember();
}
// Disable sorting if the current user is not a board member
this.autorun(() => {
- if ($checklistsDom.data('sortable')) {
- $checklistsDom.sortable('option', 'disabled', !userIsMember());
+ const disabled = !userIsMember() || Utils.isMiniScreen();
+ if (
+ $checklistsDom.data('uiSortable') ||
+ $checklistsDom.data('sortable')
+ ) {
+ $checklistsDom.sortable('option', 'disabled', disabled);
}
- if ($subtasksDom.data('sortable')) {
- $subtasksDom.sortable('option', 'disabled', !userIsMember());
+ if ($subtasksDom.data('uiSortable') || $subtasksDom.data('sortable')) {
+ $subtasksDom.sortable('option', 'disabled', disabled);
}
});
},
@@ -278,6 +308,29 @@ BlazeComponent.extendComponent({
'click .js-close-card-details'() {
Utils.goBoardId(this.data().boardId);
},
+ 'click .js-copy-link'() {
+ StringToCopyElement = document.getElementById('cardURL_copy');
+ StringToCopyElement.select();
+ if (document.execCommand('copy')) {
+ StringToCopyElement.blur();
+ } else {
+ document.getElementById('cardURL_copy').selectionStart = 0;
+ document.getElementById('cardURL_copy').selectionEnd = 999;
+ document.execCommand('copy');
+ if (window.getSelection) {
+ if (window.getSelection().empty) {
+ // Chrome
+ window.getSelection().empty();
+ } else if (window.getSelection().removeAllRanges) {
+ // Firefox
+ window.getSelection().removeAllRanges();
+ }
+ } else if (document.selection) {
+ // IE?
+ document.selection.empty();
+ }
+ }
+ },
'click .js-open-card-details-menu': Popup.open('cardDetailsActions'),
'submit .js-card-description'(event) {
event.preventDefault();
@@ -317,6 +370,9 @@ BlazeComponent.extendComponent({
this.data().setRequestedBy('');
}
},
+ 'click .js-go-to-linked-card'() {
+ Utils.goCardId(this.data().linkedId);
+ },
'click .js-member': Popup.open('cardMember'),
'click .js-add-members': Popup.open('cardMembers'),
'click .js-assignee': Popup.open('cardAssignee'),
@@ -326,6 +382,8 @@ BlazeComponent.extendComponent({
'click .js-start-date': Popup.open('editCardStartDate'),
'click .js-due-date': Popup.open('editCardDueDate'),
'click .js-end-date': Popup.open('editCardEndDate'),
+ 'click .js-show-positive-votes': Popup.open('positiveVoteMembers'),
+ 'click .js-show-negative-votes': Popup.open('negativeVoteMembers'),
'mouseenter .js-card-details'() {
const parentComponent = this.parentComponent().parentComponent();
//on mobile view parent is Board, not BoardBody.
@@ -349,6 +407,18 @@ BlazeComponent.extendComponent({
'click #toggleButton'() {
Meteor.call('toggleSystemMessages');
},
+ 'click .js-vote'(e) {
+ const forIt = $(e.target).hasClass('js-vote-positive');
+ let newState = null;
+ if (
+ this.voteState() === null ||
+ (this.voteState() === false && forIt) ||
+ (this.voteState() === true && !forIt)
+ ) {
+ newState = forIt;
+ }
+ this.data().setVote(Meteor.userId(), newState);
+ },
},
];
},
@@ -370,6 +440,54 @@ Template.cardDetails.helpers({
});
},
+ receivedSelected() {
+ if (this.getReceived().length === 0) {
+ return false;
+ } else {
+ return true;
+ }
+ },
+
+ startSelected() {
+ if (this.getStart().length === 0) {
+ return false;
+ } else {
+ return true;
+ }
+ },
+
+ endSelected() {
+ if (this.getEnd().length === 0) {
+ return false;
+ } else {
+ return true;
+ }
+ },
+
+ dueSelected() {
+ if (this.getDue().length === 0) {
+ return false;
+ } else {
+ return true;
+ }
+ },
+
+ memberSelected() {
+ if (this.getMembers().length === 0) {
+ return false;
+ } else {
+ return true;
+ }
+ },
+
+ labelSelected() {
+ if (this.getLabels().length === 0) {
+ return false;
+ } else {
+ return true;
+ }
+ },
+
assigneeSelected() {
if (this.getAssignees().length === 0) {
return false;
@@ -378,6 +496,22 @@ Template.cardDetails.helpers({
}
},
+ requestBySelected() {
+ if (this.getRequestBy().length === 0) {
+ return false;
+ } else {
+ return true;
+ }
+ },
+
+ assigneeBySelected() {
+ if (this.getAssigneeBy().length === 0) {
+ return false;
+ } else {
+ return true;
+ }
+ },
+
memberType() {
const user = Users.findOne(this.userId);
return user && user.isBoardAdmin() ? 'admin' : 'normal';
@@ -466,6 +600,7 @@ Template.cardDetailsActionsPopup.events({
'click .js-assignees': Popup.open('cardAssignees'),
'click .js-labels': Popup.open('cardLabels'),
'click .js-attachments': Popup.open('cardAttachments'),
+ 'click .js-start-voting': Popup.open('cardStartVoting'),
'click .js-custom-fields': Popup.open('cardCustomFields'),
'click .js-received-date': Popup.open('editCardReceivedDate'),
'click .js-start-date': Popup.open('editCardStartDate'),
@@ -476,6 +611,11 @@ Template.cardDetailsActionsPopup.events({
'click .js-copy-card': Popup.open('copyCard'),
'click .js-copy-checklist-cards': Popup.open('copyChecklistToManyCards'),
'click .js-set-card-color': Popup.open('setCardColor'),
+ 'click .js-cancel-voting'(event) {
+ event.preventDefault();
+ this.unsetVote();
+ Popup.close();
+ },
'click .js-move-card-to-top'(event) {
event.preventDefault();
const minOrder = _.min(
@@ -578,7 +718,7 @@ BlazeComponent.extendComponent({
_id: { $ne: Meteor.user().getTemplatesBoardId() },
},
{
- sort: ['title'],
+ sort: { sort: 1 /* boards default sorting */ },
},
);
return boards;
@@ -754,7 +894,7 @@ BlazeComponent.extendComponent({
},
},
{
- sort: ['title'],
+ sort: { sort: 1 /* boards default sorting */ },
},
);
return boards;
@@ -851,6 +991,31 @@ BlazeComponent.extendComponent({
},
}).register('cardMorePopup');
+BlazeComponent.extendComponent({
+ onCreated() {
+ this.currentCard = this.currentData();
+ this.voteQuestion = new ReactiveVar(this.currentCard.voteQuestion);
+ },
+
+ events() {
+ return [
+ {
+ 'submit .edit-vote-question'(evt) {
+ evt.preventDefault();
+ const voteQuestion = evt.target.vote.value;
+ const publicVote = $('#vote-public').hasClass('is-checked');
+ this.currentCard.setVoteQuestion(voteQuestion, publicVote);
+ Popup.close();
+ },
+ 'click a.js-toggle-vote-public'(event) {
+ event.preventDefault();
+ $('#vote-public').toggleClass('is-checked');
+ },
+ },
+ ];
+ },
+}).register('cardStartVotingPopup');
+
// Close the card details pane by pressing escape
EscapeActions.register(
'detailsPane',
diff --git a/client/components/cards/cardDetails.styl b/client/components/cards/cardDetails.styl
index 3fc4d047..3e2beadd 100644
--- a/client/components/cards/cardDetails.styl
+++ b/client/components/cards/cardDetails.styl
@@ -4,6 +4,12 @@
avatar-radius = 50%
+#cardURL_copy
+ // Have clipboard text not visible by moving it to far left
+ position: absolute
+ left: -2000px
+ top: 0px
+
.assignee
border-radius: 3px
display: block
@@ -88,17 +94,18 @@ avatar-radius = 50%
animation: flexGrowIn 0.1s
box-shadow: 0 0 7px 0 darken(white, 30%)
transition: flex-basis 0.1s
+ box-sizing: border-box
.mCustomScrollBox
padding-left: 0
.ps-scrollbar-y-rail
pointer-event: all
- position: absolute;
+ position: absolute
.card-details-canvas
width: 470px
- padding-left: 20px;
+ padding-left: 20px
.card-details-header
margin: 0 -20px 5px
@@ -108,6 +115,8 @@ avatar-radius = 50%
.close-card-details,
.card-details-menu,
+ .card-copy-button,
+ .card-copy-mobile-button,
.close-card-details-mobile-web,
.card-details-menu-mobile-web
float: right
@@ -122,6 +131,16 @@ avatar-radius = 50%
padding: 5px
margin-right: 40px
+ .card-copy-button
+ font-size: 17px
+ padding: 10px
+ margin-right: 10px
+
+ .card-copy-mobile-button
+ font-size: 17px
+ padding: 10px
+ margin-right: 10px
+
.card-details-menu
font-size: 17px
padding: 10px
@@ -223,7 +242,7 @@ input[type="submit"].attachment-add-link-submit
.card-details-canvas
width: 100%
- padding-left: 0px;
+ padding-left: 0px
.card-details-header
.close-card-details
@@ -312,3 +331,13 @@ card-details-color(background, color...)
.card-details-indigo
card-details-color(#4b0082, #ffffff) //White text for better visibility
+
+.voted
+ opacity: .7
+.vote-title
+ display: flex
+ justify-content: space-between
+.vote-result
+ display: flex
+.js-show-positive-votes
+ cursor: pointer
diff --git a/client/components/cards/checklists.jade b/client/components/cards/checklists.jade
index 279d3671..1b1e088a 100644
--- a/client/components/cards/checklists.jade
+++ b/client/components/cards/checklists.jade
@@ -1,5 +1,7 @@
template(name="checklists")
- h3 {{_ 'checklists'}}
+ h3
+ i.fa.fa-check
+ | {{_ 'checklists'}}
if toggleDeleteDialog.get
.board-overlay#card-details-overlay
+checklistDeleteDialog(checklist = checklistToDelete)
@@ -86,7 +88,8 @@ template(name="checklistItems")
template(name='checklistItemDetail')
.js-checklist-item.checklist-item
if canModifyCard
- .check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
+ .check-box-container
+ .check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
.item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}")
+viewer
= item.title
diff --git a/client/components/cards/checklists.js b/client/components/cards/checklists.js
index 57939eb8..29573d2b 100644
--- a/client/components/cards/checklists.js
+++ b/client/components/cards/checklists.js
@@ -1,4 +1,4 @@
-const { calculateIndexData, enableClickOnTouch } = Utils;
+const { calculateIndexData, capitalize } = Utils;
function initSorting(items) {
items.sortable({
@@ -36,9 +36,6 @@ function initSorting(items) {
checklistItem.move(checklistId, sortIndex.base);
},
});
-
- // ugly touch event hotfix
- enableClickOnTouch('.js-checklist-item:not(.placeholder)');
}
BlazeComponent.extendComponent({
@@ -54,11 +51,15 @@ BlazeComponent.extendComponent({
return Meteor.user() && Meteor.user().isBoardMember();
}
- // Disable sorting if the current user is not a board member
+ // Disable sorting if the current user is not a board member or is a miniscreen
self.autorun(() => {
const $itemsDom = $(self.itemsDom);
- if ($itemsDom.data('sortable')) {
- $(self.itemsDom).sortable('option', 'disabled', !userIsMember());
+ if ($itemsDom.data('uiSortable') || $itemsDom.data('sortable')) {
+ $(self.itemsDom).sortable(
+ 'option',
+ 'disabled',
+ !userIsMember() || Utils.isMiniScreen(),
+ );
}
});
},
@@ -67,7 +68,8 @@ BlazeComponent.extendComponent({
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
- !Meteor.user().isCommentOnly()
+ !Meteor.user().isCommentOnly() &&
+ !Meteor.user().isWorker()
);
},
}).register('checklistDetail');
@@ -120,7 +122,8 @@ BlazeComponent.extendComponent({
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
- !Meteor.user().isCommentOnly()
+ !Meteor.user().isCommentOnly() &&
+ !Meteor.user().isWorker()
);
},
@@ -172,6 +175,16 @@ BlazeComponent.extendComponent({
}
},
+ focusChecklistItem(event) {
+ // If a new checklist is created, pre-fill the title and select it.
+ const checklist = this.currentData().checklist;
+ if (!checklist) {
+ const textarea = event.target;
+ textarea.value = capitalize(TAPi18n.__('r-checklist'));
+ textarea.select();
+ }
+ },
+
events() {
const events = {
'click .toggle-delete-checklist-dialog'(event) {
@@ -191,6 +204,7 @@ BlazeComponent.extendComponent({
'submit .js-edit-checklist-item': this.editChecklistItem,
'click .js-delete-checklist-item': this.deleteItem,
'click .confirm-checklist-delete': this.deleteChecklist,
+ 'focus .js-add-checklist-item': this.focusChecklistItem,
keydown: this.pressKey,
},
];
@@ -228,7 +242,8 @@ Template.checklistItemDetail.helpers({
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
- !Meteor.user().isCommentOnly()
+ !Meteor.user().isCommentOnly() &&
+ !Meteor.user().isWorker()
);
},
});
@@ -244,7 +259,7 @@ BlazeComponent.extendComponent({
events() {
return [
{
- 'click .js-checklist-item .check-box': this.toggleItem,
+ 'click .js-checklist-item .check-box-container': this.toggleItem,
},
];
},
diff --git a/client/components/cards/checklists.styl b/client/components/cards/checklists.styl
index 8ac37a15..0a6d688b 100644
--- a/client/components/cards/checklists.styl
+++ b/client/components/cards/checklists.styl
@@ -113,6 +113,9 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item
&:hover
background-color: darken(white, 8%)
+ .check-box-container
+ padding-right: 1px;
+
.check-box
margin: 0.1em 0 0 0;
&.is-checked
@@ -121,7 +124,7 @@ textarea.js-add-checklist-item, textarea.js-edit-checklist-item
.item-title
flex: 1
- padding-left: 10px;
+ margin-left: 10px;
&.is-checked
color: #8c8c8c
font-style: italic
diff --git a/client/components/cards/labels.styl b/client/components/cards/labels.styl
index 9d7c7553..ee946656 100644
--- a/client/components/cards/labels.styl
+++ b/client/components/cards/labels.styl
@@ -158,6 +158,8 @@
.edit-labels-pop-over
margin-bottom: 8px
+ .card-label .viewer p
+ margin: 0
.edit-labels-pop-over .shortcut
display: inline-block
diff --git a/client/components/cards/minicard.jade b/client/components/cards/minicard.jade
index 7dd220ee..b6ccd4d7 100644
--- a/client/components/cards/minicard.jade
+++ b/client/components/cards/minicard.jade
@@ -100,6 +100,10 @@ template(name="minicard")
if getDescription
.badge.badge-state-image-only(title=getDescription)
span.badge-icon.fa.fa-align-left
+ if getVoteQuestion
+ .badge.badge-state-image-only(title=getVoteQuestion)
+ span.badge-icon.fa.fa-thumbs-up
+ span.badge-icon.fa.fa-thumbs-down
if attachments.count
.badge
span.badge-icon.fa.fa-paperclip
diff --git a/client/components/cards/minicard.js b/client/components/cards/minicard.js
index 1ea608f5..da36b87f 100644
--- a/client/components/cards/minicard.js
+++ b/client/components/cards/minicard.js
@@ -36,24 +36,20 @@ Template.minicard.helpers({
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).showDesktopDragHandles;
+ } else if (cookies.has('showDesktopDragHandles')) {
+ return true;
} else {
- if (cookies.has('showDesktopDragHandles')) {
- return true;
- } else {
- return false;
- }
+ return false;
}
},
hiddenMinicardLabelText() {
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).hiddenMinicardLabelText;
+ } else if (cookies.has('hiddenMinicardLabelText')) {
+ return true;
} else {
- if (cookies.has('hiddenMinicardLabelText')) {
- return true;
- } else {
- return false;
- }
+ return false;
}
},
});
diff --git a/client/components/cards/minicard.styl b/client/components/cards/minicard.styl
index 8607e118..7d72a588 100644
--- a/client/components/cards/minicard.styl
+++ b/client/components/cards/minicard.styl
@@ -79,7 +79,7 @@
border-radius: top 2px
.minicard-labels
- float: right
+ float: none
display: flex
flex-wrap: wrap
diff --git a/client/components/cards/subtasks.jade b/client/components/cards/subtasks.jade
index 7e64e23f..df35bed3 100644
--- a/client/components/cards/subtasks.jade
+++ b/client/components/cards/subtasks.jade
@@ -1,5 +1,7 @@
template(name="subtasks")
- h3 {{_ 'subtasks'}}
+ h3
+ i.fa.fa-sitemap
+ | {{_ 'subtasks'}}
if toggleDeleteDialog.get
.board-overlay#card-details-overlay
+subtaskDeleteDialog(subtask = subtaskToDelete)
diff --git a/client/components/cards/subtasks.js b/client/components/cards/subtasks.js
index fab860bb..4cd15c11 100644
--- a/client/components/cards/subtasks.js
+++ b/client/components/cards/subtasks.js
@@ -3,7 +3,8 @@ BlazeComponent.extendComponent({
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
- !Meteor.user().isCommentOnly()
+ !Meteor.user().isCommentOnly() &&
+ !Meteor.user().isWorker()
);
},
}).register('subtaskDetail');
@@ -19,7 +20,22 @@ BlazeComponent.extendComponent({
const crtBoard = Boards.findOne(card.boardId);
const targetBoard = crtBoard.getDefaultSubtasksBoard();
const listId = targetBoard.getDefaultSubtasksListId();
- const swimlaneId = targetBoard.getDefaultSwimline()._id;
+
+ //Get the full swimlane data for the parent task.
+ const parentSwimlane = Swimlanes.findOne({
+ boardId: crtBoard._id,
+ _id: card.swimlaneId,
+ });
+ //find the swimlane of the same name in the target board.
+ const targetSwimlane = Swimlanes.findOne({
+ boardId: targetBoard._id,
+ title: parentSwimlane.title,
+ });
+ //If no swimlane with a matching title exists in the target board, fall back to the default swimlane.
+ const swimlaneId =
+ targetSwimlane === undefined
+ ? targetBoard.getDefaultSwimline()._id
+ : targetSwimlane._id;
if (title) {
const _id = Cards.insert({
@@ -55,7 +71,8 @@ BlazeComponent.extendComponent({
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
- !Meteor.user().isCommentOnly()
+ !Meteor.user().isCommentOnly() &&
+ !Meteor.user().isWorker()
);
},
@@ -154,7 +171,8 @@ Template.subtaskItemDetail.helpers({
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
- !Meteor.user().isCommentOnly()
+ !Meteor.user().isCommentOnly() &&
+ !Meteor.user().isWorker()
);
},
});
diff --git a/client/components/import/import.jade b/client/components/import/import.jade
index 5b52f417..1551a7dd 100644
--- a/client/components/import/import.jade
+++ b/client/components/import/import.jade
@@ -15,9 +15,6 @@ template(name="importTextarea")
p: label(for='import-textarea') {{_ instruction}} {{_ 'import-board-instruction-about-errors'}}
textarea.js-import-json(placeholder="{{_ 'import-json-placeholder'}}" autofocus)
| {{jsonText}}
- if isSandstorm
- h1.warning {{_ 'import-sandstorm-backup-warning'}}
- p.warning {{_ 'import-sandstorm-warning'}}
input.primary.wide(type="submit" value="{{_ 'import'}}")
template(name="importMapMembers")
diff --git a/client/components/lists/list.js b/client/components/lists/list.js
index 89d51e85..839304f8 100644
--- a/client/components/lists/list.js
+++ b/client/components/lists/list.js
@@ -1,6 +1,6 @@
import { Cookies } from 'meteor/ostrio:cookies';
const cookies = new Cookies();
-const { calculateIndex, enableClickOnTouch } = Utils;
+const { calculateIndex } = Utils;
BlazeComponent.extendComponent({
// Proxy
@@ -114,9 +114,6 @@ BlazeComponent.extendComponent({
},
});
- // ugly touch event hotfix
- enableClickOnTouch(itemsSelector);
-
this.autorun(() => {
let showDesktopDragHandles = false;
currentUser = Meteor.user();
@@ -129,18 +126,26 @@ BlazeComponent.extendComponent({
showDesktopDragHandles = false;
}
- if (!Utils.isMiniScreen() && showDesktopDragHandles) {
+ if (Utils.isMiniScreen() || showDesktopDragHandles) {
$cards.sortable({
handle: '.handle',
});
- } else {
+ } else if (!Utils.isMiniScreen() && !showDesktopDragHandles) {
$cards.sortable({
handle: '.minicard',
});
}
- // Disable drag-dropping if the current user is not a board member or is comment only
- $cards.sortable('option', 'disabled', !userIsMember());
+ if ($cards.data('uiSortable') || $cards.data('sortable')) {
+ $cards.sortable(
+ 'option',
+ 'disabled',
+ // Disable drag-dropping when user is not member
+ !userIsMember(),
+ // Not disable drag-dropping while in multi-selection mode
+ // MultiSelection.isActive() || !userIsMember(),
+ );
+ }
});
// We want to re-run this function any time a card is added.
@@ -176,12 +181,10 @@ Template.list.helpers({
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).showDesktopDragHandles;
+ } else if (cookies.has('showDesktopDragHandles')) {
+ return true;
} else {
- if (cookies.has('showDesktopDragHandles')) {
- return true;
- } else {
- return false;
- }
+ return false;
}
},
});
diff --git a/client/components/lists/list.styl b/client/components/lists/list.styl
index 27cf678c..bc7f763f 100644
--- a/client/components/lists/list.styl
+++ b/client/components/lists/list.styl
@@ -43,9 +43,6 @@
background: white
margin: -3px 0 8px
-.list-header-card-count
- height: 35px
-
.list-header-add
flex: 0 0 auto
padding: 20px 12px 4px
@@ -60,6 +57,9 @@
background-color: #e4e4e4;
border-bottom: 6px solid #e4e4e4;
+ &.list-header-card-count
+ min-height: 35px
+ height: auto
&.ui-sortable-handle
cursor: grab
diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js
index b0974705..88f88db0 100644
--- a/client/components/lists/listBody.js
+++ b/client/components/lists/listBody.js
@@ -189,7 +189,8 @@ BlazeComponent.extendComponent({
!this.reachedWipLimit() &&
Meteor.user() &&
Meteor.user().isBoardMember() &&
- !Meteor.user().isCommentOnly()
+ !Meteor.user().isCommentOnly() &&
+ !Meteor.user().isWorker()
);
},
@@ -410,7 +411,7 @@ BlazeComponent.extendComponent({
type: 'board',
},
{
- sort: ['title'],
+ sort: { sort: 1 /* boards default sorting */ },
},
);
return boards;
@@ -596,7 +597,7 @@ BlazeComponent.extendComponent({
type: 'board',
},
{
- sort: ['title'],
+ sort: { sort: 1 /* boards default sorting */ },
},
);
return boards;
@@ -742,9 +743,25 @@ BlazeComponent.extendComponent({
},
updateList() {
+ // Use fallback when requestIdleCallback is not available on iOS and Safari
+ // https://www.afasterweb.com/2017/11/20/utilizing-idle-moments/
+ checkIdleTime =
+ window.requestIdleCallback ||
+ function(handler) {
+ const startTime = Date.now();
+ return setTimeout(function() {
+ handler({
+ didTimeout: false,
+ timeRemaining() {
+ return Math.max(0, 50.0 - (Date.now() - startTime));
+ },
+ });
+ }, 1);
+ };
+
if (this.spinnerInView()) {
this.cardlimit.set(this.cardlimit.get() + InfiniteScrollIter);
- window.requestIdleCallback(() => this.updateList());
+ checkIdleTime(() => this.updateList());
}
},
diff --git a/client/components/lists/listHeader.jade b/client/components/lists/listHeader.jade
index 631f68a0..fa1faf34 100644
--- a/client/components/lists/listHeader.jade
+++ b/client/components/lists/listHeader.jade
@@ -10,7 +10,7 @@ template(name="listHeader")
a.list-header-left-icon.fa.fa-angle-left.js-unselect-list
h2.list-header-name(
title="{{ moment modifiedAt 'LLL' }}"
- class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}js-open-inlined-form is-editable{{/unless}}{{/if}}")
+ class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}")
+viewer
= title
if wipLimit.enabled
@@ -30,7 +30,6 @@ template(name="listHeader")
if canSeeAddCard
a.js-add-card.fa.fa-plus.list-header-plus-icon
a.fa.fa-navicon.js-open-list-menu
- a.list-header-handle.handle.fa.fa-arrows.js-list-handle
else
a.list-header-menu-icon.fa.fa-angle-right.js-select-list
a.list-header-handle.handle.fa.fa-arrows.js-list-handle
@@ -56,25 +55,47 @@ template(name="editListTitleForm")
template(name="listActionPopup")
ul.pop-over-list
- li: a.js-toggle-watch-list {{#if isWatching}}{{_ 'unwatch'}}{{else}}{{_ 'watch'}}{{/if}}
+ li
+ a.js-toggle-watch-list
+ if isWatching
+ i.fa.fa-eye
+ | {{_ 'unwatch'}}
+ else
+ i.fa.fa-eye-slash
+ | {{_ 'watch'}}
unless currentUser.isCommentOnly
- hr
- ul.pop-over-list
- li: a.js-set-color-list {{_ 'set-color-list'}}
- hr
+ unless currentUser.isWorker
+ ul.pop-over-list
+ li
+ a.js-set-color-list
+ i.fa.fa-paint-brush
+ | {{_ 'set-color-list'}}
ul.pop-over-list
if cards.count
- li: a.js-select-cards {{_ 'list-select-cards'}}
- hr
+ li
+ a.js-select-cards
+ i.fa.fa-check-square
+ | {{_ 'list-select-cards'}}
if currentUser.isBoardAdmin
ul.pop-over-list
- li: a.js-set-wip-limit {{#if isWipLimitEnabled }}{{_ 'edit-wip-limit'}}{{else}}{{_ 'setWipLimitPopup-title'}}{{/if}}
+ li
+ a.js-set-wip-limit
+ i.fa.fa-ban
+ | {{#if isWipLimitEnabled }}{{_ 'edit-wip-limit'}}{{else}}{{_ 'setWipLimitPopup-title'}}{{/if}}
+ unless currentUser.isWorker
hr
- ul.pop-over-list
- li: a.js-close-list {{_ 'archive-list'}}
+ ul.pop-over-list
+ li
+ a.js-close-list
+ i.fa.fa-arrow-right
+ i.fa.fa-archive
+ | {{_ 'archive-list'}}
hr
ul.pop-over-list
- li: a.js-more {{_ 'listMorePopup-title'}}
+ li
+ a.js-more
+ i.fa.fa-link
+ | {{_ 'listMorePopup-title'}}
template(name="boardLists")
ul.pop-over-list
@@ -94,7 +115,8 @@ template(name="listMorePopup")
input.inline-input(type="text" readonly value="{{ rootUrl }}")
| {{_ 'added'}}
span.date(title=list.createdAt) {{ moment createdAt 'LLL' }}
- a.js-delete {{_ 'delete'}}
+ unless currentUser.isWorker
+ a.js-delete {{_ 'delete'}}
template(name="listDeletePopup")
p {{_ "list-delete-pop"}}
diff --git a/client/components/lists/listHeader.js b/client/components/lists/listHeader.js
index 570cc30f..46dbd748 100644
--- a/client/components/lists/listHeader.js
+++ b/client/components/lists/listHeader.js
@@ -9,9 +9,10 @@ BlazeComponent.extendComponent({
canSeeAddCard() {
const list = Template.currentData();
return (
- !list.getWipLimit('enabled') ||
- list.getWipLimit('soft') ||
- !this.reachedWipLimit()
+ (!list.getWipLimit('enabled') ||
+ list.getWipLimit('soft') ||
+ !this.reachedWipLimit()) &&
+ !Meteor.user().isWorker()
);
},
@@ -109,12 +110,10 @@ Template.listHeader.helpers({
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).showDesktopDragHandles;
+ } else if (cookies.has('showDesktopDragHandles')) {
+ return true;
} else {
- if (cookies.has('showDesktopDragHandles')) {
- return true;
- } else {
- return false;
- }
+ return false;
}
},
});
diff --git a/client/components/main/editor.js b/client/components/main/editor.js
index 39c03aa9..081c6521 100755
--- a/client/components/main/editor.js
+++ b/client/components/main/editor.js
@@ -1,87 +1,3 @@
-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 mentions = [
@@ -94,13 +10,7 @@ Template.editor.onRendered(() => {
currentBoard
.activeMembers()
.map(member => {
- const user = Users.findOne(member.userId);
- if (user._id === Meteor.userId()) {
- return null;
- }
- const value = user.username;
- const username =
- value && value.match(/\s+/) ? `"${value}"` : value;
+ const username = Users.findOne(member.userId).username;
return username.includes(term) ? username : null;
})
.filter(Boolean),
@@ -126,10 +36,9 @@ Template.editor.onRendered(() => {
? [
['view', ['fullscreen']],
['table', ['table']],
- ['font', ['bold']],
- ['color', ['color']],
- ['insert', ['video']], // iframe tag will be sanitized TODO if iframe[class=note-video-clip] can be added into safe list, insert video can be enabled
+ ['font', ['bold', 'underline']],
//['fontsize', ['fontsize']],
+ ['color', ['color']],
]
: [
['style', ['style']],
@@ -139,11 +48,47 @@ 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 = sanitizeXss;
+ 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}`,
@@ -163,37 +108,14 @@ Template.editor.onRendered(() => {
}
return undefined;
};
- let popupShown = false;
inputs.each(function(idx, input) {
mSummernotes[idx] = $(input).summernote({
placeholder,
callbacks: {
- onKeydown(e) {
- if (popupShown) {
- e.preventDefault();
- }
- },
- onKeyup(e) {
- if (popupShown) {
- e.preventDefault();
- }
- },
onInit(object) {
const originalInput = this;
- const setAutocomplete = function(jEditor) {
- if (jEditor !== undefined) {
- jEditor.escapeableTextComplete(mentions).on({
- 'textComplete:show'() {
- popupShown = true;
- },
- 'textComplete:hide'() {
- popupShown = false;
- },
- });
- }
- };
$(originalInput).on('submitted', function() {
- // resetCommentInput has been called
+ // when comment is submitted, the original textarea will be set to '', so shall we
if (!this.value) {
const sn = getSummernote(this);
sn && sn.summernote('code', '');
@@ -201,7 +123,9 @@ Template.editor.onRendered(() => {
});
const jEditor = object && object.editable;
const toolbar = object && object.toolbar;
- setAutocomplete(jEditor);
+ if (jEditor !== undefined) {
+ jEditor.escapeableTextComplete(mentions);
+ }
if (toolbar !== undefined) {
const fBtn = toolbar.find('.btn-fullscreen');
fBtn.on('click', function() {
@@ -211,6 +135,7 @@ Template.editor.onRendered(() => {
});
}
},
+
onImageUpload(files) {
const $summernote = getSummernote(this);
if (files && files.length > 0) {
@@ -289,6 +214,12 @@ Template.editor.onRendered(() => {
const thisNote = this;
const updatePastedText = function(object) {
const someNote = getSummernote(object);
+ // Fix Pasting text into a card is adding a line before and after
+ // (and multiplies by pasting more) by changing paste "p" to "br".
+ // Fixes https://github.com/wekan/wekan/2890 .
+ // == Fix Start ==
+ someNote.execCommand('defaultParagraphSeparator', false, 'br');
+ // == Fix End ==
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
@@ -331,6 +262,8 @@ 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
@@ -352,7 +285,7 @@ Blaze.Template.registerHelper(
}
return member;
});
- const mentionRegex = /\B@(?:(?:"([\w.\s]*)")|([\w.]+))/gi; // including space in username
+ const mentionRegex = /\B@([\w.]*)/gi;
let currentMention;
while ((currentMention = mentionRegex.exec(content)) !== null) {
@@ -368,7 +301,12 @@ Blaze.Template.registerHelper(
if (knowedUser.userId === Meteor.userId()) {
linkClass += ' me';
}
- const link = HTML.A(
+ // This @user mention link generation did open same Wekan
+ // window in new tab, so now A is changed to U so it's
+ // underlined and there is no link popup. This way also
+ // text can be selected more easily.
+ //const link = HTML.A(
+ const link = HTML.U(
{
class: linkClass,
// XXX Hack. Since we stringify this render function result below with
@@ -376,16 +314,17 @@ 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
@@ -397,10 +336,7 @@ Template.viewer.events({
Popup.open('member').call({ userId }, event, templateInstance);
} else {
const href = event.currentTarget.href;
- const child = event.currentTarget.firstElementChild;
- if (child && child.tagName === 'IMG') {
- prevent = false;
- } else if (href) {
+ if (href) {
window.open(href, '_blank');
}
}
diff --git a/client/components/main/header.jade b/client/components/main/header.jade
index 75e84c0c..de7ead93 100644
--- a/client/components/main/header.jade
+++ b/client/components/main/header.jade
@@ -24,6 +24,11 @@ template(name="header")
a(href="{{pathFor 'home'}}")
span.fa.fa-home
| {{_ 'all-boards'}}
+ li.separator -
+ li
+ a(href="{{pathFor 'public'}}")
+ span.fa.fa-globe
+ | {{_ 'public'}}
each currentUser.starredBoards
li.separator -
li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}")
@@ -35,6 +40,8 @@ template(name="header")
a#header-new-board-icon.js-create-board
i.fa.fa-plus(title="Create a new board")
+ +notifications
+
+headerUserBar
#header(class=currentBoard.colorClass)
diff --git a/client/components/main/header.styl b/client/components/main/header.styl
index e3c7618d..d8093861 100644
--- a/client/components/main/header.styl
+++ b/client/components/main/header.styl
@@ -99,7 +99,7 @@
height: 28px
font-size: 12px
display: flex
- z-index: 17
+ z-index: 21
#header-user-bar,
#header-new-board-icon,
@@ -127,7 +127,7 @@
&.current
color: darken(white, 5%)
- &:first-child .fa-home
+ &:first-child .fa-home,&:nth-child(3) .fa-globe
margin-right: 5px
a.js-create-board
@@ -175,7 +175,7 @@
.board-header-btn
height: 32px
line-height: @height
- font-size: 16px
+ font-size: 15px
i.fa
line-height: 32px
@@ -218,6 +218,9 @@
padding: 10px
margin: -10px 0 -10px -10px
+.announcement .viewer
+ display: inline-block
+
.announcement,
.offline-warning
width: 100%
diff --git a/client/components/main/layouts.jade b/client/components/main/layouts.jade
index 9543c5c5..08dfc58c 100644
--- a/client/components/main/layouts.jade
+++ b/client/components/main/layouts.jade
@@ -6,10 +6,16 @@ head
where the application is deployed with a path prefix, but it seems to be
difficult to do that cleanly with Blaze -- at least without adding extra
packages.
- link(rel="shortcut icon" href="/wekan-favicon.png")
- link(rel="apple-touch-icon" href="/wekan-favicon.png")
- link(rel="mask-icon" href="/wekan-logo-150.svg")
- link(rel="manifest" href="/wekan-manifest.json")
+ link(rel="shortcut icon" type="image/x-icon" href="/favicon.ico")
+ link(rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png")
+ link(rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png")
+ link(rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png")
+ link(rel="manifest" href="/site.webmanifest")
+ link(rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5")
+ meta(name="apple-mobile-web-app-title" content="Wekan")
+ meta(name="application-name" content="Wekan")
+ meta(name="msapplication-TileColor" content="#00aba9")
+ meta(name="theme-color" content="#ffffff")
template(name="userFormsLayout")
section.auth-layout
diff --git a/client/components/main/layouts.js b/client/components/main/layouts.js
index ec4a35cc..83678e73 100644
--- a/client/components/main/layouts.js
+++ b/client/components/main/layouts.js
@@ -31,6 +31,11 @@ Template.userFormsLayout.onCreated(function() {
return this.stop();
},
});
+ Meteor.call('isPasswordLoginDisabled', (_, result) => {
+ if (result) {
+ $('.at-pwd-form').hide();
+ }
+ });
});
Template.userFormsLayout.onRendered(() => {
@@ -73,6 +78,8 @@ Template.userFormsLayout.helpers({
name = 'Igbo';
} else if (lang.name === 'oc') {
name = 'Occitan';
+ } else if (lang.name === '繁体中文(台湾)') {
+ name = '繁體中文(台灣)';
}
return { tag, name };
}).sort(function(a, b) {
diff --git a/client/components/main/popup.styl b/client/components/main/popup.styl
index 023cba3d..b4815ca6 100644
--- a/client/components/main/popup.styl
+++ b/client/components/main/popup.styl
@@ -135,6 +135,10 @@ $popupWidth = 300px
margin-bottom: 8px
.pop-over-list
+ li
+ display: block
+ clear: both
+
li > a
clear: both
cursor: pointer
@@ -316,6 +320,7 @@ $popupWidth = 300px
input[type="file"]
margin: 4px 0 12px
width: 100%
+ box-sizing: border-box
.pop-over-list
li > a
diff --git a/client/components/notifications/notification.jade b/client/components/notifications/notification.jade
new file mode 100644
index 00000000..c98bbdba
--- /dev/null
+++ b/client/components/notifications/notification.jade
@@ -0,0 +1,10 @@
+template(name='notification')
+ li.notification(class="{{#if read}}read{{/if}}")
+ .read-status
+ .materialCheckBox(class="{{#if read}}is-checked{{/if}}")
+ +notificationIcon(activityData)
+ .details
+ +activity(activity=activityData mode='none')
+ if read
+ .remove
+ a.fa.fa-trash
diff --git a/client/components/notifications/notification.js b/client/components/notifications/notification.js
new file mode 100644
index 00000000..89277520
--- /dev/null
+++ b/client/components/notifications/notification.js
@@ -0,0 +1,28 @@
+Template.notification.events({
+ 'click .read-status .materialCheckBox'() {
+ const update = {};
+ update[`profile.notifications.${this.index}.read`] = this.read
+ ? null
+ : Date.now();
+ Users.update(Meteor.userId(), { $set: update });
+ },
+ 'click .remove a'() {
+ Meteor.user().removeNotification(this.activityData._id);
+ },
+});
+
+Template.notification.helpers({
+ mode: 'board',
+ isOfActivityType(activityId, type) {
+ const activity = Activities.findOne(activityId);
+ return activity && activity.activityType === type;
+ },
+ activityType(activityId) {
+ const activity = Activities.findOne(activityId);
+ return activity ? activity.activityType : '';
+ },
+ activityUser(activityId) {
+ const activity = Activities.findOne(activityId);
+ return activity && activity.userId;
+ },
+});
diff --git a/client/components/notifications/notification.styl b/client/components/notifications/notification.styl
new file mode 100644
index 00000000..0cf0cfd5
--- /dev/null
+++ b/client/components/notifications/notification.styl
@@ -0,0 +1,57 @@
+#notifications-drawer
+ &.show-read .notification.read
+ display: flex
+
+ .notification
+ display: flex
+ float: none
+ padding: 12px 8px 8px
+ color: black
+ border-bottom: 1px solid #dbdbdb
+
+ &.read
+ display: none
+
+ .read-status
+ width: 30px
+
+ input
+ width: 24px
+ height: 24px
+
+ .activity-type
+ margin: 16px 0 0
+ width: 17px
+ height: 17px
+ font-size: 17px
+ display: block
+ color: #bbb
+
+ .details
+ width: calc(100% - 30px)
+
+ .activity
+ display: flex
+
+ .activity-desc
+ width: 100%;
+
+ .activity-comment
+ display: block
+ width: 100%
+ border-radius: 3px
+ background: #fff
+ text-decoration: none
+ box-shadow: 0 1px 2px rgba(0,0,0,0.2)
+ margin-top: 5px
+ padding: 5px
+
+ .activity-meta
+ display: block
+ font-size: 0.8em
+ color: #999
+ font-style: italic
+
+ .remove
+ a:hover
+ color #eb4646 !important
diff --git a/client/components/notifications/notificationIcon.jade b/client/components/notifications/notificationIcon.jade
new file mode 100644
index 00000000..04377606
--- /dev/null
+++ b/client/components/notifications/notificationIcon.jade
@@ -0,0 +1,53 @@
+template(name='notificationIcon')
+ if($in activityType 'deleteAttachment' 'addAttachment')
+ i.fa.fa-paperclip.activity-type(title="attachment")
+ else if($in activityType 'createBoard' 'importBoard')
+ i.fa.fa-chalkboard.activity-type(title="board")
+
+ else if($in activityType 'createCard' 'importCard' 'moveCard')
+ +cardNotificationIcon
+ else if($in activityType 'moveCardBoard' 'archivedCard' 'restoredCard')
+ +cardNotificationIcon
+ //- $in can only handle up to 3 cases so we have to break this case over 2 cases... use a simple template to keep it
+ //- DRY and consistant
+
+ else if($in activityType 'addChecklist' 'removedChecklist' 'completeChecklist')
+ +checklistNotificationIcon
+ else if($in activityType 'uncompleteChecklist')
+ +checklistNotificationIcon
+ //- $in can only handle up to 3 cases so we have to break this case over 2 cases... use a simple template to keep it
+ //- DRY and consistant
+
+ else if($in activityType 'checkedItem' 'uncheckedItem' 'addChecklistItem' 'removedChecklistItem')
+ i.fa.fa-check-square.activity-type(title="checklist item")
+ else if($in activityType 'addComment')
+ i.fa.fa-comment-o.activity-type(title="comment")
+ else if($in activityType 'createCustomField' 'setCustomField' 'unsetCustomField')
+ i.fa.fa-code.activity-type(title="custom field")
+ else if($in activityType 'addedLabel' 'removedLabel')
+ i.fa.fa-tag.activity-type(title="label")
+
+ else if($in activityType 'createList' 'removeList' 'archivedList')
+ +listNotificationIcon
+ else if($in activityType 'importList')
+ +listNotificationIcon
+ //- $in can only handle up to 3 cases so we have to break this case over 2 cases... use a simple template to keep it
+ //- DRY and consistant
+
+ //- elswhere in the app we use fa-trello to indicate lists...
+ //- i personally like fa-columns a bit better
+ else if($in activityType 'unjoinMember' 'addBoardMember' 'joinMember' 'removeBoardMember')
+ i.fa.fa-user.activity-type(title="member")
+ else if($in activityType 'createSwimlane' 'archivedSwimlane')
+ i.fa.fa-th-large.activity-type(title="swimlane")
+ else
+ i.fa.fa-bug.activity-type(title="can't find icon for #{activityType}")
+
+template(name='cardNotificationIcon')
+ i.fa.fa-clone.activity-type(title="card")
+
+template(name='checklistNotificationIcon')
+ i.fa.fa-list.activity-type(title="checklist")
+
+template(name='listNotificationIcon')
+ i.fa.fa-columns.activity-type(title="list")
diff --git a/client/components/notifications/notifications.jade b/client/components/notifications/notifications.jade
new file mode 100644
index 00000000..bf8acbbf
--- /dev/null
+++ b/client/components/notifications/notifications.jade
@@ -0,0 +1,5 @@
+template(name='notifications')
+ #notifications.board-header-btns.right
+ a.notifications-drawer-toggle.fa.fa-bell(class="{{#if $gt unreadNotifications 0}}alert{{/if}}")
+ if $.Session.get 'showNotificationsDrawer'
+ +notificationsDrawer(unreadNotifications=unreadNotifications)
diff --git a/client/components/notifications/notifications.js b/client/components/notifications/notifications.js
new file mode 100644
index 00000000..c0aa6cb5
--- /dev/null
+++ b/client/components/notifications/notifications.js
@@ -0,0 +1,32 @@
+// this hides the notifications drawer if anyone clicks off of the panel
+Template.body.events({
+ click(event) {
+ if (
+ !$(event.target).is('#notifications *') &&
+ Session.get('showNotificationsDrawer')
+ ) {
+ toggleNotificationsDrawer();
+ }
+ },
+});
+
+Template.notifications.helpers({
+ unreadNotifications() {
+ const notifications = Users.findOne(Meteor.userId()).notifications();
+ const unreadNotifications = _.filter(notifications, v => !v.read);
+ return unreadNotifications.length;
+ },
+});
+
+Template.notifications.events({
+ 'click .notifications-drawer-toggle'() {
+ toggleNotificationsDrawer();
+ },
+});
+
+export function toggleNotificationsDrawer() {
+ Session.set(
+ 'showNotificationsDrawer',
+ !Session.get('showNotificationsDrawer'),
+ );
+}
diff --git a/client/components/notifications/notifications.styl b/client/components/notifications/notifications.styl
new file mode 100644
index 00000000..710cd3f9
--- /dev/null
+++ b/client/components/notifications/notifications.styl
@@ -0,0 +1,17 @@
+#notifications
+ position: relative
+
+ .notifications-drawer-toggle
+ display: block
+ line-height: 28px
+ color: #f2f2f2
+ margin: 0 10px
+ width: 28px
+ height: 28px
+ text-align: center
+ border: 0
+ padding: 0
+
+ &.alert
+ background-color: #eb4646;
+
diff --git a/client/components/notifications/notificationsDrawer.jade b/client/components/notifications/notificationsDrawer.jade
new file mode 100644
index 00000000..fee6aef6
--- /dev/null
+++ b/client/components/notifications/notificationsDrawer.jade
@@ -0,0 +1,20 @@
+template(name='notificationsDrawer')
+ section#notifications-drawer(class="{{#if $.Session.get 'showReadNotifications'}}show-read{{/if}}")
+ .header
+ if $.Session.get 'showReadNotifications'
+ a.toggle-read {{_ 'filter-by-unread'}}
+ else
+ a.toggle-read {{_ 'view-all'}}
+ h5 {{_ 'notifications'}}
+ if($gt unreadNotifications 0)
+ |(#{unreadNotifications})
+ a.fa.fa-times-thin.close
+ ul.notifications
+ each transformedProfile.notifications
+ +notification(activityData=activity index=dbIndex read=read)
+ if($gt unreadNotifications 0)
+ a.all-read {{_ 'mark-all-as-read'}}
+ if ($and ($.Session.get 'showReadNotifications') ($gt readNotifications 0))
+ a.remove-read
+ i.fa.fa-trash
+ | {{_ 'remove-all-read'}}
diff --git a/client/components/notifications/notificationsDrawer.js b/client/components/notifications/notificationsDrawer.js
new file mode 100644
index 00000000..76abeea7
--- /dev/null
+++ b/client/components/notifications/notificationsDrawer.js
@@ -0,0 +1,53 @@
+import { toggleNotificationsDrawer } from './notifications.js';
+
+Template.notificationsDrawer.onCreated(function() {
+ Meteor.subscribe('notificationActivities');
+ Meteor.subscribe('notificationCards');
+ Meteor.subscribe('notificationUsers');
+ Meteor.subscribe('notificationsAttachments');
+ Meteor.subscribe('notificationChecklistItems');
+ Meteor.subscribe('notificationChecklists');
+ Meteor.subscribe('notificationComments');
+ Meteor.subscribe('notificationLists');
+ Meteor.subscribe('notificationSwimlanes');
+});
+
+Template.notificationsDrawer.helpers({
+ transformedProfile() {
+ return Users.findOne(Meteor.userId());
+ },
+ readNotifications() {
+ const readNotifications = _.filter(
+ Meteor.user().profile.notifications,
+ v => !!v.read,
+ );
+ return readNotifications.length;
+ },
+});
+
+Template.notificationsDrawer.events({
+ 'click .all-read'() {
+ const notifications = Meteor.user().profile.notifications;
+ for (const index in notifications) {
+ if (notifications.hasOwnProperty(index) && !notifications[index].read) {
+ const update = {};
+ update[`profile.notifications.${index}.read`] = Date.now();
+ Users.update(Meteor.userId(), { $set: update });
+ }
+ }
+ },
+ 'click .close'() {
+ toggleNotificationsDrawer();
+ },
+ 'click .toggle-read'() {
+ Session.set('showReadNotifications', !Session.get('showReadNotifications'));
+ },
+ 'click .remove-read'() {
+ const user = Meteor.user();
+ for (const notification of user.profile.notifications) {
+ if (notification.read) {
+ user.removeNotification(notification.activity);
+ }
+ }
+ },
+});
diff --git a/client/components/notifications/notificationsDrawer.styl b/client/components/notifications/notificationsDrawer.styl
new file mode 100644
index 00000000..f99e1299
--- /dev/null
+++ b/client/components/notifications/notificationsDrawer.styl
@@ -0,0 +1,69 @@
+belize = #2980b9
+
+section#notifications-drawer
+ position: fixed
+ top: 28px
+ right: 0
+ width: 400px
+ background-color: #fafafa
+ box-shadow: 0 1px 2px rgba(0,0,0,0.15)
+ border-radius: 2px
+ max-height: calc(100vh - 28px - 36px)
+ color: black
+ padding-top 36px
+
+ a:hover
+ color: belize !important
+
+ .header
+ position: fixed
+ top 28px
+ right 0
+ width calc(400px - 32px)
+ padding: 8px 16px
+ background: #ededed
+ border-bottom: 1px solid #dbdbdb
+ z-index 2
+
+ .toggle-read
+ position absolute
+ left 16px
+ top calc(50% - 8px)
+ color belize
+
+ h5
+ text-align: center
+ margin: 0
+
+ .close
+ position: absolute
+ top: calc(50% - 12px)
+ right: 12px
+ font-size: 24px
+ height: 24px
+ line-height: 24px
+ opacity 1
+
+ .all-read,
+ .remove-read
+ color belize
+ background-color: #fafafa
+ margin 8px 16px 12px
+ display inline-block
+
+ .remove-read
+ float right
+
+ &:hover
+ color #eb4646 !important
+
+ i.fa
+ color inherit
+
+
+ ul.notifications
+ display: block
+ padding: 0px 16px
+ margin: 0
+ height: calc(100vh - 102px)
+ overflow-y: scroll
diff --git a/client/components/rules/actions/boardActions.jade b/client/components/rules/actions/boardActions.jade
index 6034184c..fda15062 100644
--- a/client/components/rules/actions/boardActions.jade
+++ b/client/components/rules/actions/boardActions.jade
@@ -1,29 +1,42 @@
template(name="boardActions")
div.trigger-item
div.trigger-content
- div.trigger-text
+ div.trigger-text
| {{_'r-move-card-to'}}
div.trigger-dropdown
select(id="move-gen-action")
option(value="top") {{_'r-top-of'}}
option(value="bottom") {{_'r-bottom-of'}}
- div.trigger-text
+ div.trigger-text
| {{_'r-its-list'}}
div.trigger-button.js-add-gen-move-action.js-goto-rules
i.fa.fa-plus
div.trigger-item
div.trigger-content
- div.trigger-text
+ div.trigger-text
| {{_'r-move-card-to'}}
div.trigger-dropdown
select(id="move-spec-action")
option(value="top") {{_'r-top-of'}}
option(value="bottom") {{_'r-bottom-of'}}
- div.trigger-text
- | {{_'r-list'}}
+ div.trigger-text
+ | {{_'r-the-board'}}
+ div.trigger-dropdown
+ select(id="board-id")
+ each boards
+ if $eq _id currentBoard._id
+ option(value="{{_id}}" selected) {{_ 'current'}}
+ else
+ option(value="{{_id}}") {{title}}
+ div.trigger-text
+ | {{_'r-in-list'}}
div.trigger-dropdown
input(id="listName",type=text,placeholder="{{_'r-name'}}")
+ div.trigger-text
+ | {{_'r-in-swimlane'}}
+ div.trigger-dropdown
+ input(id="swimlaneName",type=text,placeholder="{{_'r-name'}}")
div.trigger-button.js-add-spec-move-action.js-goto-rules
i.fa.fa-plus
@@ -33,14 +46,14 @@ template(name="boardActions")
select(id="arch-action")
option(value="archive") {{_'r-archive'}}
option(value="unarchive") {{_'r-unarchive'}}
- div.trigger-text
+ div.trigger-text
| {{_'r-card'}}
div.trigger-button.js-add-arch-action.js-goto-rules
i.fa.fa-plus
div.trigger-item
div.trigger-content
- div.trigger-text
+ div.trigger-text
| {{_'r-add-swimlane'}}
div.trigger-dropdown
input(id="swimlane-name",type=text,placeholder="{{_'r-name'}}")
@@ -49,15 +62,15 @@ template(name="boardActions")
div.trigger-item
div.trigger-content
- div.trigger-text
+ div.trigger-text
| {{_'r-create-card'}}
div.trigger-dropdown
input(id="card-name",type=text,placeholder="{{_'r-name'}}")
- div.trigger-text
+ div.trigger-text
| {{_'r-in-list'}}
div.trigger-dropdown
input(id="list-name",type=text,placeholder="{{_'r-name'}}")
- div.trigger-text
+ div.trigger-text
| {{_'r-in-swimlane'}}
div.trigger-dropdown
input(id="swimlane-name2",type=text,placeholder="{{_'r-name'}}")
@@ -65,8 +78,8 @@ template(name="boardActions")
i.fa.fa-plus
-
-
+
+
diff --git a/client/components/rules/actions/boardActions.js b/client/components/rules/actions/boardActions.js
index 8568d2bf..02910cc1 100644
--- a/client/components/rules/actions/boardActions.js
+++ b/client/components/rules/actions/boardActions.js
@@ -1,6 +1,22 @@
BlazeComponent.extendComponent({
onCreated() {},
+ boards() {
+ const boards = Boards.find(
+ {
+ archived: false,
+ 'members.userId': Meteor.userId(),
+ _id: {
+ $ne: Meteor.user().getTemplatesBoardId(),
+ },
+ },
+ {
+ sort: { sort: 1 /* boards default sorting */ },
+ },
+ );
+ return boards;
+ },
+
events() {
return [
{
@@ -52,15 +68,18 @@ BlazeComponent.extendComponent({
const ruleName = this.data().ruleName.get();
const trigger = this.data().triggerVar.get();
const actionSelected = this.find('#move-spec-action').value;
- const listTitle = this.find('#listName').value;
+ const swimlaneName = this.find('#swimlaneName').value;
+ const listName = this.find('#listName').value;
const boardId = Session.get('currentBoard');
+ const destBoardId = this.find('#board-id').value;
const desc = Utils.getTriggerActionDesc(event, this);
if (actionSelected === 'top') {
const triggerId = Triggers.insert(trigger);
const actionId = Actions.insert({
actionType: 'moveCardToTop',
- listTitle,
- boardId,
+ listName,
+ swimlaneName,
+ boardId: destBoardId,
desc,
});
Rules.insert({
@@ -74,8 +93,9 @@ BlazeComponent.extendComponent({
const triggerId = Triggers.insert(trigger);
const actionId = Actions.insert({
actionType: 'moveCardToBottom',
- listTitle,
- boardId,
+ listName,
+ swimlaneName,
+ boardId: destBoardId,
desc,
});
Rules.insert({
diff --git a/client/components/settings/informationBody.jade b/client/components/settings/informationBody.jade
index 2c615ffd..0f85dd9c 100644
--- a/client/components/settings/informationBody.jade
+++ b/client/components/settings/informationBody.jade
@@ -4,12 +4,16 @@ template(name='information')
| {{_ 'error-notAuthorized'}}
else
.content-title
- span {{_ 'info'}}
+ span
+ i.fa.fa-info-circle
+ | {{_ 'info'}}
.content-body
.side-menu
ul
li.active
- a.js-setting-menu(data-id="information-display") {{_ 'info'}}
+ a.js-setting-menu(data-id="information-display")
+ i.fa.fa-info-circle
+ | {{_ 'info'}}
.main-body
+statistics
diff --git a/client/components/settings/peopleBody.jade b/client/components/settings/peopleBody.jade
index d8f672b0..fef1067e 100644
--- a/client/components/settings/peopleBody.jade
+++ b/client/components/settings/peopleBody.jade
@@ -5,16 +5,22 @@ template(name="people")
else
.content-title.ext-box
.ext-box-left
- span {{_ 'people'}}
+ span
+ i.fa.fa-users
+ | {{_ 'people'}}
input#searchInput(placeholder="{{_ 'search'}}")
- button#searchButton {{_ 'search'}}
+ button#searchButton
+ i.fa.fa-search
+ | {{_ 'search'}}
.ext-box-right
span {{_ 'people-number'}} #{peopleNumber}
.content-body
.side-menu
ul
li.active
- a.js-setting-menu(data-id="people-setting") {{_ 'people'}}
+ a.js-setting-menu(data-id="people-setting")
+ i.fa.fa-users
+ | {{_ 'people'}}
.main-body
if loading.get
+spinner
@@ -34,9 +40,15 @@ template(name="peopleGeneral")
th {{_ 'active'}}
th {{_ 'authentication-method'}}
th
+ +newUserRow
each user in peopleList
+peopleRow(userId=user._id)
+template(name="newUserRow")
+ a.new-user
+ i.fa.fa-edit
+ | {{_ 'new'}}
+
template(name="peopleRow")
tr
if userData.loginDisabled
@@ -90,6 +102,7 @@ template(name="peopleRow")
td {{_ userData.authenticationMethod }}
td
a.edit-user
+ i.fa.fa-edit
| {{_ 'edit'}}
template(name="editUserPopup")
@@ -97,7 +110,7 @@ template(name="editUserPopup")
label.hide.userId(type="text" value=user._id)
label
| {{_ 'fullname'}}
- input.js-profile-fullname(type="text" value=user.profile.fullname autofocus)
+ input.js-profile-fullname(type="text" value=user.profile.fullname)
label
| {{_ 'username'}}
span.error.hide.username-taken
@@ -141,3 +154,49 @@ template(name="editUserPopup")
// div
// input#deleteButton.primary.wide(type="button" value="{{_ 'delete'}}")
+template(name="newUserPopup")
+ form
+ //label.hide.userId(type="text" value=user._id)
+ label
+ | {{_ 'fullname'}}
+ input.js-profile-fullname(type="text" value="")
+ label
+ | {{_ 'username'}}
+ span.error.hide.username-taken
+ | {{_ 'error-username-taken'}}
+ //if isLdap
+ // input.js-profile-username(type="text" value=user.username readonly)
+ //else
+ input.js-profile-username(type="text" value="")
+ label
+ | {{_ 'email'}}
+ span.error.hide.email-taken
+ | {{_ 'error-email-taken'}}
+ //if isLdap
+ // input.js-profile-email(type="email" value="{{user.emails.[0].address}}" readonly)
+ //else
+ input.js-profile-email(type="email" value="")
+ label
+ | {{_ 'admin'}}
+ select.select-role.js-profile-isadmin
+ option(value="false" selected="selected") {{_ 'no'}}
+ option(value="true") {{_ 'yes'}}
+ label
+ | {{_ 'active'}}
+ select.select-active.js-profile-isactive
+ option(value="false" selected="selected") {{_ 'yes'}}
+ option(value="true") {{_ 'no'}}
+ label
+ | {{_ 'authentication-type'}}
+ select.select-authenticationMethod.js-authenticationMethod
+ each authentications
+ if isSelected value
+ option(value="{{value}}" selected) {{_ value}}
+ else
+ option(value="{{value}}") {{_ value}}
+ hr
+ label
+ | {{_ 'password'}}
+ input.js-profile-password(type="password")
+ div.buttonsContainer
+ input.primary.wide(type="submit" value="{{_ 'save'}}")
diff --git a/client/components/settings/peopleBody.js b/client/components/settings/peopleBody.js
index 8610034e..186afd58 100644
--- a/client/components/settings/peopleBody.js
+++ b/client/components/settings/peopleBody.js
@@ -39,6 +39,9 @@ BlazeComponent.extendComponent({
this.filterPeople();
}
},
+ 'click #newUserButton'() {
+ Popup.open('newUser');
+ },
},
];
},
@@ -141,6 +144,47 @@ Template.editUserPopup.helpers({
},
});
+Template.newUserPopup.onCreated(function() {
+ this.authenticationMethods = new ReactiveVar([]);
+ this.errorMessage = new ReactiveVar('');
+
+ Meteor.call('getAuthenticationsEnabled', (_, result) => {
+ if (result) {
+ // TODO : add a management of different languages
+ // (ex {value: ldap, text: TAPi18n.__('ldap', {}, T9n.getLanguage() || 'en')})
+ this.authenticationMethods.set([
+ { value: 'password' },
+ // Gets only the authentication methods availables
+ ...Object.entries(result)
+ .filter(e => e[1])
+ .map(e => ({ value: e[0] })),
+ ]);
+ }
+ });
+});
+
+Template.newUserPopup.helpers({
+ //user() {
+ // return Users.findOne(this.userId);
+ //},
+ authentications() {
+ return Template.instance().authenticationMethods.get();
+ },
+ //isSelected(match) {
+ // const userId = Template.instance().data.userId;
+ // const selected = Users.findOne(userId).authenticationMethod;
+ // return selected === match;
+ //},
+ //isLdap() {
+ // const userId = Template.instance().data.userId;
+ // const selected = Users.findOne(userId).authenticationMethod;
+ // return selected === 'ldap';
+ //},
+ errorMessage() {
+ return Template.instance().errorMessage.get();
+ },
+});
+
BlazeComponent.extendComponent({
onCreated() {},
user() {
@@ -155,6 +199,16 @@ BlazeComponent.extendComponent({
},
}).register('peopleRow');
+BlazeComponent.extendComponent({
+ events() {
+ return [
+ {
+ 'click a.new-user': Popup.open('newUser'),
+ },
+ ];
+ },
+}).register('newUserRow');
+
Template.editUserPopup.events({
submit(event, templateInstance) {
event.preventDefault();
@@ -248,3 +302,44 @@ Template.editUserPopup.events({
Popup.close();
}),
});
+
+Template.newUserPopup.events({
+ submit(event, templateInstance) {
+ event.preventDefault();
+ const fullname = templateInstance.find('.js-profile-fullname').value.trim();
+ const username = templateInstance.find('.js-profile-username').value.trim();
+ const password = templateInstance.find('.js-profile-password').value;
+ const isAdmin = templateInstance.find('.js-profile-isadmin').value.trim();
+ const isActive = templateInstance.find('.js-profile-isactive').value.trim();
+ const email = templateInstance.find('.js-profile-email').value.trim();
+
+ Meteor.call(
+ 'setCreateUser',
+ fullname,
+ username,
+ password,
+ isAdmin,
+ isActive,
+ email.toLowerCase(),
+ function(error) {
+ const usernameMessageElement = templateInstance.$('.username-taken');
+ const emailMessageElement = templateInstance.$('.email-taken');
+ if (error) {
+ const errorElement = error.error;
+ if (errorElement === 'username-already-taken') {
+ usernameMessageElement.show();
+ emailMessageElement.hide();
+ } else if (errorElement === 'email-already-taken') {
+ usernameMessageElement.hide();
+ emailMessageElement.show();
+ }
+ } else {
+ usernameMessageElement.hide();
+ emailMessageElement.hide();
+ Popup.close();
+ }
+ },
+ );
+ Popup.close();
+ },
+});
diff --git a/client/components/settings/peopleBody.styl b/client/components/settings/peopleBody.styl
index 80387611..c223e181 100644
--- a/client/components/settings/peopleBody.styl
+++ b/client/components/settings/peopleBody.styl
@@ -33,7 +33,7 @@ table
padding: 0;
button
- min-width: 60px;
+ min-width: 90px;
.content-wrapper
margin-top: 10px
diff --git a/client/components/settings/settingBody.jade b/client/components/settings/settingBody.jade
index 04b635e8..835a3b81 100644
--- a/client/components/settings/settingBody.jade
+++ b/client/components/settings/settingBody.jade
@@ -4,22 +4,35 @@ template(name="setting")
| {{_ 'error-notAuthorized'}}
else
.content-title
+ i.fa.fa-cog
span {{_ 'settings'}}
.content-body
.side-menu
ul
li.active
- a.js-setting-menu(data-id="registration-setting") {{_ 'registration'}}
+ a.js-setting-menu(data-id="registration-setting")
+ i.fa.fa-sign-in
+ | {{_ 'registration'}}
li
- a.js-setting-menu(data-id="email-setting") {{_ 'email'}}
+ a.js-setting-menu(data-id="email-setting")
+ i.fa.fa-envelope
+ | {{_ 'email'}}
li
- a.js-setting-menu(data-id="account-setting") {{_ 'accounts'}}
+ a.js-setting-menu(data-id="account-setting")
+ i.fa.fa-users
+ | {{_ 'accounts'}}
li
- a.js-setting-menu(data-id="announcement-setting") {{_ 'admin-announcement'}}
+ a.js-setting-menu(data-id="announcement-setting")
+ i.fa.fa-bullhorn
+ | {{_ 'admin-announcement'}}
li
- a.js-setting-menu(data-id="layout-setting") {{_ 'layout'}}
+ a.js-setting-menu(data-id="layout-setting")
+ i.fa.fa-object-group
+ | {{_ 'layout'}}
li
- a.js-setting-menu(data-id="webhook-setting") {{_ 'global-webhook'}}
+ a.js-setting-menu(data-id="webhook-setting")
+ i.fa.fa-globe
+ | {{_ 'global-webhook'}}
.main-body
if loading.get
+spinner
@@ -171,12 +184,6 @@ template(name='layoutSettings')
.title {{_ 'custom-product-name'}}
.form-group
input.wekan-form-control#product-name(type="text", placeholder="" value="{{currentSetting.productName}}")
- li.layout-form
- .title {{_ 'add-custom-html-after-body-start'}}
- textarea#customHTMLafterBodyStart.wekan-form-control= currentSetting.customHTMLafterBodyStart
- li.layout-form
- .title {{_ 'add-custom-html-before-body-end'}}
- textarea#customHTMLbeforeBodyEnd.wekan-form-control= currentSetting.customHTMLbeforeBodyEnd
li
button.js-save-layout.primary {{_ 'save'}}
diff --git a/client/components/settings/settingBody.js b/client/components/settings/settingBody.js
index 4ff5aedd..62752084 100644
--- a/client/components/settings/settingBody.js
+++ b/client/components/settings/settingBody.js
@@ -48,7 +48,7 @@ BlazeComponent.extendComponent({
'members.isAdmin': true,
},
{
- sort: ['title'],
+ sort: { sort: 1 /* boards default sorting */ },
},
);
},
@@ -171,20 +171,12 @@ BlazeComponent.extendComponent({
const displayAuthenticationMethod =
$('input[name=displayAuthenticationMethod]:checked').val() === 'true';
const defaultAuthenticationMethod = $('#defaultAuthenticationMethod').val();
- const customHTMLafterBodyStart = $('#customHTMLafterBodyStart')
- .val()
- .trim();
- const customHTMLbeforeBodyEnd = $('#customHTMLbeforeBodyEnd')
- .val()
- .trim();
try {
Settings.update(Settings.findOne()._id, {
$set: {
productName,
hideLogo: hideLogoChange,
- customHTMLafterBodyStart,
- customHTMLbeforeBodyEnd,
displayAuthenticationMethod,
defaultAuthenticationMethod,
},
diff --git a/client/components/settings/settingBody.styl b/client/components/settings/settingBody.styl
index bcbd2ea1..d6ac32b2 100644
--- a/client/components/settings/settingBody.styl
+++ b/client/components/settings/settingBody.styl
@@ -41,15 +41,18 @@
&:hover
background #fff
box-shadow 0 1px 2px rgba(0,0,0,0.15);
+
a
@extends .flex
padding: 1rem 0 1rem 1rem
width: 100% - 5rem
-
span
font-size: 13px
+ i
+ margin-right: 20px
+
.main-body
padding: 0.1em 1em
-webkit-user-select: text // Safari 3.1+
diff --git a/client/components/sidebar/sidebar.jade b/client/components/sidebar/sidebar.jade
index ccfadc0c..6bfedc9c 100644
--- a/client/components/sidebar/sidebar.jade
+++ b/client/components/sidebar/sidebar.jade
@@ -37,11 +37,12 @@ template(name='homeSidebar')
template(name="membersWidget")
.board-widget.board-widget-members
h3
- i.fa.fa-user
+ i.fa.fa-users
| {{_ 'members'}}
unless currentUser.isCommentOnly
- a.board-header-btn.js-open-board-menu(title="{{_ 'boardMenuPopup-title'}}").right
- i.board-header-btn-icon.fa.fa-cog
+ unless currentUser.isWorker
+ a.board-header-btn.js-open-board-menu(title="{{_ 'boardMenuPopup-title'}}").right
+ i.board-header-btn-icon.fa.fa-cog
.board-widget-content
each currentBoard.activeMembers
@@ -71,6 +72,108 @@ template(name="boardChangeColorPopup")
if isSelected
i.fa.fa-check
+template(name="boardCardSettingsPopup")
+ form.board-card-settings
+ h3 {{_ 'show-on-card'}}
+ div.check-div
+ a.flex.js-field-has-receiveddate(class="{{#if allowsReceivedDate}}is-checked{{/if}}")
+ .materialCheckBox(class="{{#if allowsReceivedDate}}is-checked{{/if}}")
+ span
+ i.fa.fa-sign-out
+ | {{_ 'card-received'}}
+ div.check-div
+ a.flex.js-field-has-startdate(class="{{#if allowsStartDate}}is-checked{{/if}}")
+ .materialCheckBox(class="{{#if allowsStartDate}}is-checked{{/if}}")
+ span
+ i.fa.fa-hourglass-start
+ | {{_ 'card-start'}}
+ div.check-div
+ a.flex.js-field-has-duedate(class="{{#if allowsDueDate}}is-checked{{/if}}")
+ .materialCheckBox(class="{{#if allowsDueDate}}is-checked{{/if}}")
+ span
+ i.fa.fa-sign-in
+ | {{_ 'card-due'}}
+ div.check-div
+ a.flex.js-field-has-enddate(class="{{#if allowsEndDate}}is-checked{{/if}}")
+ .materialCheckBox(class="{{#if allowsEndDate}}is-checked{{/if}}")
+ span
+ i.fa.fa-hourglass-end
+ | {{_ 'card-end'}}
+ div.check-div
+ a.flex.js-field-has-members(class="{{#if allowsMembers}}is-checked{{/if}}")
+ .materialCheckBox(class="{{#if allowsMembers}}is-checked{{/if}}")
+ span
+ i.fa.fa-users
+ | {{_ 'members'}}
+ div.check-div
+ a.flex.js-field-has-assignee(class="{{#if allowsAssignee}}is-checked{{/if}}")
+ .materialCheckBox(class="{{#if allowsAssignee}}is-checked{{/if}}")
+ span
+ i.fa.fa-user
+ | {{_ 'assignee'}}
+ div.check-div
+ a.flex.js-field-has-assigned-by(class="{{#if allowsAssignedBy}}is-checked{{/if}}")
+ .materialCheckBox(class="{{#if allowsAssignedBy}}is-checked{{/if}}")
+ span
+ i.fa.fa-shopping-cart
+ | {{_ 'assigned-by'}}
+ div.check-div
+ a.flex.js-field-has-requested-by(class="{{#if allowsRequestedBy}}is-checked{{/if}}")
+ .materialCheckBox(class="{{#if allowsRequestedBy}}is-checked{{/if}}")
+ span
+ i.fa.fa-user-plus
+ | {{_ 'requested-by'}}
+ div.check-div
+ a.flex.js-field-has-labels(class="{{#if allowsLabels}}is-checked{{/if}}")
+ .materialCheckBox(class="{{#if allowsLabels}}is-checked{{/if}}")
+ span
+ i.fa.fa-tags
+ | {{_ 'labels'}}
+ div.check-div
+ a.flex.js-field-has-description-title(class="{{#if allowsDescriptionTitle}}is-checked{{/if}}")
+ .materialCheckBox(class="{{#if allowsDescriptionTitle}}is-checked{{/if}}")
+ span
+ i.fa.fa-align-left
+ | {{_ 'description'}}
+ | {{_ 'title'}}
+ div.check-div
+ a.flex.js-field-has-description-text(class="{{#if allowsDescriptionText}}is-checked{{/if}}")
+ .materialCheckBox(class="{{#if allowsDescriptionText}}is-checked{{/if}}")
+ span
+ i.fa.fa-align-left
+ | {{_ 'description'}}
+ | {{_ 'custom-field-text'}}
+ div.check-div
+ a.flex.js-field-has-checklists(class="{{#if allowsChecklists}}is-checked{{/if}}")
+ .materialCheckBox(class="{{#if allowsChecklists}}is-checked{{/if}}")
+ span
+ i.fa.fa-check
+ | {{_ 'checklists'}}
+ div.check-div
+ a.flex.js-field-has-subtasks(class="{{#if allowsSubtasks}}is-checked{{/if}}")
+ .materialCheckBox(class="{{#if allowsSubtasks}}is-checked{{/if}}")
+ span
+ i.fa.fa-sitemap
+ | {{_ 'subtasks'}}
+ div.check-div
+ a.flex.js-field-has-attachments(class="{{#if allowsAttachments}}is-checked{{/if}}")
+ .materialCheckBox(class="{{#if allowsAttachments}}is-checked{{/if}}")
+ span
+ i.fa.fa-paperclip
+ | {{_ 'attachments'}}
+ //div.check-div
+ // a.flex.js-field-has-comments(class="{{#if allowsComments}}is-checked{{/if}}")
+ // .materialCheckBox(class="{{#if allowsComments}}is-checked{{/if}}")
+ // span
+ // i.fa.fa-comment-o
+ // | {{_ 'comment'}}
+ //div.check-div
+ // a.flex.js-field-has-activities(class="{{#if allowsActivities}}is-checked{{/if}}")
+ // .materialCheckBox(class="{{#if allowsActivities}}is-checked{{/if}}")
+ // span
+ // i.fa.fa-history
+ // | {{_ 'activities'}}
+
template(name="boardSubtaskSettingsPopup")
form.board-subtask-settings
h3 {{_ 'show-parent-in-minicard'}}
@@ -130,7 +233,9 @@ template(name="chooseBoardSource")
template(name="archiveBoardPopup")
p {{_ 'close-board-pop'}}
- button.js-confirm.negate.full(type="submit") {{_ 'archive'}}
+ button.js-confirm.negate.full(type="submit")
+ i.fa.fa-archive
+ | {{_ 'archive'}}
template(name="outgoingWebhooksPopup")
each integrations
@@ -140,7 +245,7 @@ template(name="outgoingWebhooksPopup")
b &nbsp;
.materialCheckBox(class="{{#unless enabled}}is-checked{{/unless}}")
input.js-outgoing-webhooks-title(placeholder="{{_ 'webhook-title'}}" type="text" name="title" value=title)
- input.js-outgoing-webhooks-url(type="text" name="url" value=url autofocus)
+ input.js-outgoing-webhooks-url(type="text" name="url" value=url)
input.js-outgoing-webhooks-token(placeholder="{{_ 'webhook-token' }}" type="text" value=token name="token")
select.js-outgoing-webhooks-type(name="type")
each _type in types
@@ -152,7 +257,7 @@ template(name="outgoingWebhooksPopup")
input(type="hidden" value=_id name="id")
input.primary.wide(type="submit" value="{{_ 'save'}}")
form.integration-form
- input.js-outgoing-webhooks-title(placeholder="{{_ 'webhook-title'}}" type="text" name="title" autofocus)
+ input.js-outgoing-webhooks-title(placeholder="{{_ 'webhook-title'}}" type="text" name="title")
input.js-outgoing-webhooks-url(placeholder="{{_ 'URL' }}" type="text" name="url")
input.js-outgoing-webhooks-token(placeholder="{{_ 'webhook-token' }}" type="text" name="token")
select.js-outgoing-webhooks-type(name="type")
@@ -162,38 +267,98 @@ template(name="outgoingWebhooksPopup")
template(name="boardMenuPopup")
ul.pop-over-list
- li: a.js-custom-fields {{_ 'custom-fields'}}
- li: a.js-open-archives {{_ 'archived-items'}}
+ li
+ a.js-open-rules-view(title="{{_ 'rules'}}")
+ i.fa.fa-magic
+ | {{_ 'rules'}}
+ li
+ a.js-custom-fields
+ i.fa.fa-list-alt
+ | {{_ 'custom-fields'}}
+ li
+ a.js-open-archives
+ i.fa.fa-archive
+ | {{_ 'archived-items'}}
if currentUser.isBoardAdmin
- li: a.js-change-board-color {{_ 'board-change-color'}}
+ li
+ a.js-change-board-color
+ i.fa.fa-paint-brush
+ | {{_ 'board-change-color'}}
+
//-
XXX Language should be handled by sandstorm, but for now display a
language selection link in the board menu. This link is normally present
in the header bar that is not displayed on sandstorm.
if isSandstorm
- li: a.js-change-language {{_ 'language'}}
+ li
+ a.js-change-language
+ i.fa.fa-flag
+ | {{_ 'language'}}
unless isSandstorm
if currentUser.isBoardAdmin
hr
ul.pop-over-list
- li: a(href="{{exportUrl}}", download="{{exportFilename}}") {{_ 'export-board'}}
- unless currentBoard.isTemplatesBoard
- li: a.js-archive-board {{_ 'archive-board'}}
- li: a.js-outgoing-webhooks {{_ 'outgoing-webhooks'}}
- hr
- ul.pop-over-list
- li: a.js-subtask-settings {{_ 'subtask-settings'}}
+ if withApi
+ li
+ a(href="{{exportUrl}}", download="{{exportFilename}}")
+ i.fa.fa-share-alt
+ | {{_ 'export-board'}}
+ li
+ a.js-outgoing-webhooks
+ i.fa.fa-globe
+ | {{_ 'outgoing-webhooks'}}
+ li
+ a.js-card-settings
+ i.fa.fa-id-card-o
+ | {{_ 'card-settings'}}
+ li
+ a.js-subtask-settings
+ i.fa.fa-sitemap
+ | {{_ 'subtask-settings'}}
+ unless currentBoard.isTemplatesBoard
+ hr
+ ul.pop-over-list
+ li
+ a.js-archive-board
+ i.fa.fa-arrow-right
+ i.fa.fa-archive
+ | {{_ 'archive-board'}}
if isSandstorm
hr
ul.pop-over-list
- li: a(href="{{exportUrl}}", download="{{exportFilename}}") {{_ 'export-board'}}
- li: a.js-import-board {{_ 'import-board-c'}}
- li: a.js-archive-board {{_ 'archive-board'}}
- li: a.js-outgoing-webhooks {{_ 'outgoing-webhooks'}}
+ if withApi
+ li
+ a(href="{{exportUrl}}", download="{{exportFilename}}")
+ i.fa.fa-share-alt
+ i.fa.fa-sign-out
+ | {{_ 'export-board'}}
+ li
+ a.js-import-board
+ i.fa.fa-share-alt
+ i.fa.fa-sign-in
+ | {{_ 'import-board-c'}}
+ li
+ a.js-archive-board
+ i.fa.fa-arrow-right
+ i.fa.fa-archive
+ | {{_ 'archive-board'}}
+ li
+ a.js-outgoing-webhooks
+ i.fa.fa-globe
+ | {{_ 'outgoing-webhooks'}}
hr
ul.pop-over-list
- li: a.js-subtask-settings {{_ 'subtask-settings'}}
+ li
+ a.js-card-settings
+ i.fa.fa-id-card-o
+ | {{_ 'card-settings'}}
+ hr
+ ul.pop-over-list
+ li
+ a.js-subtask-settings
+ i.fa.fa-sitemap
+ | {{_ 'subtask-settings'}}
template(name="labelsWidget")
.board-widget.board-widget-labels
@@ -203,7 +368,7 @@ template(name="labelsWidget")
.board-widget-content
each currentBoard.labels
a.card-label(class="card-label-{{color}}"
- class="{{#if currentUser.isNotCommentOnly}}js-label{{/if}}")
+ class="{{#if currentUser.isNotCommentOnly}}{{#if currentUser.isNotWorker}}js-label{{/if}}{{/if}}")
span.card-label-name
+viewer
= name
@@ -232,12 +397,12 @@ template(name="memberPopup")
a.js-change-role
| {{_ 'change-permissions'}}
span.quiet (#{memberType})
- li
- if $eq currentUser._id userId
- a.js-leave-member {{_ 'leave-board'}}
- else if currentUser.isBoardAdmin
- a.js-remove-member {{_ 'remove-from-board'}}
-
+ unless currentUser.isWorker
+ li
+ if $eq currentUser._id userId
+ a.js-leave-member {{_ 'leave-board'}}
+ else if currentUser.isBoardAdmin
+ a.js-remove-member {{_ 'remove-from-board'}}
template(name="removeMemberPopup")
p {{_ 'remove-member-pop' name=user.profile.fullname username=user.username boardTitle=board.title}}
@@ -301,6 +466,12 @@ template(name="changePermissionsPopup")
if isCommentOnly
i.fa.fa-check
span.sub-name {{_ 'comment-only-desc'}}
+ li
+ a(class="{{#if isLastAdmin}}disabled{{else}}js-set-worker{{/if}}")
+ | {{_ 'worker'}}
+ if isWorker
+ i.fa.fa-check
+ span.sub-name {{_ 'worker-desc'}}
if isLastAdmin
hr
p.quiet.bottom {{_ 'last-admin-desc'}}
diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js
index caf36020..cbe00797 100644
--- a/client/components/sidebar/sidebar.js
+++ b/client/components/sidebar/sidebar.js
@@ -112,12 +112,10 @@ BlazeComponent.extendComponent({
currentUser = Meteor.user();
if (currentUser) {
Meteor.call('toggleMinicardLabelText');
+ } else if (cookies.has('hiddenMinicardLabelText')) {
+ cookies.remove('hiddenMinicardLabelText');
} else {
- if (cookies.has('hiddenMinicardLabelText')) {
- cookies.remove('hiddenMinicardLabelText');
- } else {
- cookies.set('hiddenMinicardLabelText', 'true');
- }
+ cookies.set('hiddenMinicardLabelText', 'true');
}
},
'click .js-shortcuts'() {
@@ -135,12 +133,10 @@ Template.homeSidebar.helpers({
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).hiddenMinicardLabelText;
+ } else if (cookies.has('hiddenMinicardLabelText')) {
+ return true;
} else {
- if (cookies.has('hiddenMinicardLabelText')) {
- return true;
- } else {
- return false;
- }
+ return false;
}
},
});
@@ -165,10 +161,13 @@ Template.memberPopup.helpers({
const currentBoard = Boards.findOne(Session.get('currentBoard'));
const commentOnly = currentBoard.hasCommentOnly(this.userId);
const noComments = currentBoard.hasNoComments(this.userId);
+ const worker = currentBoard.hasWorker(this.userId);
if (commentOnly) {
return TAPi18n.__('comment-only').toLowerCase();
} else if (noComments) {
return TAPi18n.__('no-comments').toLowerCase();
+ } else if (worker) {
+ return TAPi18n.__('worker').toLowerCase();
} else {
return TAPi18n.__(type).toLowerCase();
}
@@ -183,6 +182,10 @@ Template.memberPopup.helpers({
Template.boardMenuPopup.events({
'click .js-rename-board': Popup.open('boardChangeTitle'),
+ 'click .js-open-rules-view'() {
+ Modal.openWide('rulesMain');
+ Popup.close();
+ },
'click .js-custom-fields'() {
Sidebar.setView('customFields');
Popup.close();
@@ -209,9 +212,20 @@ Template.boardMenuPopup.events({
'click .js-outgoing-webhooks': Popup.open('outgoingWebhooks'),
'click .js-import-board': Popup.open('chooseBoardSource'),
'click .js-subtask-settings': Popup.open('boardSubtaskSettings'),
+ 'click .js-card-settings': Popup.open('boardCardSettings'),
+});
+
+Template.boardMenuPopup.onCreated(function() {
+ this.apiEnabled = new ReactiveVar(false);
+ Meteor.call('_isApiEnabled', (e, result) => {
+ this.apiEnabled.set(result);
+ });
});
Template.boardMenuPopup.helpers({
+ withApi() {
+ return Template.instance().apiEnabled.get();
+ },
exportUrl() {
const params = {
boardId: Session.get('currentBoard'),
@@ -271,6 +285,14 @@ Template.membersWidget.helpers({
const user = Meteor.user();
return user && user.isInvitedTo(Session.get('currentBoard'));
},
+ isWorker() {
+ const user = Meteor.user();
+ if (user) {
+ return Meteor.call(Boards.hasWorker(user.memberId));
+ } else {
+ return false;
+ }
+ },
});
Template.membersWidget.events({
@@ -465,6 +487,10 @@ BlazeComponent.extendComponent({
return this.currentBoard.allowsSubtasks;
},
+ allowsReceivedDate() {
+ return this.currentBoard.allowsReceivedDate;
+ },
+
isBoardSelected() {
return this.currentBoard.subtasksDefaultBoardId === this.currentData()._id;
},
@@ -483,7 +509,7 @@ BlazeComponent.extendComponent({
'members.userId': Meteor.userId(),
},
{
- sort: ['title'],
+ sort: { sort: 1 /* boards default sorting */ },
},
);
},
@@ -580,6 +606,359 @@ BlazeComponent.extendComponent({
BlazeComponent.extendComponent({
onCreated() {
+ this.currentBoard = Boards.findOne(Session.get('currentBoard'));
+ },
+
+ allowsReceivedDate() {
+ return this.currentBoard.allowsReceivedDate;
+ },
+
+ allowsStartDate() {
+ return this.currentBoard.allowsStartDate;
+ },
+
+ allowsDueDate() {
+ return this.currentBoard.allowsDueDate;
+ },
+
+ allowsEndDate() {
+ return this.currentBoard.allowsEndDate;
+ },
+
+ allowsSubtasks() {
+ return this.currentBoard.allowsSubtasks;
+ },
+
+ allowsMembers() {
+ return this.currentBoard.allowsMembers;
+ },
+
+ allowsAssignee() {
+ return this.currentBoard.allowsAssignee;
+ },
+
+ allowsAssignedBy() {
+ return this.currentBoard.allowsAssignedBy;
+ },
+
+ allowsRequestedBy() {
+ return this.currentBoard.allowsRequestedBy;
+ },
+
+ allowsLabels() {
+ return this.currentBoard.allowsLabels;
+ },
+
+ allowsChecklists() {
+ return this.currentBoard.allowsChecklists;
+ },
+
+ allowsAttachments() {
+ return this.currentBoard.allowsAttachments;
+ },
+
+ allowsComments() {
+ return this.currentBoard.allowsComments;
+ },
+
+ allowsDescriptionTitle() {
+ return this.currentBoard.allowsDescriptionTitle;
+ },
+
+ allowsDescriptionText() {
+ return this.currentBoard.allowsDescriptionText;
+ },
+
+ isBoardSelected() {
+ return this.currentBoard.dateSettingsDefaultBoardID;
+ },
+
+ isNullBoardSelected() {
+ return (
+ this.currentBoard.dateSettingsDefaultBoardId === null ||
+ this.currentBoard.dateSettingsDefaultBoardId === undefined
+ );
+ },
+
+ boards() {
+ return Boards.find(
+ {
+ archived: false,
+ 'members.userId': Meteor.userId(),
+ },
+ {
+ sort: { sort: 1 /* boards default sorting */ },
+ },
+ );
+ },
+
+ lists() {
+ return Lists.find(
+ {
+ boardId: this.currentBoard._id,
+ archived: false,
+ },
+ {
+ sort: ['title'],
+ },
+ );
+ },
+
+ hasLists() {
+ return this.lists().count() > 0;
+ },
+
+ isListSelected() {
+ return (
+ this.currentBoard.dateSettingsDefaultBoardId === this.currentData()._id
+ );
+ },
+
+ events() {
+ return [
+ {
+ 'click .js-field-has-receiveddate'(evt) {
+ evt.preventDefault();
+ this.currentBoard.allowsReceivedDate = !this.currentBoard
+ .allowsReceivedDate;
+ this.currentBoard.setAllowsReceivedDate(
+ this.currentBoard.allowsReceivedDate,
+ );
+ $(`.js-field-has-receiveddate ${MCB}`).toggleClass(
+ CKCLS,
+ this.currentBoard.allowsReceivedDate,
+ );
+ $('.js-field-has-receiveddate').toggleClass(
+ CKCLS,
+ this.currentBoard.allowsReceivedDate,
+ );
+ },
+ 'click .js-field-has-startdate'(evt) {
+ evt.preventDefault();
+ this.currentBoard.allowsStartDate = !this.currentBoard
+ .allowsStartDate;
+ this.currentBoard.setAllowsStartDate(
+ this.currentBoard.allowsStartDate,
+ );
+ $(`.js-field-has-startdate ${MCB}`).toggleClass(
+ CKCLS,
+ this.currentBoard.allowsStartDate,
+ );
+ $('.js-field-has-startdate').toggleClass(
+ CKCLS,
+ this.currentBoard.allowsStartDate,
+ );
+ },
+ 'click .js-field-has-enddate'(evt) {
+ evt.preventDefault();
+ this.currentBoard.allowsEndDate = !this.currentBoard.allowsEndDate;
+ this.currentBoard.setAllowsEndDate(this.currentBoard.allowsEndDate);
+ $(`.js-field-has-enddate ${MCB}`).toggleClass(
+ CKCLS,
+ this.currentBoard.allowsEndDate,
+ );
+ $('.js-field-has-enddate').toggleClass(
+ CKCLS,
+ this.currentBoard.allowsEndDate,
+ );
+ },
+ 'click .js-field-has-duedate'(evt) {
+ evt.preventDefault();
+ this.currentBoard.allowsDueDate = !this.currentBoard.allowsDueDate;
+ this.currentBoard.setAllowsDueDate(this.currentBoard.allowsDueDate);
+ $(`.js-field-has-duedate ${MCB}`).toggleClass(
+ CKCLS,
+ this.currentBoard.allowsDueDate,
+ );
+ $('.js-field-has-duedate').toggleClass(
+ CKCLS,
+ this.currentBoard.allowsDueDate,
+ );
+ },
+ 'click .js-field-has-subtasks'(evt) {
+ evt.preventDefault();
+ this.currentBoard.allowsSubtasks = !this.currentBoard.allowsSubtasks;
+ this.currentBoard.setAllowsSubtasks(this.currentBoard.allowsSubtasks);
+ $(`.js-field-has-subtasks ${MCB}`).toggleClass(
+ CKCLS,
+ this.currentBoard.allowsSubtasks,
+ );
+ $('.js-field-has-subtasks').toggleClass(
+ CKCLS,
+ this.currentBoard.allowsSubtasks,
+ );
+ },
+ 'click .js-field-has-members'(evt) {
+ evt.preventDefault();
+ this.currentBoard.allowsMembers = !this.currentBoard.allowsMembers;
+ this.currentBoard.setAllowsMembers(this.currentBoard.allowsMembers);
+ $(`.js-field-has-members ${MCB}`).toggleClass(
+ CKCLS,
+ this.currentBoard.allowsMembers,
+ );
+ $('.js-field-has-members').toggleClass(
+ CKCLS,
+ this.currentBoard.allowsMembers,
+ );
+ },
+ 'click .js-field-has-assignee'(evt) {
+ evt.preventDefault();
+ this.currentBoard.allowsAssignee = !this.currentBoard.allowsAssignee;
+ this.currentBoard.setAllowsAssignee(this.currentBoard.allowsAssignee);
+ $(`.js-field-has-assignee ${MCB}`).toggleClass(
+ CKCLS,
+ this.currentBoard.allowsAssignee,
+ );
+ $('.js-field-has-assignee').toggleClass(
+ CKCLS,
+ this.currentBoard.allowsAssignee,
+ );
+ },
+ 'click .js-field-has-assigned-by'(evt) {
+ evt.preventDefault();
+ this.currentBoard.allowsAssignedBy = !this.currentBoard
+ .allowsAssignedBy;
+ this.currentBoard.setAllowsAssignedBy(
+ this.currentBoard.allowsAssignedBy,
+ );
+ $(`.js-field-has-assigned-by ${MCB}`).toggleClass(
+ CKCLS,
+ this.currentBoard.allowsAssignedBy,
+ );
+ $('.js-field-has-assigned-by').toggleClass(
+ CKCLS,
+ this.currentBoard.allowsAssignedBy,
+ );
+ },
+ 'click .js-field-has-requested-by'(evt) {
+ evt.preventDefault();
+ this.currentBoard.allowsRequestedBy = !this.currentBoard
+ .allowsRequestedBy;
+ this.currentBoard.setAllowsRequestedBy(
+ this.currentBoard.allowsRequestedBy,
+ );
+ $(`.js-field-has-requested-by ${MCB}`).toggleClass(
+ CKCLS,
+ this.currentBoard.allowsRequestedBy,
+ );
+ $('.js-field-has-requested-by').toggleClass(
+ CKCLS,
+ this.currentBoard.allowsRequestedBy,
+ );
+ },
+ 'click .js-field-has-labels'(evt) {
+ evt.preventDefault();
+ this.currentBoard.allowsLabels = !this.currentBoard.allowsLabels;
+ this.currentBoard.setAllowsLabels(this.currentBoard.allowsLabels);
+ $(`.js-field-has-labels ${MCB}`).toggleClass(
+ CKCLS,
+ this.currentBoard.allowsAssignee,
+ );
+ $('.js-field-has-labels').toggleClass(
+ CKCLS,
+ this.currentBoard.allowsLabels,
+ );
+ },
+ 'click .js-field-has-description-title'(evt) {
+ evt.preventDefault();
+ this.currentBoard.allowsDescriptionTitle = !this.currentBoard
+ .allowsDescriptionTitle;
+ this.currentBoard.setAllowsDescriptionTitle(
+ this.currentBoard.allowsDescriptionTitle,
+ );
+ $(`.js-field-has-description-title ${MCB}`).toggleClass(
+ CKCLS,
+ this.currentBoard.allowsDescriptionTitle,
+ );
+ $('.js-field-has-description-title').toggleClass(
+ CKCLS,
+ this.currentBoard.allowsDescriptionTitle,
+ );
+ },
+ 'click .js-field-has-description-text'(evt) {
+ evt.preventDefault();
+ this.currentBoard.allowsDescriptionText = !this.currentBoard
+ .allowsDescriptionText;
+ this.currentBoard.setAllowsDescriptionText(
+ this.currentBoard.allowsDescriptionText,
+ );
+ $(`.js-field-has-description-text ${MCB}`).toggleClass(
+ CKCLS,
+ this.currentBoard.allowsDescriptionText,
+ );
+ $('.js-field-has-description-text').toggleClass(
+ CKCLS,
+ this.currentBoard.allowsDescriptionText,
+ );
+ },
+ 'click .js-field-has-checklists'(evt) {
+ evt.preventDefault();
+ this.currentBoard.allowsChecklists = !this.currentBoard
+ .allowsChecklists;
+ this.currentBoard.setAllowsChecklists(
+ this.currentBoard.allowsChecklists,
+ );
+ $(`.js-field-has-checklists ${MCB}`).toggleClass(
+ CKCLS,
+ this.currentBoard.allowsChecklists,
+ );
+ $('.js-field-has-checklists').toggleClass(
+ CKCLS,
+ this.currentBoard.allowsChecklists,
+ );
+ },
+ 'click .js-field-has-attachments'(evt) {
+ evt.preventDefault();
+ this.currentBoard.allowsAttachments = !this.currentBoard
+ .allowsAttachments;
+ this.currentBoard.setAllowsAttachments(
+ this.currentBoard.allowsAttachments,
+ );
+ $(`.js-field-has-attachments ${MCB}`).toggleClass(
+ CKCLS,
+ this.currentBoard.allowsAttachments,
+ );
+ $('.js-field-has-attachments').toggleClass(
+ CKCLS,
+ this.currentBoard.allowsAttachments,
+ );
+ },
+ 'click .js-field-has-comments'(evt) {
+ evt.preventDefault();
+ this.currentBoard.allowsComments = !this.currentBoard.allowsComments;
+ this.currentBoard.setAllowsComments(this.currentBoard.allowsComments);
+ $(`.js-field-has-comments ${MCB}`).toggleClass(
+ CKCLS,
+ this.currentBoard.allowsComments,
+ );
+ $('.js-field-has-comments').toggleClass(
+ CKCLS,
+ this.currentBoard.allowsComments,
+ );
+ },
+ 'click .js-field-has-activities'(evt) {
+ evt.preventDefault();
+ this.currentBoard.allowsActivities = !this.currentBoard
+ .allowsActivities;
+ this.currentBoard.setAllowsActivities(
+ this.currentBoard.allowsActivities,
+ );
+ $(`.js-field-has-activities ${MCB}`).toggleClass(
+ CKCLS,
+ this.currentBoard.allowsActivities,
+ );
+ $('.js-field-has-activities').toggleClass(
+ CKCLS,
+ this.currentBoard.allowsActivities,
+ );
+ },
+ },
+ ];
+ },
+}).register('boardCardSettingsPopup');
+
+BlazeComponent.extendComponent({
+ onCreated() {
this.error = new ReactiveVar('');
this.loading = new ReactiveVar(false);
},
@@ -648,7 +1027,7 @@ BlazeComponent.extendComponent({
}).register('addMemberPopup');
Template.changePermissionsPopup.events({
- 'click .js-set-admin, click .js-set-normal, click .js-set-no-comments, click .js-set-comment-only'(
+ 'click .js-set-admin, click .js-set-normal, click .js-set-no-comments, click .js-set-comment-only, click .js-set-worker'(
event,
) {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
@@ -658,11 +1037,13 @@ Template.changePermissionsPopup.events({
'js-set-comment-only',
);
const isNoComments = $(event.currentTarget).hasClass('js-set-no-comments');
+ const isWorker = $(event.currentTarget).hasClass('js-set-worker');
currentBoard.setMemberPermission(
memberId,
isAdmin,
isNoComments,
isCommentOnly,
+ isWorker,
);
Popup.back(1);
},
@@ -679,7 +1060,8 @@ Template.changePermissionsPopup.helpers({
return (
!currentBoard.hasAdmin(this.userId) &&
!currentBoard.hasNoComments(this.userId) &&
- !currentBoard.hasCommentOnly(this.userId)
+ !currentBoard.hasCommentOnly(this.userId) &&
+ !currentBoard.hasWorker(this.userId)
);
},
@@ -699,6 +1081,13 @@ Template.changePermissionsPopup.helpers({
);
},
+ isWorker() {
+ const currentBoard = Boards.findOne(Session.get('currentBoard'));
+ return (
+ !currentBoard.hasAdmin(this.userId) && currentBoard.hasWorker(this.userId)
+ );
+ },
+
isLastAdmin() {
const currentBoard = Boards.findOne(Session.get('currentBoard'));
return (
diff --git a/client/components/sidebar/sidebar.styl b/client/components/sidebar/sidebar.styl
index 740186b5..c1047277 100644
--- a/client/components/sidebar/sidebar.styl
+++ b/client/components/sidebar/sidebar.styl
@@ -109,7 +109,7 @@
color: darken(white, 40%)
.board-sidebar
- width: 248px
+ width: 548px
right: -@width
transition: top .1s, right .1s, width .1s
diff --git a/client/components/sidebar/sidebarArchives.jade b/client/components/sidebar/sidebarArchives.jade
index 466d2cb0..56423ad7 100644
--- a/client/components/sidebar/sidebarArchives.jade
+++ b/client/components/sidebar/sidebarArchives.jade
@@ -2,54 +2,60 @@ template(name="archivesSidebar")
if isArchiveReady.get
+basicTabs(tabs=tabs)
+tabContent(slug="cards")
- p.quiet
- a.js-restore-all-cards {{_ 'restore-all'}}
- | -
- a.js-delete-all-cards {{_ 'delete-all'}}
+ unless isWorker
+ p.quiet
+ a.js-restore-all-cards {{_ 'restore-all'}}
+ | -
+ a.js-delete-all-cards {{_ 'delete-all'}}
each archivedCards
.minicard-wrapper.js-minicard
+minicard(this)
if currentUser.isBoardMember
- p.quiet
- a.js-restore-card {{_ 'restore'}}
- | -
- a.js-delete-card {{_ 'delete'}}
+ unless isWorker
+ p.quiet
+ a.js-restore-card {{_ 'restore'}}
+ | -
+ a.js-delete-card {{_ 'delete'}}
if cardIsInArchivedList
p.quiet.small ({{_ 'warn-list-archived'}})
else
p.no-items-message {{_ 'no-archived-cards'}}
+tabContent(slug="lists")
- p.quiet
- a.js-restore-all-lists {{_ 'restore-all'}}
- | -
- a.js-delete-all-lists {{_ 'delete-all'}}
+ unless isWorker
+ p.quiet
+ a.js-restore-all-lists {{_ 'restore-all'}}
+ | -
+ a.js-delete-all-lists {{_ 'delete-all'}}
ul.archived-lists
each archivedLists
li.archived-lists-item
= title
if currentUser.isBoardMember
- p.quiet
- a.js-restore-list {{_ 'restore'}}
- | -
- a.js-delete-list {{_ 'delete'}}
+ unless isWorker
+ p.quiet
+ a.js-restore-list {{_ 'restore'}}
+ | -
+ a.js-delete-list {{_ 'delete'}}
else
li.no-items-message {{_ 'no-archived-lists'}}
+tabContent(slug="swimlanes")
- p.quiet
- a.js-restore-all-swimlanes {{_ 'restore-all'}}
- | -
- a.js-delete-all-swimlanes {{_ 'delete-all'}}
+ unless isWorker
+ p.quiet
+ a.js-restore-all-swimlanes {{_ 'restore-all'}}
+ | -
+ a.js-delete-all-swimlanes {{_ 'delete-all'}}
ul.archived-lists
each archivedSwimlanes
li.archived-lists-item
= title
if currentUser.isBoardMember
- p.quiet
- a.js-restore-swimlane {{_ 'restore'}}
- | -
- a.js-delete-swimlane {{_ 'delete'}}
+ unless isWorker
+ p.quiet
+ a.js-restore-swimlane {{_ 'restore'}}
+ | -
+ a.js-delete-swimlane {{_ 'delete'}}
else
li.no-items-message {{_ 'no-archived-swimlanes'}}
else
diff --git a/client/components/sidebar/sidebarArchives.js b/client/components/sidebar/sidebarArchives.js
index a4846561..75b694e9 100644
--- a/client/components/sidebar/sidebarArchives.js
+++ b/client/components/sidebar/sidebarArchives.js
@@ -139,3 +139,12 @@ BlazeComponent.extendComponent({
];
},
}).register('archivesSidebar');
+
+Template.archivesSidebar.helpers({
+ isWorker() {
+ const currentBoard = Boards.findOne(Session.get('currentBoard'));
+ return (
+ !currentBoard.hasAdmin(this.userId) && currentBoard.hasWorker(this.userId)
+ );
+ },
+});
diff --git a/client/components/sidebar/sidebarFilters.jade b/client/components/sidebar/sidebarFilters.jade
index 5f929cb9..6d899b70 100644
--- a/client/components/sidebar/sidebarFilters.jade
+++ b/client/components/sidebar/sidebarFilters.jade
@@ -46,6 +46,24 @@ template(name="filterSidebar")
i.fa.fa-check
hr
ul.sidebar-list
+ li(class="{{#if Filter.assignees.isSelected undefined}}active{{/if}}")
+ a.name.js-toggle-assignee-filter
+ span.sidebar-list-item-description
+ | {{_ 'filter-no-assignee'}}
+ if Filter.assignees.isSelected undefined
+ i.fa.fa-check
+ each currentBoard.activeMembers
+ with getUser userId
+ li(class="{{#if Filter.assignees.isSelected _id}}active{{/if}}")
+ a.name.js-toggle-assignee-filter
+ +userAvatar(userId=this._id)
+ span.sidebar-list-item-description
+ = profile.fullname
+ | (<span class="username">{{ username }}</span>)
+ if Filter.assignees.isSelected _id
+ i.fa.fa-check
+ hr
+ ul.sidebar-list
li(class="{{#if Filter.customFields.isSelected undefined}}active{{/if}}")
a.name.js-toggle-custom-fields-filter
span.sidebar-list-item-description
@@ -117,13 +135,14 @@ template(name="multiselectionSidebar")
i.fa.fa-check
else if someSelectedElementHave 'member' _id
i.fa.fa-ellipsis-h
- hr
- a.sidebar-btn.js-move-selection
- i.fa.fa-share
- span {{_ 'move-selection'}}
- a.sidebar-btn.js-archive-selection
- i.fa.fa-archive
- span {{_ 'archive-selection'}}
+ unless currentUser.isWorker
+ hr
+ a.sidebar-btn.js-move-selection
+ i.fa.fa-share
+ span {{_ 'move-selection'}}
+ a.sidebar-btn.js-archive-selection
+ i.fa.fa-archive
+ span {{_ 'archive-selection'}}
template(name="disambiguateMultiLabelPopup")
p {{_ 'what-to-do'}}
diff --git a/client/components/sidebar/sidebarFilters.js b/client/components/sidebar/sidebarFilters.js
index ee0176b9..0d402ab5 100644
--- a/client/components/sidebar/sidebarFilters.js
+++ b/client/components/sidebar/sidebarFilters.js
@@ -18,6 +18,11 @@ BlazeComponent.extendComponent({
Filter.members.toggle(this.currentData()._id);
Filter.resetExceptions();
},
+ 'click .js-toggle-assignee-filter'(evt) {
+ evt.preventDefault();
+ Filter.assignees.toggle(this.currentData()._id);
+ Filter.resetExceptions();
+ },
'click .js-toggle-archive-filter'(evt) {
evt.preventDefault();
Filter.archive.toggle(this.currentData()._id);
diff --git a/client/components/swimlanes/swimlaneHeader.js b/client/components/swimlanes/swimlaneHeader.js
index fbc45351..3032966d 100644
--- a/client/components/swimlanes/swimlaneHeader.js
+++ b/client/components/swimlanes/swimlaneHeader.js
@@ -35,12 +35,10 @@ Template.swimlaneHeader.helpers({
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).showDesktopDragHandles;
+ } else if (cookies.has('showDesktopDragHandles')) {
+ return true;
} else {
- if (cookies.has('showDesktopDragHandles')) {
- return true;
- } else {
- return false;
- }
+ return false;
}
},
});
diff --git a/client/components/swimlanes/swimlanes.jade b/client/components/swimlanes/swimlanes.jade
index 1dc23c59..9b00d9e8 100644
--- a/client/components/swimlanes/swimlanes.jade
+++ b/client/components/swimlanes/swimlanes.jade
@@ -43,19 +43,20 @@ template(name="listsGroup")
+addListForm
template(name="addListForm")
- .list.list-composer.js-list-composer(class="{{#if isMiniScreen}}mini-list{{/if}}")
- .list-header-add
- +inlinedForm(autoclose=false)
- input.list-name-input.full-line(type="text" placeholder="{{_ 'add-list'}}"
- autocomplete="off" autofocus)
- .edit-controls.clearfix
- button.primary.confirm(type="submit") {{_ 'save'}}
- unless currentBoard.isTemplatesBoard
- unless currentBoard.isTemplateBoard
- span.quiet
- | {{_ 'or'}}
- a.js-list-template {{_ 'template'}}
- else
- a.open-list-composer.js-open-inlined-form
- i.fa.fa-plus
- | {{_ 'add-list'}}
+ unless currentUser.isWorker
+ .list.list-composer.js-list-composer(class="{{#if isMiniScreen}}mini-list{{/if}}")
+ .list-header-add
+ +inlinedForm(autoclose=false)
+ input.list-name-input.full-line(type="text" placeholder="{{_ 'add-list'}}"
+ autocomplete="off" autofocus)
+ .edit-controls.clearfix
+ button.primary.confirm(type="submit") {{_ 'save'}}
+ unless currentBoard.isTemplatesBoard
+ unless currentBoard.isTemplateBoard
+ span.quiet
+ | {{_ 'or'}}
+ a.js-list-template {{_ 'template'}}
+ else
+ a.open-list-composer.js-open-inlined-form
+ i.fa.fa-plus
+ | {{_ 'add-list'}}
diff --git a/client/components/swimlanes/swimlanes.js b/client/components/swimlanes/swimlanes.js
index d072a2a2..753fa88b 100644
--- a/client/components/swimlanes/swimlanes.js
+++ b/client/components/swimlanes/swimlanes.js
@@ -1,6 +1,6 @@
import { Cookies } from 'meteor/ostrio:cookies';
const cookies = new Cookies();
-const { calculateIndex, enableClickOnTouch } = Utils;
+const { calculateIndex } = Utils;
function currentListIsInThisSwimlane(swimlaneId) {
const currentList = Lists.findOne(Session.get('currentList'));
@@ -87,14 +87,12 @@ function initSortable(boardComponent, $listsDom) {
},
});
- // ugly touch event hotfix
- enableClickOnTouch('.js-list:not(.js-list-composer)');
-
function userIsMember() {
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
- !Meteor.user().isCommentOnly()
+ !Meteor.user().isCommentOnly() &&
+ !Meteor.user().isWorker()
);
}
@@ -104,31 +102,29 @@ function initSortable(boardComponent, $listsDom) {
if (currentUser) {
showDesktopDragHandles = (currentUser.profile || {})
.showDesktopDragHandles;
+ } else if (cookies.has('showDesktopDragHandles')) {
+ showDesktopDragHandles = true;
} else {
- if (cookies.has('showDesktopDragHandles')) {
- showDesktopDragHandles = true;
- } else {
- showDesktopDragHandles = false;
- }
+ showDesktopDragHandles = false;
}
- if (!Utils.isMiniScreen() && showDesktopDragHandles) {
+ if (Utils.isMiniScreen() || showDesktopDragHandles) {
$listsDom.sortable({
handle: '.js-list-handle',
});
- } else {
+ } else if (!Utils.isMiniScreen() && !showDesktopDragHandles) {
$listsDom.sortable({
handle: '.js-list-header',
});
}
const $listDom = $listsDom;
- if ($listDom.data('sortable')) {
+ if ($listDom.data('uiSortable') || $listDom.data('sortable')) {
$listsDom.sortable(
'option',
'disabled',
- // Disable drag-dropping when user is not member
- !userIsMember(),
+ // Disable drag-dropping when user is not member/is worker
+ !userIsMember() || Meteor.user().isWorker(),
// Not disable drag-dropping while in multi-selection mode
// MultiSelection.isActive() || !userIsMember(),
);
@@ -182,17 +178,14 @@ BlazeComponent.extendComponent({
if (currentUser) {
showDesktopDragHandles = (currentUser.profile || {})
.showDesktopDragHandles;
+ } else if (cookies.has('showDesktopDragHandles')) {
+ showDesktopDragHandles = true;
} else {
- if (cookies.has('showDesktopDragHandles')) {
- showDesktopDragHandles = true;
- } else {
- showDesktopDragHandles = false;
- }
+ showDesktopDragHandles = false;
}
const noDragInside = ['a', 'input', 'textarea', 'p'].concat(
- Utils.isMiniScreen() ||
- (!Utils.isMiniScreen() && showDesktopDragHandles)
+ Utils.isMiniScreen() || showDesktopDragHandles
? ['.js-list-handle', '.js-swimlane-header-handle']
: ['.js-list-header'],
);
@@ -276,19 +269,18 @@ Template.swimlane.helpers({
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).showDesktopDragHandles;
+ } else if (cookies.has('showDesktopDragHandles')) {
+ return true;
} else {
- if (cookies.has('showDesktopDragHandles')) {
- return true;
- } else {
- return false;
- }
+ return false;
}
},
canSeeAddList() {
return (
Meteor.user() &&
Meteor.user().isBoardMember() &&
- !Meteor.user().isCommentOnly()
+ !Meteor.user().isCommentOnly() &&
+ !Meteor.user().isWorker()
);
},
});
diff --git a/client/components/users/userAvatar.jade b/client/components/users/userAvatar.jade
index ebfa48ba..7f2067ce 100644
--- a/client/components/users/userAvatar.jade
+++ b/client/components/users/userAvatar.jade
@@ -73,6 +73,7 @@ template(name="cardMemberPopup")
p.quiet @{{ user.username }}
ul.pop-over-list
if currentUser.isNotCommentOnly
+ if currentUser.isNotWorker
li: a.js-remove-member {{_ 'remove-member-from-card'}}
if $eq currentUser._id user._id
diff --git a/client/components/users/userHeader.jade b/client/components/users/userHeader.jade
index 50a80396..d0adf29d 100644
--- a/client/components/users/userHeader.jade
+++ b/client/components/users/userHeader.jade
@@ -13,21 +13,46 @@ template(name="headerUserBar")
template(name="memberMenuPopup")
ul.pop-over-list
with currentUser
- li: a.js-edit-profile {{_ 'edit-profile'}}
- li: a.js-change-settings {{_ 'change-settings'}}
- li: a.js-change-avatar {{_ 'edit-avatar'}}
+ li
+ a.js-edit-profile
+ i.fa.fa-user
+ | {{_ 'edit-profile'}}
+ li
+ a.js-change-settings
+ i.fa.fa-cog
+ | {{_ 'change-settings'}}
+ li
+ a.js-change-avatar
+ i.fa.fa-picture-o
+ | {{_ 'edit-avatar'}}
unless isSandstorm
- li: a.js-change-password {{_ 'changePasswordPopup-title'}}
- li: a.js-change-language {{_ 'changeLanguagePopup-title'}}
+ li
+ a.js-change-password
+ i.fa.fa-key
+ | {{_ 'changePasswordPopup-title'}}
+ li
+ a.js-change-language
+ i.fa.fa-flag
+ | {{_ 'changeLanguagePopup-title'}}
if currentUser.isAdmin
- li: a.js-go-setting(href="{{pathFor 'setting'}}") {{_ 'admin-panel'}}
- hr
- ul.pop-over-list
- li: a(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}") {{_ 'templates'}}
+ li
+ a.js-go-setting(href="{{pathFor 'setting'}}")
+ i.fa.fa-lock
+ | {{_ 'admin-panel'}}
+ unless currentUser.isWorker
+ hr
+ ul.pop-over-list
+ li
+ a(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}")
+ i.fa.fa-clone
+ | {{_ 'templates'}}
unless isSandstorm
hr
ul.pop-over-list
- li: a.js-logout {{_ 'log-out'}}
+ li
+ a.js-logout
+ i.fa.fa-sign-out
+ | {{_ 'log-out'}}
template(name="editProfilePopup")
form
@@ -73,23 +98,36 @@ template(name="changeLanguagePopup")
template(name="changeSettingsPopup")
ul.pop-over-list
- li
- a.js-toggle-system-messages
- | {{_ 'hide-system-messages'}}
- if hiddenSystemMessages
- i.fa.fa-check
+ //li
+ // a.js-toggle-system-messages
+ // i.fa.fa-comments-o
+ // | {{_ 'hide-system-messages'}}
+ // if hiddenSystemMessages
+ // i.fa.fa-check
li
a.js-toggle-desktop-drag-handles
+ i.fa.fa-arrows
| {{_ 'show-desktop-drag-handles'}}
if showDesktopDragHandles
i.fa.fa-check
- li
- label.bold
- | {{_ 'show-cards-minimum-count'}}
- input#show-cards-count-at.inline-input.left(type="number" value="#{showCardsCountAt}" min="0" max="99" onkeydown="return false")
- input.js-apply-show-cards-at.left(type="submit" value="{{_ 'apply'}}")
-
+ unless currentUser.isWorker
+ li
+ label.bold.clear
+ i.fa.fa-sort-numeric-asc
+ | {{_ 'show-cards-minimum-count'}}
+ input#show-cards-count-at.inline-input.left(type="number" value="#{showCardsCountAt}" min="0" max="99" onkeydown="return false")
+ label.bold.clear
+ i.fa.fa-calendar
+ | {{_ 'start-day-of-week'}}
+ select#start-day-of-week.inline-input.left
+ each day in weekDays startDayOfWeek
+ if day.isSelected
+ option(selected="true", value="#{day.value}") #{day.name}
+ else
+ option(value="#{day.value}") #{day.name}
+ input.js-apply-user-settings.left(type="submit" value="{{_ 'apply'}}")
template(name="userDeletePopup")
- p {{_ 'delete-user-confirm-popup'}}
- button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
+ unless currentUser.isWorker
+ p {{_ 'delete-user-confirm-popup'}}
+ button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
diff --git a/client/components/users/userHeader.js b/client/components/users/userHeader.js
index 5f36ef54..b7bb284e 100644
--- a/client/components/users/userHeader.js
+++ b/client/components/users/userHeader.js
@@ -45,13 +45,31 @@ Template.memberMenuPopup.events({
Template.editProfilePopup.helpers({
allowEmailChange() {
- return AccountSettings.findOne('accounts-allowEmailChange').booleanValue;
+ Meteor.call('AccountSettings.allowEmailChange', (_, result) => {
+ if (result) {
+ return true;
+ } else {
+ return false;
+ }
+ });
},
allowUserNameChange() {
- return AccountSettings.findOne('accounts-allowUserNameChange').booleanValue;
+ Meteor.call('AccountSettings.allowUserNameChange', (_, result) => {
+ if (result) {
+ return true;
+ } else {
+ return false;
+ }
+ });
},
allowUserDelete() {
- return AccountSettings.findOne('accounts-allowUserDelete').booleanValue;
+ Meteor.call('AccountSettings.allowUserDelete', (_, result) => {
+ if (result) {
+ return true;
+ } else {
+ return false;
+ }
+ });
},
});
@@ -148,6 +166,8 @@ Template.changeLanguagePopup.helpers({
name = 'Igbo';
} else if (lang.name === 'oc') {
name = 'Occitan';
+ } else if (lang.name === '繁体中文(台湾)') {
+ name = '繁體中文(台灣)';
}
return { tag, name };
}).sort(function(a, b) {
@@ -204,6 +224,27 @@ Template.changeSettingsPopup.helpers({
return cookies.get('limitToShowCardsCount');
}
},
+ weekDays(startDay) {
+ return [
+ TAPi18n.__('sunday'),
+ TAPi18n.__('monday'),
+ TAPi18n.__('tuesday'),
+ TAPi18n.__('wednesday'),
+ TAPi18n.__('thursday'),
+ TAPi18n.__('friday'),
+ TAPi18n.__('saturday'),
+ ].map(function(day, index) {
+ return { name: day, value: index, isSelected: index === startDay };
+ });
+ },
+ startDayOfWeek() {
+ currentUser = Meteor.user();
+ if (currentUser) {
+ return currentUser.getStartDayOfWeek();
+ } else {
+ return cookies.get('startDayOfWeek');
+ }
+ },
});
Template.changeSettingsPopup.events({
@@ -227,20 +268,31 @@ Template.changeSettingsPopup.events({
cookies.set('hasHiddenSystemMessages', 'true');
}
},
- 'click .js-apply-show-cards-at'(event, templateInstance) {
+ 'click .js-apply-user-settings'(event, templateInstance) {
event.preventDefault();
const minLimit = parseInt(
templateInstance.$('#show-cards-count-at').val(),
10,
);
+ const startDay = parseInt(
+ templateInstance.$('#start-day-of-week').val(),
+ 10,
+ );
+ const currentUser = Meteor.user();
if (!isNaN(minLimit)) {
- currentUser = Meteor.user();
if (currentUser) {
Meteor.call('changeLimitToShowCardsCount', minLimit);
} else {
cookies.set('limitToShowCardsCount', minLimit);
}
- Popup.back();
}
+ if (!isNaN(startDay)) {
+ if (currentUser) {
+ Meteor.call('changeStartDayOfWeek', startDay);
+ } else {
+ cookies.set('startDayOfWeek', startDay);
+ }
+ }
+ Popup.back();
},
});
diff --git a/client/lib/datepicker.js b/client/lib/datepicker.js
index 8ad66c5f..aa05310c 100644
--- a/client/lib/datepicker.js
+++ b/client/lib/datepicker.js
@@ -10,12 +10,22 @@ DatePicker = BlazeComponent.extendComponent({
this.defaultTime = defaultTime;
},
+ startDayOfWeek() {
+ const currentUser = Meteor.user();
+ if (currentUser) {
+ return currentUser.getStartDayOfWeek();
+ } else {
+ return 1;
+ }
+ },
+
onRendered() {
const $picker = this.$('.js-datepicker')
.datepicker({
todayHighlight: true,
todayBtn: 'linked',
language: TAPi18n.getLanguage(),
+ weekStart: this.startDayOfWeek(),
})
.on(
'changeDate',
diff --git a/client/lib/filter.js b/client/lib/filter.js
index 592eb4ab..24ca320b 100644
--- a/client/lib/filter.js
+++ b/client/lib/filter.js
@@ -459,13 +459,21 @@ Filter = {
// before changing the schema.
labelIds: new SetFilter(),
members: new SetFilter(),
+ assignees: new SetFilter(),
archive: new SetFilter(),
hideEmpty: new SetFilter(),
customFields: new SetFilter('_id'),
advanced: new AdvancedFilter(),
lists: new AdvancedFilter(), // we need the ability to filter list by name as well
- _fields: ['labelIds', 'members', 'archive', 'hideEmpty', 'customFields'],
+ _fields: [
+ 'labelIds',
+ 'members',
+ 'assignees',
+ 'archive',
+ 'hideEmpty',
+ 'customFields',
+ ],
// We don't filter cards that have been added after the last filter change. To
// implement this we keep the id of these cards in this `_exceptions` fields
diff --git a/client/lib/keyboard.js b/client/lib/keyboard.js
index d3f974be..e861e416 100755
--- a/client/lib/keyboard.js
+++ b/client/lib/keyboard.js
@@ -1,6 +1,16 @@
// XXX There is no reason to define these shortcuts globally, they should be
// attached to a template (most of them will go in the `board` template).
+function getHoveredCardId() {
+ const card = $('.js-minicard:hover').get(0);
+ if (!card) return null;
+ return Blaze.getData(card)._id;
+}
+
+function getSelectedCardId() {
+ return Session.get('selectedCard') || getHoveredCardId();
+}
+
Mousetrap.bind('?', () => {
FlowRouter.go('shortcuts');
});
@@ -50,9 +60,9 @@ Mousetrap.bind(['down', 'up'], (evt, key) => {
}
});
-// XXX This shortcut should also work when hovering over a card in board view
Mousetrap.bind('space', evt => {
- if (!Session.get('currentCard')) {
+ const cardId = getSelectedCardId();
+ if (!cardId) {
return;
}
@@ -62,7 +72,7 @@ Mousetrap.bind('space', evt => {
}
if (Meteor.user().isBoardMember()) {
- const card = Cards.findOne(Session.get('currentCard'));
+ const card = Cards.findOne(cardId);
card.toggleMember(currentUserId);
// We should prevent scrolling in card when spacebar is clicked
// This should do it according to Mousetrap docs, but it doesn't
@@ -70,22 +80,46 @@ Mousetrap.bind('space', evt => {
}
});
+Mousetrap.bind('c', evt => {
+ const cardId = getSelectedCardId();
+ if (!cardId) {
+ return;
+ }
+
+ const currentUserId = Meteor.userId();
+ if (currentUserId === null) {
+ return;
+ }
+
+ if (
+ Meteor.user().isBoardMember() &&
+ !Meteor.user().isCommentOnly() &&
+ !Meteor.user().isWorker()
+ ) {
+ const card = Cards.findOne(cardId);
+ card.archive();
+ // We should prevent scrolling in card when spacebar is clicked
+ // This should do it according to Mousetrap docs, but it doesn't
+ evt.preventDefault();
+ }
+});
+
Template.keyboardShortcuts.helpers({
mapping: [
{
- keys: ['W'],
+ keys: ['w'],
action: 'shortcut-toggle-sidebar',
},
{
- keys: ['Q'],
+ keys: ['q'],
action: 'shortcut-filter-my-cards',
},
{
- keys: ['F'],
+ keys: ['f'],
action: 'shortcut-toggle-filterbar',
},
{
- keys: ['X'],
+ keys: ['x'],
action: 'shortcut-clear-filters',
},
{
@@ -104,5 +138,9 @@ Template.keyboardShortcuts.helpers({
keys: ['SPACE'],
action: 'shortcut-assign-self',
},
+ {
+ keys: ['c'],
+ action: 'archive-card',
+ },
],
});
diff --git a/client/lib/textComplete.js b/client/lib/textComplete.js
index 8b6dc1f7..e97d3853 100644
--- a/client/lib/textComplete.js
+++ b/client/lib/textComplete.js
@@ -48,6 +48,11 @@ $.fn.escapeableTextComplete = function(strategies, options, ...otherArgs) {
return this;
};
-EscapeActions.register('textcomplete', () => {}, () => dropdownMenuIsOpened, {
- noClickEscapeOn: '.textcomplete-dropdown',
-});
+EscapeActions.register(
+ 'textcomplete',
+ () => {},
+ () => dropdownMenuIsOpened,
+ {
+ noClickEscapeOn: '.textcomplete-dropdown',
+ },
+);
diff --git a/client/lib/utils.js b/client/lib/utils.js
index f4fc170a..c921fddc 100644
--- a/client/lib/utils.js
+++ b/client/lib/utils.js
@@ -24,18 +24,14 @@ Utils = {
currentUser = Meteor.user();
if (currentUser) {
return (currentUser.profile || {}).boardView;
+ } else if (cookies.get('boardView') === 'board-view-lists') {
+ return 'board-view-lists';
+ } else if (cookies.get('boardView') === 'board-view-swimlanes') {
+ return 'board-view-swimlanes';
+ } else if (cookies.get('boardView') === 'board-view-cal') {
+ return 'board-view-cal';
} else {
- if (cookies.get('boardView') === 'board-view-lists') {
- return 'board-view-lists';
- } else if (
- cookies.get('boardView') === 'board-view-swimlanes'
- ) {
- return 'board-view-swimlanes';
- } else if (cookies.get('boardView') === 'board-view-cal') {
- return 'board-view-cal';
- } else {
- return false;
- }
+ return false;
}
},
@@ -43,8 +39,8 @@ Utils = {
goBoardId(_id) {
const board = Boards.findOne(_id);
return (
- board
- && FlowRouter.go('board', {
+ board &&
+ FlowRouter.go('board', {
id: board._id,
slug: board.slug,
})
@@ -55,8 +51,8 @@ Utils = {
const card = Cards.findOne(_id);
const board = Boards.findOne(card.boardId);
return (
- board
- && FlowRouter.go('card', {
+ board &&
+ FlowRouter.go('card', {
cardId: card._id,
boardId: board._id,
slug: board.slug,
@@ -151,8 +147,38 @@ Utils = {
// in a small window (even on desktop), Wekan run in compact mode.
// we can easily debug with a small window of desktop browser. :-)
isMiniScreen() {
+ // OLD WINDOW WIDTH DETECTION:
this.windowResizeDep.depend();
return $(window).width() <= 800;
+
+ // NEW TOUCH DEVICE DETECTION:
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent
+
+ /*
+ var hasTouchScreen = false;
+ if ("maxTouchPoints" in navigator) {
+ hasTouchScreen = navigator.maxTouchPoints > 0;
+ } else if ("msMaxTouchPoints" in navigator) {
+ hasTouchScreen = navigator.msMaxTouchPoints > 0;
+ } else {
+ var mQ = window.matchMedia && matchMedia("(pointer:coarse)");
+ if (mQ && mQ.media === "(pointer:coarse)") {
+ hasTouchScreen = !!mQ.matches;
+ } else if ('orientation' in window) {
+ hasTouchScreen = true; // deprecated, but good fallback
+ } else {
+ // Only as a last resort, fall back to user agent sniffing
+ var UA = navigator.userAgent;
+ hasTouchScreen = (
+ /\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(UA) ||
+ /\b(Android|Windows Phone|iPad|iPod)\b/i.test(UA)
+ );
+ }
+ }
+ */
+ //if (hasTouchScreen)
+ // document.getElementById("exampleButton").style.padding="1em";
+ //return false;
},
calculateIndexData(prevData, nextData, nItems = 1) {
@@ -227,8 +253,8 @@ Utils = {
};
if (
- 'ontouchstart' in window
- || (window.DocumentTouch && document instanceof window.DocumentTouch)
+ 'ontouchstart' in window ||
+ (window.DocumentTouch && document instanceof window.DocumentTouch)
) {
return true;
}
@@ -249,8 +275,8 @@ Utils = {
calculateTouchDistance(touchA, touchB) {
return Math.sqrt(
- Math.pow(touchA.screenX - touchB.screenX, 2)
- + Math.pow(touchA.screenY - touchB.screenY, 2),
+ Math.pow(touchA.screenX - touchB.screenX, 2) +
+ Math.pow(touchA.screenY - touchB.screenY, 2),
);
},
@@ -267,9 +293,9 @@ Utils = {
});
$(document).on('touchend', selector, function(e) {
if (
- touchStart
- && lastTouch
- && Utils.calculateTouchDistance(touchStart, lastTouch) <= 20
+ touchStart &&
+ lastTouch &&
+ Utils.calculateTouchDistance(touchStart, lastTouch) <= 20
) {
e.preventDefault();
const clickEvent = document.createEvent('MouseEvents');