#4236 made every multi-line editor submit only on Ctrl/Cmd+Enter (plain Enter
= newline) so titles could be multi-line, but that removed the fast
Enter-to-save title workflow some users rely on (v10 email feedback). Add a
per-user preference in the Member Settings (Change Settings) popup, saved to
profile.submitOnEnter and OFF by default: when on, plain Enter submits the card
title, description and other inlined forms, and Shift+Enter inserts a newline;
when off, behaviour is unchanged (Ctrl/Cmd+Enter saves).
The rule stays in the single pure isSubmitKey(event, {submitOnEnter}); the card
title, the card-description override and the shared client/lib/inlinedform.js
all route through it and read the preference from the current user. New toggle
+ helper + toggleSubmitOnEnter method + schema field, i18n (en, fi), and
editorSubmitKey.test.cjs extended with positive + negative opt-in cases.
Thanks to xet7 !
Related to #4236.
The member-menu "Notifications" item and its js-notifications-drawer-toggle
handler toggled the same notifications drawer as the header bell icon
(notifications.js), so it was a duplicate. Remove the menu entry and its
now-unused handler; the header bell is unchanged.
Add the .primary class to both "Upload an avatar" buttons so they use the
theme primary-button style (default #005377 blue background + white text,
overridden by the board/global theme color) instead of the plain grey/black
default button.
Redesign the Font popup:
- Font: one button per installed font, each shown IN ITS OWN font (via inline
style), plus a Default button. Clicking a font applies it immediately.
- Size: a row of buttons ordered by percent — smaller on the left, 100%
(Default) in the middle, larger on the right. Clicking a size applies it
immediately.
- Colors: the text/background color wheels now apply immediately on change /
reset. Removed the Save button (everything applies on click).
New .ui-font-btn / .ui-font-size-btn styling (with .active) in labels.css.
Tests updated in tests/uiFonts.test.cjs.
The Font popup now has a color wheel for the text color and one for the text
background color, each with an Unset button to go back to default. Values are
free choices (native <input type=color>), validated server-side as #rrggbb
hex (models/lib/uiFonts isHexColor6) so only a strict hex ever reaches a CSS
value — no injection surface. An unset/invalid value clears that color.
- profile.uiTextColor / profile.uiTextBgColor schema (hex-validated) + getters;
setUiColors(textColor, bgColor) method ($set valid hex, $unset otherwise).
- changeFontPopup: two color wheels + reset buttons; live preview reflects
font, size, text color and background color.
- Applied UI-wide via --wekan-ui-text-color / --wekan-ui-bg-color CSS variables
+ has-ui-text-color / has-ui-bg-color body classes (uiFont.js + uiFont.css).
- i18n text-color / text-background-color; tests extended (hex + negatives +
source guards).
Part of #4759.
New "Font" entry in the member menu opens a popup with a dropdown of fonts
DETECTED as actually installed in the browser (no free typing) and a Font
size dropdown of sensible presets (Smaller..Largest, no typed numbers). Both
have a Default option that unsets the custom choice.
Security: font names and size keys are validated server-side against a
curated whitelist / preset list (models/lib/uiFonts.js), so only a known safe
font name and a preset percentage ever reach the DOM. Labels are plain text
(Blaze-escaped) — never HTML or markdown. No CSS-injection surface.
- models/lib/uiFonts.js: UI_FONTS whitelist + SAFE_FONT_RE + isKnownFont /
fontFamilyValue and UI_FONT_SIZES presets + isKnownFontSize / fontSizeValue.
- client/lib/fontDetector.js: canvas width-comparison detection of installed
fonts, filtered to the whitelist.
- profile.uiFont / profile.uiFontSize schema + getters; setUiFont /
setUiFontSize methods (validated, null/default unsets).
- changeFontPopup (font + size dropdowns, both with Default) wired in
userHeader.js; applied UI-wide via CSS variables in uiFont.js + uiFont.css
(marker body classes), wired into client/features/main.js + client/styles.js.
- i18n change-font/font/font-default/font-size(+presets); tests/uiFonts.test.cjs
(pure + negatives + source guards) wired into test:unit:node.
This is a Member Settings feature (not an Admin Panel option).
Fixes#4759.
Both Board Settings and Member Settings Change Color now use a shared
category -> theme two-level dropdown picker with custom color wheels for the
flat (1 color) and clear (2 colors, a color slide) categories; dark/special
are fixed. Custom colors are validated server-side as #rrggbb, stored in
board.customThemeColors / profile.globalThemeCustomColors, and applied as
CSS variables (--theme-accent / --theme-accent-2) consumed by customTheme.css
to recolor the header, primary buttons and sidebar button.
- client/components/main/themeColorPicker.{jade,js}: shared picker, scope
"board" or "global", reusing the #5514 native color wheel + themeCategories.
- board.setColor(color, custom) + setGlobalThemeColor(color, custom): validate
via isValidCustomColors (flat/clear + count + hex) before storing.
- boardChangeColorPopup and changeColorPopup now render the shared picker;
removed their old swatch-list helpers/events.
- globalThemeColor.js: apply custom colors as :root CSS variables (board own
colors on a board, else the user global override) + marker body classes;
customTheme.css consumes them.
- Wire globalThemeColor.js / themeColorPicker into client/features/main.js and
customTheme.css into client/styles.js. This also fixes globalThemeColor.js
never having been imported (so the #5778 override now actually loads).
- i18n theme / theme-category[-flat/clear/dark/special]; tests
themeColorPicker.test.cjs + updated globalThemeColor.test.cjs.
Fixes#5778.
A new "Change Color" entry in the member menu works like Board Settings /
Change Color, but is a per-user GLOBAL override saved to the profile
(profile.globalThemeColor). The chosen color theme applies to the whole UI —
All Boards, Search All Boards, Admin Panel, My Cards, etc. — via a
board-color-<name> class on <body> and the header, so a user can pick a dark
theme (dark/midnight/moderndark/exodark/cleandark) or any other theme
everywhere. The first popup entry clears the override.
- models/users.js: profile.globalThemeColor field + getGlobalThemeColor /
globalThemeColorClass / setGlobalThemeColor helpers.
- server/models/users.js: setGlobalThemeColor method, validated against
BOARD_COLORS (rejects unknown colors so no arbitrary CSS class is injected)
and auth-guarded; null/empty clears it.
- client/components/main/globalThemeColor.js: Tracker autorun that adds the
class to <body> on non-board pages; on a board the board own color wins, so
the global class is cleared there to avoid two themes clashing.
- header.jade: #header and #header-quick-access fall back to the user global
theme class when not on a board (covers the #header-specific color rules).
- Change Color menu entry + changeColorPopup (color swatches + clear option),
wired in userHeader.js.
- i18n change-color / theme-default; tests/globalThemeColor.test.cjs wired
into test:unit:node.
Make it obvious which board members are backed by a real account and which are not,
both on card avatars and in the right sidebar member list.
- userAvatar (js/jade/css): the avatar gets an 'imported-member' class (dashed grey ring
+ amber '?' corner badge) for un-reconciled board-import placeholders
(authenticationMethod:'imported'), and an 'inactive-member' class (greyscale + dim +
grey badge) for members deactivated on the board (e.g. a person not in LDAP after
reconciliation). A placeholder is usually both; a real active account gets neither. The
tooltip explains the state.
- boards.js membersForSidebar(): active members first (as before) then the inactive /
imported ones appended, and sidebar.jade uses it, so the member list shows EVERYONE and
which are active vs not instead of hiding inactive members.
- i18n: imported-member-no-account, inactive-member.
Thanks to xet7.
Security: with "Self Registration" and "Forgot Password" disabled in the Admin
Panel, the Register (.at-signup-link) and Forgot Password (.at-pwd-link) links
were hidden only by a one-shot jQuery .hide() in userFormsLayout.onRendered.
When a login failed (notably the LDAP "User not Found" path), useraccounts
re-rendered the form and recreated those link nodes without the inline
display:none, so the links reappeared and exposed registration / password-reset
flows that were meant to be off.
The disable state is now also reflected as a class on the stable <body>
ancestor (registration-disabled / forgotpwd-disabled) with matching CSS in
userForm.css. Because the class sits on <body>, the rule re-hides every copy of
the links that useraccounts recreates after a failed login, instead of relying
on a single inline style that the re-render dropped.
Thanks to Alsterdetektive1 and xet7.
On phones body.mobile-mode pins <body> to position:fixed + height:100vh (an
iOS board-view anti-bounce fix). The auth pages (userFormsLayout) have no
inner #content scroller, and .auth-layout/.auth-dialog were sized to the
viewport height, so the page was trapped at 100vh: the content below the
form — the sign-up/sign-in link, legal notice and language selector — could
not be scrolled into view, and the on-screen keyboard covered it.
userFormsLayout now tags <body> with .userform-layout while mounted, and CSS
lets those pages scroll as a normal document on mobile (position:static,
height:auto, min-height:100vh) with the auth layout/dialog growing to their
content. Scoped to .userform-layout so the board-view mobile lock is
unchanged. Removed on destroy.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The custom tap:i18n reimplementation registered each language's resource
bundle under the raw Wekan tag, but i18next (cleanCode: true) resolves and
looks translations up under a normalised code. For region/script-tagged and
legacy underscore tags (zh-CN, zh-Hans, ar-DZ, af_ZA, en_AU, ...) the stored
code and the lookup code disagreed, so the bundle was never found and the UI
silently fell back to English while simple tags (de, fr) kept working.
Normalise every i18next boundary (supportedLngs, addResourceBundle,
changeLanguage, t) through one helper (toI18nCode): underscores -> hyphens
and i18next's formatLanguageCode, so storage and lookup always agree.
Safely unwrap ES-module-namespace JSON imports without mistaking the data's
own "default" translation key for a module default export. Surface failed
language switches in the user header / login form instead of leaving the UI
silently English. Add positive and negative regression tests.
Closes#5756
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When a user account is deleted its entry stays in a card's members and renders
as a blank avatar. Clicking it opened cardMemberPopup, whose template
dereferenced the missing user document and failed to render, so the orphaned
member could not be removed. The popup now detects a missing user, shows a
"Deleted user" entry with the raw id, and keeps the Remove from Card control
(the remove handler keys off the member's userId, not the user doc). Adds the
deleted-user i18n string.
No automated regression — a self-evident Blaze template guard; the underlying
unassignMember removal already worked. The board-members list still hides such
entries because activeMembers() also filters not-yet-loaded users, so
surfacing board-side orphans cleanly is a separate follow-up (noted in
changelog).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirrors the existing add-organization-to-board feature for free-form email
domains. Adds a board-admin-gated setBoardDomains method (plus add/remove
single-domain helpers) with trim/lowercase validation, a Domains tab in the
members sidebar listing board.domains with add/remove popups, and the i18n keys.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- /templates and /remaining routes (plus the home / Starred view) now address
the All Boards left-menu sub-views via the boardListMenu Session value.
- boardList honors the route: selectedMenu initializes from boardListMenu and an
autorun switches the view live for the three recognized menus, without
clobbering a workspace selection.
- The Member Settings -> Templates link now opens the All Boards / Templates page
(pathFor allboards-templates) instead of opening/creating a specific board;
unused templatesBoardId/Slug helpers removed from userHeader.js.
- New users no longer get an auto-created Templates board at signup. The
templates-container board (Templates + Card/List/Board Templates swimlanes) is
now created lazily via the new ensureTemplatesBoard Meteor method, called on
first use of the Templates view, auth-agnostic for any login method.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds limitSettings.avatarsUploadBlocked (default false — avatars enabled)
to Admin Panel > Attachments > Transfer limits. When enabled, the avatar
upload option is hidden in the user avatar popup and new avatar uploads are
rejected server-side (server/permissions/avatars.js), mirroring the existing
attachmentsUploadBlocked. Useful when avatars are synced from an external
source. Thanks to xet7.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- bookmarks: Popup.open('bookmarksPopup') resolved to a non-existent
'bookmarksPopupPopup' template (open() appends "Popup"). Fixed both calls to
Popup.open('bookmarks') so the bookmarksPopup template + bookmarksPopup-title
("Starred boards") apply.
- Removed two dead handlers that opened non-existent templates with no
triggering button: 'click .js-import' -> boardImportBoard (boardHeader.js x2,
sidebar.js) and 'click .js-due-cards-view-change' -> globalSearchViewChange
(globalSearch.js; the globalSearchHeaderBar template has no such button).
- Set the error popup titles to "List width error" / "Swimlane height error".
- docs/Features/RedStrings: added ASCII-art step-by-step for drawing a line by
dragging the connect handle, and for changing a line's relation type/color/icon.
Verified: spec 27 passes 9/9 on chromium.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
In the member menu popup the 'Change Avatar' label was indented one level too
deep, making it a child of the <i class="fa fa-picture-o"> icon element instead
of a sibling like every other menu entry. It therefore inherited the FontAwesome
icon font styling and rendered differently (uppercase-looking, off style). Dedent
the label so it sits directly under the <a>, matching the other items.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Added a visible keyboard focus indicator (`:focus-visible` outline) for links,
buttons and form controls. The global CSS reset previously stripped all focus
outlines, leaving keyboard users with no indication of focus (WCAG 2.4.7).
- Added a "Skip to main content" link that lets keyboard and screen-reader users
bypass the header chrome (WCAG 2.4.1 Bypass Blocks).
- Added landmark roles so assistive technology can navigate page regions:
`role="main"` on the content area, `role="navigation"` on the header quick-access
bar, and `role="search"` on the global search form (WCAG 1.3.1).
- Marked modals and popups as `role="dialog"` with accessible names, set
`aria-modal="true"` on modals, and moved keyboard focus into a modal when it
opens and back to the triggering element when it closes (WCAG 2.4.3 Focus Order).
- Added accessible names (`aria-label`) to icon-only controls that previously had
none: modal/popup close and back buttons, the announcement close button, the
sidebar close/back buttons and the global search input and clear button
(WCAG 4.1.2 Name, Role, Value).
- Marked decorative Font Awesome icons inside labelled controls as
`aria-hidden="true"` so they are not announced redundantly.
- Added a screen-reader-only `.sr-only` helper class.
Thanks to xet7 and Claude !
(Arabic and its variants, Persian/Farsi, Hebrew, Uyghur, Uzbek-Arabic and
Yiddish — the languages flagged `rtl: true` in `imports/i18n/languages.js`).
Previously only some pages flipped. The root `<html>` element now gets a
reactive `dir="rtl"`/`dir="ltr"` attribute that follows the chosen language, so
the whole UI mirrors at once. To make this work on every page rather than a
handful, all directional component CSS was converted to CSS logical properties
(`margin-left`→`margin-inline-start`, `left:`→`inset-inline-start:`,
`text-align: left`→`text-align: start`, `float: left`→`float: inline-start`,
and the mirror-image right/end variants) across 48 stylesheets, which flip
automatically with `dir`. Horizontal-centering rules using
`left: 50%` + `transform: translate(-50%, …)` were intentionally kept physical,
since those already center correctly in both directions. The calendar view also
renders RTL. Thanks to xet7 and Claude.
- Tests for the RTL UI. A fast, server-less `tests/rtl.test.js` (run with
`node tests/rtl.test.js`) checks that exactly the expected languages are
flagged `rtl: true`, that the direction helper maps each language to the right
`dir` value, that the root `<html>` and the client keep `dir` in sync, and —
as a regression guard — that component CSS keeps using logical properties
(no physical `margin-left`/`float: left`/bare `left:` offsets sneak back in).
A Playwright browser spec `tests/playwright/specs/18-rtl-layout.e2e.js` drives
the app in both English (LTR) and Arabic (RTL) and asserts, on the boards list,
board view, card details, my-cards / due-cards / global-search, the admin
settings page and the login page, that the direction is correct, the
translated text is visible, and leading-edge content (the boards menu, the
first board list) sits on the correct side. Thanks to xet7 and Claude.
- Fixed the "Meteor unit tests" CI job hanging until its 45-minute timeout. The
workflow set `TEST_WATCH: '0'`, but meteortesting:mocha computes
`testWatch: TEST_WATCH || …` and the string `'0'` is truthy in JavaScript, so
it turned on watch mode and the process never exited. Removing the env var lets
`meteor test --once` finish and exit with the correct status.
Thanks to xet7 and Claude !
Fixes#6136,
fixes#3376,
fixes#884
Convert all remaining BlazeComponent-based components to native Meteor
Template pattern: activities, comments, all rules actions/triggers,
swimlanes, users, gantt, import, and main utility components.