diff --git a/client/components/boards/boardsList.jade b/client/components/boards/boardsList.jade index 7fd7c2ba9..ee57f3b29 100644 --- a/client/components/boards/boardsList.jade +++ b/client/components/boards/boardsList.jade @@ -60,10 +60,10 @@ template(name="boardList") template(name="boardListHeaderBar") h1 {{_ title }} - .board-header-btns.right - a.board-header-btn.js-open-archived-board - i.fa.fa-archive - span {{_ 'archives'}} - a.board-header-btn(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}") - i.fa.fa-clone - span {{_ 'templates'}} + //.board-header-btns.right + // a.board-header-btn.js-open-archived-board + // i.fa.fa-archive + // span {{_ 'archives'}} + // a.board-header-btn(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}") + // i.fa.fa-clone + // span {{_ 'templates'}} diff --git a/client/components/main/header.jade b/client/components/main/header.jade index 220f99bef..dc8f4455b 100644 --- a/client/components/main/header.jade +++ b/client/components/main/header.jade @@ -38,10 +38,10 @@ template(name="header") span.fa.fa-home | {{_ 'all-boards'}} li.separator - - li - a(href="{{pathFor 'public'}}") - span.fa.fa-globe - | {{_ 'public'}} + //li + // a(href="{{pathFor 'public'}}") + // span.fa.fa-globe + // | {{_ 'public'}} each currentUser.starredBoards li.separator - li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}") diff --git a/client/components/main/myCards.jade b/client/components/main/myCards.jade new file mode 100644 index 000000000..01172693b --- /dev/null +++ b/client/components/main/myCards.jade @@ -0,0 +1,28 @@ +template(name="myCardsHeaderBar") + h1 + a.back-btn(href="{{pathFor 'home'}}") + i.fa.fa-chevron-left + | {{_ 'my-cards'}} + +template(name="myCardsModalTitle") + h2 + i.fa.fa-keyboard-o + | {{_ 'my-cards'}} + +template(name="myCards") + .wrapper + each board in cardsFind + .board-title + | {{_ 'board' }}: + = board.title + each swimlane in board.swimlanes + .swimlane-title + | {{_ 'swimlane' }}: + = swimlane.title + each list in swimlane.lists + .list-title + | {{_ 'list' }}: + = list.title + each card in list.cards + a.minicard-wrapper.card-title(href="{{pathFor 'card' boardId=board.id slug=board.slug cardId=card._id }}") + +minicard(card) diff --git a/client/components/main/myCards.js b/client/components/main/myCards.js new file mode 100644 index 000000000..91d5d5f72 --- /dev/null +++ b/client/components/main/myCards.js @@ -0,0 +1,154 @@ +const subManager = new SubsManager(); +Meteor.subscribe('myCards'); +Meteor.subscribe('mySwimlanes'); +Meteor.subscribe('myLists'); + +Template.myCardsHeaderBar.events({ + 'click .js-open-archived-board'() { + Modal.open('archivedBoards'); + }, +}); + +Template.myCardsHeaderBar.helpers({ + title() { + return FlowRouter.getRouteName() === 'home' ? 'my-boards' : 'public'; + }, + templatesUser() { + return Meteor.user(); + }, +}); + +Template.myCards.helpers({ + userId() { + return Meteor.userId(); + }, +}); + +BlazeComponent.extendComponent({ + onCreated() { + Meteor.subscribe('setting'); + // subManager.subscribe('myCards'); + }, + + cardsFind() { + const userId = Meteor.userId(); + const boards = []; + let board = null; + let swimlane = null; + let list = null; + + const cursor = Cards.find( + { + archived: false, + $or: [{ members: userId }, { assignees: userId }], + }, + { + sort: { + boardId: 1, + swimlaneId: 1, + listId: 1, + sort: 1, + }, + }, + ); + // eslint-disable-next-line no-console + // console.log('cursor:', cursor); + + let newBoard = false; + let newSwimlane = false; + let newList = false; + + cursor.forEach(card => { + // eslint-disable-next-line no-console + // console.log('card:', card.title); + if (list === null || list.id !== card.listId) { + // eslint-disable-next-line no-console + // console.log('new list'); + let l = Lists.findOne(card.listId); + if (!l) { + l = { + _id: card.listId, + title: 'undefined list', + }; + } + // eslint-disable-next-line no-console + // console.log('list:', l); + list = { + id: l._id, + title: l.title, + cards: [card], + }; + newList = true; + } + if (swimlane === null || card.swimlaneId !== swimlane.id) { + // eslint-disable-next-line no-console + // console.log('new swimlane'); + let s = Swimlanes.findOne(card.swimlaneId); + if (!s) { + s = { + _id: card.swimlaneId, + title: 'undefined swimlane', + }; + } + // eslint-disable-next-line no-console + // console.log('swimlane:', s); + swimlane = { + id: s._id, + title: s.title, + lists: [list], + }; + newSwimlane = true; + } + if (board === null || card.boardId !== board.id) { + // eslint-disable-next-line no-console + // console.log('new board'); + const b = Boards.findOne(card.boardId); + // eslint-disable-next-line no-console + // console.log('board:', b, b._id, b.title); + board = { + id: b._id, + title: b.title, + slug: b.slug, + swimlanes: [swimlane], + }; + newBoard = true; + } + + if (newBoard) { + boards.push(board); + } else if (newSwimlane) { + board.swimlanes.push(swimlane); + } else if (newList) { + swimlane.lists.push(list); + } else { + list.cards.push(card); + } + + newBoard = false; + newSwimlane = false; + newList = false; + }); + + // eslint-disable-next-line no-console + // console.log('boards:', boards); + return boards; + }, + + events() { + return [ + { + 'click .js-my-card'(evt) { + const card = this.currentData().card; + // eslint-disable-next-line no-console + console.log('currentData():', this.currentData()); + // eslint-disable-next-line no-console + console.log('card:', card); + if (card) { + Utils.goCardId(card._id); + } + evt.preventDefault(); + }, + }, + ]; + }, +}).register('myCards'); diff --git a/client/components/main/myCards.styl b/client/components/main/myCards.styl new file mode 100644 index 000000000..a139a17dc --- /dev/null +++ b/client/components/main/myCards.styl @@ -0,0 +1,40 @@ +.my-cards-list + .my-cards-list-item + border-bottom: 1px solid darken(white, 25%) + padding: 10px 5px + + &:last-child + border-bottom: none + + .my-cards-list-item-keys + margin-top: 5px + float: right + + kbd + padding: 5px 8px + margin: 5px + font-size: 18px + + .my-cards-list-item-action + font-size: 1.4em + margin: 5px + +.board-title + font-size: 1.4rem + font-weight: bold + +.swimlane-title + font-size: 1.2rem + font-weight: bold + margin-left: 1em + margin-top: 10px + +.list-title + margin-top: 5px + font-weight: bold + margin-left: 1.6rem + +.card-title + margin-top: 5px + margin-left: 1.8rem + max-width: 350px; diff --git a/client/components/swimlanes/swimlaneHeader.jade b/client/components/swimlanes/swimlaneHeader.jade index b4ef59767..c7c9381e5 100644 --- a/client/components/swimlanes/swimlaneHeader.jade +++ b/client/components/swimlanes/swimlaneHeader.jade @@ -17,11 +17,12 @@ template(name="swimlaneFixedHeader") unless currentUser.isCommentOnly if currentUser.isBoardAdmin a.fa.fa-plus.js-open-add-swimlane-menu.swimlane-header-plus-icon - a.fa.fa-navicon.js-open-swimlane-menu.swimlane-header-menu-icon - if isMiniScreenOrShowDesktopDragHandles + a.fa.fa-navicon.js-open-swimlane-menu + unless isMiniScreen + if showDesktopDragHandles + a.swimlane-header-handle.handle.fa.fa-arrows.js-swimlane-header-handle + if isMiniScreen a.swimlane-header-miniscreen-handle.handle.fa.fa-arrows.js-swimlane-header-handle - else - a.swimlane-header-handle.handle.fa.fa-arrows.js-swimlane-header-handle template(name="editSwimlaneTitleForm") .list-composer diff --git a/client/components/swimlanes/swimlanes.styl b/client/components/swimlanes/swimlanes.styl index 812b8ae9b..48fc57940 100644 --- a/client/components/swimlanes/swimlanes.styl +++ b/client/components/swimlanes/swimlanes.styl @@ -85,6 +85,7 @@ .swimlane-header-menu position: absolute padding: 5px 5px + font-size: 22px .swimlane-header-plus-icon margin-left: 5px diff --git a/client/components/users/userHeader.jade b/client/components/users/userHeader.jade index d0adf29d2..0e35e21fa 100644 --- a/client/components/users/userHeader.jade +++ b/client/components/users/userHeader.jade @@ -12,7 +12,35 @@ template(name="headerUserBar") template(name="memberMenuPopup") ul.pop-over-list + if currentUser.isAdmin + li + a.js-go-setting(href="{{pathFor 'setting'}}") + i.fa.fa-lock + | {{_ 'admin-panel'}} with currentUser + li + a(href="{{pathFor 'home'}}") + span.fa.fa-home + | {{_ 'all-boards'}} + li + a.js-my-cards(href="{{pathFor 'my-cards'}}") + i.fa.fa-list + | {{_ 'my-cards'}} + li + a(href="{{pathFor 'public'}}") + span.fa.fa-globe + | {{_ 'public'}} + li + a.board-header-btn.js-open-archived-board + i.fa.fa-archive + span {{_ 'archives'}} + unless currentUser.isWorker + ul.pop-over-list + li + a(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}") + i.fa.fa-clone + | {{_ 'templates'}} + hr li a.js-edit-profile i.fa.fa-user @@ -34,18 +62,6 @@ template(name="memberMenuPopup") a.js-change-language i.fa.fa-flag | {{_ 'changeLanguagePopup-title'}} - if currentUser.isAdmin - 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 diff --git a/client/components/users/userHeader.js b/client/components/users/userHeader.js index af554d9f5..d616b804f 100644 --- a/client/components/users/userHeader.js +++ b/client/components/users/userHeader.js @@ -25,6 +25,12 @@ Template.memberMenuPopup.helpers({ }); Template.memberMenuPopup.events({ + 'click .js-my-cards'() { + Popup.close(); + }, + 'click .js-open-archived-board'() { + Modal.open('archivedBoards'); + }, 'click .js-edit-profile': Popup.open('editProfile'), 'click .js-change-settings': Popup.open('changeSettings'), 'click .js-change-avatar': Popup.open('changeAvatar'), diff --git a/config/router.js b/config/router.js index 2e66c67fa..473fcbbff 100644 --- a/config/router.js +++ b/config/router.js @@ -113,6 +113,32 @@ FlowRouter.route('/shortcuts', { }, }); +FlowRouter.route('/my-cards', { + name: 'my-cards', + action() { + const myCardsTemplate = 'myCards'; + + Filter.reset(); + // EscapeActions.executeAll(); + EscapeActions.executeUpTo('popup-close'); + + Utils.manageCustomUI(); + Utils.manageMatomo(); + + // if (previousPath) { + // Modal.open(myCardsTemplate, { + // header: 'myCardsModalTitle', + // onCloseGoTo: previousPath, + // }); + // } else { + BlazeLayout.render('defaultLayout', { + headerBar: 'myCardsHeaderBar', + content: myCardsTemplate, + }); + // } + }, +}); + FlowRouter.route('/import/:source', { name: 'import', triggersEnter: [AccountsTemplates.ensureSignedIn], diff --git a/i18n/ar.i18n.json b/i18n/ar.i18n.json index 6d258a5d0..65353fe89 100644 --- a/i18n/ar.i18n.json +++ b/i18n/ar.i18n.json @@ -137,6 +137,7 @@ "boardChangeWatchPopup-title": "تغيير المتابعة", "boardMenuPopup-title": "Board Settings", "boardChangeViewPopup-title": "عرض اللوحات", + "board": "لوحة", "boards": "لوحات", "board-view": "عرض اللوحات", "board-view-cal": "التقويم", @@ -148,6 +149,7 @@ "cancel": "إلغاء", "card-archived": "البطاقة منقولة الى الارشيف", "board-archived": "اللوحات منقولة الى الارشيف", + "card": "نطاقة", "card-comments-title": "%s تعليقات لهذه البطاقة", "card-delete-notice": "هذا حذف أبديّ . سوف تفقد كل الإجراءات المنوطة بهذه البطاقة", "card-delete-pop": "سيتم إزالة جميع الإجراءات من تبعات النشاط، وأنك لن تكون قادرا على إعادة فتح البطاقة. لا يوجد التراجع.", @@ -403,6 +405,7 @@ "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.", "leaveBoardPopup-title": "مغادرة اللوحة ؟", "link-card": "ربط هذه البطاقة", + "list": "قائمة", "list-archive-cards": "Move all cards in this list to Archive", "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", "list-move-cards": "نقل بطاقات هذه القائمة", @@ -831,7 +834,7 @@ "saturday": "Saturday", "sunday": "Sunday", "status": "Status", - "swimlane": "Swimlane", + "swimlane": "خط السباحة", "owner": "Owner", "last-modified-at": "Last modified at", "last-activity": "Last activity", @@ -848,5 +851,6 @@ "displayName": "Display Name", "shortName": "Short Name", "website": "Website", - "person": "Person" + "person": "Person", + "my-cards": "بطاقاتي" } diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index 53d5e6892..306abe799 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -848,5 +848,9 @@ "displayName": "Display Name", "shortName": "Short Name", "website": "Website", - "person": "Person" + "person": "Person", + "my-cards": "My Cards", + "card": "Card", + "list": "List", + "board": "Board" } diff --git a/server/publications/boards.js b/server/publications/boards.js index b80a6b23a..27b034662 100644 --- a/server/publications/boards.js +++ b/server/publications/boards.js @@ -42,6 +42,66 @@ Meteor.publish('boards', function() { ); }); +Meteor.publish('mySwimlanes', function() { + const userId = this.userId; + const swimlanes = []; + + Cards.find({ + archived: false, + $or: [{ members: userId }, { assignees: userId }], + }).forEach(card => { + swimlanes.push(card.swimlaneId); + }); + + return Swimlanes.find( + { + archived: false, + _id: { $in: swimlanes }, + }, + { + fields: { + _id: 1, + title: 1, + type: 1, + sort: 1, + }, + // sort: { + // sort: ['boardId', 'listId', 'sort'], + // }, + }, + ); +}); + +Meteor.publish('myLists', function() { + const userId = this.userId; + const lists = []; + + Cards.find({ + archived: false, + $or: [{ members: userId }, { assignees: userId }], + }).forEach(card => { + lists.push(card.listId); + }); + + return Lists.find( + { + archived: false, + _id: { $in: lists }, + }, + { + fields: { + _id: 1, + title: 1, + type: 1, + sort: 1, + }, + // sort: { + // sort: ['boardId', 'listId', 'sort'], + // }, + }, + ); +}); + Meteor.publish('archivedBoards', function() { const userId = this.userId; if (!Match.test(userId, String)) return []; diff --git a/server/publications/cards.js b/server/publications/cards.js index 61210ce56..e9209f01e 100644 --- a/server/publications/cards.js +++ b/server/publications/cards.js @@ -2,3 +2,33 @@ Meteor.publish('card', cardId => { check(cardId, String); return Cards.find({ _id: cardId }); }); + +Meteor.publish('myCards', function() { + const userId = this.userId; + + return Cards.find( + { + archived: false, + $or: [{ members: userId }, { assignees: userId }], + }, + { + fields: { + _id: 1, + archived: 1, + boardId: 1, + swimlaneId: 1, + listId: 1, + title: 1, + type: 1, + sort: 1, + members: 1, + assignees: 1, + colors: 1, + dueAt: 1, + }, + // sort: { + // sort: ['boardId', 'listId', 'sort'], + // }, + }, + ); +});