diff --git a/client/components/main/bookmarks.jade b/client/components/main/bookmarks.jade new file mode 100644 index 000000000..b1af77489 --- /dev/null +++ b/client/components/main/bookmarks.jade @@ -0,0 +1,29 @@ +template(name="bookmarks") + .panel + h2 {{_ 'bookmarks'}} + if currentUser + if hasStarredBoards + ul + each starredBoards + li + a(href="{{pathFor 'board' id=_id slug=slug}}")= title + a.js-toggle-star(title="{{_ 'star-board-short-unstar'}}") + i.fa.fa-star + else + p {{_ 'no-starred-boards'}} + else + p {{_ 'please-sign-in'}} + +// Desktop popup +template(name="bookmarksPopup") + ul.pop-over-list + if hasStarredBoards + each starredBoards + li + a(href="{{pathFor 'board' id=_id slug=slug}}") + i.fa.fa-star + | #{title} + a.js-toggle-star.right(title="{{_ 'star-board-short-unstar'}}") + i.fa.fa-star + else + li {{_ 'no-starred-boards'}} diff --git a/client/components/main/bookmarks.js b/client/components/main/bookmarks.js new file mode 100644 index 000000000..6ec110593 --- /dev/null +++ b/client/components/main/bookmarks.js @@ -0,0 +1,55 @@ +Template.bookmarks.helpers({ + hasStarredBoards() { + const user = ReactiveCache.getCurrentUser(); + if (!user) return false; + const { starredBoards = [] } = user.profile || {}; + return Array.isArray(starredBoards) && starredBoards.length > 0; + }, + starredBoards() { + const user = ReactiveCache.getCurrentUser(); + if (!user) return []; + const { starredBoards = [] } = user.profile || {}; + if (!Array.isArray(starredBoards) || starredBoards.length === 0) return []; + return Boards.find({ _id: { $in: starredBoards } }, { sort: { sort: 1 } }); + }, +}); + +Template.bookmarks.events({ + 'click .js-toggle-star'(e) { + e.preventDefault(); + const boardId = this._id; + const user = ReactiveCache.getCurrentUser(); + if (user && boardId) { + user.toggleBoardStar(boardId); + } + }, +}); + +Template.bookmarksPopup.helpers({ + hasStarredBoards() { + const user = ReactiveCache.getCurrentUser(); + if (!user) return false; + const { starredBoards = [] } = user.profile || {}; + return Array.isArray(starredBoards) && starredBoards.length > 0; + }, + starredBoards() { + const user = ReactiveCache.getCurrentUser(); + if (!user) return []; + const { starredBoards = [] } = user.profile || {}; + if (!Array.isArray(starredBoards) || starredBoards.length === 0) return []; + return Boards.find({ _id: { $in: starredBoards } }, { sort: { sort: 1 } }); + }, +}); + +Template.bookmarksPopup.events({ + 'click .js-toggle-star'(e) { + e.preventDefault(); + const boardId = this._id; + const user = ReactiveCache.getCurrentUser(); + if (user && boardId) { + user.toggleBoardStar(boardId); + } + }, +}); + + diff --git a/client/components/main/header.jade b/client/components/main/header.jade index 0ceb2ae9d..4ce881972 100644 --- a/client/components/main/header.jade +++ b/client/components/main/header.jade @@ -12,7 +12,7 @@ template(name="header") span.fa.fa-home | {{_ 'all-boards'}} - // Logo - always visible in desktop mode + // Logo - visible; on mobile constrained by CSS unless currentSetting.hideLogo if currentSetting.customTopLeftCornerLogoImageUrl if currentSetting.customTopLeftCornerLogoLinkUrl @@ -82,6 +82,12 @@ template(name="header") a.board-header-btn.js-mobile-mode-toggle(title="{{_ 'mobile-desktop-toggle'}}" class="{{#if mobileMode}}mobile-active{{else}}desktop-active{{/if}}") i.fa.fa-mobile.mobile-icon(class="{{#if mobileMode}}active{{/if}}") i.fa.fa-desktop.desktop-icon(class="{{#unless mobileMode}}active{{/unless}}") + + // Bookmarks button - desktop opens popup, mobile routes to page + a.board-header-btn.js-open-bookmarks(title="{{_ 'bookmarks'}}") + i.fa.fa-bookmark + + // Notifications +notifications if currentSetting.customHelpLinkUrl diff --git a/client/components/main/header.js b/client/components/main/header.js index f56a47f44..99c3aabc1 100644 --- a/client/components/main/header.js +++ b/client/components/main/header.js @@ -104,6 +104,9 @@ Template.header.events({ const currentMode = Utils.getMobileMode(); Utils.setMobileMode(!currentMode); }, + 'click .js-open-bookmarks'(evt) { + // Already added but ensure single definition -- safe guard + }, 'click .js-close-announcement'() { $('.announcement').hide(); }, @@ -124,6 +127,14 @@ Template.header.events({ location.reload(); } }, + 'click .js-open-bookmarks'(evt) { + // Desktop: open popup, Mobile: route to page + if (Utils.isMiniScreen()) { + FlowRouter.go('bookmarks'); + } else { + Popup.open('bookmarksPopup')(evt); + } + }, }); Template.offlineWarning.events({ diff --git a/client/components/main/layouts.css b/client/components/main/layouts.css index 567468b0e..a32b6cb30 100644 --- a/client/components/main/layouts.css +++ b/client/components/main/layouts.css @@ -533,13 +533,70 @@ a:not(.disabled).is-active i.fa { /* Header mobile layout */ #header { padding: 8px; - flex-wrap: wrap; + /* Keep top bar on a single row on small screens */ + flex-wrap: nowrap; + align-items: center; gap: 8px; } #header-quick-access { - flex-direction: column; + /* Keep quick-access items in one row */ + display: flex; + flex-direction: row; + align-items: center; gap: 8px; + width: 100%; + } + + /* Hide elements that should move to the hamburger menu on mobile */ + #header-quick-access .header-quick-access-list, + #header-quick-access #header-help { + display: none !important; + } + + /* Show only the home icon (hide the trailing text) on mobile */ + #header-quick-access .home-icon a { + display: inline-flex; + align-items: center; + max-width: 28px; /* enough to display the icon */ + overflow: hidden; + white-space: nowrap; + } + + /* Hide text in home icon on mobile, show only icon */ + #header-quick-access .home-icon a span:not(.fa) { + display: none !important; + } + + /* Ensure proper spacing for mobile header elements */ + #header-quick-access .zoom-controls { + margin-left: auto; + margin-right: 8px; + } + + .mobile-mode-toggle { + margin-right: 8px; + } + + #header-user-bar { + margin-left: auto; + } + + /* Ensure header elements don't wrap on very small screens */ + #header-quick-access { + min-width: 0; /* Allow flexbox to shrink */ + } + + /* Make sure logo doesn't take too much space on mobile */ + #header-quick-access img { + max-height: 24px; + max-width: 120px; + } + + /* Ensure zoom controls are compact on mobile */ + .zoom-controls .zoom-level { + padding: 4px 8px; + font-size: 12px; } /* Modal mobile optimization */ diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js index 693007021..a6de901d3 100644 --- a/client/components/sidebar/sidebar.js +++ b/client/components/sidebar/sidebar.js @@ -65,8 +65,10 @@ BlazeComponent.extendComponent({ }, reachNextPeak() { - const activitiesComponent = this.childComponents('activities')[0]; - activitiesComponent.loadNextPage(); + const activitiesChildren = this.childComponents('activities'); + if (activitiesChildren && activitiesChildren.length > 0 && activitiesChildren[0] && typeof activitiesChildren[0].loadNextPage === 'function') { + activitiesChildren[0].loadNextPage(); + } }, isTongueHidden() { diff --git a/client/components/users/userHeader.jade b/client/components/users/userHeader.jade index 14fa948d7..cb296878b 100644 --- a/client/components/users/userHeader.jade +++ b/client/components/users/userHeader.jade @@ -12,6 +12,11 @@ template(name="headerUserBar") template(name="memberMenuPopup") ul.pop-over-list + // Bookmarks at the very top + li + a.js-open-bookmarks + i.fa.fa-bookmark + | {{_ 'bookmarks'}} with currentUser li a.js-my-cards(href="{{pathFor 'my-cards'}}") @@ -37,6 +42,15 @@ template(name="memberMenuPopup") a.board-header-btn.js-open-archived-board i.fa.fa-archive span {{_ 'archives'}} + li + a.js-notifications-drawer-toggle + i.fa.fa-bell + | {{_ 'notifications'}} + if currentSetting.customHelpLinkUrl + li + a(href="{{currentSetting.customHelpLinkUrl}}", title="{{_ 'help'}}", target="_blank", rel="noopener noreferrer") + i.fa.fa-question + | {{_ 'help'}} unless currentUser.isWorker ul.pop-over-list li diff --git a/client/components/users/userHeader.js b/client/components/users/userHeader.js index dd7d56b1d..ad3fa6c56 100644 --- a/client/components/users/userHeader.js +++ b/client/components/users/userHeader.js @@ -62,6 +62,15 @@ Template.memberMenuPopup.helpers({ }); Template.memberMenuPopup.events({ + 'click .js-open-bookmarks'(e) { + e.preventDefault(); + if (Utils.isMiniScreen()) { + FlowRouter.go('bookmarks'); + Popup.back(); + } else { + Popup.open('bookmarksPopup')(e); + } + }, 'click .js-my-cards'() { Popup.back(); }, @@ -78,6 +87,9 @@ Template.memberMenuPopup.events({ 'click .js-change-password': Popup.open('changePassword'), 'click .js-change-language': Popup.open('changeLanguage'), 'click .js-support': Popup.open('support'), + 'click .js-notifications-drawer-toggle'() { + Session.set('showNotificationsDrawer', !Session.get('showNotificationsDrawer')); + }, 'click .js-logout'(event) { event.preventDefault(); diff --git a/config/router.js b/config/router.js index 888393cfd..1ab853dbd 100644 --- a/config/router.js +++ b/config/router.js @@ -240,6 +240,25 @@ FlowRouter.route('/global-search', { }, }); +// Mobile Bookmarks page +FlowRouter.route('/bookmarks', { + name: 'bookmarks', + triggersEnter: [AccountsTemplates.ensureSignedIn], + action() { + Filter.reset(); + Session.set('sortBy', ''); + EscapeActions.executeUpTo('popup-close'); + + Utils.manageCustomUI(); + Utils.manageMatomo(); + + BlazeLayout.render('defaultLayout', { + headerBar: 'boardListHeaderBar', + content: 'bookmarks', + }); + }, +}); + FlowRouter.route('/broken-cards', { name: 'broken-cards', action() {