diff options
Diffstat (limited to 'client/components')
32 files changed, 639 insertions, 115 deletions
diff --git a/client/components/boards/boardBody.jade b/client/components/boards/boardBody.jade index 288590da..fe10c921 100644 --- a/client/components/boards/boardBody.jade +++ b/client/components/boards/boardBody.jade @@ -25,7 +25,7 @@ template(name="boardBody") +list(this) if currentCardIsInThisList +cardDetails(currentCard) - if currentUser.isBoardMember + if canSeeAddList +addListForm template(name="addListForm") diff --git a/client/components/boards/boardBody.js b/client/components/boards/boardBody.js index 4703bc91..370db13b 100644 --- a/client/components/boards/boardBody.js +++ b/client/components/boards/boardBody.js @@ -204,3 +204,9 @@ BlazeComponent.extendComponent({ }]; }, }).register('addListForm'); + +Template.boardBody.helpers({ + canSeeAddList() { + return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly(); + }, +}); diff --git a/client/components/boards/boardHeader.jade b/client/components/boards/boardHeader.jade index 03768b36..e61aea35 100644 --- a/client/components/boards/boardHeader.jade +++ b/client/components/boards/boardHeader.jade @@ -65,7 +65,7 @@ template(name="boardHeaderBar") if $eq watchLevel "muted" i.fa.fa-bell-slash span {{_ watchLevel}} - + else a.board-header-btn.js-log-in( title="{{_ 'log-in'}}") @@ -81,7 +81,7 @@ template(name="boardHeaderBar") a.board-header-btn-close.js-filter-reset(title="{{_ 'filter-clear'}}") i.fa.fa-times-thin - if currentUser.isBoardMember + if canModifyBoard a.board-header-btn.js-multiselection-activate( title="{{#if MultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}}" class="{{#if MultiSelection.isActive}}emphasis{{/if}}") @@ -92,7 +92,7 @@ template(name="boardHeaderBar") i.fa.fa-times-thin .separator - a.board-header-btn.js-open-board-menu + a.board-header-btn.js-open-board-menu(title="{{_ 'boardMenuPopup-title'}}") i.board-header-btn-icon.fa.fa-navicon template(name="boardMenuPopup") @@ -170,7 +170,7 @@ template(name="boardChangeColorPopup") if isSelected i.fa.fa-check -template(name="createBoardPopup") +template(name="createBoard") form label | {{_ 'title'}} diff --git a/client/components/boards/boardHeader.js b/client/components/boards/boardHeader.js index a76b566a..10d9925a 100644 --- a/client/components/boards/boardHeader.js +++ b/client/components/boards/boardHeader.js @@ -15,17 +15,17 @@ Template.boardMenuPopup.events({ }), }); -Template.boardMenuPopup.helpers({ - exportUrl() { - const boardId = Session.get('currentBoard'); - const loginToken = Accounts._storedLoginToken(); - return FlowRouter.url(`api/boards/${boardId}?authToken=${loginToken}`); - }, - exportFilename() { - const boardId = Session.get('currentBoard'); - return `wekan-export-board-${boardId}.json`; - }, -}); +// Template.boardMenuPopup.helpers({ +// exportUrl() { +// const boardId = Session.get('currentBoard'); +// const loginToken = Accounts._storedLoginToken(); +// return FlowRouter.url(`api/boards/${boardId}?authToken=${loginToken}`); +// }, +// exportFilename() { +// const boardId = Session.get('currentBoard'); +// return `wekan-export-board-${boardId}.json`; +// }, +// }); Template.boardChangeTitlePopup.events({ submit(evt, tpl) { @@ -97,6 +97,12 @@ BlazeComponent.extendComponent({ }, }).register('boardHeaderBar'); +Template.boardHeaderBar.helpers({ + canModifyBoard() { + return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly(); + }, +}); + BlazeComponent.extendComponent({ backgroundColors() { return Boards.simpleSchema()._schema.color.allowedValues; @@ -119,10 +125,15 @@ BlazeComponent.extendComponent({ }, }).register('boardChangeColorPopup'); -BlazeComponent.extendComponent({ +const CreateBoard = BlazeComponent.extendComponent({ + template() { + return 'createBoard'; + }, + onCreated() { this.visibilityMenuIsOpen = new ReactiveVar(false); this.visibility = new ReactiveVar('private'); + this.boardId = new ReactiveVar(''); }, visibilityCheck() { @@ -143,15 +154,12 @@ BlazeComponent.extendComponent({ const title = this.find('.js-new-board-title').value; const visibility = this.visibility.get(); - const boardId = Boards.insert({ + this.boardId.set(Boards.insert({ title, permission: visibility, - }); - - Utils.goBoardId(boardId); + })); - // Immediately star boards crated with the headerbar popup. - Meteor.user().toggleBoardStar(boardId); + Utils.goBoardId(this.boardId.get()); }, events() { @@ -166,6 +174,14 @@ BlazeComponent.extendComponent({ }, }).register('createBoardPopup'); +(class HeaderBarCreateBoard extends CreateBoard { + onSubmit(evt) { + super.onSubmit(evt); + // Immediately star boards crated with the headerbar popup. + Meteor.user().toggleBoardStar(this.boardId.get()); + } +}).register('headerBarCreateBoardPopup'); + BlazeComponent.extendComponent({ visibilityCheck() { const currentBoard = Boards.findOne(Session.get('currentBoard')); diff --git a/client/components/cards/cardDate.jade b/client/components/cards/cardDate.jade index a2a28bbd..525f27ed 100644 --- a/client/components/cards/cardDate.jade +++ b/client/components/cards/cardDate.jade @@ -15,6 +15,12 @@ template(name="editCardDate") button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}} template(name="dateBadge") - a.js-edit-date.card-date(title="{{showTitle}}" class="{{classes}}") - time(datetime="{{showISODate}}") - | {{showDate}} + if canModifyCard + a.js-edit-date.card-date(title="{{showTitle}}" class="{{classes}}") + time(datetime="{{showISODate}}") + | {{showDate}} + else + a.card-date(title="{{showTitle}}" class="{{classes}}") + time(datetime="{{showISODate}}") + | {{showDate}} + diff --git a/client/components/cards/cardDate.js b/client/components/cards/cardDate.js index 4d129e8e..3f69f384 100644 --- a/client/components/cards/cardDate.js +++ b/client/components/cards/cardDate.js @@ -86,6 +86,12 @@ const EditCardDate = BlazeComponent.extendComponent({ }, }); +Template.dateBadge.helpers({ + canModifyCard() { + return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly(); + }, +}); + // editCardStartDatePopup (class extends EditCardDate { onCreated() { diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade index cf113951..9c95e92f 100644 --- a/client/components/cards/cardDetails.jade +++ b/client/components/cards/cardDetails.jade @@ -8,7 +8,7 @@ template(name="cardDetails") if currentUser.isBoardMember a.fa.fa-navicon.card-details-menu.js-open-card-details-menu h2.card-details-title.js-card-title( - class="{{#if currentUser.isBoardMember}}js-open-inlined-form is-editable{{/if}}") + class="{{#if canModifyCard}}js-open-inlined-form is-editable{{/if}}") = title if isWatching i.fa.fa-eye.card-details-watch @@ -22,16 +22,16 @@ template(name="cardDetails") each members +userAvatar(userId=this cardId=../_id) | {{! XXX Hack to hide syntaxic coloration /// }} - if currentUser.isBoardMember + 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-labels h3.card-details-item-title {{_ 'labels'}} - a(class="{{#if currentUser.isBoardMember}}js-add-labels{{else}}is-disabled{{/if}}" title="{{_ 'card-labels-title'}}") + 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)= name - if currentUser.isBoardMember + if canModifyCard a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}") i.fa.fa-plus @@ -47,7 +47,7 @@ template(name="cardDetails") //- XXX We should use "editable" to avoid repetiting ourselves - if currentUser.isBoardMember + if canModifyCard h3.card-details-item-title {{_ 'description'}} +inlinedCardDescription(classNames="card-description js-card-description") +editor(autofocus=true) @@ -101,23 +101,24 @@ template(name="editCardTitleForm") template(name="cardDetailsActionsPopup") ul.pop-over-list li: a.js-toggle-watch-card {{#if isWatching}}{{_ 'unwatch'}}{{else}}{{_ 'watch'}}{{/if}} - 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-start-date {{_ 'editCardStartDatePopup-title'}} - li: a.js-due-date {{_ 'editCardDueDatePopup-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 - ul.pop-over-list - li: a.js-move-card {{_ 'moveCardPopup-title'}} - unless archived - li: a.js-archive {{_ 'archive-card'}} - li: a.js-more {{_ 'cardMorePopup-title'}} + 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-start-date {{_ 'editCardStartDatePopup-title'}} + li: a.js-due-date {{_ 'editCardDueDatePopup-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 + ul.pop-over-list + li: a.js-move-card {{_ 'moveCardPopup-title'}} + unless archived + li: a.js-archive {{_ 'archive-card'}} + li: a.js-more {{_ 'cardMorePopup-title'}} template(name="moveCardPopup") +boardLists diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index b7e0ef76..b39f8e59 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -28,6 +28,10 @@ BlazeComponent.extendComponent({ return card.findWatcher(Meteor.userId()); }, + canModifyCard() { + return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly(); + }, + scrollParentContainer() { const cardPanelWidth = 510; const bodyBoardComponent = this.parentComponent(); @@ -140,6 +144,10 @@ Template.cardDetailsActionsPopup.helpers({ isWatching() { return this.findWatcher(Meteor.userId()); }, + + canModifyCard() { + return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly(); + }, }); Template.cardDetailsActionsPopup.events({ diff --git a/client/components/cards/checklists.jade b/client/components/cards/checklists.jade index 987e6cd9..a0d89351 100644 --- a/client/components/cards/checklists.jade +++ b/client/components/cards/checklists.jade @@ -3,12 +3,13 @@ template(name="checklists") .card-checklist-items each checklist in currentCard.checklists +checklistDetail(checklist = checklist) - +inlinedForm(classNames="js-add-checklist" cardId = cardId) - +addChecklistItemForm - else - a.js-open-inlined-form - i.fa.fa-plus - | {{_ 'add-checklist'}}... + if canModifyCard + +inlinedForm(classNames="js-add-checklist" cardId = cardId) + +addChecklistItemForm + else + a.js-open-inlined-form + i.fa.fa-plus + | {{_ 'add-checklist'}}... template(name="checklistDetail") +inlinedForm(classNames="js-edit-checklist-title" checklist = checklist) @@ -16,9 +17,13 @@ template(name="checklistDetail") else .checklist-title .checkbox.fa.fa-check-square-o - a.js-delete-checklist {{_ "delete"}}... + if canModifyCard + a.js-delete-checklist {{_ "delete"}}... span.checklist-stat(class="{{#if checklist.isFinished}}is-finished{{/if}}") {{checklist.finishedCount}}/{{checklist.itemCount}} - h2.title.js-open-inlined-form.is-editable {{checklist.title}} + if canModifyCard + h2.title.js-open-inlined-form.is-editable {{checklist.title}} + else + h2.title {{checklist.title}} +checklistItems(checklist = checklist) template(name="addChecklistItemForm") @@ -37,7 +42,7 @@ template(name="editChecklistItemForm") button.primary.confirm.js-submit-edit-checklist-item-form(type="submit") {{_ 'save'}} a.fa.fa-times-thin.js-close-inlined-form span(title=createdAt) {{ moment createdAt }} - if currentUser.isBoardMember + if canModifyCard a.js-delete-checklist-item {{_ "delete"}}... template(name="checklistItems") @@ -47,7 +52,7 @@ template(name="checklistItems") +editChecklistItemForm(type = 'item' item = item checklist = checklist) else +itemDetail(item = item checklist = checklist) - if currentUser.isBoardMember + if canModifyCard +inlinedForm(classNames="js-add-checklist-item" checklist = checklist) +addChecklistItemForm else @@ -57,5 +62,10 @@ template(name="checklistItems") template(name='itemDetail') .item - .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}}") {{item.title}} + if canModifyCard + .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}}") {{item.title}} + else + .materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}") + .item-title(class="{{#if item.isFinished }}is-checked{{/if}}") {{item.title}} + diff --git a/client/components/cards/checklists.js b/client/components/cards/checklists.js index 5dac7bd3..b8f5e443 100644 --- a/client/components/cards/checklists.js +++ b/client/components/cards/checklists.js @@ -26,6 +26,10 @@ BlazeComponent.extendComponent({ checklist.setTitle(title); }, + canModifyCard() { + return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly(); + }, + editChecklistItem(event) { event.preventDefault(); @@ -73,6 +77,12 @@ BlazeComponent.extendComponent({ }, }).register('checklists'); +Template.itemDetail.helpers({ + canModifyCard() { + return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly(); + }, +}); + BlazeComponent.extendComponent({ toggleItem() { const checklist = this.currentData().checklist; diff --git a/client/components/cards/minicard.jade b/client/components/cards/minicard.jade index 8b46ee74..5409ec62 100644 --- a/client/components/cards/minicard.jade +++ b/client/components/cards/minicard.jade @@ -7,6 +7,13 @@ template(name="minicard") each labels .minicard-label(class="card-label-{{color}}" title="{{name}}") .minicard-title= title + .dates + if startAt + .date + +minicardStartDate + if dueAt + .date + +minicardDueDate if members .minicard-members.js-minicard-members each members @@ -23,14 +30,7 @@ template(name="minicard") .badge span.badge-icon.fa.fa-paperclip span.badge-text= attachments.count - if startAt - .badge - +minicardStartDate - if dueAt - .badge - +minicardDueDate if checklists.count .badge(class="{{#if checklistFinished}}is-finished{{/if}}") span.badge-icon.fa.fa-check-square-o span.badge-text.check-list-text {{checklistFinishedCount}}/{{checklistItemCount}} - diff --git a/client/components/cards/minicard.styl b/client/components/cards/minicard.styl index 12a89785..a6aad896 100644 --- a/client/components/cards/minicard.styl +++ b/client/components/cards/minicard.styl @@ -77,7 +77,12 @@ height: @width border-radius: 2px margin-left: 3px - + .dates + display: flex; + flex-direction: row; + flex-wrap: wrap; + .date + margin-right: 3px .badges float: left margin-top: 7px @@ -91,25 +96,20 @@ margin-right: 11px margin-bottom: 3px font-size: 0.9em - + &.is-finished + background: #3cb500 + padding: 0px 3px + border-radius: 3px + color: white + &:last-of-type margin-right: 0 .badge-icon, .badge-text vertical-align: middle - - &.is-finished - background: #3cb500 - padding: 0px 3px - border-radius: 3px - color: white - - .badge-icon, - .badge-text - vertical-align: middle//didn't figure why use top, it'd be easier to fill bg if it's middle. This was introduced in commit "91cfcf7b12b5e7c137c2e765b2c378dde6b82966" & "* Improve the design of the minicards badges" was mentioned. &.badge-comment - margin-bottom: 0.1rem + margin-bottom: 0.1rem .badge-text font-size: 0.9em @@ -119,7 +119,6 @@ padding-left: 0px line-height: 12px - .minicard-members float: right margin: 2px -8px -2px 0 diff --git a/client/components/lists/list.js b/client/components/lists/list.js index e1909783..9c191348 100644 --- a/client/components/lists/list.js +++ b/client/components/lists/list.js @@ -44,7 +44,7 @@ BlazeComponent.extendComponent({ placeholder: 'minicard-wrapper placeholder', start(evt, ui) { ui.placeholder.height(ui.helper.height()); - EscapeActions.executeUpTo('popup'); + EscapeActions.executeUpTo('popup-close'); boardComponent.setIsDragging(true); }, stop(evt, ui) { @@ -79,10 +79,10 @@ BlazeComponent.extendComponent({ }); function userIsMember() { - return Meteor.user() && Meteor.user().isBoardMember(); + return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly(); } - // Disable drag-dropping if the current user is not a board member + // Disable drag-dropping if the current user is not a board member or is comment only this.autorun(() => { $cards.sortable('option', 'disabled', !userIsMember()); }); diff --git a/client/components/lists/listBody.jade b/client/components/lists/listBody.jade index e659b179..01aa7179 100644 --- a/client/components/lists/listBody.jade +++ b/client/components/lists/listBody.jade @@ -12,7 +12,7 @@ template(name="listBody") .materialCheckBox.multi-selection-checkbox.js-toggle-multi-selection( class="{{#if MultiSelection.isSelected _id}}is-checked{{/if}}") +minicard(this) - if currentUser.isBoardMember + if canSeeAddCard +inlinedForm(autoclose=false position="bottom") +addCardForm(listId=_id position="bottom") else diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index 34c2dcd6..bdc812c7 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -239,3 +239,10 @@ BlazeComponent.extendComponent({ }); }, }).register('addCardForm'); + + +Template.listBody.helpers({ + canSeeAddCard() { + return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly(); + }, +}); diff --git a/client/components/lists/listHeader.jade b/client/components/lists/listHeader.jade index 81afebe1..11905586 100644 --- a/client/components/lists/listHeader.jade +++ b/client/components/lists/listHeader.jade @@ -25,17 +25,18 @@ template(name="editListTitleForm") template(name="listActionPopup") ul.pop-over-list li: a.js-toggle-watch-list {{#if isWatching}}{{_ 'unwatch'}}{{else}}{{_ 'watch'}}{{/if}} - hr - ul.pop-over-list - li: a.js-add-card {{_ 'add-card'}} - if cards.count - li: a.js-select-cards {{_ 'list-select-cards'}} - hr - ul.pop-over-list - li: a.js-close-list {{_ 'archive-list'}} - hr - ul.pop-over-list - li: a.js-more {{_ 'listMorePopup-title'}} + unless currentUser.isCommentOnly + hr + ul.pop-over-list + li: a.js-add-card {{_ 'add-card'}} + if cards.count + li: a.js-select-cards {{_ 'list-select-cards'}} + hr + ul.pop-over-list + li: a.js-close-list {{_ 'archive-list'}} + hr + ul.pop-over-list + li: a.js-more {{_ 'listMorePopup-title'}} template(name="boardLists") ul.pop-over-list diff --git a/client/components/main/header.js b/client/components/main/header.js index 0337c72b..49acbfef 100644 --- a/client/components/main/header.js +++ b/client/components/main/header.js @@ -13,5 +13,5 @@ Template.header.helpers({ }); Template.header.events({ - 'click .js-create-board': Popup.open('createBoard'), + 'click .js-create-board': Popup.open('headerBarCreateBoard'), }); diff --git a/client/components/main/layouts.jade b/client/components/main/layouts.jade index 943ebe0f..12fac0a8 100644 --- a/client/components/main/layouts.jade +++ b/client/components/main/layouts.jade @@ -2,6 +2,7 @@ head title Wekan meta(name="viewport" content="maximum-scale=1.0,width=device-width,initial-scale=1.0,user-scalable=0") + meta(http-equiv="X-UA-Compatible" content="IE=edge") //- XXX We should use pathFor in the following `href` to support the case 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 diff --git a/client/components/main/layouts.js b/client/components/main/layouts.js index 3df17f41..1e50b01a 100644 --- a/client/components/main/layouts.js +++ b/client/components/main/layouts.js @@ -1,4 +1,6 @@ Meteor.subscribe('boards'); +Meteor.subscribe('setting'); +Meteor.subscribe('user-admin'); BlazeLayout.setRoot('body'); diff --git a/client/components/main/layouts.styl b/client/components/main/layouts.styl index 38fd83ec..349ee743 100644 --- a/client/components/main/layouts.styl +++ b/client/components/main/layouts.styl @@ -351,6 +351,11 @@ a background: darken(white, 13%) overflow-y: auto + a + text-decoration: underline + &:hover + color: #333 + .basicTabs-container .tabs-content-container padding: 0 padding-top: 15px diff --git a/client/components/settings/invitationCode.jade b/client/components/settings/invitationCode.jade new file mode 100644 index 00000000..171a2663 --- /dev/null +++ b/client/components/settings/invitationCode.jade @@ -0,0 +1,5 @@ +template(name='invitationCode') + .at-input#invitationcode + label(for='at-field-code') {{_ 'invitation-code'}} + + input#at-field-invitationcode(type="text" name='at-field-invitationcode' placeholder="{{_ 'invitation-code'}}") diff --git a/client/components/settings/invitationCode.js b/client/components/settings/invitationCode.js new file mode 100644 index 00000000..a403d8ab --- /dev/null +++ b/client/components/settings/invitationCode.js @@ -0,0 +1,6 @@ +Template.invitationCode.onRendered(() => { + const setting = Settings.findOne(); + if (!setting || !setting.disableRegistration) { + $('#invitationcode').hide(); + } +}); diff --git a/client/components/settings/settingBody.jade b/client/components/settings/settingBody.jade new file mode 100644 index 00000000..d490ec93 --- /dev/null +++ b/client/components/settings/settingBody.jade @@ -0,0 +1,79 @@ +template(name="setting") + .setting-content + .content-title + span {{_ 'settings'}} + .content-body + .side-menu + ul + li.active + a.js-setting-menu(data-id="registration-setting") {{_ 'registration'}} + li + a.js-setting-menu(data-id="email-setting") {{_ 'email'}} + .main-body + if loading.get + +spinner + else if generalSetting.get + +general + else if emailSetting.get + +email + +template(name="general") + ul#registration-setting.setting-detail + li + a.flex.js-toggle-registration + .materialCheckBox(class="{{#if currentSetting.disableRegistration}}is-checked{{/if}}") + + span {{_ 'disable-self-registration'}} + li + .invite-people(class="{{#if currentSetting.disableRegistration}}{{else}}hide{{/if}}") + ul + li + .title {{_ 'invite-people'}} + textarea#email-to-invite.form-control(rows='5', placeholder="{{_ 'email-addresses'}}") + li + .title {{_ 'to-boards'}} + .bg-white + each boards + a.option.flex.js-toggle-board-choose(id= _id) + .materialCheckBox(data-id= _id) + + span= title + + li + button.js-email-invite.primary {{_ 'invite'}} + +template(name='email') + ul#email-setting.setting-detail + li.smtp-form + .title {{_ 'smtp-host'}} + .description {{_ 'smtp-host-description'}} + .form-group + input.form-control#mail-server-host(type="text", placeholder="smtp.domain.com" value="{{currentSetting.mailServer.host}}") + li.smtp-form + .title {{_ 'smtp-port'}} + .description {{_ 'smtp-port-description'}} + .form-group + input.form-control#mail-server-port(type="text", placeholder="25" value="{{currentSetting.mailServer.port}}") + li.smtp-form + .title {{_ 'smtp-username'}} + .form-group + input.form-control#mail-server-username(type="text", placeholder="{{_ 'username'}}" value="{{currentSetting.mailServer.username}}") + li.smtp-form + .title {{_ 'smtp-password'}} + .form-group + input.form-control#mail-server-password(type="text", placeholder="{{_ 'password'}}" value="{{currentSetting.mailServer.password}}") + li.smtp-form + .title {{_ 'smtp-tls'}} + .form-group + a.flex.js-toggle-tls + .materialCheckBox#mail-server-tls(class="{{#if currentSetting.mailServer.enableTLS}}is-checked{{/if}}") + + span {{_ 'smtp-tls-description'}} + + li.smtp-form + .title {{_ 'send-from'}} + .form-group + input.form-control#mail-server-from(type="email", placeholder="no-reply@domain.com" value="{{currentSetting.mailServer.from}}") + + li + button.js-save.primary Save diff --git a/client/components/settings/settingBody.js b/client/components/settings/settingBody.js new file mode 100644 index 00000000..f96312a5 --- /dev/null +++ b/client/components/settings/settingBody.js @@ -0,0 +1,132 @@ +Meteor.subscribe('setting'); +Meteor.subscribe('mailServer'); + +BlazeComponent.extendComponent({ + onCreated() { + this.error = new ReactiveVar(''); + this.loading = new ReactiveVar(false); + this.generalSetting = new ReactiveVar(true); + this.emailSetting = new ReactiveVar(false); + }, + + setError(error) { + this.error.set(error); + }, + + setLoading(w) { + this.loading.set(w); + }, + + checkField(selector) { + const value = $(selector).val(); + if(!value || value.trim() === ''){ + $(selector).parents('li.smtp-form').addClass('has-error'); + throw Error('blank field'); + } else { + return value; + } + }, + + currentSetting(){ + return Settings.findOne(); + }, + + boards() { + return Boards.find({ + archived: false, + 'members.userId': Meteor.userId(), + 'members.isAdmin': true, + }, { + sort: ['title'], + }); + }, + toggleRegistration(){ + this.setLoading(true); + const registrationClosed = this.currentSetting().disableRegistration; + Settings.update(Settings.findOne()._id, {$set:{disableRegistration: !registrationClosed}}); + this.setLoading(false); + if(registrationClosed){ + $('.invite-people').slideUp(); + }else{ + $('.invite-people').slideDown(); + } + }, + toggleTLS(){ + $('#mail-server-tls').toggleClass('is-checked'); + }, + switchMenu(event){ + const target = $(event.target); + if(!target.hasClass('active')){ + $('.side-menu li.active').removeClass('active'); + target.parent().addClass('active'); + const targetID = target.data('id'); + this.generalSetting.set('registration-setting' === targetID); + this.emailSetting.set('email-setting' === targetID); + } + }, + + checkBoard(event){ + let target = $(event.target); + if(!target.hasClass('js-toggle-board-choose')){ + target = target.parent(); + } + const checkboxId = target.attr('id'); + $(`#${checkboxId} .materialCheckBox`).toggleClass('is-checked'); + $(`#${checkboxId}`).toggleClass('is-checked'); + }, + + inviteThroughEmail(){ + const emails = $('#email-to-invite').val().trim().split('\n').join(',').split(','); + const boardsToInvite = []; + $('.js-toggle-board-choose .materialCheckBox.is-checked').each(function () { + boardsToInvite.push($(this).data('id')); + }); + const validEmails = []; + emails.forEach((email) => { + if (email && SimpleSchema.RegEx.Email.test(email.trim())) { + validEmails.push(email.trim()); + } + }); + if (validEmails.length) { + this.setLoading(true); + Meteor.call('sendInvitation', validEmails, boardsToInvite, () => { + // if (!err) { + // TODO - show more info to user + // } + this.setLoading(false); + }); + } + }, + + saveMailServerInfo(){ + this.setLoading(true); + $('li').removeClass('has-error'); + + try{ + const host = this.checkField('#mail-server-host'); + const port = this.checkField('#mail-server-port'); + const username = $('#mail-server-username').val().trim(); + const password = $('#mail-server-password').val().trim(); + const from = this.checkField('#mail-server-from'); + const tls = $('#mail-server-tls.is-checked').length > 0; + Settings.update(Settings.findOne()._id, {$set:{'mailServer.host':host, 'mailServer.port': port, 'mailServer.username': username, + 'mailServer.password': password, 'mailServer.enableTLS': tls, 'mailServer.from': from}}); + } catch (e) { + return; + } finally { + this.setLoading(false); + } + + }, + + events(){ + return [{ + 'click a.js-toggle-registration': this.toggleRegistration, + 'click a.js-toggle-tls': this.toggleTLS, + 'click a.js-setting-menu': this.switchMenu, + 'click a.js-toggle-board-choose': this.checkBoard, + 'click button.js-email-invite': this.inviteThroughEmail, + 'click button.js-save': this.saveMailServerInfo, + }]; + }, +}).register('setting'); diff --git a/client/components/settings/settingBody.styl b/client/components/settings/settingBody.styl new file mode 100644 index 00000000..118d364c --- /dev/null +++ b/client/components/settings/settingBody.styl @@ -0,0 +1,112 @@ +.flex + display: -webkit-box + display: -moz-box + display: -webkit-flex + display: -moz-flex + display: -ms-flexbox + display: flex + +.setting-content + padding 30px + color: #727479 + background: #dedede + width 100% + height 100% + position: absolute; + + .content-title + font-size 20px + + .content-body + display flex + padding-top 15px + height 100% + + .side-menu + background-color: #f7f7f7; + border: 1px solid #f0f0f0; + border-radius: 4px; + width: 250px; + box-shadow: inset -1px -1px 3px rgba(0,0,0,.05); + + ul + + li + margin: 0.1rem 0.2rem; + + &.active + background #fff + box-shadow 0 1px 2px rgba(0,0,0,0.15); + + &: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 + + .main-body + padding: 0.1em 1em + + ul + li + padding: 0.5rem 0.5rem; + + a + .is-checked + border-bottom: 2px solid #2980b9; + border-right: 2px solid #2980b9; + + span + padding: 0 0.5rem + + .invite-people + padding-left 20px; + li + min-width: 500px; + + ul.no-margin-bottom + margin-bottom: 0; + + .bg-white + a + background #f7f7f7 + &.is-checked + background #fff + + +.option + @extends .flex + -webkit-border-radius: 3px; + border-radius: 3px; + background: #fff; + text-decoration: none; + -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.2); + box-shadow: 0 1px 2px rgba(0,0,0,0.2); + margin-top: 5px; + padding: 5px; + +.title + font-weight 700; + margin-bottom 0.5rem; +.description + margin-bottom 0.5rem; +.bg-white + background #f9fbfc; + +.form-control.has-error + border-color: #a94442; + box-shadow: inset 0 1px 1px rgba(0,0,0,.075); + +li.has-error + color #a94442 + .form-group + .form-control + border-color: #a94442; + box-shadow: inset 0 1px 1px rgba(0,0,0,.075); + diff --git a/client/components/settings/settingHeader.jade b/client/components/settings/settingHeader.jade new file mode 100644 index 00000000..fb884056 --- /dev/null +++ b/client/components/settings/settingHeader.jade @@ -0,0 +1,21 @@ +template(name="settingHeaderBar") + h1.header-setting-menu + span {{_ 'admin-panel'}} + + .setting-header-btns.left + unless isMiniScreen + unless isSandstorm + if currentUser + a.setting-header-btn.settings.active + i.fa(class="fa-cog") + span {{_ 'settings'}} +//TODO +// a.setting-header-btn.people +// i.fa(class="fa-users") +// span {{_ 'people'}} + + else + a.setting-header-btn.js-log-in( + title="{{_ 'log-in'}}") + i.fa.fa-sign-in + span {{_ 'log-in'}} diff --git a/client/components/settings/settingHeader.styl b/client/components/settings/settingHeader.styl new file mode 100644 index 00000000..995ed26d --- /dev/null +++ b/client/components/settings/settingHeader.styl @@ -0,0 +1,25 @@ +#header #header-main-bar .setting-header-btn + &.active, + &:hover:not(.is-disabled) + background: rgba(0, 0, 0, .15) + color: darken(white, 5%) + margin-left: 20px; + padding-right: 10px; + height: 28px; + font-size: 13px; + float: left; + overflow: hidden; + line-height: @height; + margin: 0 2px; + + i.fa + float: left + display: block + line-height: 28px + color: darken(white, 5%) + margin: 0 10px + + + span + display: inline-block + margin-top: 1px + margin-right: 10px
\ No newline at end of file diff --git a/client/components/sidebar/sidebar.jade b/client/components/sidebar/sidebar.jade index f3fdd1bc..6045b371 100644 --- a/client/components/sidebar/sidebar.jade +++ b/client/components/sidebar/sidebar.jade @@ -1,16 +1,22 @@ template(name="sidebar") .board-sidebar.sidebar(class="{{#if isOpen}}is-open{{/if}}") a.sidebar-tongue.js-toggle-sidebar( - class="{{#if isTongueHidden}}is-hidden{{/if}}") + class="{{#if isTongueHidden}}is-hidden{{/if}}", + title="{{showTongueTitle}}") i.fa.fa-angle-left - .sidebar-content.js-board-sidebar-content.js-perfect-scrollbar - a.hide-btn.js-hide-sidebar - i.fa.fa-angle-right - unless isDefaultView - h2 - a.fa.fa-chevron-left.js-back-home - = getViewTitle - +Template.dynamic(template=getViewTemplate) + .sidebar-shadow + .sidebar-content.sidebar-shortcuts + a.board-header-btn.js-shortcuts + i.fa.fa-keyboard-o + span {{_ 'keyboard-shortcuts' }} + .sidebar-content.js-board-sidebar-content.js-perfect-scrollbar + a.hide-btn.js-hide-sidebar + i.fa.fa-angle-right + unless isDefaultView + h2 + a.fa.fa-chevron-left.js-back-home + = getViewTitle + +Template.dynamic(template=getViewTemplate) template(name='homeSidebar') +membersWidget @@ -54,7 +60,7 @@ template(name="labelsWidget") .board-widget-content each currentBoard.labels a.card-label(class="card-label-{{color}}" - class="{{#if currentUser.isBoardMember}}js-label{{/if}}") + class="{{#if currentUser.isNotCommentOnly}}js-label{{/if}}") span.card-label-name= name if currentUser.isBoardAdmin a.card-label.add-label.js-add-label @@ -132,9 +138,15 @@ template(name="changePermissionsPopup") li a(class="{{#if isLastAdmin}}disabled{{else}}js-set-normal{{/if}}") | {{_ 'normal'}} - unless isAdmin + if isNormal i.fa.fa-check span.sub-name {{_ 'normal-desc'}} + li + a(class="{{#if isLastAdmin}}disabled{{else}}js-set-comment-only{{/if}}") + | {{_ 'comment-only'}} + if isCommentOnly + i.fa.fa-check + span.sub-name {{_ 'comment-only-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 f32a27c5..1290fd13 100644 --- a/client/components/sidebar/sidebar.js +++ b/client/components/sidebar/sidebar.js @@ -89,11 +89,21 @@ BlazeComponent.extendComponent({ return TAPi18n.__(viewTitles[this.getView()]); }, + showTongueTitle() { + if (this.isOpen()) + return `${TAPi18n.__('sidebar-close')}`; + else + return `${TAPi18n.__('sidebar-open')}`; + }, + events() { return [{ 'click .js-hide-sidebar': this.hide, 'click .js-toggle-sidebar': this.toggle, 'click .js-back-home': this.setView, + 'click .js-shortcuts'() { + FlowRouter.go('shortcuts'); + }, }]; }, }).register('sidebar'); @@ -111,7 +121,17 @@ Template.memberPopup.helpers({ }, memberType() { const type = Users.findOne(this.userId).isBoardAdmin() ? 'admin' : 'normal'; - return TAPi18n.__(type).toLowerCase(); + if(type === 'normal'){ + const currentBoard = Boards.findOne(Session.get('currentBoard')); + const commentOnly = currentBoard.hasCommentOnly(this.userId); + if(commentOnly){ + return TAPi18n.__('comment-only').toLowerCase(); + } else { + return TAPi18n.__(type).toLowerCase(); + } + } else { + return TAPi18n.__(type).toLowerCase(); + } }, isInvited() { return Users.findOne(this.userId).isInvitedTo(Session.get('currentBoard')); @@ -134,8 +154,8 @@ Template.memberPopup.events({ Popup.close(); }), 'click .js-leave-member'() { - const currentBoard = Boards.findOne(Session.get('currentBoard')); - Meteor.call('quitBoard', currentBoard, (err, ret) => { + const boardId = Session.get('currentBoard'); + Meteor.call('quitBoard', boardId, (err, ret) => { if (!ret && ret) { Popup.close(); FlowRouter.go('home'); @@ -298,11 +318,12 @@ BlazeComponent.extendComponent({ }).register('addMemberPopup'); Template.changePermissionsPopup.events({ - 'click .js-set-admin, click .js-set-normal'(event) { + 'click .js-set-admin, click .js-set-normal, click .js-set-comment-only'(event) { const currentBoard = Boards.findOne(Session.get('currentBoard')); const memberId = this.userId; const isAdmin = $(event.currentTarget).hasClass('js-set-admin'); - currentBoard.setMemberPermission(memberId, isAdmin); + const isCommentOnly = $(event.currentTarget).hasClass('js-set-comment-only'); + currentBoard.setMemberPermission(memberId, isAdmin, isCommentOnly); Popup.back(1); }, }); @@ -313,6 +334,16 @@ Template.changePermissionsPopup.helpers({ return currentBoard.hasAdmin(this.userId); }, + isNormal() { + const currentBoard = Boards.findOne(Session.get('currentBoard')); + return !currentBoard.hasAdmin(this.userId) && !currentBoard.hasCommentOnly(this.userId); + }, + + isCommentOnly() { + const currentBoard = Boards.findOne(Session.get('currentBoard')); + return !currentBoard.hasAdmin(this.userId) && currentBoard.hasCommentOnly(this.userId); + }, + isLastAdmin() { const currentBoard = Boards.findOne(Session.get('currentBoard')); return currentBoard.hasAdmin(this.userId) && (currentBoard.activeAdmins() === 1); diff --git a/client/components/sidebar/sidebar.styl b/client/components/sidebar/sidebar.styl index 24abe990..8f2f493e 100644 --- a/client/components/sidebar/sidebar.styl +++ b/client/components/sidebar/sidebar.styl @@ -6,11 +6,19 @@ bottom: 0 right: 0 - .sidebar-content - padding: 12px + .sidebar-shadow + position: absolute + top: 0 + bottom: 0 + right: 0 + left: 0 background: darken(white, 3%) box-shadow: -10px 0px 5px -10px darken(white, 30%) z-index: 10 + + .sidebar-content + padding: 12px + margin-bottom: 1.6em position: absolute top: 0 bottom: 0 @@ -73,6 +81,16 @@ i.fa margin-right: 10px + .sidebar-shortcuts + margin: 0 + padding: 0 + top: auto + text-align: center + font-size: 0.8em + line-height: 1.6em + vertical-align: middle + color: darken(white, 40%) + .board-sidebar width: 248px right: -@width diff --git a/client/components/users/userHeader.jade b/client/components/users/userHeader.jade index ad41e8aa..51b0888b 100644 --- a/client/components/users/userHeader.jade +++ b/client/components/users/userHeader.jade @@ -17,6 +17,8 @@ template(name="memberMenuPopup") li: a.js-change-password {{_ 'changePasswordPopup-title'}} li: a.js-change-language {{_ 'changeLanguagePopup-title'}} li: a.js-edit-notification {{_ 'editNotificationPopup-title'}} + if currentUser.isAdmin + li: a.js-go-setting(href='/setting') {{_ 'admin-panel'}} hr ul.pop-over-list li: a.js-logout {{_ 'log-out'}} diff --git a/client/components/users/userHeader.js b/client/components/users/userHeader.js index 98053ed1..73a11fc0 100644 --- a/client/components/users/userHeader.js +++ b/client/components/users/userHeader.js @@ -15,6 +15,9 @@ Template.memberMenuPopup.events({ AccountsTemplates.logout(); }, + 'click .js-go-setting'() { + Popup.close(); + }, }); Template.editProfilePopup.events({ |