mirror of
https://github.com/wekan/wekan.git
synced 2026-11-05 21:44:59 -05:00
Add a header-icons collapse toggle beside the board title (#6680 follow-up).
A single button, right after the board title (inside the same .home-icon wrapper), that hides every icon from the mobile/desktop toggle through the notification bell: mobile/desktop mode, drag-handles toggle, the three board-wide resize-lock icons, the starred-boards group, create-board, the board/all-boards header buttons, the view menu, the Admin Panel tabs, and notifications. Purely a per-viewer display preference, not persisted anywhere - a plain Session var (wekan-header-icons-collapsed), the same shape as mobileMode() right beside it in header.js, not a board setting like the resize locks. Every icon in that range gets a shared .js-header-collapsible-icon marker class; the four `+templateName` inclusions and +notifications cannot carry a class of their own, so each is wrapped in a `span.js-header-collapsible- icon` that stays `display: contents` outside the collapsed state, so introducing the wrapper does not change how those icons behave as flex items of #header-quick-access when nothing is collapsed. `#header-quick-access.header-icons-collapsed .js-header-collapsible-icon` is the one CSS rule that actually hides them. tests/headerIconsCollapse.test.cjs pins the button's position, the Session-var (not board-setting) shape, that every icon in the range is marked and nothing outside it is, and both CSS rules. Thanks to xet7.
This commit is contained in:
1 parent
c481ce1737
commit
d1b01924e1
5 files changed
+198
-14
No files matched your search
@@ -477,6 +477,24 @@ body.mobile-mode #header-quick-access .allBoards {
|
||||
#header-quick-access .js-toggle-same-width-for-all-lists .fa {
|
||||
font-size: 0.7em;
|
||||
}
|
||||
|
||||
/* #6680 follow-up: collapse every icon from the mobile/desktop toggle
|
||||
through the notification bell into one .js-toggle-header-icons-collapsed
|
||||
button beside the title. Some marked elements are pre-existing div
|
||||
wrappers with their own box styling (.mobile-mode-toggle,
|
||||
.header-star-group) and keep their normal display outside the collapsed
|
||||
state; the plain `span.js-header-collapsible-icon` wrappers added around
|
||||
+boardHeaderButtons/+allBoardsHeaderButtons/+boardViewMenu/
|
||||
+allBoardsViewMenu/+adminPanelTabs/+notifications did not exist before
|
||||
this feature and must stay `display: contents` so introducing them does
|
||||
not change how their own children behave as flex items of
|
||||
#header-quick-access when NOT collapsed. */
|
||||
#header-quick-access .header-quick-access-end > span.js-header-collapsible-icon {
|
||||
display: contents;
|
||||
}
|
||||
#header-quick-access.header-icons-collapsed .js-header-collapsible-icon {
|
||||
display: none !important;
|
||||
}
|
||||
#header-quick-access #header-user-bar,
|
||||
#header-quick-access #header-new-board-icon,
|
||||
#header-quick-access ul li {
|
||||
|
||||
@@ -8,7 +8,7 @@ template(name="header")
|
||||
//- nodragscroll hands dragging on this bar to client/lib/headerDragscroll.js,
|
||||
//- which forwards it to whatever actually scrolls. Without it the page-level
|
||||
//- dragscroll on the body element would scroll the page at the same time.
|
||||
#header-quick-access.nodragscroll(class="{{themeColorClass}}" role="navigation" aria-label="{{_ 'all-boards'}}")
|
||||
#header-quick-access.nodragscroll(class="{{themeColorClass}} {{#if headerIconsCollapsed}}header-icons-collapsed{{/if}}" role="navigation" aria-label="{{_ 'all-boards'}}")
|
||||
//- Home icon - always at left side of logo. The house is the link to All
|
||||
Boards; the TEXT beside it is the page you are on, which on a board is
|
||||
the board's title. It used to say "All Boards" on every page, so the one
|
||||
@@ -40,6 +40,18 @@ template(name="header")
|
||||
else
|
||||
= headerTitleText
|
||||
|
||||
//- Collapse every icon from the mobile/desktop toggle through the
|
||||
notification bell (everything given the .js-header-collapsible-icon
|
||||
class below), right beside the title they clutter up next to. A
|
||||
purely visual, per-viewer preference - not persisted server-side,
|
||||
the same way mobileMode() itself is read - so it is a plain
|
||||
Session var, read/written in header.js exactly like mobileMode.
|
||||
a.board-header-btn.js-toggle-header-icons-collapsed(title="{{_ 'toggle-header-icons-collapsed'}}")
|
||||
if headerIconsCollapsed
|
||||
i.fa.fa-angle-double-right
|
||||
else
|
||||
i.fa.fa-angle-double-left
|
||||
|
||||
// Logo - visible; on mobile constrained by CSS
|
||||
unless currentSetting.hideLogo
|
||||
if currentSetting.customTopLeftCornerLogoImageUrl
|
||||
@@ -56,7 +68,10 @@ template(name="header")
|
||||
img(src="{{pathFor '/logo-header.png'}}" alt="{{headerLogoAlt}}" title="{{headerLogoAlt}}")
|
||||
|
||||
// Mobile/desktop mode toggle (swapped with zoom)
|
||||
.mobile-mode-toggle
|
||||
// #6680 follow-up: .js-header-collapsible-icon marks every icon the new
|
||||
// .js-toggle-header-icons-collapsed button (by the title) hides as a
|
||||
// group - see header.css. This is the FIRST one of that range.
|
||||
.mobile-mode-toggle.js-header-collapsible-icon
|
||||
a.board-header-btn.js-mobile-mode-toggle(title="{{_ 'mobile-desktop-toggle'}}" class="{{#if mobileMode}}mobile-active{{else}}desktop-active{{/if}}")
|
||||
if mobileMode
|
||||
i.mobile-icon
|
||||
@@ -66,7 +81,7 @@ template(name="header")
|
||||
i.fa.fa-desktop
|
||||
|
||||
// Drag handles toggle - between zoom and mobile mode toggle
|
||||
a.board-header-btn.js-toggle-desktop-drag-handles(title="{{_ 'show-desktop-drag-handles'}}")
|
||||
a.board-header-btn.js-toggle-desktop-drag-handles.js-header-collapsible-icon(title="{{_ 'show-desktop-drag-handles'}}")
|
||||
i.fa.fa-arrows
|
||||
if isShowDesktopDragHandles
|
||||
i.fa.fa-check
|
||||
@@ -80,7 +95,7 @@ template(name="header")
|
||||
// then swimlane height (up-down arrow).
|
||||
if isBoardPage
|
||||
if canLockBoardResize
|
||||
a.board-header-btn.js-toggle-list-width-resize-lock(title="{{_ 'lock-list-width-resize'}}")
|
||||
a.board-header-btn.js-toggle-list-width-resize-lock.js-header-collapsible-icon(title="{{_ 'lock-list-width-resize'}}")
|
||||
i.fa.fa-arrows-h
|
||||
if isListWidthResizeLocked
|
||||
i.fa.fa-ban
|
||||
@@ -89,13 +104,13 @@ template(name="header")
|
||||
// #6680: board-wide "same width for all lists" - the same mode the
|
||||
// per-list "Set width" popup already offers per user, exposed here
|
||||
// as one board setting that applies to every viewer.
|
||||
a.board-header-btn.js-toggle-same-width-for-all-lists(title="{{_ 'same-width-for-all-lists'}}")
|
||||
a.board-header-btn.js-toggle-same-width-for-all-lists.js-header-collapsible-icon(title="{{_ 'same-width-for-all-lists'}}")
|
||||
i.fa.fa-columns
|
||||
if isSameWidthForAllLists
|
||||
i.fa.fa-check
|
||||
else
|
||||
i.fa.fa-ban
|
||||
a.board-header-btn.js-toggle-swimlane-height-resize-lock(title="{{_ 'lock-swimlane-height-resize'}}")
|
||||
a.board-header-btn.js-toggle-swimlane-height-resize-lock.js-header-collapsible-icon(title="{{_ 'lock-swimlane-height-resize'}}")
|
||||
i.fa.fa-arrows-v
|
||||
if isSwimlaneHeightResizeLocked
|
||||
i.fa.fa-ban
|
||||
@@ -125,7 +140,7 @@ template(name="header")
|
||||
every pop-over; nothing here has to ask for it.
|
||||
//- One GROUP: the two adjacent buttons are about the same thing - how
|
||||
many items you have starred, and whether this one is among them.
|
||||
.header-star-group
|
||||
.header-star-group.js-header-collapsible-icon
|
||||
a.board-header-btn.js-open-starred-boards(title="{{_ 'allboards.starred'}}")
|
||||
i.fa.fa-caret-down
|
||||
span.board-star-counter {{starredBoardsCount}}
|
||||
@@ -152,7 +167,7 @@ template(name="header")
|
||||
|
||||
//- The top-bar Create Board control shares the same form and board-template
|
||||
picker as Add Board on the overview. #3070
|
||||
a#header-new-board-icon.board-header-btn.js-create-board(title="{{_ 'add-board'}}")
|
||||
a#header-new-board-icon.board-header-btn.js-create-board.js-header-collapsible-icon(title="{{_ 'add-board'}}")
|
||||
i.fa.fa-plus
|
||||
|
||||
//- The "100%" zoom control that used to sit here is REMOVED. It scaled the
|
||||
@@ -164,13 +179,15 @@ template(name="header")
|
||||
//- Everything the board's second header bar held, as icons, left of the
|
||||
bell. client/components/boards/boardHeader.jade
|
||||
if isBoardPage
|
||||
+boardHeaderButtons
|
||||
span.js-header-collapsible-icon
|
||||
+boardHeaderButtons
|
||||
|
||||
//- All Boards' four controls - Sort, Search, Multi-Selection, Boards in
|
||||
Archive - as icons, left of the bell, exactly where a board's own
|
||||
controls are. client/components/boards/boardsList.jade
|
||||
if isAllBoardsPage
|
||||
+allBoardsHeaderButtons
|
||||
span.js-header-collapsible-icon
|
||||
+allBoardsHeaderButtons
|
||||
|
||||
//- The view menu of whatever page has one - Swimlanes / Lists / Calendar
|
||||
/ Gantt / Table / Stats on a board, Lists / Table on All Boards. A view
|
||||
@@ -178,18 +195,22 @@ template(name="header")
|
||||
in the bar that is always on screen, not in a second bar or behind a
|
||||
panel. docs/Features/Page/Header.md
|
||||
if isBoardPage
|
||||
+boardViewMenu
|
||||
span.js-header-collapsible-icon
|
||||
+boardViewMenu
|
||||
else if isAllBoardsPage
|
||||
+allBoardsViewMenu
|
||||
span.js-header-collapsible-icon
|
||||
+allBoardsViewMenu
|
||||
|
||||
//- The Admin Panel's four tabs, to the LEFT of the bell. Drawn only in
|
||||
the Admin Panel: they are navigation between its four pages.
|
||||
client/components/settings/settingHeader.jade
|
||||
if isAdminPanel
|
||||
+adminPanelTabs
|
||||
span.js-header-collapsible-icon
|
||||
+adminPanelTabs
|
||||
|
||||
// Notifications
|
||||
+notifications
|
||||
span.js-header-collapsible-icon
|
||||
+notifications
|
||||
|
||||
//- Everything that belongs to YOU rather than to the page - the divider
|
||||
that separates the two, help, your account, and the sidebar toggle
|
||||
|
||||
@@ -364,6 +364,14 @@ Template.header.helpers({
|
||||
}
|
||||
return Utils.getMobileMode();
|
||||
},
|
||||
|
||||
// #6680 follow-up: whether every .js-header-collapsible-icon (mobile/
|
||||
// desktop toggle through the notification bell) is hidden. A plain,
|
||||
// per-viewer, per-session preference - not persisted - the same shape as
|
||||
// mobileMode() above, not a board setting like the resize locks are.
|
||||
headerIconsCollapsed() {
|
||||
return !!Session.get('wekan-header-icons-collapsed');
|
||||
},
|
||||
});
|
||||
|
||||
Template.header.events({
|
||||
@@ -469,6 +477,12 @@ Template.header.events({
|
||||
if (!board) return;
|
||||
board.setSwimlaneHeightResizeLocked(!board.getSwimlaneHeightResizeLocked());
|
||||
},
|
||||
// #6680 follow-up: collapse every icon from the mobile/desktop toggle
|
||||
// through the notification bell, right beside the board title. Purely
|
||||
// visual and per-session, like mobileMode() - see headerIconsCollapsed().
|
||||
'click .js-toggle-header-icons-collapsed'() {
|
||||
Session.set('wekan-header-icons-collapsed', !Session.get('wekan-header-icons-collapsed'));
|
||||
},
|
||||
// #6680: the board-wide "same width for all lists" toggle. Enabling it is
|
||||
// a board setting (admin-only, like the two lock toggles); it does not
|
||||
// touch each member's own personal fixed-width choice, it just overrides
|
||||
|
||||
@@ -1370,6 +1370,7 @@
|
||||
"lock-list-width-resize": "Lock/unlock resizing list width between lists",
|
||||
"lock-swimlane-height-resize": "Lock/unlock resizing swimlane height",
|
||||
"same-width-for-all-lists": "Same width for all lists (whole board)",
|
||||
"toggle-header-icons-collapsed": "Collapse/expand header icons (mobile/desktop toggle to notifications)",
|
||||
"drag-to-resize-sidebar": "Drag to resize sidebar",
|
||||
"drag-to-resize-left-menu": "Drag to resize left menu",
|
||||
"submit-on-enter": "Submit editors with Enter",
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
'use strict';
|
||||
|
||||
// #6680 follow-up: a collapse button beside the board title hides every icon
|
||||
// from the mobile/desktop toggle through the notification bell, so a viewer
|
||||
// who does not need them can get the whole run out of the way at once.
|
||||
//
|
||||
// This is a static wiring test (no Meteor runtime here), pinning:
|
||||
// - the toggle button sits right after the board title, inside the same
|
||||
// .home-icon wrapper, with an icon that swaps by state;
|
||||
// - headerIconsCollapsed()/the click handler use a plain per-session
|
||||
// Session var, the same shape as mobileMode() beside it - not a board
|
||||
// setting, since this is a personal viewing preference, not something
|
||||
// one viewer should be able to hide for everyone else;
|
||||
// - every icon in the described range carries .js-header-collapsible-icon,
|
||||
// and nothing outside that range does;
|
||||
// - the CSS actually hides marked icons when collapsed, and the NEW
|
||||
// wrapper spans stay display:contents outside that state so introducing
|
||||
// them does not change any existing icon's flex-item behavior.
|
||||
//
|
||||
// Run: node tests/headerIconsCollapse.test.cjs
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const repoRoot = path.resolve(__dirname, '..');
|
||||
const read = rel => fs.readFileSync(path.join(repoRoot, rel), 'utf8');
|
||||
|
||||
let passed = 0;
|
||||
function test(name, fn) { fn(); passed += 1; console.log(' ok -', name); }
|
||||
|
||||
console.log('headerIconsCollapse:');
|
||||
|
||||
const jade = read('client/components/main/header.jade');
|
||||
const js = read('client/components/main/header.js');
|
||||
const css = read('client/components/main/header.css');
|
||||
|
||||
test('the toggle button sits right after the board title, inside .home-icon', () => {
|
||||
const titleAt = jade.indexOf('span.header-page-title');
|
||||
const toggleAt = jade.indexOf('a.board-header-btn.js-toggle-header-icons-collapsed');
|
||||
assert.ok(titleAt !== -1 && toggleAt !== -1, 'both elements exist');
|
||||
assert.ok(toggleAt > titleAt, 'the toggle comes after the title');
|
||||
// Both are 8-space-indented children of "span.home-icon.allBoards" (6
|
||||
// spaces): the toggle line itself must be indented exactly like the title.
|
||||
const titleIndent = jade.slice(0, titleAt).match(/( *)$/)[1].length;
|
||||
const toggleIndent = jade.slice(0, toggleAt).match(/( *)$/)[1].length;
|
||||
assert.strictEqual(toggleIndent, titleIndent,
|
||||
'the toggle is a sibling of the title, not nested somewhere else');
|
||||
// And no OTHER top-level element (e.g. "// Logo", a comment at the same
|
||||
// indent as span.home-icon itself) sits between them.
|
||||
const homeIconIndent = titleIndent - 2;
|
||||
const betweenLines = jade.slice(titleAt, toggleAt).split('\n').slice(1, -1);
|
||||
for (const l of betweenLines) {
|
||||
if (!l.trim()) continue;
|
||||
const indent = l.match(/^ */)[0].length;
|
||||
assert.ok(indent > homeIconIndent, `line inside .home-icon must be indented further: "${l}"`);
|
||||
}
|
||||
});
|
||||
|
||||
test('the icon swaps by headerIconsCollapsed, and the tooltip is translated', () => {
|
||||
const block = jade.slice(jade.indexOf('js-toggle-header-icons-collapsed'),
|
||||
jade.indexOf('js-toggle-header-icons-collapsed') + 300);
|
||||
assert.ok(/title="\{\{_ 'toggle-header-icons-collapsed'\}\}"/.test(block));
|
||||
assert.ok(/if headerIconsCollapsed/.test(block));
|
||||
assert.ok(block.includes('fa-angle-double-right') && block.includes('fa-angle-double-left'),
|
||||
'both icon states are drawn');
|
||||
});
|
||||
|
||||
test('headerIconsCollapsed() and its click handler use a plain Session var, like mobileMode()', () => {
|
||||
assert.ok(/headerIconsCollapsed\(\) \{\s*return !!Session\.get\('wekan-header-icons-collapsed'\);/
|
||||
.test(js.replace(/\n\s*/g, ' ')), 'the helper reads the Session var');
|
||||
const at = js.indexOf("'click .js-toggle-header-icons-collapsed'");
|
||||
assert.ok(at !== -1, 'the click handler exists');
|
||||
const body = js.slice(at, js.indexOf('},', at));
|
||||
assert.ok(body.includes("Session.set('wekan-header-icons-collapsed'"),
|
||||
'the handler writes the same Session var');
|
||||
// Negative: this must NOT be a board setting (no Boards.updateAsync/
|
||||
// board.setXyz call) - it is a personal viewing preference.
|
||||
assert.ok(!/board\.set|Boards\.updateAsync/.test(body),
|
||||
'the handler never persists to the board (negative)');
|
||||
});
|
||||
|
||||
test('every icon in the mobile-toggle-to-notifications range carries the marker class', () => {
|
||||
for (const needle of [
|
||||
'.mobile-mode-toggle.js-header-collapsible-icon',
|
||||
'js-toggle-desktop-drag-handles.js-header-collapsible-icon',
|
||||
'js-toggle-list-width-resize-lock.js-header-collapsible-icon',
|
||||
'js-toggle-same-width-for-all-lists.js-header-collapsible-icon',
|
||||
'js-toggle-swimlane-height-resize-lock.js-header-collapsible-icon',
|
||||
'.header-star-group.js-header-collapsible-icon',
|
||||
'js-create-board.js-header-collapsible-icon',
|
||||
]) {
|
||||
assert.ok(jade.includes(needle), `expected to find: ${needle}`);
|
||||
}
|
||||
// The four template inclusions and notifications are wrapped, since a
|
||||
// Spacebars `+templateName` inclusion cannot carry a class of its own.
|
||||
for (const included of ['+boardHeaderButtons', '+allBoardsHeaderButtons',
|
||||
'+boardViewMenu', '+allBoardsViewMenu', '+adminPanelTabs', '+notifications']) {
|
||||
const at = jade.indexOf(included);
|
||||
assert.ok(at !== -1, `${included} is still included`);
|
||||
const before = jade.slice(Math.max(0, at - 60), at);
|
||||
assert.ok(/span\.js-header-collapsible-icon\s*\n\s*$/.test(before),
|
||||
`${included} is wrapped in span.js-header-collapsible-icon`);
|
||||
}
|
||||
});
|
||||
|
||||
test('nothing outside that range carries the marker class (negative)', () => {
|
||||
for (const outside of ['home-icon', 'header-logo', 'header-account-group',
|
||||
'header-notify-icon', 'js-open-notifications']) {
|
||||
// None of these element definitions should have the collapsible class
|
||||
// directly on them - they are either before the range (home/logo) or
|
||||
// after it (the account group and its contents).
|
||||
const re = new RegExp(`${outside}[^\\n]*js-header-collapsible-icon`);
|
||||
assert.ok(!re.test(jade), `${outside} must not carry the collapsible class`);
|
||||
}
|
||||
});
|
||||
|
||||
test('the CSS hides every marked icon when collapsed, and the new wrapper spans stay transparent otherwise', () => {
|
||||
assert.ok(/#header-quick-access\.header-icons-collapsed \.js-header-collapsible-icon \{[^}]*display:\s*none\s*!important/
|
||||
.test(css.replace(/\s+/g, ' ')), 'the collapsed rule hides marked icons');
|
||||
assert.ok(/#header-quick-access \.header-quick-access-end > span\.js-header-collapsible-icon \{[^}]*display:\s*contents/
|
||||
.test(css.replace(/\s+/g, ' ')), 'the new span wrappers default to display: contents');
|
||||
});
|
||||
|
||||
test('the root element carries the collapsed class reactively', () => {
|
||||
assert.ok(/#header-quick-access\.nodragscroll\(class="\{\{themeColorClass\}\} \{\{#if headerIconsCollapsed\}\}header-icons-collapsed\{\{\/if\}\}"/
|
||||
.test(jade), 'the class is bound to the same helper the button reads');
|
||||
});
|
||||
|
||||
console.log(`\nheaderIconsCollapse: ${passed} tests passed`);
|
||||
Reference in new issue
Block a user