Compare commits

...
97 Commits
Author SHA1 Message Date
github-actions[bot] 42507de1d0 Bump versions for v12.92 2026-11-03 18:51:08 +00:00
Lauri Ojansivu 131b0cc509 Prepare v12.92 release 2026-11-03 20:48:47 +02:00
Lauri Ojansivu 93ae5ac6ee Merge remote-tracking branch 'origin/main' 2026-11-03 20:46:48 +02:00
Lauri Ojansivu 34bb91d1f5 CHANGELOG: a missing REST checklist answers a JSON 404. 2026-11-03 20:43:21 +02:00
Lauri Ojansivu d231df56fc REST API: a missing checklist, or one under another card, answers a JSON 404, not an empty 500.
GET /api/boards/:boardId/cards/:cardId/checklists/:checklistId answered 500 with
no body when the checklist did not exist or belonged to another card. It now
answers 404 with the same error text the checklist update and delete routes
use; board access and the parent-card check are unchanged. Route tests drive
the registered handler (found, missing, other card, other board, denied, lookup
failure) and a browser spec reads the checklist over REST and after a reload.

Thanks to AhmedLukman and xet7 !
2026-11-03 20:43:06 +02:00
github-actions[bot] e422ab691e Bump versions for v12.91 2026-11-03 18:01:08 +00:00
Lauri Ojansivu 3f712a0445 Prepare v12.91 release 2026-11-03 19:59:16 +02:00
Lauri Ojansivu d99dadadee Merge remote-tracking branch 'origin/main' 2026-11-03 19:42:04 +02:00
Lauri Ojansivu a58b7bee2c CHANGELOG: code scanning alert 600, a test replacing getLabel with itself. 2026-11-03 19:17:12 +02:00
Lauri Ojansivu 62da564a1e Code scanning #600: a test no longer replaces getLabel with itself, and the guard knows anchored patterns.
tests/boardLabelLookup.test.cjs sliced the getLabel method out of models/boards.js
and then ran .replace(/^getLabel/, 'getLabel') on it, which does nothing: the
slice already starts at getLabel. tests/noIdentityReplacement.test.cjs skipped
every regular expression with an anchor, so it could not see it; a leading ^ or
a trailing $ over plain characters is now compared like any other.

Thanks to GitHub CodeQL and xet7 !
2026-11-03 19:16:44 +02:00
github-actions[bot] 2ada2e2d61 Bump versions for v12.90 2026-11-03 15:58:18 +00:00
Lauri Ojansivu 5de1acf496 Prepare v12.90 release 2026-11-03 17:57:04 +02:00
Lauri Ojansivu 3008bb5606 Merge remote-tracking branch 'origin/main' 2026-11-03 17:55:41 +02:00
Lauri Ojansivu 4f7521ae3c Tigre: the login page background label uses the Tigre word for color, not the Tigrinya one.
Thanks to Raymond-Voogt and xet7 !
2026-11-03 17:31:40 +02:00
Lauri Ojansivu 1c63049913 CHANGELOG: login page background color and a readable support page. 2026-11-03 17:20:07 +02:00
Lauri Ojansivu 6b27764452 Admin Panel: a login page background colour, and a readable layout for the login text and the support page.
Lay-out has a new Login page background color (loginBackgroundColor). Only a
hex colour is accepted, so the value in the CSS custom property can never carry
CSS; the text under the login logo turns white on a dark colour and black on a
light one. The colour is on body only while the sign-in layout shows. The
support page and the text under the login logo get a centred column with sized,
bold headings and space between paragraphs. The label is translated into every
language file.

Thanks to Raymond-Voogt and xet7 !

Fixes #6866,
2026-11-03 17:19:22 +02:00
Lauri Ojansivu ae49937b44 Tests: the Playwright specs expect the accessibility-audit colours and drop below the last checklist row.
The belize and nephritis buttons, the limegreen header, the red-string and
typed-relation lines are now the darker shades that keep their contrast, and
the checklist drag released on the sortable's own bottom edge.
2026-11-03 16:48:44 +02:00
Lauri Ojansivu 7f27ea1f26 CHANGELOG: Show focus outline, and Escape closes the add-list composer. 2026-11-03 16:04:58 +02:00
Lauri Ojansivu 144777ae6b Member Settings: a per-member switch to hide the focus outline.
Change Settings has Show focus outline, on by default: the blue ring round the
focused control is an accessibility feature (WCAG 2.4.7), and it also shows
after a click on a minicard. A member who turns it off - profile.hideFocusOutline,
a toggle method that takes the signed-in user only - gets a class on body that
hides the outline and its halo for them and nobody else. The label is translated
into every language file (best effort; the low-confidence ones are listed in the
changelog).

Thanks to mimZD and xet7 !

Refs #6755,
2026-11-03 16:04:44 +02:00
Lauri Ojansivu f4b5025a7b Escape closes the inline add-list composer.
The composer opened after a list (or in an empty swimlane) stays open after a
list is created, so several can be added, and only its X closed it. Escape now
closes it at the level of an inlined form, with the same reset as the X, and not
on a click elsewhere.

Thanks to mimZD and xet7 !

Fixes #6865,
2026-11-03 15:39:24 +02:00
github-actions[bot] cdcf8ec39d Bump versions for v12.89 2026-11-03 13:35:18 +00:00
Lauri Ojansivu 739f9ed7a0 Prepare v12.89 release 2026-11-03 15:33:40 +02:00
Lauri Ojansivu f06d082e34 Merge remote-tracking branch 'origin/main' 2026-11-03 15:33:13 +02:00
Lauri Ojansivu 4036b16774 CHANGELOG: shortcuts run once on a non-Latin keyboard. 2026-11-03 15:30:41 +02:00
Lauri Ojansivu 0110e84ce5 Keyboard shortcuts run once on a non-Latin layout.
On a Farsi (or Cyrillic) keyboard the d shortcut opened the Due date popup and
closed it at a glance. keyboard.js re-dispatched every key typed on another
script as its Latin key, a leftover from Mousetrap; hotkeys-js resolves the key
from event.code itself, so the shortcut ran twice and the repeat clicked the
same control again, closing it. The re-dispatch is removed, and a test drives
real key events for Latin, Farsi and Cyrillic at the library.

Thanks to mimZD and xet7 !

Fixes #6840,
2026-11-03 15:30:39 +02:00
github-actions[bot] 00a50ae139 Bump versions for v12.88 2026-11-03 12:58:20 +00:00
Lauri Ojansivu c67a67bc9a Prepare v12.88 release 2026-11-03 14:57:20 +02:00
Lauri Ojansivu 0b8a7face3 CHANGELOG: every theme and chosen color is readable; padded REST labels. 2026-11-03 12:37:32 +02:00
Lauri Ojansivu 8b2114cd0f Themes: every board theme and every color a member chooses is readable.
Eight themes (nephritis, pumpkin, moderatepink, strongcyan, limegreen, relax,
corteza, modern) had white text on an accent at 2.1 to 4.3:1: their accents and
hover, selected and emphasis shades are darkened until it reads at 4.5:1. The
filter sidebar's headings, the clear themes' quiet text and cleanlight's
add-dependency link are darker. A color a member chooses is made legible: a
custom theme accent or slide end is darkened under the header's white text, the
text color is moved to read on the theme's surfaces and no longer reaches the
header, labels and primary buttons, a dependency line is drawn at 3:1, Kanri's
tag text is the better of dark and light, and a custom label, card, list or
swimlane color already derives its text. All 25 themes show no colour contrast
failure on the board, the sidebars, Card Settings, the card and its popups.

Thanks to Samunosuke and xet7 !
2026-11-03 12:37:10 +02:00
Lauri Ojansivu 0426c7741a Board labels: a padded name finds the stored label, in every way a label is added.
A label is looked up by name and color before one is added, and the lookup
compared the name as sent while the schema stores it trimmed: a padded ' Bug '
missed the stored 'Bug', and each repeat created another label with the same
name and color. The REST builder now trims, as the pull request did, and so
does the lookup itself, which the card's label form, the importers and the list
sync share. A legacy label stored with padding is still found.

Thanks to AhmedLukman and xet7 !
2026-11-03 12:37:10 +02:00
Lauri Ojansivu c2a28a9233 CHANGELOG: the dark board themes are readable again. 2026-11-03 11:42:09 +02:00
Lauri Ojansivu a6b10e3592 Dark themes: the quiet greys, Card Settings, popups and sidebars are readable again.
The accessibility audit made the quiet text greys dark for every theme, and
old rules never followed the dark themes: Card Settings drew white text on
light grey blocks, popup items, the sidebar's shortcuts row, quiet buttons and
sidebar labels were black on dark, and an !important list header beat each
theme's own. The quiet text is a variable with a light value on the dark
surfaces; Card Settings' sections, popup items and the other rules follow the
theme. axe-core finds no colour contrast failure in cleandark, moderndark, dark
and exodark on the board, sidebar, Card Settings, an opened card and the custom
fields popup.

Thanks to Samunosuke and xet7 !

Fixes #6863,
2026-11-03 11:41:58 +02:00
github-actions[bot] 1ac4d1b1f9 Bump versions for v12.87 2026-11-02 22:34:10 +00:00
Lauri Ojansivu 222492f313 Prepare v12.87 release 2026-11-03 00:33:10 +02:00
Lauri Ojansivu 4f95b10f0d Merge remote-tracking branch 'origin/main' 2026-11-03 00:26:43 +02:00
Lauri Ojansivu 86a18665f0 CHANGELOG: REST single-item GETs answer 404 when nothing is found. 2026-11-03 00:22:26 +02:00
Lauri Ojansivu 1ded34dfdd REST API: a single-item GET that finds nothing answers 404, not 200 with an empty body.
The scoped card GET (/api/boards/:boardId/lists/:listId/cards/:cardId) sent
200 with no body for a missing, wrong-list, wrong-board or archived card. The
list, swimlane, custom field, comment and integration GETs did the same, and
the user GET threw a TypeError for an unknown name. Each now answers 404 after
the board check, so a stranger still learns nothing about which ids exist.

Thanks to AhmedLukman and xet7 !
2026-11-03 00:22:24 +02:00
github-actions[bot] db791e1d40 Bump versions for v12.86 2026-11-02 17:03:48 +00:00
Lauri Ojansivu e8e22dc25e Prepare v12.86 release 2026-11-02 19:02:52 +02:00
Lauri Ojansivu 08d20930de Merge remote-tracking branch 'origin/main' 2026-11-02 18:56:24 +02:00
Lauri Ojansivu 10f244de77 CHANGELOG: daily jobs run once per installation. 2026-11-02 18:49:22 +02:00
Lauri Ojansivu 4e28465964 Daily jobs run once per installation: notification cleanup and the file integrity audit.
The notification cleanup ran at every start and daily on every server, reading
and rewriting the same users 24 times at a rollout; a notification added by
another server between a read and its write was lost. A run now claims a
document per job and day in dailyJobRuns, and the timer looks hourly for an
unclaimed day. The file integrity audit read 'lastRun', checked it, then wrote
it, so two servers could both scan; one atomic update now claims the run.

Continuous backup has no leader: that is documented as single-server, and the
lease it needs is not built, so #6861 item 3 stays open and this commit does
not close it.

Thanks to Raymond-Voogt and xet7 !
2026-11-02 18:49:19 +02:00
github-actions[bot] 2936a682ce Bump versions for v12.85 2026-11-02 15:27:08 +00:00
Lauri Ojansivu ab80aaf9a3 Prepare v12.85 release 2026-11-02 17:26:05 +02:00
Lauri Ojansivu 0fb943b6fb Merge remote-tracking branch 'origin/main' 2026-11-02 17:25:22 +02:00
Lauri Ojansivu 6d8b0c15ec CHANGELOG: a cleared connection pool is a lost connection; the board key backfill uses the key index. 2026-11-02 17:18:17 +02:00
Lauri Ojansivu abc23a7eda Database problems: a cleared connection pool is a lost connection; the board key backfill uses the key index.
collection2 wraps a failed write as 'ValidationError: Failed validation <driver
text>', and the driver's 'Connection pool for <host> was cleared' names a
replica set member that MONGO_URL does not list, so the schema rule matched.
The text is now a lost connection. The board key backfill looked for boards
without a key with an $or including an exists-false branch, a scan of every
board at every start of every server; one $in on the key index finds the same
boards.

Thanks to Raymond-Voogt and xet7 !

Fixes #6860,
2026-11-02 17:17:58 +02:00
Lauri Ojansivu d9aa210012 Accessibility: the results of the fixes in Audit.md, page by page. 2026-11-02 17:17:07 +02:00
Lauri Ojansivu 2e8e1e2ce2 CHANGELOG: the accessibility audit findings are fixed in the default theme. 2026-11-02 16:08:47 +02:00
Lauri Ojansivu c8bcc4817d Accessibility: fix the findings of the axe-core audit in the default theme.
The default theme's blue is #2573a7, so the header's white and #f2f2f2 text
passes WCAG AA; label colours, quiet greys and grey tiles have text that
passes too. The Starred menu, the user menu and initials avatars carry their
visible text in their accessible name. The card's Pomodoro field and the
comment and description editors have a name. A card section heading is an h3
holding a real button with aria-expanded, and its menu and layout toggle sit
beside it. The sign-in form is the main landmark with a level-one title, every
page has a level-one heading, and the shortcuts list is reachable by keyboard.

After the fixes axe-core finds no violation on 22 pages. Spec 45 runs the scan
and tests/accessibilityAuditFixes.test.cjs pins each fault by its shape.

Thanks to xet7 !
2026-11-02 16:08:32 +02:00
Lauri Ojansivu 2dd25c733e Accessibility: the result of an automated axe-core audit of seven pages.
Thanks to xet7 !
2026-11-02 15:03:54 +02:00
github-actions[bot] cb6c6533c5 Bump versions for v12.84 2026-11-02 12:45:02 +00:00
Lauri Ojansivu 292b804474 Prepare v12.84 release 2026-11-02 14:43:46 +02:00
Lauri Ojansivu 623fccf06e Merge remote-tracking branch 'origin/main' 2026-11-02 14:39:48 +02:00
Lauri Ojansivu cfd7ad2e5f Catch two unhandled rejections found by the wider promise lint.
The instance-editing hook looked up the board of an edit without a catch,
and the service worker registration had none.

Thanks to xet7 !
2026-11-02 14:37:12 +02:00
Lauri Ojansivu 762e5a9432 CHANGELOG: npm run lint:promises. 2026-11-02 14:30:59 +02:00
Lauri Ojansivu f1d2a497bc Lint for promises that are not awaited, handled or marked void.
npm run lint:promises runs typescript-eslint's no-floating-promises and
no-misused-promises over models/lib, server/lib and imports/lib, with type
information read from tsconfig.lint.json (nothing is type-checked). The
eleven findings were promises that never reject (their own catch) and now
say so with void, and three checks that used a promise as a boolean.

Thanks to xet7 !
2026-11-02 14:30:49 +02:00
Lauri Ojansivu 2f9c972d46 CHANGELOG: the code-quality findings. 2026-11-02 14:15:17 +02:00
Lauri Ojansivu 582a22c291 Fix GitHub code scanning code-quality findings.
Real faults: a card's original-position description formatted a promise
because it did not await the history; the attachment swipe distances and
two drag-scroll locals were undeclared globals; a file that cannot be read
during a GridFS upload left the upload open; api.py called exit() instead of
sys.exit(); a rules test redefined a property and a security-log test kept
an unreachable branch. Also 31 missing semicolons, unused imports in the
card details, exporters and attachment API, unused Python imports, and
comments on empty except clauses. The list-sync scanner test cut its source
at the last Meteor.startup, which the new index startup of #6855 had moved.

Thanks to GitHub CodeQL and xet7 !
2026-11-02 14:15:02 +02:00
github-actions[bot] d200234535 Bump versions for v12.83 2026-11-02 11:40:29 +00:00
Lauri Ojansivu 8548bae527 Prepare v12.83 release 2026-11-02 13:38:58 +02:00
Lauri Ojansivu d6b2d55c36 Merge remote-tracking branch 'origin/main' 2026-11-02 13:35:16 +02:00
Lauri Ojansivu f7e3533b13 CHANGELOG: LDAP account binding. 2026-11-02 13:32:03 +02:00
Lauri Ojansivu e91c14cd9a LDAP: a reused login name no longer opens the previous owner's account.
An account found by username or e-mail that records another directory
entry's identifier (same attribute, other value) is refused at login and
skipped by the import. An existing account now records its identifier and
its attribute, so LDAP_UNIQUE_IDENTIFIER_FIELD takes effect for it.
LDAP_EMAIL_MATCH_REQUIRE looks users up by username instead of _id, which
never matched. The background sync skips an account whose username matches
several entries instead of binding it to the first.

Thanks to Raymond-Voogt and xet7 !

Fixes #6859,
2026-11-02 13:31:40 +02:00
github-actions[bot] e7439e1d45 Bump versions for v12.82 2026-11-02 11:00:45 +00:00
Lauri Ojansivu 4a34b91ecf Prepare v12.82 release 2026-11-02 12:59:36 +02:00
Lauri Ojansivu d9984d2069 Merge remote-tracking branch 'origin/main' 2026-11-02 12:54:11 +02:00
Lauri Ojansivu e982282954 CHANGELOG: card numbers on a move, a restore and a set number. 2026-11-02 12:42:43 +02:00
Lauri Ojansivu e68938698c Card numbers: a card moved to another board, restored or given a number cannot take one another card has.
#6815 checked the number of every new card, but a card also enters a board's
numbering by an update: a move from the browser, which computed the
destination number from the cards it had loaded (often none, so 1), a restore
from the archive, and a number set by the REST API or an import. A before-update
hook now does what the insert hook does: a taken number is replaced by a fresh
one from the board's counter, a free one is kept and the counter raised past it.

Thanks to Raymond-Voogt and xet7 !

Fixes #6858,
2026-11-02 12:42:33 +02:00
github-actions[bot] 55299a5172 Bump versions for v12.81 2026-11-02 10:22:59 +00:00
Lauri Ojansivu 8bbc6fcda1 Prepare v12.81 release 2026-11-02 12:20:06 +02:00
Lauri Ojansivu 71463fb445 Merge remote-tracking branch 'origin/main' 2026-11-02 12:17:05 +02:00
Lauri Ojansivu b60a1e5a85 CHANGELOG: the database health probe reports a finding once. 2026-11-02 12:15:29 +02:00
Lauri Ojansivu 114e9ecd5a Database health: one server probes the database per period, so a finding is reported once.
Every server read the same MongoDB log and counters and recorded each
finding itself: one collection scan became up to 24 identical rows in Admin
Panel / Problems on 24 replicas. A probe now claims the period in a shared
document, and the state that compares one probe with the next lives there.
A claim that cannot be made at all does not stop the probe.

Thanks to Raymond-Voogt and xet7 !

Fixes #6857,
2026-11-02 12:15:09 +02:00
github-actions[bot] c6739fe452 Bump versions for v12.80 2026-11-02 10:01:31 +00:00
Lauri Ojansivu efd45c651e Prepare v12.80 release 2026-11-02 12:00:25 +02:00
Lauri Ojansivu fdc2c57b8f Merge remote-tracking branch 'origin/main' 2026-11-02 11:52:22 +02:00
Lauri Ojansivu 2c75b95908 CHANGELOG: minimatch 11.0.4 for GHSA-g3hv-75wg-m249. 2026-11-02 11:42:44 +02:00
Lauri Ojansivu a3630d96ce Update minimatch to 11.0.4, GHSA-g3hv-75wg-m249 (Dependabot alerts #180 and #182).
minimatch before 11.0.1 grows its glob parts without bound while it
preprocesses a pattern, then compares them pairwise. archiver (readdir-glob),
@typescript-eslint/typescript-estree and @babel/cli's glob used 10.2.6; they
now use the root minimatch 11.0.4.

Thanks to dependabot and xet7 !
2026-11-02 11:41:52 +02:00
github-actions[bot] db2aafe16c Bump versions for v12.79 2026-11-02 09:35:57 +00:00
Lauri Ojansivu c4b90d08a0 Prepare v12.79 release 2026-11-02 11:34:58 +02:00
Lauri Ojansivu cb5278ddb3 CHANGELOG: a collection-scan row says which query it is. 2026-11-02 11:31:54 +02:00
Lauri Ojansivu bc79de8c57 Database health: a collection scan row names the command, the operators per field and the queryHash.
A row said only which fields a slow query filtered on, which is not enough
to find the query in the code or to run explain(). It now shows the command
(for a slow getMore, the query it continues, instead of 'filter on nothing'),
the operators used on each field - never values - and MongoDB's queryHash.

Thanks to Raymond-Voogt and xet7 !

Fixes #6856,
2026-11-02 11:31:47 +02:00
Lauri Ojansivu 81714cd695 CHANGELOG: the schema re-check and due-date reminders run once. 2026-11-02 11:28:59 +02:00
Lauri Ojansivu d42369e96a Full collection scans run once, not at every start of every server; due-date reminders are sent once.
The startup schema re-check claims a run in _wekan_migration so one server
re-checks while the others skip (a claim older than 2 hours is taken over),
and a run that leaves work unresolved is retried once a day instead of at
every start. The daily due-date reminder scan is claimed per day in
dueReminderRuns, so 24 replicas no longer send 24 reminders per card.
A partial index on lists serves the 15-minute list sync scan, and a sparse
index on boards.dueReminderDays serves the due-date lookup.

Thanks to Raymond-Voogt and xet7 !

Fixes #6855,
2026-11-02 11:28:44 +02:00
Lauri Ojansivu efff590f74 CHANGELOG: the announcement shows on Home and All Boards. 2026-11-02 11:26:12 +02:00
Lauri Ojansivu 64b5762020 Announcement: shown on Home and every All Boards list, not only on a board.
The banner waited for membership of the current board, and Home, Remaining,
Starred, Templates and the other All Boards pages have none. It now shows to
every signed-in user there, and to the members of the open board on a board.
Signed-out visitors still do not see it.

Thanks to titver968 and xet7 !

Fixes #6854,
2026-11-02 11:25:57 +02:00
Lauri Ojansivu c29a59eb95 CHANGELOG: any year by number in the date picker. 2026-11-02 11:17:04 +02:00
Lauri Ojansivu dcaa316a01 Date picker popup: the year is a number field, any year by typing or stepping.
A list of years had to end somewhere. The year is now a whole number
(0 is 1 BC) found by jumping through the calendar in use, so thousands of
years back or ahead are one entry away, up to the range a date holds.

Thanks to brlin-tw and xet7 !

Fixes #6853,
2026-11-02 11:17:02 +02:00
Lauri Ojansivu 7721c5f655 CHANGELOG: year, month and day drop-downs in the date picker. 2026-11-02 11:10:08 +02:00
Lauri Ojansivu 492895200b Date picker popup: year, month and day drop-downs under the calendar.
The heading above the calendar shows the month and year only, not the
YYYY-MM-DD of the selected day. Under the calendar, year, month and day
drop-downs pick any past, present or future date, in every calendar
system.

Thanks to brlin-tw and xet7 !

Fixes #6853,
2026-11-02 11:09:34 +02:00
Lauri Ojansivu 1b346dc465 CHANGELOG: the date picker heading names the year. 2026-11-02 10:50:22 +02:00
Lauri Ojansivu 45c291b406 Date picker popup: the heading names the year as well as the month.
Thanks to brlin-tw and xet7 !

Fixes #6853,
2026-11-02 10:50:13 +02:00
Lauri Ojansivu c3af2a08a9 CHANGELOG: the macOS sandbox keychain and Claude Code token. 2026-11-02 10:16:03 +02:00
Lauri Ojansivu 21c94ce098 Fix macOS Sandbox for VSCodium.
Thanks to xet7 !
2026-11-02 10:14:47 +02:00
Lauri Ojansivu 2d8a28c2f6 Merge remote-tracking branch 'origin/main' 2026-11-02 09:44:15 +02:00
Lauri Ojansivu c58b2896d1 Fix macOS Sandbox for VSCodium.
Thanks to xet7 !
2026-11-02 09:43:32 +02:00
458 changed files with 6169 additions and 778 deletions

No files matched your search

+18
View File
@@ -0,0 +1,18 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module",
"project": [
"./tsconfig.lint.json"
]
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": "error"
}
}
+733
View File
@@ -124,6 +124,739 @@ its reason.
</details>
# v12.92 2026-11-03 WeKan ® release
**In short:** A missing **REST checklist** answers a JSON 404 instead of an
empty 500.
This release fixes the following bugs:
**REST API** - answers for things that are not there.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/d231df56fc">A missing checklist, or one under another card, answers a JSON 404 instead of an empty 500</a>. Thanks to AhmedLukman and xet7.</summary>
[\#6867](https://github.com/wekan/wekan/pull/6867). `GET
/api/boards/:boardId/cards/:cardId/checklists/:checklistId` answered `500` with
no body when the checklist did not exist or belonged to another card. It now
answers `404` with `Checklist not found or does not belong to the specified
card`, the text the update and delete routes already use; the board-access check
and the parent-card check are unchanged. `tests/restChecklistGet.test.cjs`
drives
the registered handler (found, missing, another card, another board, access
denied, and a failing lookup that must not become a false 404) and
`tests/playwright/specs/rest-checklist-not-found.e2e.js` reads the checklist
over REST and after a page reload.
</details>
Thanks to above GitHub users for their contributions and translators for their translations.
# v12.91 2026-11-03 WeKan ® release
**In short:** A **GitHub CodeQL** finding in a test is fixed, and the guard
against
replacing text with itself now also sees patterns with a `^` or `$` anchor.
This release has the following developer-tooling fix:
**Code scanning** - the tests that guard the tree.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/62da564a1e">A test no longer replaces getLabel with itself, and the identity-replacement guard knows anchored patterns</a>. Thanks to GitHub CodeQL and xet7.</summary>
Code scanning alert #600 (`js/identity-replacement`, CWE-116):
`tests/boardLabelLookup.test.cjs` cut the `getLabel` method out of
`models/boards.js` and then ran `.replace(/^getLabel/, 'getLabel')` on it, which
does nothing because the slice already starts at `getLabel`. The line is gone.
`tests/noIdentityReplacement.test.cjs` skipped every regular expression with an
anchor, so it could not see the shape; a leading `^` or a trailing `$` over
plain characters is now compared like any other, with samples of both for the
guard to detect and the existing samples that must not be reported.
</details>
Thanks to above GitHub users for their contributions and translators for their translations.
# v12.90 2026-11-03 WeKan ® release
**In short:** The login page can have its own **background color**, Member
Settings
has a per-member switch, **Show focus outline**, to hide the blue focus ring,
and
**Escape closes the add-list composer**.
This release adds the following new features:
**Member Settings** - what each member can set for themselves.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/144777ae6b">Change Settings has Show focus outline, to hide the blue focus ring for yourself</a>. Thanks to mimZD and xet7.</summary>
[\#6755](https://github.com/wekan/wekan/issues/6755). The blue ring WeKan draws
round the focused control is an accessibility feature (WCAG 2.4.7) and stays on
by
default, but it also shows after a click on a minicard, and a member asked
whether
it can be turned off. Change Settings now has **Show focus outline**, ticked
while
the ring shows. Unticking it stores `profile.hideFocusOutline` for the signed-in
member (a method that takes no user id from the client) and puts a class on the
page that hides the outline and its halo for that member only; field borders and
the colour a button takes on focus stay. The label is translated into every
language file; the translations of these were written without a native speaker
and
are the least certain: Akan, Aymara, Bambara, Tibetan, Breton, Cherokee,
Dzongkha,
Ewe, Fulah, Fijian, Guarani, Manx, Hausa, Hawaiian, Igbo, Inuktitut,
Kalaallisut,
Kashmiri, Cornish, Luganda, Ladin, Nahuatl, Neapolitan, Northern Ndebele,
Northern
Sotho, Chichewa, Oromo, Quechua, Kirundi, Aromanian, Kinyarwanda, Northern Sami,
Samoan, Shona, Swati, Southern Sotho, Tigrinya, Tigre, Klingon, Tswana, Tongan,
Tsonga, Venda, Volapük, Walamo, Wolof, Xhosa and Tamazight.
`tests/hideFocusOutline6755.test.cjs` and
`tests/playwright/specs/hide-focus-outline-6755.e2e.js` pin it, including that
one
member hiding the ring does not hide it for another.
</details>
**The login page** - how the sign-in page and the support page look.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/6b27764452">Admin Panel / Lay-out has a Login page background color, and the login text and the support page have a readable layout</a>. Thanks to Raymond-Voogt and xet7.</summary>
[\#6866](https://github.com/wekan/wekan/issues/6866). The login page stayed
grey whatever the Admin Panel said: the custom HTML fields only render after
sign-in and the text below the login logo is sanitised. The new setting
`loginBackgroundColor` takes a hex colour only (`#rgb` or `#rrggbb`); anything
else means "not set", so the value in the CSS custom property can never carry
CSS of its own. The text under the logo is white on a dark colour and black on a
light one. It is put on the page only while the sign-in layout is shown and
removed when it closes. The support page was text flush against the window edge
with no headings: it is now a centred white column of at most 860px, and the
text under the login logo gets the same column without a card of its own, both
with sized bold headings and space between paragraphs. The label is translated
into every language file. `tests/loginBackground6866.test.cjs` pins the colour
decision, that nothing but the validated value reaches the page, and the layout.
</details>
and fixes the following bugs:
**Lists** - the add-list composer.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/f4b5025a7b">Escape closes the inline add-list composer</a>. Thanks to mimZD and xet7.</summary>
[\#6865](https://github.com/wekan/wekan/issues/6865). The composer that opens after
a list (or in an empty swimlane) stays open once a list is created, so several
can
be added, and only its X closed it: Escape did nothing. Escape now closes it,
with
the same reset as the X, at the level of an inlined form, and a click elsewhere
still does not. `tests/addListEscape6865.test.cjs` and
`tests/playwright/specs/add-list-escape-6865.e2e.js` pin it before and after a
list
is created.
</details>
Thanks to above GitHub users for their contributions and translators for their translations.
# v12.89 2026-11-03 WeKan ® release
**In short:** Keyboard **shortcuts run once on a non-Latin keyboard**: `d` no
longer opens the Due date popup and closes it again on a Farsi layout.
This release fixes the following bugs:
**Keyboard shortcuts** - what a key does on each layout.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/0110e84ce5">A shortcut runs once on a Farsi, Cyrillic or other non-Latin keyboard</a>. Thanks to mimZD and xet7.</summary>
[\#6840](https://github.com/wekan/wekan/issues/6840). `d` opened the Due date
popup and closed it at a glance, "sometimes", for a reporter whose keyboard was
Farsi. `client/lib/keyboard.js` re-dispatched every key typed on another script
as its Latin key, a leftover from Mousetrap, which read the typed character.
hotkeys-js, which replaced it, resolves a key from `event.code` and `keyCode`
and so already ran the shortcut for the physical d key on any layout; the copy
ran
every shortcut a second time, the due date editor opened and the repeat clicked
the
same control again, closing it. The same doubling was behind Shift+D on a Latin
keyboard (#6750). The re-dispatch is gone. `tests/keyboardNonLatinOnce.test.cjs`
pins that no handler turns a typed character back into a key, and reads
hotkeys-js's own rule; `tests/playwright/specs/keyboard-nonlatin-once.e2e.js`
sends
real key events for Latin, Farsi and Cyrillic and counts one run each.
</details>
Thanks to above GitHub users for their contributions and translators for their translations.
# v12.88 2026-11-03 WeKan ® release
**In short:** **Every board theme** and **every color a member chooses** is
readable: the dark themes' **Card Settings**, popups and sidebars, the header of
eight themes, and a custom accent, text or label color made legible. A padded
**REST label** name no longer creates a second label.
This release fixes the following bugs:
**Dark themes** - cleandark, moderndark, dark and exodark.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/a6b10e3592">Card Settings, popups, sidebars and the quiet text are readable in the dark themes</a>. Thanks to Samunosuke and xet7.</summary>
[\#6863](https://github.com/wekan/wekan/issues/6863). Card Settings drew white
text on light grey blocks (1.07:1 in cleandark); popup items, the board
sidebar's Keyboard shortcuts row, "Add custom field" and its sister buttons and
the sidebar's labels were black on dark (1.18 to 2.19:1); and a list header's
`!important` light grey beat each theme's own and left its light text on it
(1.09:1 in moderndark, 1.27:1 in exodark). Part of it was new: the accessibility
audit made the quiet text greys dark for every theme, so the dark themes showed
dark grey on dark; the rest was older, rules that never followed the dark
themes. The quiet text is now a variable, `#595959` on the light themes and a
light grey where the surface is dark (cleandark, moderndark, and dark and
exodark on the board, the card and the sidebar); Card Settings' sections, its
muted text, the popup items, the quiet buttons, the shortcuts row, the sidebar
labels, dark's swimlane header and moderndark's selected minicard and primary
buttons follow the theme; and the list header's `!important` is gone.
`tests/playwright/specs/46-axe-theme-contrast.e2e.js` runs axe-core's colour
contrast rule on the four themes (the board, the sidebar, Card Settings, an
opened card and the custom fields popup) and finds nothing;
`tests/darkThemeContrast6863.test.cjs` pins each rule. The header colours of
relax, nephritis and modern, and a few lines of clearblue and cleanlight, are
still below 4.5 and are listed in the accessibility audit.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/8b2114cd0f">Every board theme, and every color a member chooses, is readable</a>. Thanks to Samunosuke and xet7.</summary>
[\#6863](https://github.com/wekan/wekan/issues/6863) asked to check the other
themes too, and the same scan was run on all 25. White text on the accent was
2.1 to 4.3:1 in nephritis, pumpkin, moderatepink, strongcyan, limegreen, relax,
corteza and modern, and their dark, hover and emphasis shades were as low: the
accents are darkened, with the same hue, until white text and the header's
`#f2f2f2` labels read at 4.5:1, and the hover and selected shades follow
(limegreen
is a forest green now, strongcyan a teal: white at 4.5:1 cannot be had on a
bright
accent). The colour-slide themes that end in the same colours read too. The
filter sidebar's headings (`#808080`, in every theme), the clear themes' quiet
text and cleanlight's add-dependency link are darker. No colour contrast failure
is left on the board, the sidebars, Card Settings, an opened card and its popups
in any theme. axe-core cannot measure text over a gradient, so the six
colour-slide headers are not covered by it.
A color a member chooses is one half of a pair, and the other half is worked out
(`models/lib/contrastColor.js`). A custom theme accent, or the two ends of a
custom
slide, is darkened under the header's white text until it reads, and the
picker's
preview shows the color that will be drawn. The text color from Member Settings
is
moved, with its hue, until it reads at 4.5:1 on the surfaces of the theme in
use,
says so when it changed, and no longer reaches the header bars, labels and
primary
buttons, which sit on a color of their own (`#626262` was 1.18:1 on the blue
header). A dependency line is drawn at 3:1 against the surface. Kanri's tag text
is the better of dark and light, which the brightness test it replaces was not.
A
custom label, card, list or swimlane color already derived its text, black or
white, and that is now pinned at 4.5:1 for every color.
`tests/playwright/specs/46-axe-theme-contrast.e2e.js` scans every theme and four
colors that do not read; `tests/customColorContrast.test.cjs` pins the rules.
</details>
**REST API** - board labels.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/0426c7741a">A padded label name finds the stored label instead of creating another</a>. Thanks to AhmedLukman and xet7.</summary>
[\#6864](https://github.com/wekan/wekan/pull/6864). A label is looked up by name
and color before one is added, and the lookup compared the name as it was sent
while the schema stores it trimmed, so a padded " Bug " missed the stored "Bug"
and every repeat of the request created another label with the same name and
color. The pull request trimmed the name in the REST builder; the lookup is
shared
by the card's label form, the importers and the list sync, so it trims too, and
a
legacy label stored with padding is still found. A different color, letter case
or inner space stays a different label. `tests/boardLabelLookup.test.cjs` pins
it.
</details>
Thanks to above GitHub users for their contributions and translators for their translations.
# v12.87 2026-11-03 WeKan ® release
**In short:** A **REST API** "get one" that finds nothing answers **404**
instead of 200 with an empty body.
This release fixes the following bugs:
**REST API** - what a request for one item answers when there is none.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/1ded34dfdd">A single-item GET that finds nothing answers 404, not an empty 200</a>. Thanks to AhmedLukman and xet7.</summary>
[\#6862](https://github.com/wekan/wekan/pull/6862). The scoped card GET
(`/api/boards/:boardId/lists/:listId/cards/:cardId`) sent HTTP 200 with no body
for a missing, wrong-list, wrong-board or archived card, which a client cannot
tell from a card with nothing in it. The pull request fixed that one route; the
same shape was in the list, swimlane, custom field, comment and integration
GETs, and the user GET threw a TypeError for an unknown name, which reached the
caller as a server error. Each now answers 404 ("Card not found", "List not
found" and so on). The board check stays first in each, so someone with no
access gets the same answer for an existing and a missing id. The global card
GET, which finds a card by id alone, already answered 404.
`tests/restSingleItem404.test.cjs` runs every one of these routes from its own
source with a cache that finds nothing, something, and a stranger, and fails if
any single-item GET route under server/ has no not-found branch.
</details>
Thanks to above GitHub users for their contributions and translators for their translations.
# v12.86 2026-11-02 WeKan ® release
**In short:** The daily **notification cleanup** and the **file integrity
audit**
run once for the whole installation, not once per server.
This release fixes the following bugs:
**Database load** - work that every server of a cluster repeated.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/4e28465964">The notification cleanup and the file integrity audit run once, not on every server</a>. Thanks to Raymond-Voogt and xet7.</summary>
[\#6861](https://github.com/wekan/wekan/issues/6861). The notification cleanup
ran at every start and daily on every server, reading and rewriting the same
18,000 users 24 times at a rollout, and a notification another server added
between a read and its write was lost. A run now claims a document per job and
day in the dailyJobRuns collection (a duplicate key means another server has
it), and the timer looks hourly for an unclaimed day, so the cleanup still
happens daily when no server restarts. The file integrity audit read its last
run, checked it, and then wrote it, so two servers whose hourly check fell at
the same moment both scanned every file; one atomic update, matching only while
the last run is older than the interval, now claims the run. Continuous backup
has no leader and every server writes its own stream into the same directory;
that needs a lease, which is not built, so the documentation now says it is for
one server, and item 3 of the issue stays open. `tests/dailyClaim.test.cjs`
pins the claims with 24 servers at once, that other database errors are not
swallowed, and that neither job runs unclaimed.
</details>
Thanks to above GitHub users for their contributions and translators for their translations.
# v12.85 2026-11-02 WeKan ® release
**In short:** The findings of the **accessibility audit** are fixed in the
default theme: header and label **colour contrast**, names that contain the
visible text, named form fields, card section headings that are real buttons,
and landmarks and headings on every page. axe-core now finds no violation on
22 pages. A cleared database connection pool is reported as a lost connection.
This release fixes the following bugs:
**Accessibility** - what an automated axe-core scan of the default theme found
(see Audit.md in docs/Features/General/Accessibility).
<details>
<summary><a href="https://github.com/wekan/wekan/commit/c8bcc4817d">axe-core finds no violation on 22 pages of the default theme</a>. Thanks to xet7.</summary>
The audit of 2026-11-02 found, on seven pages, colour contrast of the header
bars and the labels (WCAG 1.4.3), a Label in Name mismatch on the Starred menu,
the user menu and the avatars (2.5.3), form fields without a name (4.1.2),
headings that were buttons holding other controls (4.1.2), and missing
landmarks and headings. Each is fixed, and the scan, now on 22 pages (the
signed-out ones, All Boards and its lists, the other pages, the Admin Panel, a
board and an opened card), finds nothing.
The default theme's blue is `#2573a7` instead of `#2980b9` everywhere the
stylesheets, the accent table and the colour picker name it, so white and the
`#f2f2f2` button labels reach 5.14 and 4.59. The label colours green, red, sky,
magenta and gray and the Global search list chips have black text. The quiet
greys `#8c8c8c` and `#727479`, the "no items" text and the grey board tiles
are darker. The Starred menu, the user menu link and an avatar drawn as initials
have those letters or numbers in their accessible name. The Pomodoro field and
the comment, description and edit-comment editors have a name (the editor takes
a `labelKey`). A section heading of the card details is an `h3` holding a real
`button` with `aria-expanded`; the section menu and the layout toggle are
beside the button, and Enter, Space and a click on the heading still fold the
section. The sign-in and register form is the `main` landmark, its title is the
level-one heading and the empty logo `h1` is a spacer; every other page's title
in the header bar is the level-one heading, Activity, Import and Bookmarks have
one, and the shortcuts list can be reached by keyboard.
`tests/accessibilityAuditFixes.test.cjs` pins each fault by its shape (contrast
ratios are computed from the stylesheets), and
`tests/playwright/specs/45-axe-accessibility.e2e.js` runs axe-core on the pages
and fails on any violation. Other board colours and themes, the phone width,
dark mode, popups, and the views not scanned still need the same check.
</details>
**Database problems** - how Admin Panel -> Problems and the startup read the
database.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/abc23a7eda">A cleared connection pool is a lost connection, and the board key backfill uses the key index</a>. Thanks to Raymond-Voogt and xet7.</summary>
[\#6860](https://github.com/wekan/wekan/issues/6860). While a MongoDB pod
restarted, a method's "Connection pool for <host> was cleared" reached Admin
Panel -> Problems as "did not match its schema": collection2 wraps a failed
write as "ValidationError: Failed validation" plus the driver's text, so the
class name PoolClearedError was gone, and the host in the text is a replica set
member's name (wekan-mongodb-0.wekan-mongodb-internal in a StatefulSet) that
MONGO_URL does not list, so the "getaddrinfo ENOTFOUND" behind it did not count
as the database's. The text itself is now a lost connection, for the Problems
page and for both startup guards, and a message about a pool setting is not.
The board key backfill looked for boards without a key with an `$or` that
included an exists-false branch, a scan of every board at every start of every
server (32,600 boards in the report); `key: null` already matches a missing
field, so one `$in` on the `key` index finds the same boards, and the same
selector still guards the writes, so a key a board admin set meanwhile is never
replaced. `tests/databaseErrorsWrappedCause.test.cjs` and
`tests/boardKeyBackfill.test.cjs` pin both, and that no exists-false branch on
`key` is left.
</details>
Thanks to above GitHub users for their contributions and translators for their translations.
# v12.84 2026-11-02 WeKan ® release
**In short:** GitHub code scanning's **code-quality** findings are fixed: a
promise formatted as text, undeclared globals, an upload that hung on an
unreadable file, and the lint around them. `npm run lint:promises`
now finds promises that are not awaited or handled.
This release fixes the following bugs:
**Code quality** - findings of GitHub code scanning.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/582a22c291">The real faults among the code-quality findings, and the lint around them</a>. Thanks to GitHub CodeQL and xet7.</summary>
A card's original-position description formatted a promise, because it did
not await the history it reads. The attachment swipe distances and two
drag-scroll locals were undeclared globals. A file that cannot be read during
a GridFS upload now unlinks itself instead of leaving the upload open, since
`pipe()` does not pass a read error on. `api.py` called `exit()`, which is
missing when the site module is not loaded, and its usage branch never
exited; it uses `sys.exit()`. A rules test redefined a property and a
security-log test kept an unreachable branch. Besides these: 31 missing
semicolons, unused imports in the card details, the exporters and the
attachment API, unused Python imports, and a comment on each empty except
clause. The list-sync scanner test cut its source at the last
`Meteor.startup`, which the index startup of the schema-scan fix had moved;
it now cuts at the first one after the scanner. Two unhandled rejections are
caught as well: the instance-editing
hook's board lookup and the service worker registration.
`tests/codeqlFindingsRegression.test.cjs` pins the shape of each fault.
Findings in vendored bundles (the Office viewer, Jade, capnp-es, exceljs)
and style-only ones are left as they are.
</details>
and has the following developer-tooling fixes:
<details>
<summary><a href="https://github.com/wekan/wekan/commit/f1d2a497bc">npm run lint:promises finds promises that are not awaited, handled or marked void</a>. Thanks to xet7.</summary>
GitHub code scanning's "Missing await" finding was a real fault in
`models/cards.js` (a description formatted a promise), and a plain ESLint
cannot see that kind of mistake because it does not know which calls return a
promise. `npm run lint:promises` runs typescript-eslint's
`no-floating-promises` and `no-misused-promises` over `models/lib`,
`server/lib` and `imports/lib`, with the type information read from
`tsconfig.lint.json`; the code stays JavaScript and nothing is type-checked.
The eleven findings were promises that already catch their own errors, now
marked `void`, and three checks that used a promise as a boolean.
`tests/promiseLint.test.cjs` keeps the configuration in place and the tree
clean.
</details>
Thanks to above GitHub users for their contributions and translators for their translations.
# v12.83 2026-11-02 WeKan ® release
**In short:** An **LDAP** login with a reused login name no longer opens the
previous owner's account, and existing accounts record their directory
identifier.
This release fixes the following bugs:
**LDAP** - matching a directory entry to a WeKan account at login, import and
background sync.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/e91c14cd9a">A reused login name no longer opens the previous owner's account</a>. Thanks to Raymond-Voogt and xet7.</summary>
[\#6859](https://github.com/wekan/wekan/issues/6859). The login looked for the
account by the entry's unique identifier, then by username, then with
LDAP_EMAIL_MATCH_ENABLE by e-mail, and the last two did not check whether the
account already belonged to another directory entry. When a directory gave a
deleted employee's sAMAccountName to a new colleague, the new colleague logged
into the old account and saw its boards, cards and comments; two entries
sharing an e-mail address did the same, and so did the import with
LDAP_MERGE_EXISTING_USERS. An account that records an identifier of the same
attribute with a different value is now refused at login ("LDAP
authentication failed") and skipped by the import. An account without an
identifier, with an identifier of another attribute, or with an identifier
without its attribute (made before \#4654) is still accepted, so switching
LDAP_UNIQUE_IDENTIFIER_FIELD to objectGUID locks nobody out. When that field is
empty the identifier is the login name itself, so a reused name cannot be
detected: set it to objectGUID or entryUUID.
Three more faults in the same matching are fixed. An existing account never
recorded services.ldap.id and its attribute (only a new account did), so
accounts from before \#4654 never got an attribute and a changed
LDAP_UNIQUE_IDENTIFIER_FIELD never took effect; the login and the sync now
record both. LDAP_EMAIL_MATCH_REQUIRE looked the user up with _id equal to the
username, which never matches a random id, so every existing user's login
tried to create the account again; it uses the username. The background sync
bound an account without an identifier to the first of several entries with
its name; it now skips that account, neither updating nor disabling it.
`tests/ldapAccountBinding.test.cjs` pins each of these, and that no lookup by
_id equal to the username is left in the package and no name or e-mail match
reaches the login without the check. A refused login is not recorded in
Admin Panel -> Problems: the LDAP package cannot reach the application's
security log, and the new colleague who is refused is not an attacker.
</details>
Thanks to above GitHub users for their contributions and translators for their translations.
# v12.82 2026-11-02 WeKan ® release
**In short:** A card moved to another board, restored from the archive or given
a number no longer takes a **card number** another card on that board has.
This release fixes the following bugs:
**Card numbers** - the number each card has on its board.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/e68938698c">A moved, restored or renumbered card cannot take a number another card has</a>. Thanks to Raymond-Voogt and xet7.</summary>
[\#6858](https://github.com/wekan/wekan/issues/6858). Follow-up to
[\#6815](https://github.com/wekan/wekan/issues/6815), which checked the number
of every new card. A card also enters a board's numbering through an update: a
move to another board, where the browser computed the number from the
destination cards it had loaded, usually none, so the card got 1 or another
low number already taken; a restore from the archive, since an archived card
may share its number with an open one; and a number set by the REST API or an
import. Three card numbers used by more than one open card showed up again in
Admin Panel -> Problems -> Data health two days after the renumbering. A
before-update hook now does what the insert hook does: a number another card
on that board has is replaced by a fresh one from the board's counter, after
raising the counter to the board's highest number; a free number is kept and
the counter raised past it. The card itself never counts as taken, and
ordinary edits, moves within a board, archiving and cards without a number
are left alone. `tests/cardNumberUnique6815.test.cjs` pins which updates are
checked, the move, the restore and the free numbers.
</details>
Thanks to above GitHub users for their contributions and translators for their translations.
# v12.81 2026-11-02 WeKan ® release
**In short:** The **database health** probe reports a finding once, not once
per server.
This release fixes the following bugs:
**Database health** - the probe behind Admin Panel -> Problems -> Database.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/114e9ecd5a">One server probes the database per period, so a finding is reported once</a>. Thanks to Raymond-Voogt and xet7.</summary>
[\#6857](https://github.com/wekan/wekan/issues/6857). Every server read the
same MongoDB log and counters every five minutes and recorded each finding
itself, so one collection scan became up to 24 identical rows with 24
replicas - more than 400 warnings over 68 pages in the report. A probe now
claims the period in one shared document (collection databaseHealthProbe);
only the server that gets it probes, a duplicate key means another server has
it. The state that compares one probe with the next - log lines seen, the
scanned-documents counter, uptime, restarts, latencies - lives in that
document, so the next probe on whichever server continues where the last one
stopped. A server without the claim still checks its own data directory, and
a claim that cannot be made at all does not stop the probe.
`tests/databaseHealthProbeClaim.test.cjs` pins the claim, the shared state
and that each finding is reported once.
</details>
Thanks to above GitHub users for their contributions and translators for their translations.
# v12.80 2026-11-02 WeKan ® release
**In short:** **minimatch** is updated to 11.0.4 for GHSA-g3hv-75wg-m249, a
glob pattern that used up CPU and memory.
This release updates the following dependencies:
- **minimatch 10.2.6 → 11.0.4** — Dependabot alerts #180 and #182,
[GHSA-g3hv-75wg-m249](https://github.com/advisories/GHSA-g3hv-75wg-m249)
(CVE-2026-118764, CWE-405, moderate): before 11.0.1 a crafted glob pattern
made it grow its glob parts without bound and compare them pairwise, using
up CPU and memory. It came in through archiver, typescript-eslint and
Babel's CLI; all of them now use 11.0.4, and
`tests/minimatchGlobparts.test.cjs` fails if a 10.x copy returns
([commit](https://github.com/wekan/wekan/commit/a3630d96ce)).
Thanks to dependabot.
Thanks to above GitHub users for their contributions and translators for their translations.
# v12.79 2026-11-02 WeKan ® release
**In short:** A cluster of servers runs the **schema re-check** and the
**due-date reminders** once, and a slow-query warning says which query it is.
The site **announcement** shows on Home and All
Boards too. The **date picker** popup shows the year and has year, month and
day drop-downs under the calendar. The **macOS sandbox** for VSCodium no longer
asks for a keychain, starts from the checkout, and can carry a **Claude Code**
token so its login sticks.
This release fixes the following bugs:
**Database load** - work that every server of a cluster repeated.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/d42369e96a">The schema re-check and the due-date reminders run once, not on every server</a>. Thanks to Raymond-Voogt and xet7.</summary>
[\#6855](https://github.com/wekan/wekan/issues/6855). With 24 replicas on one
database every start of every server re-checked the schema, reading 1.1
million cards several times, and each server sent its own due-date reminder
for every card. The re-check now claims a run in the migration collection, so
one server does it and the others skip; a claim older than 2 hours, from a
server killed mid-run, is taken over. A run that leaves work it cannot do
(an attachment whose file is gone) is retried once a day instead of at every
start, and a clean run clears that. The daily reminder scan is claimed per day
in the dueReminderRuns collection, so the first server scans and the rest get
a duplicate key and skip. A partial index on lists serves the 15-minute list
sync scan, and a sparse index on boards.dueReminderDays the due-date lookup.
`tests/schemaUpgradeSteps.test.cjs` and `tests/dueScanClaim.test.cjs` pin the
claims, the stale takeover, the daily retry and that the scheduler claims
before it scans.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/bc79de8c57">A collection-scan row names the command, the operators per field and the queryHash</a>. Thanks to Raymond-Voogt and xet7.</summary>
[\#6856](https://github.com/wekan/wekan/issues/6856). Admin Panel -> Problems
-> Database named only the collection and the filtered fields, for example
"wekan.lists (filter on archived,syncSource.enabled,syncSource.type)", which
does not say which query it is, and a slow getMore read "filter on nothing".
The row now shows the command (find, distinct, aggregate, ...), for a getMore
the query it continues, each field with the operators used on it - for example
`archived:$exists` or `$nor[sort:$gte$lte]` - and MongoDB's queryHash, so
the query can be found in the code and with explain(). Values never reach the
row. `tests/databaseHealthScans.test.cjs` pins the shape, the queryHash, the
getMore origin and that no value leaks.
</details>
**Announcement** - the site announcement of Admin Panel -> Announcement.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/64b5762020">The announcement shows on Home and on every All Boards list</a>. Thanks to titver968 and xet7.</summary>
[\#6854](https://github.com/wekan/wekan/issues/6854). The banner waited
for the user to be a member of the current board, and Home, Remaining, Starred,
Templates and the other All Boards pages have no current board, so it showed
only on boards. It now shows to every signed-in user on those pages, and to the
members of the open board on a board page; a visitor who is not signed in still
does not see it (#1281).
`tests/announcementAudience.test.cjs` pins the rule and that no membership test
of a current board is left in front of the banner.
</details>
**Date picker** - the calendar in the Received, Start, Due and End date popups.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/45c291b406">The calendar heading shows the year as well as the month</a>. Thanks to brlin-tw and xet7.</summary>
[\#6853](https://github.com/wekan/wekan/issues/6853). The popup calendar
heading named only the month, so after the arrows had moved past December
there was no sign of which year was shown; the selected date beside it does
not change when the month does. The heading now reads for example "January
2027", in every calendar system. `tests/calendarPickerControls.test.cjs` pins
the year in the heading and that no picker mode leaves it out again, and the
Playwright date display spec expects it.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/dcaa316a01">Any year by number, and month and day drop-downs, under the calendar</a>. Thanks to brlin-tw and xet7.</summary>
[\\#6853](https://github.com/wekan/wekan/issues/6853). The heading above the
calendar now shows only the month and the year, no longer the YYYY-MM-DD of
the selected day. Under the calendar are three controls: a year field, where
any whole year can be typed or stepped to - thousands of years back or ahead,
0 being 1 BC and -2999 3000 BC, as far as a date reaches (about 275,000 years
either side of 1970) - a drop-down of the months of that year, and one of the
days of that month. A pick selects the date and shows its month; Enter in the
year field applies the year instead of saving the popup, and a value that is
not a year puts the field back. The months and days are read from the calendar
system in use, so they hold for the Islamic, Hebrew, Japanese and every other
system, including leap months and a short thirteenth month. A day that the new
month lacks falls to its last day. Calendars whose eras restart or switch the
count (Japanese, ROC, Coptic, Ethiopic) refuse a year no number can name.
`tests/calendarDropdowns.test.cjs` checks ten calendar systems and that no
date text is left in the heading.
</details>
and has the following developer-tooling fixes:
**The macOS sandbox** - running VSCodium and Claude Code in the sandbox on a
Mac.
<details>
<summary><a href="https://github.com/wekan/wekan/commit/c58b2896d1">VSCodium no longer asks for a keychain in the macOS sandbox</a>. Thanks to xet7.</summary>
The sandbox refuses `~/Library/Keychains`, so VSCodium's secret storage asked
for a keychain to keep "VSCodium Key" in, with a "Reset To Defaults" button
that would have replaced the login keychain. `sandbox.sh` now starts VSCodium
with `--use-mock-keychain`, so the real Keychain is never touched. The README
explains the price: extension secrets in `.tools/vscodium/user-data` are only
obfuscated. `tests/macosSandbox.test.cjs` covers the sandbox script.
</details>
<details>
<summary><a href="https://github.com/wekan/wekan/commit/21c94ce098">Claude Code stays signed in inside the macOS sandbox, and the sandbox starts in the checkout</a>. Thanks to xet7.</summary>
Claude Code keeps its login in the Keychain, which the sandbox cannot reach.
`sandbox.sh --claude-token` now saves a token from `claude setup-token` to
`.tools/secrets/claude-oauth-token` (read from the clipboard, line breaks
joined, a cut-short token refused) and passes it as `CLAUDE_CODE_OAUTH_TOKEN`,
with onboarding marked done and the extension's login prompt off. The script
also changes to the checkout first, because Finder starts it in the home
directory, which the sandbox refuses ("EPERM: process.cwd failed"), and starts
VSCodium with `nohup` so closing the launcher's Terminal does not close it.
</details>
Thanks to above GitHub users for their contributions and translators for their translations.
# v12.78 2026-11-02 WeKan ® release
**In short:** **CrossBoardExportBleed** and **UnassignedAttachBleed** are
+26 -32
View File
@@ -5,49 +5,43 @@ each one changed and the project status are in
Written by `node releases/changelog-short.mjs` from CHANGELOG-big.md:
make changes there.
# v12.78 2026-11-02 WeKan ® release
# v12.92 2026-11-03 WeKan ® release
**CrossBoardExportBleed** and **UnassignedAttachBleed** are
fixed: a checklist scope stays on its board, every export obeys Disable all
export and Anonymize export users, and an assigned-only member changes
attachments only on their own cards. **SAML** logins use **node-saml 5.2.0**,
and **continuous backup** no longer misses a change made right after it starts.
**Keyboard shortcuts** keep working after a popup closes, and the Rules page has
a way back to the board.
A missing **REST checklist** answers a JSON 404 instead of an
empty 500.
[All changes in v12.78](CHANGELOG-big.md#v1278-2026-11-02-wekan--release)
[All changes in v12.92](CHANGELOG-big.md#v1292-2026-11-03-wekan--release)
# v12.77 2026-10-31 WeKan ® release
# v12.91 2026-11-03 WeKan ® release
Two **node test guards** follow the movable popups and the date
picker of v12.76.
A **GitHub CodeQL** finding in a test is fixed, and the guard
against
replacing text with itself now also sees patterns with a `^` or `$` anchor.
[All changes in v12.77](CHANGELOG-big.md#v1277-2026-10-31-wekan--release)
[All changes in v12.91](CHANGELOG-big.md#v1291-2026-11-03-wekan--release)
# v12.76 2026-10-31 WeKan ® release
# v12.90 2026-11-03 WeKan ® release
Security features: a **password policy** with an optional
breached-password check, **Admin Panel / Problems / Password length**,
**compulsory two-factor authentication**, sign-out of **idle sessions** and of
every session after a password change, an **audit log** of what administrators
did, **HSTS**, a cross-site request guard, and **S3 encryption at rest**.
**Sign-up** works again; the date picker has **Today**, the whole card title
edits and **every popup can be moved**; and an old **Sandstorm** grain migrated
from MongoDB 3 shows its boards again.
The login page can have its own **background color**, Member
Settings
has a per-member switch, **Show focus outline**, to hide the blue focus ring,
and
**Escape closes the add-list composer**.
[All changes in v12.76](CHANGELOG-big.md#v1276-2026-10-31-wekan--release)
[All changes in v12.90](CHANGELOG-big.md#v1290-2026-11-03-wekan--release)
# v12.75 2026-10-30 WeKan ® release
# v12.89 2026-11-03 WeKan ® release
Changing a password signs the account out in every other browser,
and now a test proves it in two real browsers.
Keyboard **shortcuts run once on a non-Latin keyboard**: `d` no
longer opens the Due date popup and closes it again on a Farsi layout.
[All changes in v12.75](CHANGELOG-big.md#v1275-2026-10-30-wekan--release)
[All changes in v12.89](CHANGELOG-big.md#v1289-2026-11-03-wekan--release)
# v12.74 2026-10-30 WeKan ® release
# v12.88 2026-11-03 WeKan ® release
**Caddy** and **FerretDB**, the Go servers WeKan ships, no longer
open MPTCP listeners, which Linux kernel bug **CVE-2026-98432** is reachable
through. The real fix is a patched host kernel.
**Every board theme** and **every color a member chooses** is
readable: the dark themes' **Card Settings**, popups and sidebars, the header of
eight themes, and a custom accent, text or label color made legible. A padded
**REST label** name no longer creates a second label.
[All changes in v12.74](CHANGELOG-big.md#v1274-2026-10-30-wekan--release)
[All changes in v12.88](CHANGELOG-big.md#v1288-2026-11-03-wekan--release)
+1 -1
View File
@@ -38,7 +38,7 @@ FROM --platform=$BUILDPLATFORM debian:trixie AS bundle
ARG TARGETARCH
ARG TARGETVARIANT
ARG VERSION=12.78
ARG VERSION=12.92
ARG DEBIAN_FRONTEND=noninteractive
COPY --chmod=755 releases/check-telemetry.py /tmp/check-telemetry.py
+1 -1
View File
@@ -1,5 +1,5 @@
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
appVersion: "v12.78.0"
appVersion: "v12.92.0"
files:
userUploads:
- README.md
+43 -50
View File
@@ -8,13 +8,6 @@
# TODO:
# addcustomfieldtoboard: There is error: Settings must be object. So adding does not work yet.
try:
# python 3
from urllib.parse import urlencode
except ImportError:
# python 2
from urllib import urlencode
import json
import requests
import sys
@@ -163,7 +156,7 @@ If *nix: chmod +x api.py => ./api.py users
if arguments == 0:
print(syntax)
exit
sys.exit()
# TODO:
# print(" python3 api.py attachmentjson BOARDID ATTACHMENTID # One attachment as JSON base64")
@@ -329,7 +322,7 @@ if arguments == 8:
if new_card_id:
edcard = wekanurl + apiboards + boardid + s + l + s + listid + s + cs + s + new_card_id
put_data = {'labelIds': labelIds}
body = requests.put(edcard, data=put_data, headers=headers)
requests.put(edcard, data=put_data, headers=headers)
print("=== EDIT CARD ===\n")
body = requests.get(edcard, headers=headers)
data2 = body.text.replace('}', "}\n")
@@ -372,7 +365,7 @@ if arguments == 6:
print(edcard)
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
put_data = {'title': '{}'.format(newcardtitle), 'description': '{}'.format(newcarddescription)}
body = requests.put(edcard, data=put_data, headers=headers)
requests.put(edcard, data=put_data, headers=headers)
print("=== EDIT CARD ===\n")
body = requests.get(edcard, headers=headers)
data2 = body.text.replace('}',"}\n")
@@ -411,7 +404,7 @@ if arguments == 5:
print(edcard)
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
put_data = {'labelIds': labelIds}
body = requests.put(edcard, data=put_data, headers=headers)
requests.put(edcard, data=put_data, headers=headers)
print("=== ADD LABEL ===\n")
body = requests.get(edcard, headers=headers)
data2 = body.text.replace('}',"}\n")
@@ -437,7 +430,7 @@ if arguments == 5:
print(edcard)
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
put_data = {'color': '{}'.format(newcolor)}
body = requests.put(edcard, data=put_data, headers=headers)
requests.put(edcard, data=put_data, headers=headers)
print("=== EDIT CARD COLOR ===\n")
body = requests.get(edcard, headers=headers)
data2 = body.text.replace('}', "}\n")
@@ -456,7 +449,7 @@ if arguments >= 4:
print(edlist)
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
put_data = {'title': '{}'.format(newlisttitle)}
body = requests.put(edlist, data=put_data, headers=headers)
requests.put(edlist, data=put_data, headers=headers)
print("=== EDIT LIST ===\n")
body = requests.get(edlist, headers=headers)
data2 = body.text.replace('}', "}\n")
@@ -482,7 +475,7 @@ if arguments >= 4:
print(edlist)
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
put_data = {'color': '{}'.format(newcolor)}
body = requests.put(edlist, data=put_data, headers=headers)
requests.put(edlist, data=put_data, headers=headers)
print("=== EDIT LIST COLOR ===\n")
body = requests.get(edlist, headers=headers)
data2 = body.text.replace('}', "}\n")
@@ -1050,7 +1043,7 @@ if arguments >= 1:
if arguments < 6:
print("Usage: python3 api.py uploadattachment BOARDID SWIMLANEID LISTID CARDID FILEPATH [STORAGE_BACKEND]")
print("Storage backends: fs, gridfs, s3")
exit(1)
sys.exit(1)
boardid = sys.argv[2]
swimlaneid = sys.argv[3]
@@ -1064,7 +1057,7 @@ if arguments >= 1:
import os, mimetypes
if not os.path.isfile(filepath):
print(f"Error: File '{filepath}' not found")
exit(1)
sys.exit(1)
filename = os.path.basename(filepath)
file_type = mimetypes.guess_type(filepath)[0] or 'application/octet-stream'
params = {'boardId': boardid, 'swimlaneId': swimlaneid, 'listId': listid, 'cardId': cardid,
@@ -1092,7 +1085,7 @@ if arguments >= 1:
# ------- DOWNLOAD ATTACHMENT START -----------
if arguments < 3:
print("Usage: python3 api.py downloadattachment ATTACHMENTID OUTPUTPATH")
exit(1)
sys.exit(1)
attachmentid = sys.argv[2]
outputpath = sys.argv[3]
@@ -1121,7 +1114,7 @@ if arguments >= 1:
# ------- ATTACHMENT INFO START -----------
if arguments < 2:
print("Usage: python3 api.py attachmentinfo ATTACHMENTID")
exit(1)
sys.exit(1)
attachmentid = sys.argv[2]
@@ -1160,7 +1153,7 @@ if arguments >= 1:
# ------- LIST CARD ATTACHMENTS START -----------
if arguments < 5:
print("Usage: python3 api.py listcardattachments BOARDID SWIMLANEID LISTID CARDID")
exit(1)
sys.exit(1)
boardid = sys.argv[2]
swimlaneid = sys.argv[3]
@@ -1199,7 +1192,7 @@ if arguments >= 1:
# ------- COPY/MOVE ATTACHMENT START -----------
if arguments < 6:
print("Usage: python3 api.py copymoveattachment ATTACHMENTID TARGETBOARDID TARGETSWIMLANEID TARGETLISTID TARGETCARDID [copy|move]")
exit(1)
sys.exit(1)
attachmentid = sys.argv[2]
targetboardid = sys.argv[3]
@@ -1210,7 +1203,7 @@ if arguments >= 1:
if operation not in ['copy', 'move']:
print("Operation must be 'copy' or 'move'")
exit(1)
sys.exit(1)
# Prepare request data
request_data = {
@@ -1253,7 +1246,7 @@ if arguments >= 1:
# ------- DELETE ATTACHMENT START -----------
if arguments < 2:
print("Usage: python3 api.py deleteattachment ATTACHMENTID")
exit(1)
sys.exit(1)
attachmentid = sys.argv[2]
@@ -1322,7 +1315,7 @@ if arguments >= 1:
# ------- ADD BOARD MEMBER START -----------
if arguments < 3:
print("Usage: python3 api.py addboardmember BOARDID USERID ROLE")
exit(1)
sys.exit(1)
boardid = sys.argv[2]
userid = sys.argv[3]
role = sys.argv[4] if arguments > 3 else 'normal'
@@ -1339,7 +1332,7 @@ if arguments >= 1:
# ------- REMOVE BOARD MEMBER START -----------
if arguments < 3:
print("Usage: python3 api.py removeboardmember BOARDID USERID")
exit(1)
sys.exit(1)
boardid = sys.argv[2]
userid = sys.argv[3]
remove_member_url = wekanurl + apiboards + boardid + s + 'members' + s + userid + s + 'remove'
@@ -1354,7 +1347,7 @@ if arguments >= 1:
# ------- SET BOARD MEMBER ROLE START -----------
if arguments < 4:
print("Usage: python3 api.py setboardmemberrole BOARDID USERID ROLE")
exit(1)
sys.exit(1)
boardid = sys.argv[2]
userid = sys.argv[3]
role = sys.argv[4]
@@ -1370,7 +1363,7 @@ if arguments >= 1:
# ------- LIST BOARD DOMAINS START -----------
if arguments < 2:
print("Usage: python3 api.py boarddomains BOARDID")
exit(1)
sys.exit(1)
boardid = sys.argv[2]
domains_url = wekanurl + apiboards + boardid + s + 'domains'
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
@@ -1383,7 +1376,7 @@ if arguments >= 1:
# ------- ADD BOARD DOMAIN START -----------
if arguments < 3:
print("Usage: python3 api.py addboarddomain BOARDID DOMAIN")
exit(1)
sys.exit(1)
boardid = sys.argv[2]
domain = sys.argv[3]
add_domain_url = wekanurl + apiboards + boardid + s + 'domains'
@@ -1398,7 +1391,7 @@ if arguments >= 1:
# ------- REMOVE BOARD DOMAIN START -----------
if arguments < 3:
print("Usage: python3 api.py removeboarddomain BOARDID DOMAIN")
exit(1)
sys.exit(1)
boardid = sys.argv[2]
domain = sys.argv[3]
remove_domain_url = wekanurl + apiboards + boardid + s + 'domains' + s + domain
@@ -1413,7 +1406,7 @@ if arguments >= 1:
if arguments < 4:
print("Usage: python3 api.py {} BOARDID LISTID CARDID IDS".format(sys.argv[1]))
print("IDS = comma-separated userIds, or '' to clear")
exit(1)
sys.exit(1)
boardid = sys.argv[2]
listid = sys.argv[3]
cardid = sys.argv[4]
@@ -1435,7 +1428,7 @@ if arguments >= 1:
print("Usage: python3 api.py setcarddate BOARDID LISTID CARDID DATETYPE DATEVALUE")
print("DATETYPE: received, start, due, end")
print("DATEVALUE: ISO 8601, e.g. 2026-06-07T12:00:00.000Z")
exit(1)
sys.exit(1)
boardid = sys.argv[2]
listid = sys.argv[3]
cardid = sys.argv[4]
@@ -1453,7 +1446,7 @@ if arguments >= 1:
edcard = wekanurl + apiboards + boardid + s + l + s + listid + s + cs + s + cardid
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
put_data = {datemap[datetype]: datevalue}
body = requests.put(edcard, data=put_data, headers=headers)
requests.put(edcard, data=put_data, headers=headers)
print("=== SET CARD DATE {} ===\n".format(datetype.upper()))
print(body.text)
# ------- SET CARD DATE END -----------
@@ -1463,7 +1456,7 @@ if arguments >= 1:
if arguments < 4:
print("Usage: python3 api.py setcardlabels BOARDID LISTID CARDID LABELIDS")
print("LABELIDS = comma-separated labelIds, or '' to clear")
exit(1)
sys.exit(1)
boardid = sys.argv[2]
listid = sys.argv[3]
cardid = sys.argv[4]
@@ -1483,7 +1476,7 @@ if arguments >= 1:
# ------- MOVE CARD START -----------
if arguments < 7:
print("Usage: python3 api.py movecard BOARDID LISTID CARDID NEWBOARDID NEWSWIMLANEID NEWLISTID")
exit(1)
sys.exit(1)
boardid = sys.argv[2]
listid = sys.argv[3]
cardid = sys.argv[4]
@@ -1497,7 +1490,7 @@ if arguments >= 1:
'newSwimlaneId': newswimlaneid,
'newListId': newlistid,
}
body = requests.put(edcard, data=put_data, headers=headers)
requests.put(edcard, data=put_data, headers=headers)
print("=== MOVE CARD ===\n")
print(body.text)
# ------- MOVE CARD END -----------
@@ -1508,7 +1501,7 @@ if arguments >= 1:
print("Usage: python3 api.py bulkaddcards BOARDID LISTID AUTHORID SWIMLANEID CARDSJSONFILE")
print("CARDSJSONFILE = JSON file with a list of titles, e.g. [\"Card A\", \"Card B\"],")
print(" or a list of card objects [{\"title\": \"Card A\", \"description\": \"...\"}, ...]")
exit(1)
sys.exit(1)
boardid = sys.argv[2]
listid = sys.argv[3]
authorid = sys.argv[4]
@@ -1516,16 +1509,16 @@ if arguments >= 1:
cardsjsonfile = sys.argv[6] if arguments > 5 else None
if not cardsjsonfile:
print("CARDSJSONFILE is required")
exit(1)
sys.exit(1)
try:
with open(cardsjsonfile, 'r') as f:
raw_cards = json.load(f)
except Exception as e:
print("Error reading CARDSJSONFILE: {}".format(e))
exit(1)
sys.exit(1)
if not isinstance(raw_cards, list) or len(raw_cards) == 0:
print("CARDSJSONFILE must contain a non-empty JSON list")
exit(1)
sys.exit(1)
cards = []
for item in raw_cards:
if isinstance(item, str):
@@ -1534,7 +1527,7 @@ if arguments >= 1:
cards.append(item)
else:
print("Each card must be a string (title) or an object")
exit(1)
sys.exit(1)
bulk_url = wekanurl + apiboards + boardid + s + l + s + listid + s + cs + s + 'bulk'
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey), 'Content-Type': 'application/json'}
post_data = {'authorId': authorid, 'swimlaneId': swimlaneid, 'cards': cards}
@@ -1548,7 +1541,7 @@ if arguments >= 1:
if arguments < 3:
print("Usage: python3 api.py bulkdeletecards BOARDID CARDIDS")
print("CARDIDS = comma-separated cardIds")
exit(1)
sys.exit(1)
boardid = sys.argv[2]
cardids_raw = sys.argv[3]
cardids = [x for x in cardids_raw.split(',') if x]
@@ -1565,7 +1558,7 @@ if arguments >= 1:
if arguments < 5:
print("Usage: python3 api.py bulkcardlabels BOARDID CARDIDS ADDLABELIDS REMOVELABELIDS")
print("CARDIDS / ADDLABELIDS / REMOVELABELIDS = comma-separated, or '' for none")
exit(1)
sys.exit(1)
boardid = sys.argv[2]
cardids = [x for x in sys.argv[3].split(',') if x]
add_labelids = [x for x in sys.argv[4].split(',') if x]
@@ -1582,7 +1575,7 @@ if arguments >= 1:
# ------- CREATE LINKED CARD START -----------
if arguments < 6:
print("Usage: python3 api.py linkcard BOARDID LISTID SWIMLANEID AUTHORID LINKEDCARDID")
exit(1)
sys.exit(1)
boardid = sys.argv[2]
listid = sys.argv[3]
swimlaneid = sys.argv[4]
@@ -1600,7 +1593,7 @@ if arguments >= 1:
# ------- ADD/REMOVE CARD MEMBER / ASSIGNEE START -----------
if arguments < 5:
print("Usage: python3 api.py {} BOARDID LISTID CARDID USERID".format(sys.argv[1]))
exit(1)
sys.exit(1)
boardid = sys.argv[2]
listid = sys.argv[3]
cardid = sys.argv[4]
@@ -1620,14 +1613,14 @@ if arguments >= 1:
# ------- COPY CARD START -----------
if arguments < 4:
print("Usage: python3 api.py copycard BOARDID LISTID CARDID TOSWIMLANEID [TOBOARDID] [TOLISTID] [POSITION]")
exit(1)
sys.exit(1)
boardid = sys.argv[2]
listid = sys.argv[3]
cardid = sys.argv[4]
toswimlaneid = sys.argv[5] if arguments > 4 else None
if not toswimlaneid:
print("TOSWIMLANEID is required")
exit(1)
sys.exit(1)
toboardid = sys.argv[6] if arguments > 5 else boardid
tolistid = sys.argv[7] if arguments > 6 else listid
url = wekanurl + apiboards + boardid + s + l + s + listid + s + cs + s + cardid + s + 'copy'
@@ -1644,7 +1637,7 @@ if arguments >= 1:
# ------- COPY/MOVE SWIMLANE START -----------
if arguments < 3:
print("Usage: python3 api.py {} BOARDID SWIMLANEID [TOBOARDID] [POSITION]".format(sys.argv[1]))
exit(1)
sys.exit(1)
boardid = sys.argv[2]
swimlaneid = sys.argv[3]
toboardid = sys.argv[4] if arguments > 3 else boardid
@@ -1663,7 +1656,7 @@ if arguments >= 1:
# ------- COPY/MOVE LIST START -----------
if arguments < 3:
print("Usage: python3 api.py {} BOARDID LISTID [TOSWIMLANEID] [TOBOARDID] [POSITION]".format(sys.argv[1]))
exit(1)
sys.exit(1)
boardid = sys.argv[2]
listid = sys.argv[3]
toswimlaneid = sys.argv[4] if arguments > 3 else None
@@ -1702,7 +1695,7 @@ if arguments >= 1:
# ------- GET CARD SETTINGS START -----------
if arguments < 2:
print("Usage: python3 api.py getcardsettings BOARDID")
exit(1)
sys.exit(1)
boardid = sys.argv[2]
url = wekanurl + apiboards + boardid + s + 'cardSettings'
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
@@ -1717,7 +1710,7 @@ if arguments >= 1:
if arguments < 4:
print("Usage: python3 api.py setcardsetting BOARDID KEY VALUE")
print("e.g. python3 api.py setcardsetting BOARDID allowsDueDate true")
exit(1)
sys.exit(1)
boardid = sys.argv[2]
key = sys.argv[3]
value = sys.argv[4]
@@ -1945,7 +1938,7 @@ if arguments >= 3 and sys.argv[1] == 'uploadbackground':
response = requests.post(url, headers=headers, params=params, data=f)
except Exception as e:
print(f"Error reading file: {e}")
exit(1)
sys.exit(1)
print(response.text)
if arguments >= 3 and sys.argv[1] == 'downloadbackground':
+4 -1
View File
@@ -62,7 +62,10 @@ try {
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
const swPath = new URL('pwa-service-worker.js', Meteor.absoluteUrl()).pathname;
navigator.serviceWorker.register(swPath);
navigator.serviceWorker.register(swPath).catch(error => {
// No service worker (a private window, an old browser): the app works without the offline cache.
if (process.env.DEBUG === 'true') console.warn('service worker registration failed:', error);
});
});
}
+1 -1
View File
@@ -53,7 +53,7 @@
background-color: #fff;
border: 0;
box-shadow: 0 1px 2px rgba(0,0,0,0.23);
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
height: 36px;
margin-block: 4px 6px;
margin-inline: 0 4px;
+2 -2
View File
@@ -15,7 +15,7 @@ template(name="commentForm")
a.js-cancel-reply(title="{{_ 'close' }}")
i.fa.fa-times-thin
form.js-new-comment-form
+editor(class="js-new-comment-input")
+editor(class="js-new-comment-input" labelKey="comment")
| {{getUnsavedValue 'cardComment' currentCard._id}}
.add-controls
button.primary.confirm.clear.js-add-comment(type="submit") {{_ 'comment'}}
@@ -40,7 +40,7 @@ template(name="comment")
= parentComment.text
+inlinedForm(classNames='js-edit-comment')
+editor(autofocus=true)
+editor(autofocus=true labelKey="comment")
= text
.edit-controls
button.primary(type="submit" title="{{_ 'edit'}}" aria-label="{{_ 'edit'}}")
+203 -97
View File
@@ -6,9 +6,9 @@
/* Ordinary solid-colour themes. */
.board-color-nephritis {
--board-theme-accent: #27ae60;
--board-theme-dark: #1f8b4d;
--board-theme-hover: #2cc66d;
--board-theme-accent: #217d49;
--board-theme-dark: #1c6c3f;
--board-theme-hover: #1f7645;
--board-theme-emphasis: #ae2775;
--board-theme-emphasis-hover: #9d2369;
--board-theme-emphasis-close: #8b1f5e;
@@ -36,7 +36,10 @@
}
.board-color-belize {
--board-theme-accent: #2980b9;
/* The default theme. The earlier blue #2980b9 gave the header's white text a ratio of 4.30 and
its #f2f2f2 button labels 3.84, below the 4.5 WCAG 2.1 AA (1.4.3) asks of
small text; this blue keeps the hue and gives 5.14 and 4.59. */
--board-theme-accent: #2573a7;
--board-theme-dark: #216694;
--board-theme-hover: #2e90d0;
--board-theme-emphasis: #b96229;
@@ -47,7 +50,7 @@
--board-theme-selected-hover-bg: #f8fbfd;
--board-theme-bright: #459cd6;
--board-theme-canvas-scrollbar: #2573a7f2;
--board-theme-sidebar-scrollbar: #2980b9f2;
--board-theme-sidebar-scrollbar: #2573a7f2;
}
.board-color-wisteria {
@@ -81,12 +84,12 @@
}
.board-color-pumpkin {
--board-theme-accent: #e67e22;
--board-theme-dark: #be6415;
--board-theme-hover: #e98b38;
--board-theme-emphasis: #228ae6;
--board-theme-emphasis-hover: #187dd5;
--board-theme-emphasis-close: #156fbe;
--board-theme-accent: #a75919;
--board-theme-dark: #904d15;
--board-theme-hover: #9d5417;
--board-theme-emphasis: #196ec2;
--board-theme-emphasis-hover: #1765b2;
--board-theme-emphasis-close: #155ca3;
--board-theme-checklist-bg: #f9e5d1;
--board-theme-selected-bg: #fdf2e9;
--board-theme-selected-hover-bg: #fefbf8;
@@ -96,12 +99,12 @@
}
.board-color-moderatepink {
--board-theme-accent: #cd5a91;
--board-theme-dark: #b53773;
--board-theme-hover: #d26b9c;
--board-theme-emphasis: #5acd96;
--board-theme-emphasis-hover: #43c688;
--board-theme-emphasis-close: #37b579;
--board-theme-accent: #bd3a79;
--board-theme-dark: #a33268;
--board-theme-hover: #b23772;
--board-theme-emphasis: #217b54;
--board-theme-emphasis-hover: #1e714d;
--board-theme-emphasis-close: #1c6747;
--board-theme-checklist-bg: #f4dde8;
--board-theme-selected-bg: #faeef4;
--board-theme-selected-hover-bg: #fefafc;
@@ -111,9 +114,9 @@
}
.board-color-strongcyan {
--board-theme-accent: #00aecc;
--board-theme-dark: #008ba3;
--board-theme-hover: #00c8eb;
--board-theme-accent: #00798d;
--board-theme-dark: #006879;
--board-theme-hover: #007285;
--board-theme-emphasis: #cc1e00;
--board-theme-emphasis-hover: #b81b00;
--board-theme-emphasis-close: #a31800;
@@ -126,12 +129,12 @@
}
.board-color-limegreen {
--board-theme-accent: #4bbf6b;
--board-theme-dark: #389d54;
--board-theme-hover: #5dc57a;
--board-theme-emphasis: #bf4b9f;
--board-theme-emphasis-hover: #b13f91;
--board-theme-emphasis-close: #9d3881;
--board-theme-accent: #277c48;
--board-theme-dark: #226b3e;
--board-theme-hover: #257544;
--board-theme-emphasis: #b34093;
--board-theme-emphasis-hover: #a53b87;
--board-theme-emphasis-close: #96367b;
--board-theme-checklist-bg: #daf4de;
--board-theme-selected-bg: #edf9f0;
--board-theme-selected-hover-bg: #fafdfb;
@@ -141,12 +144,12 @@
}
.board-color-corteza {
--board-theme-accent: #568ba2;
--board-theme-dark: #456f82;
--board-theme-hover: #6597ad;
--board-theme-emphasis: #a26d56;
--board-theme-emphasis-hover: #92624d;
--board-theme-emphasis-close: #825745;
--board-theme-accent: #4a738a;
--board-theme-dark: #406377;
--board-theme-hover: #466c82;
--board-theme-emphasis: #94614f;
--board-theme-emphasis-hover: #885949;
--board-theme-emphasis-close: #7c5142;
--board-theme-checklist-bg: #dce6ec;
--board-theme-selected-bg: #eef3f6;
--board-theme-selected-hover-bg: #fafcfc;
@@ -901,10 +904,10 @@ THEME - Relax
.board-list li.board-color-relax,
.board-color-relax .setting-content .content-body .side-menu ul li.active,
.board-color-relax .setting-content .content-body .side-menu ul li.active:hover {
background-color: #27ae61;
background-color: #217d4a;
}
.board-color-relax .is-selected .minicard {
border-inline-start: 3px solid #27ae61;
border-inline-start: 3px solid #217d4a;
}
.board-color-relax .minicard {
border-radius: 7px;
@@ -915,17 +918,17 @@ THEME - Relax
.board-color-relax input[type=submit].primary,
.board-color-relax .sidebar .sidebar-content .sidebar-btn,
.board-color-relax .selected-calendar-picker button.primary {
background-color: #1f8b4e;
background-color: #1c6c40;
border-radius: 7px;
}
.board-color-relax.pop-over .pop-over-list li a:not(.disabled):hover,
.board-color-relax .sidebar .sidebar-content .sidebar-btn:hover,
.board-color-relax .sidebar-list li a:hover {
background-color: #2cc66f;
background-color: #1f7646;
}
.board-color-relax#header ul li.current,
.board-color-relax#header-quick-access ul li.current {
border-bottom: 2px solid #2cc66f;
border-bottom: 2px solid #1f7646;
}
.board-color-relax#header-quick-access {
color: #fff;
@@ -941,14 +944,14 @@ THEME - Relax
background: #8b1f5d;
}
.board-color-relax .materialCheckBox.is-checked {
border-bottom: 2px solid #27ae61;
border-right: 2px solid #27ae61;
border-bottom: 2px solid #217d4a;
border-right: 2px solid #217d4a;
}
.board-color-relax .checklist-progress-bar {
background-color: #d4f1dd !important;
}
.board-color-relax .checklist-progress-bar .checklist-progress {
background-color: #27ae61 !important;
background-color: #217d4a !important;
}
.board-color-relax .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard {
background: #e7faef;
@@ -957,17 +960,17 @@ THEME - Relax
background: #f8fdfa;
}
.board-color-relax .toggle-label:after {
background-color: #1f8b4e;
background-color: #1c6c40;
}
.board-color-relax .toggle-switch:checked ~ .toggle-label {
background-color: #3dd37e;
}
.board-color-relax .toggle-switch:checked ~ .toggle-label:after {
background-color: #1f8b4e;
background-color: #1c6c40;
}
@media screen and (max-width: 800px) {
.board-color-relax.pop-over .header {
background: #27ae61;
background: #217d4a;
color: #fff;
}
}
@@ -1554,7 +1557,7 @@ THEME - Clear Blue
.board-backgrounds-list .board-color-clearblue.background-box,
.public-board-row.board-color-clearblue,
.board-list li.board-color-clearblue {
background: linear-gradient(180deg, #499bea 0%, #00aecc 100%);
background: linear-gradient(180deg, #499bea 0%, #00798d 100%);
}
.board-color-clearblue .is-selected .minicard {
border-inline-start: 3px solid #499bea;
@@ -1566,7 +1569,7 @@ THEME - Clear Blue
}
.board-color-clearblue#header ul li.current,
.board-color-clearblue#header-quick-access ul li.current {
border-bottom: 4px solid #00c8eb;
border-bottom: 4px solid #007285;
}
.board-color-clearblue#header-quick-access {
color: #fff;
@@ -1585,14 +1588,14 @@ THEME - Clear Blue
.board-color-clearblue#header-quick-access,
.board-color-clearblue .setting-content .content-body .side-menu ul li.active,
.board-color-clearblue .setting-content .content-body .side-menu ul li.active:hover {
background-color: #00aecc;
border-bottom: 1px solid #008ba3;
background-color: #00798d;
border-bottom: 1px solid #006879;
border-top: 1px solid #00687a;
}
/* clearblue's bar is a colour SLIDE, and the slide has to be on the bar that is
actually on screen. It was only ever on `#header #header-main-bar` - the
inner element of the SECOND bar - because that is where the theme used to
live. The first bar got the flat `background-color: #00aecc` from the rule
live. The first bar got the flat `background-color: #00798d` from the rule
above instead, which is exactly strongcyan's colour: picking clearblue
painted the header strongcyan.
`#header-quick-access` has no inner bar of its own, so the slide goes
@@ -1601,7 +1604,7 @@ THEME - Clear Blue
The All Boards left menu's selected row is named here too. Every other theme
paints that row through `--theme-accent`, which is one colour and is exactly
right for them; a slide is not a colour, so the variable cannot carry it and
clearblue's row came out flat #00aecc while the Admin Panel's row beside it
clearblue's row came out flat #00798d while the Admin Panel's row beside it
slid. The Admin Panel's row has always been named in this rule; this is the
All Boards row joining it. docs/Features/General/Theme/Change-Color.md */
.board-color-clearblue#header #header-main-bar,
@@ -1610,13 +1613,13 @@ THEME - Clear Blue
.board-color-clearblue .setting-content .content-body .side-menu ul li.active:hover,
.board-color-clearblue .boards-left-menu .menu-item.active a,
.board-color-clearblue .boards-left-menu .menu-item.active a:hover {
background: linear-gradient(180deg, #499bea 0%, #00aecc 100%);
background: linear-gradient(180deg, #499bea 0%, #00798d 100%);
}
.board-color-clearblue#header #header-main-bar p {
margin-bottom: 6px;
}
.board-color-clearblue#header #header-main-bar .board-header-btn.emphasis {
background: #00c8eb;
background: #007285;
}
.board-color-clearblue#header #header-main-bar .board-header-btn.emphasis:hover,
.board-color-clearblue#header #header-main-bar .board-header-btn.emphasis .board-header-btn-close {
@@ -1648,7 +1651,7 @@ THEME - Clear Blue
background-color: #197ddc;
}
.board-color-clearblue.board-wrapper {
background: linear-gradient(135deg, #499bea 0%, #00aecc 100%);
background: linear-gradient(135deg, #499bea 0%, #00798d 100%);
}
.board-color-clearblue .swimlane {
background: none;
@@ -1740,7 +1743,7 @@ THEME - Clear Blue
}
.board-color-clearblue .quiet,
.board-color-clearblue .quiet a {
color: rgba(0,0,0,0.4);
color: rgba(0,0,0,0.68);
}
.board-color-clearblue .list-header .list-header-watch-icon {
color: rgba(0,0,0,0.5);
@@ -1767,7 +1770,7 @@ THEME - Clear Blue
}
.board-color-clearblue .sidebar-shadow {
box-shadow: none;
border-inline-start: 9px solid #00aecc;
border-inline-start: 9px solid #00798d;
}
.board-color-clearblue .is-open .sidebar-shadow {
box-shadow: -10px 0 8px rgba(0,0,0,0.3);
@@ -1833,7 +1836,7 @@ THEME - Clear Blue
/* Apply scrollbar to sidebar content*/
.board-color-clearblue .sidebar .sidebar-content {
scrollbar-color: #00aecc #ffffff00;
scrollbar-color: #00798d #ffffff00;
}
/* Remove margins in between columns/fix spacing */
@@ -1984,10 +1987,10 @@ THEME - Modern
.board-list li.board-color-modern,
.board-color-modern .setting-content .content-body .side-menu ul li.active,
.board-color-modern .setting-content .content-body .side-menu ul li.active:hover {
background-color: #2a80b8;
background-color: #2372a4;
}
.board-color-modern .is-selected .minicard {
border-inline-start: 3px solid #2a80b8;
border-inline-start: 3px solid #2372a4;
}
.board-color-modern .minicard {
border-radius: 7px;
@@ -1998,40 +2001,40 @@ THEME - Modern
.board-color-modern input[type=submit].primary,
.board-color-modern .sidebar .sidebar-content .sidebar-btn,
.board-color-modern .selected-calendar-picker button.primary {
background-color: #226693;
background-color: #1e628d;
border-radius: 7px;
}
.board-color-modern.pop-over .pop-over-list li a:not(.disabled):hover,
.board-color-modern .sidebar .sidebar-content .sidebar-btn:hover,
.board-color-modern .sidebar-list li a:hover {
background-color: #2f90cf;
background-color: #216b9a;
}
.board-color-modern#header ul li.current,
.board-color-modern#header-quick-access ul li.current {
border-bottom: 2px solid #2f90cf;
border-bottom: 2px solid #216b9a;
}
.board-color-modern#header-quick-access {
color: #fff;
}
.board-color-modern#header #header-main-bar .board-header-btn.emphasis {
background: #b8622a;
background: #a45b23;
}
.board-color-modern#header #header-main-bar .board-header-btn.emphasis:hover,
.board-color-modern#header #header-main-bar .board-header-btn.emphasis .board-header-btn-close {
background: #a65826;
background: #975420;
}
.board-color-modern#header #header-main-bar .board-header-btn.emphasis:hover .board-header-btn-close {
background: #934e22;
background: #8a4c1d;
}
.board-color-modern .materialCheckBox.is-checked {
border-bottom: 2px solid #2a80b8;
border-right: 2px solid #2a80b8;
border-bottom: 2px solid #2372a4;
border-right: 2px solid #2372a4;
}
.board-color-modern .checklist-progress-bar {
background-color: #d1e7f5 !important;
}
.board-color-modern .checklist-progress-bar .checklist-progress {
background-color: #2a80b8 !important;
background-color: #2372a4 !important;
}
.board-color-modern .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard {
background: #e8f3fa;
@@ -2040,17 +2043,17 @@ THEME - Modern
background: #f8fbfd;
}
.board-color-modern .toggle-label:after {
background-color: #226693;
background-color: #1e628d;
}
.board-color-modern .toggle-switch:checked ~ .toggle-label {
background-color: #469cd5;
}
.board-color-modern .toggle-switch:checked ~ .toggle-label:after {
background-color: #226693;
background-color: #1e628d;
}
@media screen and (max-width: 800px) {
.board-color-modern.pop-over .header {
background: #2a80b8;
background: #2372a4;
color: #fff;
}
}
@@ -2379,7 +2382,7 @@ THEME - Modern Dark
.board-color-moderndark button[type=submit].primary,
.board-color-moderndark .board-color-modern input[type=submit].primary,
.board-color-moderndark .selected-calendar-picker button.primary {
background-color: #777;
background-color: #6e6e6e;
border-radius: 7px;
}
.board-color-moderndark .toggle-switch:checked~.toggle-label {
@@ -3012,7 +3015,15 @@ THEME - Exodark
.pop-over.board-color-exodark {
background: #2b2b2b;
color: #fff;
--wekan-quiet-text: #b4b4b4;
/* Card Settings opens in this popup: dark sections for its white text. */
--card-settings-section-bg: rgba(255, 255, 255, 0.08);
--card-settings-helper-bg: #3a3a46;
--card-settings-notch-bg: #2c3340;
}
/* The popup's own links keep the light popup's black otherwise: black on
#2b2b2b was 1.48:1 (#6863). */
.pop-over.board-color-exodark .header {
background: #2b2b2b;
color: #fff;
@@ -3791,7 +3802,7 @@ THEME - Clean Light
font-size: calc(16px * var(--wekan-ui-font-scale, 1));
font-weight: 400;
line-height: calc(24px * var(--wekan-ui-font-scale, 1));
color: rgba(10, 10, 20, 0.5);
color: rgba(10, 10, 20, 0.7);
}
.board-color-cleanlight .card-details-header,
@@ -4614,7 +4625,7 @@ THEME - Clean Light
/* ===============
THEME - cleargreen
A colour SLIDE, like clearblue: #8ad59f down to #4bbf6b.
A colour SLIDE, like clearblue: #8ad59f down to #277c48.
The base is limegreen's own colour, so the flat theme and the slide theme are
the same colour family and a reader picking either gets what they expected.
@@ -4631,7 +4642,7 @@ docs/Features/General/Theme/Change-Color.md
.board-backgrounds-list .board-color-cleargreen.background-box,
.public-board-row.board-color-cleargreen,
.board-list li.board-color-cleargreen {
background: linear-gradient(180deg, #8ad59f 0%, #4bbf6b 100%);
background: linear-gradient(180deg, #8ad59f 0%, #277c48 100%);
}
.board-color-cleargreen .is-selected .minicard {
border-inline-start: 3px solid #8ad59f;
@@ -4658,7 +4669,7 @@ docs/Features/General/Theme/Change-Color.md
.board-color-cleargreen#header-quick-access,
.board-color-cleargreen .setting-content .content-body .side-menu ul li.active,
.board-color-cleargreen .setting-content .content-body .side-menu ul li.active:hover {
background-color: #4bbf6b;
background-color: #277c48;
border-bottom: 1px solid #3c9956;
border-top: 1px solid #2d7340;
}
@@ -4669,7 +4680,7 @@ docs/Features/General/Theme/Change-Color.md
.board-color-cleargreen .setting-content .content-body .side-menu ul li.active:hover,
.board-color-cleargreen .boards-left-menu .menu-item.active a,
.board-color-cleargreen .boards-left-menu .menu-item.active a:hover {
background: linear-gradient(180deg, #8ad59f 0%, #4bbf6b 100%);
background: linear-gradient(180deg, #8ad59f 0%, #277c48 100%);
}
.board-color-cleargreen#header #header-main-bar p {
margin-bottom: 6px;
@@ -4707,7 +4718,7 @@ docs/Features/General/Theme/Change-Color.md
background-color: #42a85e;
}
.board-color-cleargreen.board-wrapper {
background: linear-gradient(135deg, #8ad59f 0%, #4bbf6b 100%);
background: linear-gradient(135deg, #8ad59f 0%, #277c48 100%);
}
.board-color-cleargreen .swimlane {
background: none;
@@ -4799,7 +4810,7 @@ docs/Features/General/Theme/Change-Color.md
}
.board-color-cleargreen .quiet,
.board-color-cleargreen .quiet a {
color: rgba(0,0,0,0.4);
color: rgba(0,0,0,0.68);
}
.board-color-cleargreen .list-header .list-header-watch-icon {
color: rgba(0,0,0,0.5);
@@ -4826,7 +4837,7 @@ docs/Features/General/Theme/Change-Color.md
}
.board-color-cleargreen .sidebar-shadow {
box-shadow: none;
border-inline-start: 9px solid #4bbf6b;
border-inline-start: 9px solid #277c48;
}
.board-color-cleargreen .is-open .sidebar-shadow {
box-shadow: -10px 0 8px rgba(0,0,0,0.3);
@@ -4891,7 +4902,7 @@ docs/Features/General/Theme/Change-Color.md
/* Apply scrollbar to sidebar content*/
.board-color-cleargreen .sidebar .sidebar-content {
scrollbar-color: #4bbf6b #ffffff00;
scrollbar-color: #277c48 #ffffff00;
}
/* Remove margins in between columns/fix spacing */
@@ -4910,7 +4921,7 @@ docs/Features/General/Theme/Change-Color.md
/* ===============
THEME - clearorange
A colour SLIDE, like clearblue: #efab6f down to #e67e22.
A colour SLIDE, like clearblue: #efab6f down to #a75919.
The base is pumpkin's own colour, so the flat theme and the slide theme are
the same colour family and a reader picking either gets what they expected.
@@ -4927,7 +4938,7 @@ docs/Features/General/Theme/Change-Color.md
.board-backgrounds-list .board-color-clearorange.background-box,
.public-board-row.board-color-clearorange,
.board-list li.board-color-clearorange {
background: linear-gradient(180deg, #efab6f 0%, #e67e22 100%);
background: linear-gradient(180deg, #efab6f 0%, #a75919 100%);
}
.board-color-clearorange .is-selected .minicard {
border-inline-start: 3px solid #efab6f;
@@ -4954,7 +4965,7 @@ docs/Features/General/Theme/Change-Color.md
.board-color-clearorange#header-quick-access,
.board-color-clearorange .setting-content .content-body .side-menu ul li.active,
.board-color-clearorange .setting-content .content-body .side-menu ul li.active:hover {
background-color: #e67e22;
background-color: #a75919;
border-bottom: 1px solid #b8651b;
border-top: 1px solid #8a4c14;
}
@@ -4965,7 +4976,7 @@ docs/Features/General/Theme/Change-Color.md
.board-color-clearorange .setting-content .content-body .side-menu ul li.active:hover,
.board-color-clearorange .boards-left-menu .menu-item.active a,
.board-color-clearorange .boards-left-menu .menu-item.active a:hover {
background: linear-gradient(180deg, #efab6f 0%, #e67e22 100%);
background: linear-gradient(180deg, #efab6f 0%, #a75919 100%);
}
.board-color-clearorange#header #header-main-bar p {
margin-bottom: 6px;
@@ -5003,7 +5014,7 @@ docs/Features/General/Theme/Change-Color.md
background-color: #ca6f1e;
}
.board-color-clearorange.board-wrapper {
background: linear-gradient(135deg, #efab6f 0%, #e67e22 100%);
background: linear-gradient(135deg, #efab6f 0%, #a75919 100%);
}
.board-color-clearorange .swimlane {
background: none;
@@ -5095,7 +5106,7 @@ docs/Features/General/Theme/Change-Color.md
}
.board-color-clearorange .quiet,
.board-color-clearorange .quiet a {
color: rgba(0,0,0,0.4);
color: rgba(0,0,0,0.68);
}
.board-color-clearorange .list-header .list-header-watch-icon {
color: rgba(0,0,0,0.5);
@@ -5122,7 +5133,7 @@ docs/Features/General/Theme/Change-Color.md
}
.board-color-clearorange .sidebar-shadow {
box-shadow: none;
border-inline-start: 9px solid #e67e22;
border-inline-start: 9px solid #a75919;
}
.board-color-clearorange .is-open .sidebar-shadow {
box-shadow: -10px 0 8px rgba(0,0,0,0.3);
@@ -5187,7 +5198,7 @@ docs/Features/General/Theme/Change-Color.md
/* Apply scrollbar to sidebar content*/
.board-color-clearorange .sidebar .sidebar-content {
scrollbar-color: #e67e22 #ffffff00;
scrollbar-color: #a75919 #ffffff00;
}
/* Remove margins in between columns/fix spacing */
@@ -5206,7 +5217,7 @@ docs/Features/General/Theme/Change-Color.md
/* ===============
THEME - clearpink
A colour SLIDE, like clearblue: #df94b8 down to #cd5a91.
A colour SLIDE, like clearblue: #df94b8 down to #bd3a79.
The base is moderatepink's own colour, so the flat theme and the slide theme are
the same colour family and a reader picking either gets what they expected.
@@ -5223,7 +5234,7 @@ docs/Features/General/Theme/Change-Color.md
.board-backgrounds-list .board-color-clearpink.background-box,
.public-board-row.board-color-clearpink,
.board-list li.board-color-clearpink {
background: linear-gradient(180deg, #df94b8 0%, #cd5a91 100%);
background: linear-gradient(180deg, #df94b8 0%, #bd3a79 100%);
}
.board-color-clearpink .is-selected .minicard {
border-inline-start: 3px solid #df94b8;
@@ -5250,7 +5261,7 @@ docs/Features/General/Theme/Change-Color.md
.board-color-clearpink#header-quick-access,
.board-color-clearpink .setting-content .content-body .side-menu ul li.active,
.board-color-clearpink .setting-content .content-body .side-menu ul li.active:hover {
background-color: #cd5a91;
background-color: #bd3a79;
border-bottom: 1px solid #a44874;
border-top: 1px solid #7b3657;
}
@@ -5261,7 +5272,7 @@ docs/Features/General/Theme/Change-Color.md
.board-color-clearpink .setting-content .content-body .side-menu ul li.active:hover,
.board-color-clearpink .boards-left-menu .menu-item.active a,
.board-color-clearpink .boards-left-menu .menu-item.active a:hover {
background: linear-gradient(180deg, #df94b8 0%, #cd5a91 100%);
background: linear-gradient(180deg, #df94b8 0%, #bd3a79 100%);
}
.board-color-clearpink#header #header-main-bar p {
margin-bottom: 6px;
@@ -5299,7 +5310,7 @@ docs/Features/General/Theme/Change-Color.md
background-color: #b44f80;
}
.board-color-clearpink.board-wrapper {
background: linear-gradient(135deg, #df94b8 0%, #cd5a91 100%);
background: linear-gradient(135deg, #df94b8 0%, #bd3a79 100%);
}
.board-color-clearpink .swimlane {
background: none;
@@ -5391,7 +5402,7 @@ docs/Features/General/Theme/Change-Color.md
}
.board-color-clearpink .quiet,
.board-color-clearpink .quiet a {
color: rgba(0,0,0,0.4);
color: rgba(0,0,0,0.68);
}
.board-color-clearpink .list-header .list-header-watch-icon {
color: rgba(0,0,0,0.5);
@@ -5418,7 +5429,7 @@ docs/Features/General/Theme/Change-Color.md
}
.board-color-clearpink .sidebar-shadow {
box-shadow: none;
border-inline-start: 9px solid #cd5a91;
border-inline-start: 9px solid #bd3a79;
}
.board-color-clearpink .is-open .sidebar-shadow {
box-shadow: -10px 0 8px rgba(0,0,0,0.3);
@@ -5483,7 +5494,7 @@ docs/Features/General/Theme/Change-Color.md
/* Apply scrollbar to sidebar content*/
.board-color-clearpink .sidebar .sidebar-content {
scrollbar-color: #cd5a91 #ffffff00;
scrollbar-color: #bd3a79 #ffffff00;
}
/* Remove margins in between columns/fix spacing */
@@ -5687,7 +5698,7 @@ docs/Features/General/Theme/Change-Color.md
}
.board-color-clearpurple .quiet,
.board-color-clearpurple .quiet a {
color: rgba(0,0,0,0.4);
color: rgba(0,0,0,0.68);
}
.board-color-clearpurple .list-header .list-header-watch-icon {
color: rgba(0,0,0,0.5);
@@ -5983,7 +5994,7 @@ docs/Features/General/Theme/Change-Color.md
}
.board-color-clearred .quiet,
.board-color-clearred .quiet a {
color: rgba(0,0,0,0.4);
color: rgba(0,0,0,0.68);
}
.board-color-clearred .list-header .list-header-watch-icon {
color: rgba(0,0,0,0.5);
@@ -6090,3 +6101,98 @@ docs/Features/General/Theme/Change-Color.md
}
/* === END clearred THEME === */
/* The quiet text colour (#595959 on the light themes' greys, WCAG 1.4.3) is a
variable, so the themes whose popups and sidebars are DARK can have a light
one: a dark grey on a dark surface was nearly invisible (#6863). cleandark and
moderndark are those; dark and exodark keep light popups and sidebars, where
the dark grey is right. Popups carry the theme class themselves, so the
variable reaches them. */
:is(
.board-color-cleandark,
.board-color-moderndark
) {
--wekan-quiet-text: #b4b4b4;
/* Card Settings: its sections are light grey blocks (cardAddFields.css), and
these themes' text on them is white (1.07:1 in cleandark). */
--card-settings-section-bg: rgba(255, 255, 255, 0.08);
--card-settings-helper-bg: #3a3a46;
--card-settings-notch-bg: #2c3340;
}
/* moderndark's surfaces are lighter greys (#545454): the quiet text needs more. */
.board-color-moderndark {
--wekan-quiet-text: #dcdcdc;
}
/* The board sidebar's "Keyboard shortcuts" row is outside .sidebar-content, so
the white of its buttons does not reach it, and sidebar.css makes it black:
black on these sidebars was 1.34:1. */
:is(
.board-color-cleandark,
.board-color-moderndark
) .sidebar .sidebar-shortcuts {
color: #fff;
}
/* moderndark: grey (#808080) on its near-black sidebar was 4.02. */
.board-color-moderndark :is(.js-open-board-menu, h3[data-fold], .activity-title) {
color: var(--wekan-quiet-text);
}
/* dark: the swimlane header's text was black on its #2c3e50 bar (1.91:1). */
.board-color-dark .swimlane-header {
color: #fff;
}
/* dark and exodark: the board, the opened card and the sidebar are dark surfaces
(#222, #2b2b2b, #2c3e50). Their popups differ: exodark's is dark too (above),
dark's is light, so the dark theme's popups keep the dark quiet text. */
:is(
.board-color-dark,
.board-color-exodark
) :is(.board-canvas, .card-details, .sidebar) {
--wekan-quiet-text: #b4b4b4;
}
/* A "quiet button" (Add custom field, Link fields to another card, ...) is #222 in
forms.css, for a light surface; on a dark popup or sidebar it was 1.18:1 (#6863). */
:is(
.board-color-cleandark,
.board-color-moderndark
) .quiet-button,
:is(
.board-color-cleandark,
.board-color-moderndark,
.board-color-exodark
).pop-over .quiet-button {
color: #e0e0e0;
}
/* The board sidebar of the dark themes: what is not a button or a heading
(a form label, the list of swimlanes' lists and cards) kept the light
sidebar's black: 1.34:1 on cleandark's #23232b (#6863). */
:is(
.board-color-cleandark,
.board-color-moderndark
) .sidebar .sidebar-content :is(label, .minilists, .minicards) {
color: #fff;
}
/* Popup items and sidebar lists: popup.css forces `.pop-over-list li > a` to black
with !important, which is right on a light surface. moderndark's popups and
sidebar and exodark's popups are dark: black on #454545 was 2.19:1, on #2b2b2b
1.48:1 (#6863). */
:is(
.board-color-moderndark,
.board-color-exodark
).pop-over .pop-over-list li > a,
:is(
.board-color-moderndark,
.board-color-exodark
).pop-over .pop-over-list li > a *,
.board-color-moderndark .sidebar .pop-over-list li > a,
.board-color-moderndark .sidebar .pop-over-list li > a * {
color: #fff !important;
}
/* moderndark: the sidebar's headings were #808080 on #222 (4.02:1). */
.board-color-moderndark .sidebar .sidebar-content h3 {
color: var(--wekan-quiet-text);
}
/* moderndark: the selected minicard's title was #ccc on #666 (3.57:1). */
.board-color-moderndark .is-selected > .minicard .minicard-title,
.board-color-moderndark .is-selected > .minicard .minicard-title * {
color: #fff;
}
+7 -7
View File
@@ -173,7 +173,7 @@
crossed it - which is the bug that had to be fixed in the Admin Panel's. */
.boards-left-menu .menu-item.active a,
.boards-left-menu .menu-item.active a:hover {
background: var(--theme-accent, #2980b9);
background: var(--theme-accent, #2573a7);
}
/* The Admin Panel puts the selected background on the LI, not the anchor, so
the fill reaches the row's full width. Here the anchor is what fills, so it
@@ -215,7 +215,7 @@
docs/Features/Pages/Starred.md */
.board-list .board-list-item-bookmark .board-list-item {
border: 4px solid #fff;
background: var(--theme-accent, #2980b9);
background: var(--theme-accent, #2573a7);
color: #fff;
/* Room at the end for the unstar button, which sits over the tile. */
padding-inline-end: 34px;
@@ -481,7 +481,7 @@
content: '';
display: block;
height: 30px;
border: 2px dashed var(--theme-accent, #2980b9);
border: 2px dashed var(--theme-accent, #2573a7);
border-radius: 4px;
background: rgba(41, 128, 185, 0.08);
margin: 2px 0;
@@ -694,7 +694,7 @@
as the pointer crossed it. */
.workspace-node.active > .workspace-node-content .js-select-space,
.workspace-node.active > .workspace-node-content:hover .js-select-space {
background: var(--theme-accent, #2980b9);
background: var(--theme-accent, #2573a7);
}
.workspace-node.active > .workspace-node-content .js-select-space,
.workspace-node.active > .workspace-node-content .js-select-space i {
@@ -1046,7 +1046,7 @@
display: flex;
align-items: center;
justify-content: center;
background-color: #999; /* Darker background for better text contrast */
background-color: #767676; /* Grey with white text: 4.54, above the 4.5 of WCAG 1.4.3 (#999 gave 2.84) */
border-radius: 3px;
/* Same total vertical padding as .board-list-item (42px -> same tile height),
but shifted up (top 24->18, bottom 18->24) so the "+ Add Board" text sits
@@ -1661,7 +1661,7 @@
.js-board {
display: block;
background-color: #999; /* Default gray background if no color class is applied */
background-color: #767676; /* Default gray background if no color class is applied */
border-radius: 3px; /* Rounded corners for board items */
overflow: hidden; /* Ensure children respect rounded corners */
margin: 0; /* grid gap spaces the tiles now (#6523) */
@@ -1724,7 +1724,7 @@
chose, not a colour, and painting over it would be losing something rather
than calming it down. */
body.has-theme-board-tiles .board-list > li.js-board {
background: var(--theme-accent-fill, var(--theme-accent, #2980b9));
background: var(--theme-accent-fill, var(--theme-accent, #2573a7));
}
body.has-theme-board-tiles .board-list > li.js-board .board-list-item:not(.has-background-image) {
background-color: transparent;
+10 -3
View File
@@ -1,6 +1,9 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { ReactiveVar } from 'meteor/reactive-var';
import { Utils } from '/client/lib/utils';
import { ensureContrast } from '/models/lib/contrastColor';
import { surfacesFor } from '/models/lib/uiFonts';
import { activeThemeName } from '/client/lib/activeTheme';
import {
DEPENDENCY_ICON_CHOICES,
DEPENDENCY_TYPES,
@@ -138,13 +141,17 @@ Template.dependencyOverlay.onCreated(function () {
const y2 = dependent.y + dependent.h / 2;
const dx = Math.max(40, Math.abs(x2 - x1) / 2);
const path = `M ${x1} ${y1} C ${x1 + dx} ${y1}, ${x2 - dx} ${y2}, ${x2} ${y2}`;
const markerId = markerIdForColor(dep.color);
// The colour is the member's free choice (a colour wheel), drawn over the
// board: made visible against the theme's surface - a line is a graphic
// that says something, 3:1 (WCAG 1.4.11). The stored colour is untouched.
const drawColor = ensureContrast(dep.color, surfacesFor(activeThemeName()), 3);
const markerId = markerIdForColor(drawColor);
if (meta.directed) {
markerColors.add(dep.color);
markerColors.add(drawColor);
}
lines.push({
path,
color: dep.color,
color: drawColor,
type: dep.type,
icon: dep.icon,
directed: meta.directed,
+2 -2
View File
@@ -23,9 +23,9 @@
* so the row keeps the table's white background: with the light text below that
* is white on white, and only the row's emoji was visible. The All Boards tile
* has the same problem and solves it the same way — `.board-list-item` falls
* back to a grey (#999) so its white text always has something to sit on. */
* back to a grey (#767676) so its white text always has something to sit on. */
.public-board-row {
background-color: #999;
background-color: #767676;
background-size: cover;
background-position: center;
cursor: pointer;
+2 -2
View File
@@ -338,8 +338,8 @@ function openPrevAttachment() {
function processTouch(){
xDist = touchEndCoords.x - touchStartCoords.x;
yDist = touchEndCoords.y - touchStartCoords.y;
const xDist = touchEndCoords.x - touchStartCoords.x;
const yDist = touchEndCoords.y - touchStartCoords.y;
// Left swipe
if (Math.abs(xDist) > Math.abs(yDist) && xDist < 0) {
+15 -13
View File
@@ -37,12 +37,12 @@
}
.card-settings-pane-collapse {
padding: 2px 4px;
color: #808080;
color: var(--wekan-quiet-text, #595959);
text-decoration: none;
cursor: pointer;
}
.card-settings-pane-collapse:hover,
.card-settings-pane-collapse:focus-visible { color: #2980b9; }
.card-settings-pane-collapse:focus-visible { color: #2573a7; }
.card-settings-pane-title {
margin: 0;
font-size: calc(17px * var(--wekan-ui-font-scale, 1));
@@ -113,7 +113,9 @@ body.card-settings-splitting-active {
/* A section: the checklist's grey block, its title line and its items. */
.card-settings-inline-section {
background: #f7f7f7;
/* The checklist's grey block; a dark theme has its own (#6863: white text on
this light grey was 1.07:1 in cleandark). */
background: var(--card-settings-section-bg, #f7f7f7);
border-radius: 3px;
margin-block-end: 8px;
padding: 4px 8px 6px;
@@ -139,19 +141,19 @@ body.card-settings-splitting-active {
overflow-wrap: anywhere;
}
.card-settings-inline-section-title.js-card-settings-section-handle { cursor: grab; }
.card-settings-inline-section-count { color: #808080; white-space: nowrap; }
.card-settings-inline-section-count { color: var(--wekan-quiet-text, #595959); white-space: nowrap; }
.card-settings-inline-collapse,
.card-settings-inline-section-move,
.card-settings-inline-menu {
padding: 2px 4px;
color: #808080;
color: var(--wekan-quiet-text, #595959);
text-decoration: none;
cursor: pointer;
}
.card-settings-inline-collapse:hover,
.card-settings-inline-collapse:focus-visible,
.card-settings-inline-menu:hover,
.card-settings-inline-menu:focus-visible { color: #2980b9; }
.card-settings-inline-menu:focus-visible { color: #2573a7; }
/* The section's own checkbox, left of its title: the rows' look. A section
hidden whole on this side keeps its rows' ticks, drawn faded. */
.card-settings-section-side { flex: 0 0 auto; }
@@ -171,11 +173,11 @@ body.card-settings-splitting-active {
min-width: 28px;
min-height: 28px;
border-radius: 3px;
color: #808080;
color: var(--wekan-quiet-text, #595959);
text-decoration: none;
}
.card-settings-add-custom-field-button:hover,
.card-settings-add-custom-field-button:focus-visible { background: rgba(0, 0, 0, 0.06); color: #2980b9; }
.card-settings-add-custom-field-button:focus-visible { background: rgba(0, 0, 0, 0.06); color: #2573a7; }
/* Collapsed: the items hidden, not removed, so their drag stays bound. */
.card-settings-inline-section.is-collapsed .card-settings-inline-items,
.card-settings-inline-section.is-collapsed .card-settings-add-custom-field { display: none; }
@@ -189,7 +191,7 @@ body.card-settings-splitting-active {
border-bottom: 0;
padding-inline-start: 22px;
}
.card-settings-inline .card-settings-line.ui-sortable-helper { background: #fff; }
.card-settings-inline .card-settings-line.ui-sortable-helper { background: var(--card-settings-helper-bg, #fff); }
.card-settings-inline .materialCheckBox { transition: none; }
.card-settings-inline .card-field-order-toggle.is-animating .materialCheckBox { transition: 0.2s; }
/* The hamburgers' checkboxes, likewise: only the one just clicked animates. */
@@ -232,7 +234,7 @@ body.card-settings-splitting-active {
.card-settings-inline-menu.is-open,
.card-settings-add-custom-field-button.is-open {
background: rgba(41, 128, 185, 0.15);
color: #2980b9;
color: #2573a7;
border-radius: 3px;
}
.card-settings-inline-panel {
@@ -257,7 +259,7 @@ body.card-settings-splitting-active {
border-inline-start: 1px solid rgba(41, 128, 185, 0.45);
border-block-start: 1px solid rgba(41, 128, 185, 0.45);
transform: rotate(45deg);
background-color: #eef4f9;
background-color: var(--card-settings-notch-bg, #eef4f9);
}
/* The + is at the start of its line: the notch is under it there. */
.card-settings-inline-panel-create::before {
@@ -270,11 +272,11 @@ body.card-settings-splitting-active {
position: absolute;
inset-block-start: 4px;
inset-inline-end: 6px;
color: #808080;
color: var(--wekan-quiet-text, #595959);
padding: 2px 4px;
}
.card-settings-inline-panel-close:hover,
.card-settings-inline-panel-close:focus-visible { color: #2980b9; }
.card-settings-inline-panel-close:focus-visible { color: #2573a7; }
.card-settings-section-action-title {
display: flex;
align-items: center;
+1 -1
View File
@@ -38,7 +38,7 @@
background-color: #fff;
border: 0;
box-shadow: 0 1px 2px rgba(0,0,0,0.23);
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
height: 36px;
margin: 4px 4px 6px 0;
width: 92%;
+1 -1
View File
@@ -3,5 +3,5 @@ template(name="descriptionForm")
.new-description.js-new-description(
class="{{#if descriptionFormIsOpen}}is-open{{/if}}")
form.js-new-description-form
+editor(class="js-new-description-input" autofocus="autofocus")
+editor(class="js-new-description-input" autofocus="autofocus" labelKey="description")
| {{getUnsavedValue 'cardDescription' _id getDescription}}
+25 -4
View File
@@ -1096,7 +1096,7 @@ body.mobile-mode .card-details .card-details-header .close-card-details-mobile-w
}
.card-details-item-location .card-location-remove {
margin-inline-start: 8px;
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
cursor: pointer;
}
.card-details-item-location .card-location-remove:hover {
@@ -1212,8 +1212,8 @@ body.mobile-mode .card-details .card-details-header .close-card-details-mobile-w
and Activities - has one rule above it and a caret in its heading that
collapses it. The rule comes from the same template as the caret, so eleven
sections cannot end up with ten separators.
The heading is a button: it says so to a screen reader (role/tabindex in the
template) and it looks like one to a mouse. */
The heading holds a real button (cardSectionHeader in the template) that a
screen reader and the keyboard reach, and it looks like one to a mouse. */
/* The GROUP is the full-width row of the card, and the fields inside it share
rows within it - which is what puts Assignee and Creator beside Members
rather than under it. The class that made an individual field full width is
@@ -1248,11 +1248,32 @@ body.mobile-mode .card-details .card-details-header .close-card-details-mobile-w
changes length, so it gets a fixed box. `text-align: center` rather than a
side, because the glyph itself differs between the open and closed states
(down, and right or left) and a fixed side would make it jump. */
.card-details h3.card-details-item-title.js-toggle-card-section > .fa:first-child {
.card-details h3.card-details-item-title.js-toggle-card-section .card-details-section-toggle > .fa:first-child {
width: 12px;
text-align: center;
flex: 0 0 auto;
}
/* The button inside the heading looks like the heading: no button chrome, the
heading's own size, weight and colour, the caret, icon and label in a row. */
.card-details h3.card-details-item-title .card-details-section-toggle {
align-items: center;
background: none;
border: 0;
border-radius: 3px;
color: inherit;
cursor: pointer;
display: inline-flex;
font: inherit;
gap: 6px;
margin: 0;
padding: 0;
text-align: start;
width: auto;
}
.card-details h3.card-details-item-title .card-details-section-toggle:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
}
.card-details h3.card-details-item-title.js-toggle-card-section:hover {
opacity: 0.85;
}
+16 -9
View File
@@ -1055,15 +1055,22 @@ template(name="cardSectionHeader")
sits under a rule of the layout's own.
unless noRule
hr.card-details-section-rule
h3.card-details-item-title.js-toggle-card-section(data-section="{{section}}" role="button" tabindex="0")
i.fa(class="{{sectionCaret section}}")
if icon
i.fa(class="{{icon}}")
| {{_ label}}
//- An optional count after the name - "Attachments (3)" - for a section
whose size is worth knowing before it is opened.
if count
| ({{count}})
//- A real heading with a real button in it (the WAI disclosure pattern): the h3
keeps the outline, the button is what the keyboard and a screen reader reach
and says whether the section is open. The section menu and the layout toggle
are beside the button, not inside it - a button must not contain controls
(WCAG 4.1.2; axe nested-interactive and aria-allowed-role). A click anywhere
on the heading still folds the section: the handler is on the h3.
h3.card-details-item-title.js-toggle-card-section(data-section="{{section}}")
button.card-details-section-toggle(type="button" aria-expanded="{{#if isSectionOpen section}}true{{else}}false{{/if}}")
i.fa(class="{{sectionCaret section}}")
if icon
i.fa(class="{{icon}}")
| {{_ label}}
//- An optional count after the name - "Attachments (3)" - for a section
whose size is worth knowing before it is opened.
if count
| ({{count}})
//- Custom Fields puts its layout selector on the heading row, between the
title and hamburger. A collapsed section hides actions for content that
is not visible, so the selector only exists while this section is open.
+2 -31
View File
@@ -38,26 +38,7 @@ import {
datePickerRendered,
datePickerHelpers,
} from '/client/lib/datepicker';
import {
formatDateTime,
formatDate,
formatTime,
getISOWeek,
isValidDate,
isBefore,
isAfter,
isSame,
add,
subtract,
startOf,
endOf,
format,
parseDate,
now,
createDate,
fromNow,
calendar
} from '/imports/lib/dateUtils';
import { formatDateTime, add, now } from '/imports/lib/dateUtils';
import Cards from '/models/cards';
import Boards from '/models/boards';
import Checklists from '/models/checklists';
@@ -1209,16 +1190,6 @@ Template.cardSectionHeader.events({
if (!section) return;
cardSectionState.set(section, !isCardSectionOpen(section));
},
// A heading that can be operated with the mouse can be operated with the
// keyboard: it carries role="button" and tabindex, so Enter and Space are
// what a screen reader user presses.
'keydown .js-toggle-card-section'(event) {
if (event.target !== event.currentTarget) return;
if (event.key !== 'Enter' && event.key !== ' ') return;
event.preventDefault();
const section = event.currentTarget.dataset.section;
if (section) cardSectionState.set(section, !isCardSectionOpen(section));
},
});
Template.cardDetails.events({
@@ -2396,7 +2367,7 @@ const filterMembers = (filterTerm) => {
.map(({ member }) => member);
}
return members;
}
};
Template.editCardRequesterForm.onRendered(function () {
autosize(this.$('.js-edit-card-requester'));
+1 -1
View File
@@ -9,5 +9,5 @@ template(name="cardPomodoro")
button.js-stop-pomodoro.pomodoro-btn.negate(type="button") {{_ 'stop'}}
else
if canControlPomodoro
input.js-pomodoro-work-minutes.pomodoro-minutes(type="number" min="1" value="{{workMinutesInput}}")
input.js-pomodoro-work-minutes.pomodoro-minutes(type="number" min="1" value="{{workMinutesInput}}" aria-label="{{_ 'pomodoro-work'}} ({{_ 'minutes'}})")
button.js-start-pomodoro.pomodoro-btn.primary(type="button") {{_ 'pomodoro-start'}}
+6 -6
View File
@@ -1,5 +1,5 @@
.js-add-checklist {
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
}
textarea.js-add-checklist-item,
textarea.js-edit-checklist-item {
@@ -11,7 +11,7 @@ textarea.js-edit-checklist-item {
.delete-text,
.js-delete-checklist-item,
.js-convert-checklist-item-to-card {
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
text-decoration: underline;
word-wrap: break-word;
float: inline-end;
@@ -139,7 +139,7 @@ textarea.js-edit-checklist-item {
overflow-wrap: anywhere;
}
.checklist-item .item-title.is-checked {
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
font-style: italic;
text-decoration: line-through;
}
@@ -167,11 +167,11 @@ textarea.js-edit-checklist-item {
.checklist-item .checklist-item-due-date-add {
align-self: center;
margin-inline-start: 6px;
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
text-decoration: none;
}
.checklist-item .checklist-item-due-date-add:hover {
color: #2980b9;
color: #2573a7;
}
.js-delete-checklist-item,
.js-convert-checklist-item-to-card {
@@ -235,7 +235,7 @@ textarea.js-edit-checklist-item {
.checklist-title .checklist-collapse:hover,
.checklist-title .checklist-collapse:focus-visible {
color: #2980b9;
color: #2573a7;
}
.checklist-title .checklist-due-date-add {
+7 -2
View File
@@ -27,8 +27,8 @@
}
.ui-font-btn.active,
.ui-font-size-btn.active {
background: #2980b9;
border-color: #2980b9;
background: #2573a7;
border-color: #2573a7;
color: #fff;
}
/* The "Unset" colour reset links look like buttons. */
@@ -155,6 +155,7 @@
}
.card-label-green {
background-color: #3cb500;
color: #000;
}
.card-label-green:hover {
color: #000;
@@ -169,6 +170,7 @@
}
.card-label-red {
background-color: #eb4646;
color: #000;
}
.card-label-purple {
background-color: #a632db;
@@ -182,6 +184,7 @@
}
.card-label-sky {
background-color: #00c2e0;
color: #000;
}
.card-label-black {
background-color: #4d4d4d;
@@ -213,6 +216,7 @@
}
.card-label-magenta {
background-color: #f0f;
color: #000;
}
.card-label-gold {
background-color: #ffd700;
@@ -223,6 +227,7 @@
}
.card-label-gray {
background-color: #808080;
color: #000;
}
.card-label-saddlebrown {
background-color: #8b4513;
+2 -2
View File
@@ -1011,7 +1011,7 @@
.minicard-list-name {
clear: inline-start;
font-size: 0.75em;
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
margin-top: 2px;
display: flex;
align-items: center;
@@ -1023,7 +1023,7 @@
.minicard-swimlane-name {
clear: inline-start;
font-size: 0.75em;
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
margin-top: 2px;
display: flex;
align-items: center;
+7 -7
View File
@@ -1,5 +1,5 @@
.js-add-subtask {
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
}
textarea.js-add-subtask-item,
textarea.js-edit-subtask-item {
@@ -12,7 +12,7 @@ textarea.js-edit-subtask-item {
.subtask-title .js-delete-subtask,
.subtask-title .js-view-subtask,
.js-delete-subtask-item {
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
text-decoration: underline;
word-wrap: break-word;
flex-shrink: 0;
@@ -163,7 +163,7 @@ textarea.js-edit-subtask-item {
padding-inline-start: 10px;
}
.subtasks-item .item-title.is-checked {
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
font-style: italic;
}
.subtasks-item .item-title .viewer p {
@@ -186,7 +186,7 @@ textarea.js-edit-subtask-item {
instead of disappearing — the same dimmed/italic treatment a checked
checklist item gets (.subtasks-item .item-title.is-checked above). */
.subtask.is-completed .subtask-title .title {
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
font-style: italic;
text-decoration: line-through;
}
@@ -199,7 +199,7 @@ textarea.js-edit-subtask-item {
.subtasks-hide-completed-toggle {
display: inline-block;
margin: 0.2em 0 0.5em 1.33em;
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
cursor: pointer;
}
.subtasks-hide-completed-toggle:hover {
@@ -224,7 +224,7 @@ textarea.js-edit-subtask-item {
flex-shrink: 0;
margin: 0 0.5em;
padding-top: 6px;
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
font-size: 0.85em;
white-space: nowrap;
overflow: hidden;
@@ -251,5 +251,5 @@ textarea.js-edit-subtask-item {
}
.subtask-title.is-done .title {
text-decoration: line-through;
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
}
+20 -2
View File
@@ -107,6 +107,24 @@
white-space: nowrap;
padding-inline: 6px;
}
.selected-calendar-heading .selected-calendar-date {
margin-inline-start: 4px;
/* #6853: year, month and day drop-downs under the calendar. */
.selected-calendar-selects {
display: flex;
gap: 4px;
margin-block-start: 6px;
}
.selected-calendar-selects select,
.selected-calendar-selects input {
flex: 1;
box-sizing: border-box;
height: auto;
margin-bottom: 0;
min-width: 0;
padding-inline: 4px;
}
.selected-calendar-selects .js-calendar-year { flex: 1.4; }
.calendar-date-input .selected-calendar-selects select:focus-visible,
.calendar-date-input .selected-calendar-selects input:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
}
+11 -2
View File
@@ -20,8 +20,6 @@ template(name="calendarDateInput")
button.primary.js-calendar-previous(type="button" aria-label="{{_ 'previous'}} {{_ 'month'}}") ‹
span(aria-live="polite")
| {{monthLabel}}
if inline
span.selected-calendar-date {{headingDateText}}
//- #6838: back to today's month from wherever the arrows went. It only
//- shows the month; the date stays as it is until a day is clicked.
button.primary.js-calendar-today(type="button" aria-label="{{_ 'today'}}") {{_ 'today'}}
@@ -39,3 +37,14 @@ template(name="calendarDateInput")
td(class="{{#if this.today}}is-today-cell{{/if}}")
unless this.empty
button.primary.js-calendar-day(type="button" class="{{#if today}}is-today{{/if}}" data-date=iso title=title aria-label=title aria-pressed=selected aria-current=ariaCurrent tabindex=tabIndex) {{label}}
//- #6853: any year, month and day by a pick, under the calendar.
.selected-calendar-selects
input.js-calendar-year(type="number" step="1" inputmode="numeric" value=yearValue aria-label="{{_ 'predicate-year'}}")
select.js-calendar-month(aria-label="{{_ 'month'}}")
each monthOptions
option(value=value selected=selected) {{label}}
select.js-calendar-day-select(aria-label="{{_ 'day'}}")
if noDaySelected
option(value="" selected disabled) –
each dayOptions
option(value=value selected=selected) {{label}}
+74 -5
View File
@@ -6,7 +6,10 @@ import { TAPi18n } from '/imports/i18n';
import { formatDate } from '/imports/lib/dateUtils';
import { formatDateForDisplay, dateDisplayPreferences } from '/client/lib/dateDisplay';
const { CALENDAR_SYSTEMS, nativeCalendarParts } = require('/imports/lib/calendarSystems');
const { calendarMonthDays, calendarMonthRange, shiftedDay, shiftCalendarMonth, shiftCalendarYear } = require('/imports/lib/calendarMonth');
const {
calendarMonthDays, calendarMonthRange, shiftedDay, shiftCalendarMonth, shiftCalendarYear,
calendarSignedYear, dateInCalendarYear, calendarYearMonths, sameDayInMonth,
} = require('/imports/lib/calendarMonth');
function fromInput(value) {
if (!value) return null;
@@ -53,9 +56,45 @@ Template.calendarDateInput.helpers({
useNativeInput() { return !this.inline && dateDisplayPreferences().calendarSystem === 'gregorian'; },
inputType() { return this.withTime ? 'datetime-local' : 'date'; },
selectedDateText() { return formatDateForDisplay(Template.instance().selectedDate.get(), false); },
headingDateText() {
// #6853: the year, month and day drop-downs under the calendar. Each option
// carries a date as its value, found in the selected calendar system, so any
// past, present or future year, month and day is one pick away.
// The year as a number any year can be typed or stepped to, also one before
// the era (0 is 1 BC) or thousands of years away.
yearValue() { return calendarSignedYear(Template.instance().shownDate.get(), selectedSystem().intl); },
monthOptions() {
const tpl = Template.instance();
return formatDateForDisplay(tpl.selectedDate.get() || tpl.shownDate.get(), false);
const calendar = selectedSystem().intl;
const language = TAPi18n.getLanguage() || 'en';
return calendarYearMonths(tpl.shownDate.get(), calendar).map(month => {
let label;
try {
label = new Intl.DateTimeFormat(language, { calendar, month: 'long', numberingSystem: 'latn' }).format(month.date);
} catch (error) { label = formatDateForDisplay(month.date, false); }
return { _id: formatDate(month.date), value: formatDate(month.date), label, selected: month.selected };
});
},
dayOptions() {
const tpl = Template.instance();
const calendar = selectedSystem().intl;
const { start, end } = calendarMonthRange(tpl.shownDate.get(), calendar);
const selected = tpl.selectedDate.get();
const selectedIso = selected ? formatDate(selected) : '';
const days = [];
for (let day = shiftedDay(start, 0); day < end; day = shiftedDay(day, 1)) {
days.push({
_id: formatDate(day), value: formatDate(day),
label: nativeCalendarParts(day, calendar).day, selected: formatDate(day) === selectedIso,
});
}
return days;
},
noDaySelected() {
const tpl = Template.instance();
const selected = tpl.selectedDate.get();
if (!selected) return true;
const { start, end } = calendarMonthRange(tpl.shownDate.get(), selectedSystem().intl);
return selected < start || selected >= end;
},
expanded() { return !!Template.instance().data.inline || Template.instance().expanded.get(); },
calendarName() { return TAPi18n.__(selectedSystem().labelKey); },
@@ -63,8 +102,11 @@ Template.calendarDateInput.helpers({
const date = Template.instance().shownDate.get();
try {
return new Intl.DateTimeFormat(TAPi18n.getLanguage() || 'en', {
calendar: selectedSystem().intl, month: 'long',
...(Template.instance().data.inline ? {} : { year: 'numeric' }), numberingSystem: 'latn',
// #6853: the year is always shown, also in the popup: month and year
// above the calendar, never the YYYY-MM-DD of the selected day.
calendar: selectedSystem().intl, month: 'long', year: 'numeric', numberingSystem: 'latn',
// A year before the era needs its era, or 3000 BC reads as 3000.
...(calendarSignedYear(date, selectedSystem().intl) <= 0 ? { era: 'short' } : {}),
}).format(date);
} catch (error) { return formatDateForDisplay(date, false); }
},
@@ -136,6 +178,33 @@ Template.calendarDateInput.events({
},
'click .js-calendar-year-previous'(event, tpl) { event.preventDefault(); tpl.shownDate.set(shiftCalendarYear(tpl.shownDate.get(), -1, selectedSystem().intl)); },
'click .js-calendar-year-next'(event, tpl) { event.preventDefault(); tpl.shownDate.set(shiftCalendarYear(tpl.shownDate.get(), 1, selectedSystem().intl)); },
// #6853: a pick in a drop-down selects that date and shows its month.
'change .js-calendar-year'(event, tpl) {
const input = event.currentTarget;
const target = dateInCalendarYear(tpl.shownDate.get(), Number(input.value), selectedSystem().intl);
if (!target || input.value.trim() === '') {
// A year that is not a whole number, or beyond what a date can hold:
// the field goes back to the year being shown.
input.value = calendarSignedYear(tpl.shownDate.get(), selectedSystem().intl);
return;
}
tpl.shownDate.set(target); tpl.focusedDate.set(target); writeValue(tpl, target);
},
// Enter in the year field applies the year, not the popup's Save.
'keydown .js-calendar-year'(event) {
if (event.key !== 'Enter') return;
event.preventDefault(); event.stopPropagation();
event.currentTarget.dispatchEvent(new Event('change', { bubbles: true }));
},
'change .js-calendar-month'(event, tpl) {
const target = sameDayInMonth(tpl.selectedDate.get() || tpl.shownDate.get(), fromInput(event.currentTarget.value), selectedSystem().intl);
tpl.shownDate.set(target); tpl.focusedDate.set(target); writeValue(tpl, target);
},
'change .js-calendar-day-select'(event, tpl) {
const target = fromInput(event.currentTarget.value);
if (!target) return;
tpl.shownDate.set(target); tpl.focusedDate.set(target); writeValue(tpl, target);
},
'click .js-calendar-day'(event, tpl) {
event.preventDefault();
writeValue(tpl, fromInput(event.currentTarget.dataset.date));
+9 -7
View File
@@ -192,9 +192,11 @@ body.mobile-mode [id^="swimlane-"] .list[style*="--list-width"]:not(.list-collap
min-height: 30px !important;
height: auto !important;
padding: 28px 21px 6px !important;
/* Make sure the background covers the full height */
background-color: #e4e4e4 !important;
border-bottom: 8px solid #e4e4e4 !important;
/* Make sure the background covers the full height. Not !important: it beat
every theme's own list header (moderndark's #6a6a6a, exodark's #222), and
left their light text on this grey at 1.09:1 and 1.27:1 (#6863). */
background-color: #e4e4e4;
border-bottom: 8px solid #e4e4e4;
/* Use original display for consistent button positioning */
display: block !important;
position: relative !important;
@@ -470,7 +472,7 @@ form.inlined-form .list-composer {
.list.list-composer .open-list-composer,
.list .list-composer .open-list-composer {
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
}
.list.list-composer .list-name-input,
.list .list-composer .list-name-input {
@@ -534,7 +536,7 @@ form.inlined-form .list-composer {
padding: 0;
border-radius: 0;
background: transparent;
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
font-weight: bold;
font-size: calc(12px * var(--wekan-ui-font-scale, 1));
vertical-align: middle;
@@ -722,7 +724,7 @@ form.inlined-form .list-composer {
}
.list-header .cardCount {
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
font-size: calc(12px * var(--wekan-ui-font-scale, 1));
font-weight: bold;
}
@@ -781,7 +783,7 @@ form.inlined-form .list-composer {
}
.list-body .open-minicard-composer {
border-radius: 2px;
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
display: block;
/* Smaller than a card title, because it is not a card: it is the affordance
for making one. At the inherited 16px it was the largest text in the list
+1 -1
View File
@@ -74,7 +74,7 @@
selected thing. The fill is the slide where the theme has one. */
.theme-color-picker ul.pop-over-list.theme-scope-row > li.active > a,
.theme-color-picker ul.pop-over-list.theme-scope-row > li.active > a:hover {
background: var(--theme-accent-fill, var(--theme-accent, #2980b9));
background: var(--theme-accent-fill, var(--theme-accent, #2573a7));
color: #fff !important;
}
.theme-color-picker ul.pop-over-list.theme-scope-row > li.active > a i {
+1 -1
View File
@@ -39,7 +39,7 @@ template(name="dueCards")
else
.global-search-results-list-wrapper
.no-results
h3 {{_ 'dueCards-noResults-title'}}
h2 {{_ 'dueCards-noResults-title'}}
p {{_ 'dueCards-noResults-description'}}
template(name="dueCardsViewChangePopup")
+1
View File
@@ -7,6 +7,7 @@ template(name="editor")
dir="auto"
class="{{class}}"
id=id
aria-label=labelText
autofocus=autofocus
placeholder="{{_ 'comment-placeholder'}}")
+Template.contentBlock
+10
View File
@@ -45,6 +45,16 @@ function syncMarkdownUrlSchemes(setting) {
return schemes;
}
// WCAG 4.1.2: a field has a name. The including template passes the KEY of its
// name (labelKey="comment"), and the editor translates it, as every included
// template does (tests/jadeInclusionArgsNoMustache.test.cjs).
Template.editor.helpers({
labelText() {
const data = Template.currentData() || {};
return data.labelKey ? TAPi18n.__(data.labelKey) : null;
},
});
const specialHandles = [
{userId: 'board_members', username: 'board_members'},
{userId: 'card_members', username: 'card_members'},
+2
View File
@@ -106,6 +106,8 @@ code {
}
.list-title {
background-color: #a9a9a9;
/* White on this grey was 2.35; black is 8.94 (WCAG 1.4.3). */
color: #000;
}
.global-search-footer {
border: none;
+3 -1
View File
@@ -4,6 +4,7 @@ import { Session } from 'meteor/session';
import { ReactiveCache } from '/imports/reactiveCache';
import { Template } from 'meteor/templating';
import { activeAccent, activeFill } from '/models/lib/themeAccents';
import { readableAccent } from '/models/lib/contrastColor';
// #5778 + docs/Theme/Theme.md: apply the theme color to the whole UI (All Boards,
// Search, Admin Panel, My Cards, etc.) via a `board-color-<name>` class on <body>,
@@ -99,7 +100,8 @@ Meteor.startup(() => {
document.body.classList.remove('has-custom-theme-color');
}
if (c2) {
root.style.setProperty('--theme-accent-2', c2);
// Made readable like the first end: white text is drawn across the slide.
root.style.setProperty('--theme-accent-2', readableAccent(c2));
document.body.classList.add('has-custom-theme-slide');
} else {
root.style.removeProperty('--theme-accent-2');
+4 -4
View File
@@ -17,7 +17,7 @@
#header {
color: #fff;
transition: background-color 0.4s;
background: #2980b9;
background: #2573a7;
z-index: 17;
}
#header #header-main-bar {
@@ -247,7 +247,7 @@
float: inline-start;
display: block;
line-height: calc(28px * var(--wekan-ui-font-scale, 1));
color: #27ae60;
color: #217d49;
margin: 0 10px;
cursor: pointer;
}
@@ -298,7 +298,7 @@ body:not(.board-view) #header #header-main-bar h1 {
of colour now, so the header reads as one header. The per-theme rules do
the same, by naming this bar in the selector list the main bar already has
(boardColors.css) rather than by restating each hex a second time. */
background: #2980b9;
background: #2573a7;
font-size: calc(12px * var(--wekan-ui-font-scale, 1));
display: flex;
z-index: 1000;
@@ -745,7 +745,7 @@ body.mobile-mode #header-quick-access .allBoards {
color: #fff !important;
/* The active theme, like the rest of the bar; the stock blue where no theme
has published an accent. */
background: var(--theme-accent, #2980b9) !important;
background: var(--theme-accent, #2573a7) !important;
}
#header-quick-access .mobile-mode-toggle .board-header-btn.mobile-active i.mobile-icon .fa,
+7 -3
View File
@@ -44,7 +44,9 @@ template(name="header")
that do the same job. Board admins only - everybody else gets the
plain title. The `is-editable` class also gives the element a minimum
width, so a board whose title is EMPTY still has an area to click.
span.header-page-title(title="{{headerTitleFullPath}}"
//- role="heading" aria-level="1": every page has one level-one heading, and this
//- is the page's name (WCAG 1.3.1 / 2.4.6; axe page-has-heading-one).
span.header-page-title(title="{{headerTitleFullPath}}" role="heading" aria-level="1"
class="{{#if canEditBoardTitle}}js-edit-board-title is-editable{{/if}}")
if headerTitleKey
| {{_ headerTitleKey}}
@@ -119,7 +121,8 @@ template(name="header")
//- 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.js-header-collapsible-icon
a.board-header-btn.js-open-starred-boards(title="{{_ 'allboards.starred'}}")
//- aria-label: WCAG 2.5.3 Label in Name - the visible text is the star count.
a.board-header-btn.js-open-starred-boards(title="{{_ 'allboards.starred'}}" aria-label="{{starredBoardsCount}} {{_ 'allboards.starred'}}")
i.fa.fa-caret-down
span.board-star-counter {{starredBoardsCount}}
@@ -245,7 +248,8 @@ template(name="header")
if appIsOffline
+offlineWarning
if currentUser.isBoardMember
//- #6854: on Home and All Boards too, not only on a board.
if announcementForUser
if hasAnnouncement
.announcement(role="status" aria-live="polite")
p
+9
View File
@@ -7,6 +7,7 @@ import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import Announcements, {
announcementVersion,
shouldShowAnnouncement,
showAnnouncementToUser,
} from '/models/announcements';
import { Utils } from '/client/lib/utils';
// What this bar calls the page you are on. models/lib/pageTitles.js
@@ -316,6 +317,14 @@ Template.header.helpers({
return ['waiting', 'failed', 'offline'].includes(status.status);
},
announcementForUser() {
const user = ReactiveCache.getCurrentUser();
return showAnnouncementToUser({
userId: user && user._id,
board: Utils.getCurrentBoard(),
});
},
hasAnnouncement() {
const announcement = Announcements.findOne();
if (!announcement || !announcement.enabled) {
@@ -4,7 +4,8 @@ template(name="shortcutsModalTitle")
| {{_ 'keyboard-shortcuts'}}
template(name="keyboardShortcuts")
.wrapper.shortcuts-list
//- tabindex: the list scrolls when the window is short, and a scrolling region must be reachable by keyboard (WCAG 2.1.1; axe scrollable-region-focusable).
.wrapper.shortcuts-list(tabindex="0" role="region" aria-label="{{_ 'keyboard-shortcuts'}}")
each mapping
.shortcuts-list-item
.shortcuts-list-item-keys
+87 -2
View File
@@ -49,6 +49,20 @@ summary:focus-visible,
box-shadow: 0 0 0 4px rgba(21, 101, 192, 0.35);
border-radius: 2px;
}
/* #6755: a member who turned off "Show focus outline" (Member Settings / Change
* Settings) sees no ring: the outline and its halo, for keyboard focus and for the
* fallback below. Field borders and the colour a button takes on focus are not the
* ring and stay. The ring is an accessibility feature, so it is on unless hidden. */
body.has-hide-focus-outline :focus-visible,
body.has-hide-focus-outline a:focus,
body.has-hide-focus-outline button:focus,
body.has-hide-focus-outline [tabindex]:focus {
outline: none !important;
outline-offset: 0 !important;
}
body.has-hide-focus-outline :is(a, button, input, select, textarea, summary, [tabindex], [role="button"], [contenteditable="true"]):focus-visible {
box-shadow: none !important;
}
/* Browsers that do not support :focus-visible fall back to :focus so keyboard
* users are never left without an indicator. */
.no-focus-visible-support a:focus,
@@ -378,14 +392,14 @@ h6 {
}
.quiet,
.quiet a {
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
}
.error,
.error a {
color: #eb3800;
}
.no-items-message {
color: #666;
color: var(--wekan-quiet-text, #595959);
margin: 36px 0;
text-align: center;
}
@@ -1133,6 +1147,77 @@ a:not(.disabled).is-active i.fa {
align-items: center;
justify-content: center;
}
/* The administrator's login page background colour (layouts.js sets the two
custom properties and the class while the sign-in layout is shown). The
foreground is white on a dark colour and black on a light one. */
body.has-login-background {
background: var(--login-background);
}
body.has-login-background .textBelowCustomLoginLogo,
body.has-login-background .textBelowCustomLoginLogo a {
color: var(--login-foreground);
}
body.has-login-background hr {
border-color: var(--login-foreground);
opacity: 0.4;
}
/* The text under the login logo: one column, real headings, space between
paragraphs. It has no card or background of its own, so the page colour shows
through. */
.textBelowCustomLoginLogo > * {
width: 100%;
max-width: 860px;
box-sizing: border-box;
padding: 0 16px;
}
.textBelowCustomLoginLogo h1,
.textBelowCustomLoginLogo h2,
.textBelowCustomLoginLogo h3,
.support-page-content h1,
.support-page-content h2,
.support-page-content h3 {
font-weight: 700;
line-height: 1.25;
margin: 1em 0 0.5em;
}
.textBelowCustomLoginLogo h1,
.support-page-content h1 { font-size: 1.8em; }
.textBelowCustomLoginLogo h2,
.support-page-content h2 { font-size: 1.45em; }
.textBelowCustomLoginLogo h3,
.support-page-content h3 { font-size: 1.2em; }
.textBelowCustomLoginLogo p,
.textBelowCustomLoginLogo ul,
.textBelowCustomLoginLogo ol,
.support-page-content p,
.support-page-content ul,
.support-page-content ol {
margin: 0 0 1em;
}
/* The support page: a centred column instead of text flush against the window's
left edge. */
.support-page {
padding: 24px 16px;
}
.support-page-content {
box-sizing: border-box;
max-width: 860px;
margin: 0 auto;
padding: 24px 28px;
background: #fff;
color: #1a1a1a;
border-radius: 8px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
overflow-wrap: anywhere;
}
@media (max-width: 600px) {
.support-page {
padding: 8px;
}
.support-page-content {
padding: 16px;
}
}
.auth-layout .auth-dialog {
margin: 0 !important;
}
+6 -3
View File
@@ -27,11 +27,12 @@ template(name="userFormsLayout")
section.auth-layout
if logoSettingsReady
if currentSetting.hideLogo
h1.at-form-landing-logo
//- A spacer, not a heading: an empty h1 is announced as an empty heading.
div.at-form-landing-logo(aria-hidden="true")
br
br
unless currentSetting.hideLogo
h1.at-form-landing-logo
div.at-form-landing-logo(aria-hidden="true")
if currentSetting.customLoginLogoImageUrl
if currentSetting.customLoginLogoLinkUrl
a(href="{{currentSetting.customLoginLogoLinkUrl}}")
@@ -49,7 +50,9 @@ template(name="userFormsLayout")
+viewer
| {{currentSetting.textBelowCustomLoginLogo}}
hr
section.auth-layout
//- role="main": the sign-in and register form is the page's main landmark
(WCAG 1.3.1 / 2.4.1), so the title, the links and the language picker are in one.
section.auth-layout(role="main")
section.auth-dialog
if isLoading
+loader
+51
View File
@@ -2,6 +2,7 @@ import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
const { OAUTH_PROVIDERS, providerByKey } = require('/models/lib/oauthProviders');
const { loginOriginMismatch, offersProviderLogin } = require('/models/lib/loginOriginMismatch');
const { loginBackgroundStyle } = require('/models/lib/loginBackground');
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import { Accounts } from 'meteor/accounts-base';
import Users from '/models/users';
@@ -672,6 +673,56 @@ Template.userFormsLayout.onDestroyed(function () {
this.removeAuthenticationSubmit?.();
});
// The administrator's login page background colour (Admin Panel -> Lay-out).
// It is put on <body> only while the sign-in layout is shown, as two custom
// properties and a class, and taken off again when it closes so the pages after
// sign-in keep their own background. loginBackgroundStyle accepts a hex colour
// only, so the value can never carry CSS of its own.
const LOGIN_BACKGROUND_PROPERTIES = ['--login-background', '--login-foreground'];
function clearLoginBackground() {
document.body.classList.remove('has-login-background');
LOGIN_BACKGROUND_PROPERTIES.forEach(name => document.body.style.removeProperty(name));
}
Template.userFormsLayout.onRendered(function () {
this.autorun(() => {
const setting = this.currentSetting.get();
const style = loginBackgroundStyle(setting && setting.loginBackgroundColor);
if (!style) {
clearLoginBackground();
return;
}
document.body.style.setProperty('--login-background', style.background);
document.body.style.setProperty('--login-foreground', style.foreground);
document.body.classList.add('has-login-background');
});
});
Template.userFormsLayout.onDestroyed(clearLoginBackground);
// The form's title ("Sign In", "Create an Account") is an h3 of the useraccounts
// package and the first heading of the page: a level-one heading, whatever the
// package draws it as (WCAG 1.3.1; axe page-has-heading-one and heading-order).
// The package redraws the form when the user moves between sign in and register,
// so the title is marked again whenever the dialog changes.
Template.userFormsLayout.onRendered(function () {
const dialog = this.find('.auth-dialog');
if (!dialog) return;
const mark = () => {
dialog.querySelectorAll('.at-title h1, .at-title h2, .at-title h3').forEach(heading => {
if (heading.getAttribute('aria-level') !== '1') heading.setAttribute('aria-level', '1');
});
};
mark();
this.titleObserver = new MutationObserver(mark);
this.titleObserver.observe(dialog, { childList: true, subtree: true });
});
Template.userFormsLayout.onDestroyed(function () {
if (this.titleObserver) this.titleObserver.disconnect();
});
function getAuthenticationMethod(
settings,
match,
@@ -111,7 +111,7 @@
/* Disabled = the same themed outline, faded. Not a grey block. */
/* A disabled pager still has to look like the THEME. It is filled with the
theme colour, and dimming it is what takes the theme back out: at 0.4 the
belize blue #2980b9 arrived on the page's grey as rgb(122,170,202), a pale
belize blue #2573a7 arrived on the page's grey as rgb(122,170,202), a pale
wash with no family resemblance to the header above it - and on a one-page
list, where BOTH arrows are disabled, that is the whole pager.
+5 -5
View File
@@ -807,7 +807,7 @@ html[dir='rtl'] .pop-over > button.js-date-popup-resize > span {
above pins every row's text to `#000 !important`; the icon is named separately
for the same reason. */
.pop-over-list li > a:not(.disabled):hover {
background-color: var(--theme-accent, #2980b9);
background-color: var(--theme-accent, #2573a7);
color: #fff !important;
}
.pop-over-list li > a:not(.disabled):hover i,
@@ -822,7 +822,7 @@ html[dir='rtl'] .pop-over > button.js-date-popup-resize > span {
background: #fff;
}
.pop-over-list li > a .sub-name {
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
display: block;
font-size: calc(12px * var(--wekan-ui-font-scale, 1));
font-weight: 400;
@@ -867,7 +867,7 @@ html[dir='rtl'] .pop-over > button.js-date-popup-resize > span {
background-color: #2e85b8;
}
.pop-over-list li > a.disabled {
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
cursor: default;
}
.pop-over-list li > a.disabled .vis-icon {
@@ -878,7 +878,7 @@ html[dir='rtl'] .pop-over > button.js-date-popup-resize > span {
}
.pop-over-list li > a.disabled:hover .sub-name,
.pop-over-list li > a.disabled:hover .quiet {
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
}
.pop-over-list li > a.disabled:active {
background: none;
@@ -972,7 +972,7 @@ html[dir='rtl'] .pop-over > button.js-date-popup-resize > span {
user's own theme, the board's colour or the site theme - whichever is in
force. The fallback is the stock WeKan blue, so an unthemed instance is
unchanged. */
background: var(--theme-accent, #2980b9);
background: var(--theme-accent, #2573a7);
height: calc(48px * var(--wekan-ui-font-scale, 1));
padding: 0px 0px;
border: 0px;
@@ -43,3 +43,6 @@ template(name="themeColorPicker")
input.js-theme-wheel(type="color" data-index="{{index}}" value="{{value}}" title="{{_ 'custom-color'}}")
.theme-preview
span.background-box(class="{{previewClass}}" style="{{previewStyle}}")
//- WCAG 1.4.3: a colour that white text cannot be read on is darkened; this says so.
if customAdjusted
p.theme-custom-adjusted.js-theme-custom-adjusted {{_ 'custom-color'}}: {{customAdjusted}} (4.5:1)
+13 -3
View File
@@ -2,7 +2,7 @@ import { ReactiveCache } from '/imports/reactiveCache';
import { ReactiveVar } from 'meteor/reactive-var';
import { TAPi18n } from '/imports/i18n';
import { Utils } from '/client/lib/utils';
import { isHexColor, toHex } from '/models/lib/contrastColor';
import { isHexColor, toHex, readableAccent } from '/models/lib/contrastColor';
import { BOARD_COLORS } from '/models/metadata/colors';
import {
THEME_CATEGORY_ORDER,
@@ -33,7 +33,7 @@ import {
// 3. the user's own override
// See docs/Features/General/Theme/Change-Color.md for the whole design.
const DEFAULT_WHEEL = ['#2980b9', '#6dd5fa']; // stock flat accent / second slide stop
const DEFAULT_WHEEL = ['#2573a7', '#6dd5fa']; // stock flat accent / second slide stop
function readCurrent(scope) {
if (scope === 'board') {
@@ -138,7 +138,10 @@ Template.themeColorPicker.helpers({
const tpl = Template.instance();
const cat = customCategory(tpl);
if (!allowsCustomColor(cat)) return '';
const cc = tpl.customColors.get();
// The colours as the theme applies them: made readable under white text
// (models/lib/themeAccents.js activeAccent), so the preview is what the header
// will be, not what the wheel showed.
const cc = tpl.customColors.get().map(c => (c ? readableAccent(c) : c));
if (!cc.some(Boolean)) return '';
if (customColorCount(cat) === 2 && cc[0] && cc[1]) {
return `background: linear-gradient(135deg, ${cc[0]}, ${cc[1]}) !important;`;
@@ -146,6 +149,13 @@ Template.themeColorPicker.helpers({
if (cc[0]) return `background: ${cc[0]} !important;`;
return '';
},
// The colours that had to change to be readable, for the note under the preview.
customAdjusted() {
const tpl = Template.instance();
const chosen = tpl.customColors.get().filter(Boolean).map(c => toHex(c)).filter(Boolean);
const changed = chosen.map(c => readableAccent(c)).filter((c, i) => c !== chosen[i]);
return changed.join(' ');
},
});
// Collect the custom colors to save: [] unless the user actually engaged a wheel, in
+12 -1
View File
@@ -61,9 +61,20 @@ html.has-ui-font-size {
* follow this anyway; the ones with a colour of their own - a red alert, a
* green tick - keep saying what they mean. */
body.has-ui-text-color,
body.has-ui-text-color *:not(.fa) {
body.has-ui-text-color *:not(.fa):not(
#header, #header *,
#header-quick-access, #header-quick-access *,
.card-label, .card-label *,
button.primary, button.primary *
) {
color: var(--wekan-ui-text-color) !important;
}
/* The elements left out of the rule above sit on a COLOUR of their own - the
* theme's header bars, a label's colour, a primary button's accent - with a text
* colour that is chosen to read on it (4.5:1, WCAG 1.4.3). The member's colour is
* made legible on the page's surfaces (models/lib/uiFonts.js readableUiTextColor),
* which is not the same as legible on those: #626262 on the blue header was
* 1.18:1. */
/* There was a "text background color" here (#4759), a colour painted behind the
* text. It is REMOVED. A background needs elements to sit on, and neither choice
+16 -2
View File
@@ -1,6 +1,7 @@
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { fontFamilyValue, fontSizeValue, fontScaleValue, colorValue } from '/models/lib/uiFonts';
import { fontFamilyValue, fontSizeValue, fontScaleValue, readableUiTextColor } from '/models/lib/uiFonts';
import { activeThemeName } from '/client/lib/activeTheme';
// #4759: apply the user's chosen UI font AND font-size preset to the whole UI by
// setting CSS variables on :root and toggling marker classes on <body> (uiFont.css
@@ -37,6 +38,17 @@ Meteor.startup(() => {
}
}
// #6755: a member who hid the focus outline (Member Settings / Change Settings):
// a class on <body>, which layouts.css turns into no ring.
Tracker.autorun(() => {
const user = Meteor.user();
try {
document.body.classList.toggle('has-hide-focus-outline', !!(user && user.profile && user.profile.hideFocusOutline === true));
} catch (_) {
// document.body may not exist yet in exotic embeddings; ignore.
}
});
Tracker.autorun(() => {
const user = Meteor.user();
const profile = (user && user.profile) || {};
@@ -50,7 +62,9 @@ Meteor.startup(() => {
// of it that happened to be written in rem. No class goes with it - a
// variable that is not set falls back to 1 in the calc itself.
toggle('--wekan-ui-font-scale', null, fontScaleValue(profile.uiFontSize));
toggle('--wekan-ui-text-color', 'has-ui-text-color', colorValue(profile.uiTextColor));
// The chosen colour, made legible on the surface of the theme in use (4.5:1,
// WCAG 1.4.3): a colour that already reads is applied as chosen.
toggle('--wekan-ui-text-color', 'has-ui-text-color', readableUiTextColor(profile.uiTextColor, activeThemeName()));
// There was a `--wekan-ui-bg-color` here, for "text background color". It is
// removed: a colour behind the text needs elements to sit on, and there is no
// set of them that looks right - on the boxes it painted the page, on the text
@@ -22,7 +22,7 @@ section#notifications-drawer {
padding-top: 36px;
}
section#notifications-drawer a:hover {
color: #2980b9 !important;
color: #2573a7 !important;
}
section#notifications-drawer .header {
position: fixed;
@@ -47,7 +47,7 @@ section#notifications-drawer .header .notification-menu-toggle {
line-height: calc(24px * var(--wekan-ui-font-scale, 1));
}
section#notifications-drawer .header .notification-menu-toggle:hover {
color: #2980b9;
color: #2573a7;
}
section#notifications-drawer .header .notification-menu {
position: absolute;
@@ -90,7 +90,7 @@ section#notifications-drawer .header .notification-menu .menu-item .check-icon {
min-width: 20px;
margin-inline-end: 8px;
text-align: center;
color: #2980b9;
color: #2573a7;
font-weight: bold;
}
section#notifications-drawer .header .notification-menu .menu-item .menu-icon {
+3 -3
View File
@@ -100,7 +100,7 @@
}
/* Drag-and-drop workflow editor */
.rules-workflow .workflow-help {
color: #727479;
color: var(--wekan-quiet-text, #5a5c61);
font-size: calc(13px * var(--wekan-ui-font-scale, 1));
margin-bottom: 9px;
}
@@ -284,7 +284,7 @@
display: flex;
}
.triggers-content {
color: #727479;
color: var(--wekan-quiet-text, #5a5c61);
background: #dedede;
}
.triggers-content .triggers-body {
@@ -437,7 +437,7 @@
}
.rules-import-export .rules-ie-hint {
font-size: calc(12px * var(--wekan-ui-font-scale, 1));
color: #727479;
color: var(--wekan-quiet-text, #5a5c61);
margin-bottom: 0.3em;
}
.rules-import-export .rules-ie-textarea {
@@ -36,8 +36,8 @@
}
.migration-progress-header {
/* WeKan blue (matches the main header #2980b9) */
background: linear-gradient(135deg, #2980b9 0%, #1a5276 100%);
/* WeKan blue (matches the main header #2573a7) */
background: linear-gradient(135deg, #2573a7 0%, #1a5276 100%);
color: white;
padding: 20px;
display: flex;
@@ -87,7 +87,7 @@
.migration-progress-overall-fill {
/* WeKan blue */
background: linear-gradient(90deg, #2980b9, #3498db);
background: linear-gradient(90deg, #2573a7, #3498db);
height: 100%;
border-radius: 10px;
transition: width 0.3s ease;
@@ -138,7 +138,7 @@
.migration-progress-step-fill {
/* WeKan blue */
background: linear-gradient(90deg, #3498db, #2980b9);
background: linear-gradient(90deg, #3498db, #2573a7);
height: 100%;
border-radius: 8px;
transition: width 0.3s ease;
@@ -156,7 +156,7 @@
padding: 15px;
background: #f8f9fa;
border-radius: 6px;
border-inline-start: 4px solid #2980b9;
border-inline-start: 4px solid #2573a7;
}
.migration-progress-status-label {
+7 -7
View File
@@ -2029,7 +2029,7 @@ Template.modifyTeamsUsers.events({
currentUser = ReactiveCache.getUser(selectedUserChkBoxUserIds[i]);
// Copy, so the cached minimongo document is not mutated in place.
userTms = (currentUser.teams || []).slice();
currUserTeamIndex = userTms.findIndex(function(t){ return t.teamId == selectedEltValueId});
currUserTeamIndex = userTms.findIndex(function(t){ return t.teamId == selectedEltValueId;});
if(currUserTeamIndex == -1){
userTms.push({
"teamId": selectedEltValueId,
@@ -2048,7 +2048,7 @@ Template.modifyTeamsUsers.events({
for(let i = 0; i < selectedUserChkBoxUserIds.length; i++){
currentUser = ReactiveCache.getUser(selectedUserChkBoxUserIds[i]);
userTms = (currentUser.teams || []).slice();
currUserTeamIndex = userTms.findIndex(function(t){ return t.teamId == selectedEltValueId});
currUserTeamIndex = userTms.findIndex(function(t){ return t.teamId == selectedEltValueId;});
if(currUserTeamIndex != -1){
userTms.splice(currUserTeamIndex, 1);
}
@@ -2336,7 +2336,7 @@ Template.editUserPopup.events({
userTms.push({
"teamId": userTeamsIdsList[i],
"teamDisplayName": userTeamsList[i],
})
});
}
}
@@ -2349,7 +2349,7 @@ Template.editUserPopup.events({
userOrganizations.push({
"orgId": userOrgsIdsList[i],
"orgDisplayName": userOrgsList[i],
})
});
}
}
@@ -2553,7 +2553,7 @@ const UpdateUserOrgsOrTeamsElement = function(isNewUser = false){
}
selectedElt.value = "-1";
selectedElt.style.display = "none";
}
};
Template.newOrgPopup.events({
submit(event, templateInstance) {
@@ -2633,7 +2633,7 @@ Template.newUserPopup.events({
userTms.push({
"teamId": userTeamsIdsList[i],
"teamDisplayName": userTeamsList[i],
})
});
}
}
@@ -2645,7 +2645,7 @@ Template.newUserPopup.events({
userOrganizations.push({
"orgId": userOrgsIdsList[i],
"orgDisplayName": userOrgsList[i],
})
});
}
}
+7 -7
View File
@@ -10,7 +10,7 @@
height: 100%;
}
.setting-content {
color: #727479;
color: var(--wekan-quiet-text, #5a5c61);
background: #dedede;
width: 100%;
height: 100%;
@@ -99,7 +99,7 @@
selected entry back to white as soon as the pointer crossed it. */
.setting-content .content-body .side-menu ul li.active,
.setting-content .content-body .side-menu ul li.active:hover {
background: var(--theme-accent, #2980b9);
background: var(--theme-accent, #2573a7);
}
/* …and when a THEME is active, the row is painted by that theme's own #header rule
@@ -488,7 +488,7 @@ body.left-menu-resizing-active {
}
.setting-content label:has(> input[type="checkbox"]:focus-visible) {
outline: 2px solid #2980b9;
outline: 2px solid #2573a7;
outline-offset: 2px;
border-radius: 3px;
}
@@ -511,7 +511,7 @@ body.left-menu-resizing-active {
}
.setting-content input[type="checkbox"]:focus-visible {
outline: 2px solid var(--theme-accent, #2980b9);
outline: 2px solid var(--theme-accent, #2573a7);
outline-offset: 2px;
}
@@ -562,13 +562,13 @@ body.left-menu-resizing-active {
}
.setting-content .schedule-day-btn.active {
background: var(--theme-accent, #2980b9);
border-color: var(--theme-accent, #2980b9);
background: var(--theme-accent, #2573a7);
border-color: var(--theme-accent, #2573a7);
color: #fff;
}
.setting-content .schedule-day-btn:focus-visible {
outline: 2px solid var(--theme-accent, #2980b9);
outline: 2px solid var(--theme-accent, #2573a7);
outline-offset: 2px;
}
@@ -630,6 +630,10 @@ template(name='tableVisibilityModeSettings')
.title {{_ 'text-below-custom-login-logo'}}
.form-group
textarea#text-below-custom-login-logo.wekan-form-control(data-editor-submit=".js-visibility-logo-save")= currentSetting.textBelowCustomLoginLogo
li.tableVisibilityMode-form
.title {{_ 'login-background-color'}}
.form-group
input.wekan-form-control#login-background-color(type="text", placeholder="#1b365d" value="{{currentSetting.loginBackgroundColor}}")
li.tableVisibilityMode-form
.title {{_ 'custom-top-left-corner-logo-image-url'}}
.form-group
@@ -811,6 +811,7 @@ Template.tableVisibilityModeSettings.events({
['#custom-login-logo-image-url', 'customLoginLogoImageUrl'],
['#custom-login-logo-link-url', 'customLoginLogoLinkUrl'],
['#text-below-custom-login-logo', 'textBelowCustomLoginLogo'],
['#login-background-color', 'loginBackgroundColor'],
['#custom-top-left-corner-logo-image-url', 'customTopLeftCornerLogoImageUrl'],
['#custom-top-left-corner-logo-link-url', 'customTopLeftCornerLogoLinkUrl'],
['#custom-top-left-corner-logo-height', 'customTopLeftCornerLogoHeight'],
+1 -1
View File
@@ -43,7 +43,7 @@
display: none;
}
.sidebar .sidebar-content h3 {
color: #808080;
color: var(--wekan-quiet-text, #595959);
font-size: 1em;
margin-bottom: 10px;
font-weight: bold;
+5 -5
View File
@@ -3782,7 +3782,7 @@ Template.addMemberPopup.helpers({
const board = ReactiveCache.getBoard(boardId);
return board && board.hasMember(userId);
}
})
});
Template.addBoardOrgPopup.onCreated(function() {
initGroupPickerState(this, 'org');
@@ -3823,7 +3823,7 @@ Template.addBoardOrgPopup.events({
"orgId": selectedOrgId,
"orgDisplayName": selectedOrgDisplayName,
"isActive" : true,
})
});
if (selectedOrgId != "-1") {
Meteor.call('setBoardOrgs', boardOrganizations, currentBoard._id);
@@ -3994,7 +3994,7 @@ Template.addBoardTeamPopup.events({
"teamId": selectedTeamId,
"teamDisplayName": selectedTeamDisplayName,
"isActive" : true,
})
});
if (selectedTeamId != "-1") {
let members = currentBoard.members;
@@ -4011,7 +4011,7 @@ Template.addBoardTeamPopup.events({
let index;
if (boardTeamUsers && boardTeamUsers.length > 0) {
boardTeamUsers.forEach((u) => {
index = members.findIndex(function(m){ return m.userId == u._id});
index = members.findIndex(function(m){ return m.userId == u._id;});
if(index == -1){
members.push({
"isActive": true,
@@ -4076,7 +4076,7 @@ Template.removeBoardTeamPopup.events({
let index;
if (boardTeamUsers && boardTeamUsers.length > 0) {
boardTeamUsers.forEach((u) => {
index = members.findIndex(function(m){ return m.userId == u._id});
index = members.findIndex(function(m){ return m.userId == u._id;});
if(index !== -1 && !members[index].isAdmin){
members.splice(index, 1);
}
+3 -3
View File
@@ -417,14 +417,14 @@ body.swimlane-resizing-active * {
`outline`, not `border` or `box-shadow`: it is drawn outside the box and so
moves neither the swimlane's rows nor the list's cards by a pixel. */
.is-revealed {
outline: 3px solid #2980b9;
outline: 3px solid #2573a7;
outline-offset: -3px;
animation: wekan-reveal-fade 2s ease-out forwards;
}
@keyframes wekan-reveal-fade {
0% { outline-color: #2980b9; }
70% { outline-color: #2980b9; }
0% { outline-color: #2573a7; }
70% { outline-color: #2573a7; }
100% { outline-color: transparent; }
}
+21 -3
View File
@@ -1039,12 +1039,30 @@ Template.addListInline.events({
// #2809
'click .js-link-list': Popup.open('linkList'),
'click .js-close-add-list-inline'() {
Session.set('wekan-add-list-after', null);
Session.set('wekan-add-list-swimlane', null);
Session.set('wekan-add-list-empty', null);
closeAddListComposer();
},
});
// Close the inline add-list composer (the one opened after a list, or in an empty
// swimlane). The X in the composer and Escape do the same thing.
function closeAddListComposer() {
Session.set('wekan-add-list-after', null);
Session.set('wekan-add-list-swimlane', null);
Session.set('wekan-add-list-empty', null);
}
// #6865: Escape closes the add-list composer. Only the X did: the composer stays
// open after a list is created, so one can add several, and then Escape did
// nothing. It is closed at the level of an inlined form (below popups and modals,
// above the card's details and the sidebar), and not by a click elsewhere, as an
// inlined form is not.
EscapeActions.register(
'inlinedForm',
closeAddListComposer,
() => !!(Session.get('wekan-add-list-after') || Session.get('wekan-add-list-empty')),
{ enabledOnClick: false },
);
Template.listsGroup.helpers({
// #6745: board-role checks only (see listHeader.js) - not the whole user.
currentUser() {
+2 -2
View File
@@ -140,7 +140,7 @@
display: flex;
align-items: center;
padding: 0 12px;
color: #8c8c8c !important;
color: var(--wekan-quiet-text, #595959) !important;
}
/* Outweighs .pop-over-list li > a:not(.disabled):hover, which would otherwise
paint the delete button with the generic blue row-hover colour. */
@@ -149,7 +149,7 @@
background-color: #b04632;
}
.avatar-upload-hints {
color: #8c8c8c;
color: var(--wekan-quiet-text, #595959);
font-size: calc(12px * var(--wekan-ui-font-scale, 1));
line-height: calc(16px * var(--wekan-ui-font-scale, 1));
margin: 6px 0;
+3 -1
View File
@@ -1,5 +1,7 @@
template(name="userAvatar")
a.member(class="js-{{#if assignee}}assignee{{else}}member{{/if}} {{memberAccountClass}}" title="{{#if userData}}{{userData.profile.fullname}} ({{userData.username}}) {{_ memberType}}{{#if isImportedMember}} — {{_ 'imported-member-no-account'}}{{else}}{{#if isInactiveMember}} — {{_ 'inactive-member'}}{{/if}}{{/if}}{{/if}}")
//- WCAG 2.5.3 Label in Name: the visible text of an initials avatar is its initials,
//- so the accessible name starts with them (initialsInName). A picture has no text.
a.member(class="js-{{#if assignee}}assignee{{else}}member{{/if}} {{memberAccountClass}}" title="{{#if userData}}{{userData.profile.fullname}} ({{userData.username}}) {{_ memberType}}{{#if isImportedMember}} — {{_ 'imported-member-no-account'}}{{else}}{{#if isInactiveMember}} — {{_ 'inactive-member'}}{{/if}}{{/if}}{{/if}}" aria-label="{{initialsInName}}{{#if userData}}{{userData.profile.fullname}} ({{userData.username}}) {{_ memberType}}{{#if isImportedMember}} — {{_ 'imported-member-no-account'}}{{else}}{{#if isInactiveMember}} — {{_ 'inactive-member'}}{{/if}}{{/if}}{{/if}}")
if userData
if userData.profile.avatarUrl
img.avatar.avatar-image(src="{{avatarUrl}}" alt="")
+12
View File
@@ -45,6 +45,18 @@ const AVATAR_USER_FIELDS = {
};
Template.userAvatar.helpers({
// WCAG 2.5.3 Label in Name: an avatar drawn as initials shows those letters as
// text, so the link's accessible name has to contain them. The name is the
// initials followed by the title, "AB Ann Brown (ann) Admin". A picture shows
// no text, so its name stays the title.
initialsInName() {
const user = ReactiveCache.getUser(this.userId, { fields: AVATAR_USER_FIELDS });
if (user && user.profile && user.profile.avatarUrl) return '';
const defaultAvatar = Template.userAvatar.__helpers.get('defaultAvatarUrl');
if (user && defaultAvatar && defaultAvatar.call(this)) return '';
const initials = (user && user.getInitials && user.getInitials()) || '';
return initials ? `${initials} ` : '';
},
defaultAvatarUrl() {
if (!(Meteor.settings.public && Meteor.settings.public.defaultAvatar)) return '';
const userId = this.userId;
+13 -1
View File
@@ -1,6 +1,8 @@
template(name="headerUserBar")
#header-user-bar
a.header-user-bar-name.js-open-header-member-menu(title="{{_ 'memberMenuPopup-title'}}")
//- aria-label: WCAG 2.5.3 Label in Name - the name shown (and the initials of the
//- avatar) are part of the accessible name, followed by what the link opens.
a.header-user-bar-name.js-open-header-member-menu(title="{{_ 'memberMenuPopup-title'}}" aria-label="{{userBarLabel}}")
.header-user-bar-avatar
+userAvatar(userId=currentUser._id)
unless isMiniScreen
@@ -408,6 +410,9 @@ template(name="changeFontPopup")
.color-wheel-wrap
input.js-ui-text-color(type="color" value=textColorHex title="{{_ 'text-color'}}")
a.js-reset-text-color(class="{{#unless hasTextColor}}disabled{{/unless}}") {{_ 'remove-btn'}}
//- WCAG 1.4.3: the colour is made legible on the theme's surface; this says so.
if textColorAdjusted
p.ui-text-color-adjusted.js-ui-text-color-adjusted {{_ 'text-color'}}: {{readableTextColor}} (4.5:1)
template(name="changeSettingsPopup")
//- #6835: its own class - a popup keeps the screens before it (the member
@@ -434,6 +439,13 @@ template(name="changeSettingsPopup")
| {{_ 'keep-title-tags'}}
if isKeepTitleTags
i.fa.fa-check
//- #6755: the blue focus outline (an accessibility feature, so on by default).
li
a.js-toggle-hide-focus-outline
i.fa.fa-square-o
| {{_ 'show-focus-outline'}}
if isShowFocusOutline
i.fa.fa-check
//- #6827: the Add card form stays open after a card is added.
li
a.js-toggle-keep-add-card-form-open(title="{{_ 'keep-add-card-form-open-description'}}")
+34 -2
View File
@@ -9,9 +9,23 @@ import Users from '/models/users';
import { Utils } from '/client/lib/utils';
import { ReactiveVar } from 'meteor/reactive-var';
import { detectAvailableFonts } from '/client/lib/fontDetector';
import { fontFamilyValue, fontSizeValue, UI_FONT_SIZES, isHexColor6, colorValue } from '/models/lib/uiFonts';
import { fontFamilyValue, fontSizeValue, UI_FONT_SIZES, isHexColor6, colorValue, readableUiTextColor } from '/models/lib/uiFonts';
import { activeThemeName } from '/client/lib/activeTheme';
const { allBoardsPath, SECTION_ARCHIVE } = require('/models/lib/allBoardsUrls');
// WCAG 2.5.3 Label in Name: the link shows the avatar's initials (when the avatar
// is not a picture) and the name, so its accessible name starts with exactly
// those, followed by what it opens.
Template.headerUserBar.helpers({
userBarLabel() {
const user = ReactiveCache.getCurrentUser();
if (!user) return TAPi18n.__('memberMenuPopup-title');
const initials = Template.userAvatar.__helpers.get('initialsInName').call({ userId: user._id });
const name = (user.profile && user.profile.fullname) || user.username || '';
return `${initials}${name} ${TAPi18n.__('memberMenuPopup-title')}`.replace(/\s+/g, ' ').trim();
},
});
Template.headerUserBar.events({
'click .js-open-header-member-menu': Popup.open('memberMenu'),
'click .js-change-avatar': Popup.open('changeAvatar'),
@@ -489,6 +503,11 @@ Template.changeSettingsPopup.helpers({
const currentUser = ReactiveCache.getCurrentUser();
return currentUser ? currentUser.hasKeepTitleTags() : false;
},
// #6755: the outline is shown unless this member hid it.
isShowFocusOutline() {
const currentUser = ReactiveCache.getCurrentUser();
return currentUser ? !currentUser.hasHideFocusOutline() : true;
},
isKeepAddCardFormOpen() {
const currentUser = ReactiveCache.getCurrentUser();
return currentUser ? currentUser.hasKeepAddCardFormOpen() : false;
@@ -555,6 +574,9 @@ Template.changeSettingsPopup.events({
'click .js-toggle-unread-changes'() {
if (ReactiveCache.getCurrentUser()) Meteor.call('toggleUnreadChanges');
},
'click .js-toggle-hide-focus-outline'() {
if (ReactiveCache.getCurrentUser()) Meteor.call('toggleHideFocusOutline');
},
'click .js-toggle-keep-add-card-form-open'() {
if (ReactiveCache.getCurrentUser()) Meteor.call('toggleKeepAddCardFormOpen');
},
@@ -653,12 +675,22 @@ Template.changeFontPopup.helpers({
hasTextColor() {
return !!Template.instance().textColor.get();
},
// The chosen text color as it is applied: made legible on the theme's surface
// (4.5:1), and whether that changed it, so the member is told why the page does
// not show exactly what the wheel did.
readableTextColor() {
return readableUiTextColor(Template.instance().textColor.get(), activeThemeName());
},
textColorAdjusted() {
const chosen = colorValue(Template.instance().textColor.get());
return !!chosen && readableUiTextColor(chosen, activeThemeName()) !== chosen.toLowerCase();
},
// Preview reflects the chosen font, size and text color.
previewStyle() {
const tpl = Template.instance();
const family = fontFamilyValue(tpl.selected.get());
const size = fontSizeValue(tpl.selectedSize.get());
const color = colorValue(tpl.textColor.get());
const color = readableUiTextColor(tpl.textColor.get(), activeThemeName());
return [
family && `font-family: ${family};`,
size && `font-size: ${size};`,
+18
View File
@@ -0,0 +1,18 @@
import { Meteor } from 'meteor/meteor';
import { Session } from 'meteor/session';
import { ReactiveCache } from '/imports/reactiveCache';
// The name of the theme the page is drawn in, in the order of themes of
// client/components/main/globalThemeColor.js: the member's own override, then the
// board's own colour on a board page, then the site theme. '' for WeKan's default.
// Reactive, so a Tracker.autorun that reads it follows a change of theme.
export function activeThemeName() {
const user = Meteor.user();
const own = user && user.profile && user.profile.globalThemeColor;
if (own) return own;
const boardId = Session.get('currentBoard');
const board = boardId ? ReactiveCache.getBoard(boardId) : null;
if (board && board.color) return board.color;
const setting = ReactiveCache.getCurrentSetting();
return (!board && setting && setting.themeColor) || '';
}
+2 -2
View File
@@ -419,7 +419,7 @@ export default Popup => {
const fileFullPath = `${zipDirName}/webfonts/${filename}`;
zip.file(fileFullPath, responseBody);
});
}
};
const downloadFontAwesomeFonts = async (zip, zipDirName) => {
// List of Font Awesome webfont files to include
@@ -476,7 +476,7 @@ export default Popup => {
} catch (e) {
console.log('Error in downloadFontAwesomeFonts:', e);
}
}
};
const downloadCardCovers = async (elements, zip, boardSlug, clonedHtmlElement, zipDirName) => {
await asyncForEach(elements, async elem => {
+10 -29
View File
@@ -74,35 +74,16 @@ hotkeys.filter = (event) => {
};
// Handle non-Latin keyboards
window.addEventListener('keydown', (e) => {
// Only handle event if coming from body
if (e.target !== document.body) return;
// Only a character typed on a non-Latin layout: one non-ASCII character.
// Comparing it with the key code's lowercase letter also matched every
// SHIFTED Latin key ('D', '!', and 'Shift' itself), and re-dispatched it, so
// each Shift shortcut ran twice: Shift+D opened the due date editor and the
// copy clicked the same control again, closing it (#6750).
if (typeof e.key !== 'string' || e.key.length !== 1 || e.key.charCodeAt(0) < 128) return;
// Trigger the corresponding action by dispatching a new event with the ASCII key
const key = String.fromCharCode(e.which).toLowerCase();
if (!/^[a-z0-9]$/.test(key)) return;
// Create a synthetic event for hotkeys to handle, with the same modifiers,
// so Shift shortcuts keep working on non-Latin layouts.
const syntheticEvent = new KeyboardEvent('keydown', {
key: key,
keyCode: e.which,
which: e.which,
shiftKey: e.shiftKey,
ctrlKey: e.ctrlKey,
altKey: e.altKey,
metaKey: e.metaKey,
bubbles: true,
cancelable: true,
});
document.dispatchEvent(syntheticEvent);
});
//
// Nothing to do here, on purpose (#6840). This used to re-dispatch a key typed on
// another script (a Farsi or Cyrillic letter) as its Latin key, for the old
// Mousetrap, which read the typed character. hotkeys-js does not: it resolves a
// key from `event.code` ('KeyD') and `keyCode`, whatever character the layout
// types, so it already runs the shortcut for the physical d key on any layout.
// The copy then ran every shortcut a SECOND time - the due date editor opened
// and the repeat clicked the same control, closing it again, so `d` "opens the
// popup and closes it at a glance" on a Farsi keyboard (and Shift+D did the same
// on a Latin one, #6750). tests/keyboardNonLatinOnce.test.cjs.
function getHoveredCardId() {
const card = $('.js-minicard:hover').get(0);
+2 -2
View File
@@ -119,7 +119,7 @@ window.Popup = new (class {
dataContext: openOptions.dataContext || (this && this.currentData && this.currentData()) || (options && options.dataContextIfCurrentDataIsUndefined) || this,
});
const $contentWrapper = $('.content-wrapper')
const $contentWrapper = $('.content-wrapper');
if ($contentWrapper.length > 0) {
const contentWrapper = $contentWrapper[0];
self._getTopStack().scrollTop = contentWrapper.scrollTop;
@@ -195,7 +195,7 @@ window.Popup = new (class {
/// steps back is greater than the popup stack size, the popup will be closed.
back(n = 1) {
if (this._stack.length > n) {
const $contentWrapper = $('.content-wrapper')
const $contentWrapper = $('.content-wrapper');
if ($contentWrapper.length > 0) {
const contentWrapper = $contentWrapper[0];
const stack = this._stack[this._stack.length - n];
+12
View File
@@ -20,6 +20,18 @@ only what changes: documents, files and log lines. It writes them seconds after
they happen, to a directory that can be another disk, an NFS share or an
[rclone mount](Rclone/Rclone.md).
## One server only, for now
Continuous backup is for installations with **one WeKan server**. When it is
switched on in the Admin Panel, every WeKan server that shares the database
applies the setting, so with several servers (for example the replicas of a
Kubernetes Deployment) each of them tails the oplog and writes its own archives
into the same target directory: the same backup stream, many times, into one
place. It has no leader: one server should run the stream and another take it
over when that one stops (a lease), and that is not built yet
([#6861](https://github.com/wekan/wekan/issues/6861)). With several servers, use
the database's own backup instead.
## Licences: only copyfree code by default
The built-in engines are part of WeKan and use the MIT licence. They depend
@@ -111,7 +111,7 @@ from a probe that runs at startup and every five minutes:
| `db.network-filesystem` | The data directory is on a CIFS/SMB, NFS or FUSE filesystem. Only when WeKan can see that directory itself (the snap, the bundle with its embedded database, or a shared volume). |
| `db.slow-storage` | Reads averaged over 100 ms across the interval - storage latency, the "Slow query" symptom. |
| `db.disk-space` | The filesystem under the data directory is below 5% or 512 MiB free - the real "no space left on device", before it happens. |
| `db.collection-scan` | MongoDB's own log (`getLog`) has "Slow query" lines where a query read a whole collection of at least 10,000 documents (COLLSCAN): an index WeKan does not know about is missing. The row names the collection and the fields filtered on - never values - so they can be reported to WeKan. Needs the `getLog` privilege (`clusterMonitor`); without it, and on FerretDB, this check is skipped. |
| `db.collection-scan` | MongoDB's own log (`getLog`) has "Slow query" lines where a query read a whole collection of at least 10,000 documents (COLLSCAN): an index WeKan does not know about is missing. The row names the collection, the fields filtered on, the command (`find`, `distinct`, ...; for a slow `getMore`, the query it continues), the operators used on each field (`archived:$exists`, `$nor[sort:$gte$lte]`) and the MongoDB `queryHash` - never values - so they can be reported to WeKan and found with `explain()`. Needs the `getLog` privilege (`clusterMonitor`); without it, and on FerretDB, this check is skipped. |
| `db.scan-rate` | The database read more than 50,000 documents per second over the 5-minute interval (`serverStatus` `scannedObjects`): queries without a fitting index, or one query asked far too often. |
| `db.index-created` | WeKan found a collection with data but without an index it queries by, and created it. Fixed automatically; nothing to do. |
@@ -5,15 +5,6 @@
# Trello API Python CLI
# License: MIT / WeKan Team
try:
# python 3
from urllib.parse import urlencode
from urllib.request import urlretrieve
except ImportError:
# python 2
from urllib import urlencode
import json
import requests
import sys
@@ -71,7 +62,7 @@ if arguments == 0:
print(" python3 api.py reactions ACTIONID # Reactions of ACTIONID")
print(" python3 api.py attachments CARDID # List attachments of CARDID")
print(" python3 api.py download ATTACHMENTURL # Download file from attachment URL like https://.../image.png with streaming and minimal RAM usage")
exit
sys.exit()
if arguments == 2:
@@ -0,0 +1,390 @@
# Accessibility audit: automated axe-core trial
This page records the result of a small automated accessibility trial run on
2026-11-02. It is a **measurement, not a conformance claim**. WeKan aims for
[WCAG 2.1 AA](https://www.w3.org/WAI/WCAG21/quickref/) (see
[Accessibility.md](Accessibility.md)); this audit shows where an automated
scan finds that goal not yet met on seven key pages.
An automated scan finds only part of the problems: many WCAG criteria cannot
be decided by a program, and the rest needs people using a keyboard and a
screen reader. A clean result here would not have meant "accessible", and a
finding here is a starting point, not a verdict.
## Status: fixed, and checked again
The findings below were **fixed the same day** in the default theme and the scan
was run again. The result: **axe-core finds no violation on any of 22 pages**,
for the same rule sets (`wcag2a`, `wcag2aa`, `wcag21a`, `wcag21aa`,
`best-practice`). This is a floor, not a conformance claim; the limits at the end
of this page still apply.
### Before and after, page by page
The first scan covered 7 pages. For the re-check the list grew to 22, so the
"before" column for the 15 added pages is a scan made before any fix, with the
same tool, browser and viewport.
| Page | Violating elements before | After |
|---|---|---|
| Sign in | 6 | 0 |
| Sign up | 6 | 0 |
| All Boards (home) | 14 | 0 |
| Templates | 13 | 0 |
| Remaining | 14 | 0 |
| Archive | 8 | 0 |
| Public | 7 | 0 |
| Activity | 7 | 0 |
| Support | 7 | 0 |
| Shortcuts | 7 | 0 |
| My cards | 7 | 0 |
| Due cards | 7 | 0 |
| My attachments | 7 | 0 |
| Starred items | 7 | 0 |
| Global search | 15 | 0 |
| Bookmarks | 8 | 0 |
| Import | 6 | 0 |
| Admin Panel: Settings | 17 | 0 |
| Admin Panel: Information | 17 | 0 |
| Admin Panel: Translation | 19 | 0 |
| Accessibility page | 7 | 0 |
| Board (swimlanes view) | 16 | 0 |
| Board with a card open | 45 | 0 |
### What was done
| Finding | What was done |
|---|---|
| 1. Colour contrast | The default theme's blue is `#2573a7` (white 5.14, `#f2f2f2` 4.59) instead of `#2980b9`, everywhere the stylesheets, the theme accent table and the colour picker name it. The label colours green, red, sky, magenta and gray, and the Global search list chips, have black text instead of white. The quiet text greys `#8c8c8c` and `#727479` are `#595959` and `#5a5c61`; the grey board tiles are `#767676`; the "no items" text is `#595959`. |
| 2. Label in Name | The Starred menu's name starts with the star count; the user menu link's name is the avatar's initials, the user's name and "Member Settings"; an avatar drawn as initials has the initials in its name. axe reads the letters inside the avatar's SVG as visible text whatever `aria-hidden` says, so the name has to contain them. |
| 3. Fields without a label | The Pomodoro minutes field and the comment, description and edit-comment editors have an `aria-label`. The editor takes the key of its name (`labelKey`) and translates it, like every included template. |
| 4 and 5. Button holding controls, role not allowed | A section heading in the card details is a real `h3` holding a real `button` (the WAI disclosure pattern) with `aria-expanded="true"` or `"false"`; the section menu and the layout toggle are beside the button. Enter, Space and a click on the heading still fold the section; this was checked in the browser. |
| 6. Sign in and register | The form is the page's `main` landmark; the empty logo `h1` is a spacer, not a heading; the form's title is the level-one heading (the package draws it as an `h3`, so it gets `aria-level="1"`, and again whenever the form is redrawn). |
| 7. No level-one heading | The page title in the header bar is a level-one heading (`role="heading"`); Activity, the import start page and Bookmarks got a title (they had none, so their heading would have been empty); the "No Due Cards Found" heading is an `h2`. |
### Two more found while adding the scan to the test suite
The Playwright spec runs in a 1280 x 720 window, shorter than the 1280 x 900 of
the audit, and with a fresh user. It found two faults the audit had not:
- **`/shortcuts`**: the list scrolls in a short window and had nothing
focusable, so a keyboard user could not scroll it (`scrollable-region-focusable`,
WCAG 2.1.1). The list now has `tabindex="0"`, a `region` role and a name.
- **`/bookmarks`**: the page had no title in the header bar, so the new
level-one heading was empty (`empty-heading`). It is named "Starred boards".
This is also why the viewport belongs in an audit: the result depends on it.
### Checks that keep it fixed
| Check | Result on 2026-11-02 |
|---|---|
| `tests/accessibilityAuditFixes.test.cjs`: each fault pinned by its shape; contrast ratios are computed from the stylesheets, for every label colour, the header blue and the greys | 7 tests pass |
| `tests/playwright/specs/45-axe-accessibility.e2e.js`: axe-core on the pages, fails on any violation, and presses Enter and Space on a section heading | 4 tests pass (Chromium) |
| Four existing Playwright specs that touch the card sections, the date format and subtasks, run against the changed UI | 43 tests pass (Chromium) |
| All Node suites (`node tests/run-node-suites.cjs`) | 2118 suites, 0 failed |
Five existing unit tests pinned the old markup (the old `#2980b9` in many
stylesheets, a `role="button"` on the heading, `h3` in the title, a `#999`
tile colour, an editor harness) and were updated to the new markup; each keeps
the assertion that made it worth having.
### Dark themes (#6863), found after the fix above
The colour change above made the quiet text greys dark for every theme, and the
dark themes (`cleandark`, `moderndark`, `dark`, `exodark`) have dark surfaces:
dark grey text on them was nearly invisible. [Issue #6863](https://github.com/wekan/wekan/issues/6863)
reported it ("Dark Themes broken in 12.87"), together with faults that were
older than the audit: Card Settings drew white text on light grey blocks
(1.07:1), popup items, the sidebar's shortcuts row and the "quiet buttons" were
black on dark (1.18 to 2.19:1), and a list header's `!important` light grey beat
each theme's own and left its light text at 1.09:1 and 1.27:1. The audit had
scanned the default theme only, which is why none of this showed.
What was done:
- The quiet text colour is a variable, `--wekan-quiet-text`: `#595959` on the
light themes (the fallback), `#b4b4b4` on `cleandark`, `#dcdcdc` on
`moderndark` (lighter greys), and on `dark` and `exodark` for the board, the
opened card and the sidebar (`exodark`'s popups too). `dark`'s popups are light,
so they keep the dark one.
- Card Settings' sections take their background from a variable, a faint lift of
the dark surface instead of `#f7f7f7`; its muted text was `#808080` on
`#f7f7f7` (3.68:1) and is the quiet variable now, also on the light themes.
- Popup items and the board sidebar's lists were forced black with `!important`
by popup.css; `moderndark` and `exodark` now force them white where their
surfaces are dark. Quiet buttons, the sidebar's labels and swimlane lists, the
shortcuts row, `dark`'s swimlane header and `moderndark`'s selected minicard
and primary buttons got light text or a darker fill.
- The list header's `!important` background is gone, so each theme's own list
header applies.
`tests/playwright/specs/46-axe-theme-contrast.e2e.js` scans the four themes, in the
board, the sidebar, Card Settings, an opened card and the custom fields popup,
and finds nothing; `tests/darkThemeContrast6863.test.cjs` pins each rule. A
crawl of more popups and sidebar tabs in `cleandark`, `moderndark`, `dark` and
`exodark` also found nothing.
### The other themes, and colors a member chooses
The same scan was then run on **all 25 board themes** (the board, the filter and
board sidebars, an opened card, Card Settings, and the popups of the card), with
this result before the fix: 0 failures in `belize`, `pomegranate`, `wisteria`,
`natural`, `midnight` and `appleglasspastel`, and failures in the rest:
- **The header and the buttons of eight themes**: white text on the theme's
accent was 2.1 to 4.3:1 in `nephritis` (`#27ae60`), `pumpkin`, `moderatepink`,
`strongcyan`, `limegreen`, `relax`, `corteza` and `modern`, and their dark,
hover and emphasis shades were just as low. The accents are darkened, with the
same hue, until white text and the header's `#f2f2f2` labels read at 4.5:1; the
hover, selected and emphasis shades follow, so a hovered button is still
visibly different. This is the one change that is easy to see: `limegreen` is a
forest green now, `strongcyan` a teal, `pumpkin` a burnt orange. White text at
4.5:1 cannot be had on a bright accent. The colour-slide themes `clearblue`,
`cleargreen`, `clearorange` and `clearpink` end in the same colours, so their
bottom colour changed too, and their buttons (Start Flow) read.
- **The filter sidebar's headings** were `#808080` on `#f7f7f7` (3.68:1) in every
theme, the default one too; they use the quiet text variable.
- **`clear*` themes' quiet text** was `rgba(0,0,0,0.4)` (2.83:1); it is 0.68.
- **`cleanlight`'s add-dependency link** was 50% black on `#f8f8f9` (3.64:1); it
is 70%.
Afterwards **all 25 themes show no colour contrast failure** in those states.
`tests/playwright/specs/46-axe-theme-contrast.e2e.js` runs the scan on every
theme.
What the scan cannot see: axe-core cannot measure text over a **gradient**, so the
headers of the six `clear*` slides are not covered by it (it reports them as
"needs review"). Their ends were not changed beyond the shared accent colours
above, and white text on the light top of a slide (`clearblue`'s `#499bea` is
3.0:1) is not verified here.
**Colors a member chooses.** A free color choice is one half of a pair, and the
other half is worked out so the pair is legible, whatever was picked
(`models/lib/contrastColor.js`, `tests/customColorContrast.test.cjs`):
| Chosen | Paired with | What is done |
|---|---|---|
| A custom label, card, list or swimlane color | its text | The text is black or white, whichever has the higher contrast (`contrastText`): never below 4.5:1 on any color, checked on a sample of every color and on the named colors. Kanri's export of a label's text follows the same rule. |
| A custom theme accent, or the two ends of a custom slide | the header's white text and `#f2f2f2` labels | The color is darkened, with the same hue, until both are at 4.5:1 (`readableAccent`). The picker's preview shows the darkened color and says so. |
| The text color a member picks (Member Settings / Font) | the surface of the theme in use | The color is lightened or darkened, with the same hue, until it reads at 4.5:1 on the surfaces of that theme (`readableUiTextColor`); a color that already reads is applied as chosen. The popup says when it changed it. The text color no longer reaches the header bars, labels and primary buttons, which sit on a color of their own: a grey `#626262` was 1.18:1 on the blue header. |
| A dependency line's color | the board | The line is drawn at 3:1 against the theme's surface (WCAG 1.4.11); the stored color is untouched. |
A text color cannot be legible on a light and a dark surface at once. The four
themes with a dark board (`cleandark`, `moderndark`, `dark`, `exodark`) use the
dark surfaces, the others the light ones; the light popups of `dark` and
`exodark` are a case the choice cannot cover.
### Known side effects of the colour change
- `#2980b9` was the default blue in 21 files; `#2573a7` is slightly darker and
was checked by eye on the card and the sign-in page only.
- The quiet greys changed in 12 stylesheets. The dark themes were not scanned at
first and showed them dark on dark; that is fixed above (#6863).
- The PDF export's progress bar still draws `#2980b9`; it is not part of the
web pages.
The rest of this page is the audit as it was found, kept as a record.
## How it was run
| | |
|---|---|
| WeKan | v12.83.0, release bundle built from `main`, started on its own port |
| Database | FerretDB v1.104.0 with the SQLite backend (a fresh, empty database) |
| Scanner | [axe-core](https://github.com/dequelabs/axe-core) 4.14.0, injected into each page |
| Rule sets | `wcag2a`, `wcag2aa`, `wcag21a`, `wcag21aa` and `best-practice` |
| Browser | Chromium 156 (headless), driven by Playwright |
| Viewport | 1280 x 900 (laptop), one pass, no mobile size |
| Theme and language | The default theme (blue header, `#2980b9`), English, light mode |
| User | A seeded site admin, signed in with a resume token |
| Data | One private board with three lists and three cards |
The scan ran after each page had settled (about 2.5 seconds after load).
Sign-in and sign-up were scanned signed out; the other pages signed in.
## Summary
| Page | Rules violated | Elements |
|---|---|---|
| Sign in (`/sign-in`) | 4 | 6 |
| Sign up (`/sign-up`) | 4 | 6 |
| All Boards (`/`) | 2 | 14 |
| Board, swimlanes view | 2 | 16 |
| Board with a card open | 5 | 45 |
| Global search (`/globalsearch`) | 2 | 7 |
| Accessibility page (`/accessibility`) | 3 | 7 |
Counted by severity, the rules that were violated are:
| Impact | Rules | Where |
|---|---|---|
| Critical | `label` | card details |
| Serious | `color-contrast`, `label-content-name-mismatch`, `nested-interactive` | header bars on every signed-in page; card details |
| Moderate | `heading-order`, `landmark-one-main`, `region`, `page-has-heading-one` | sign in and up; accessibility page |
| Minor | `empty-heading`, `aria-allowed-role` | sign in and up; card details |
axe also reported `incomplete` results, checks it could not decide and that need
a person's eyes: colour contrast on 2 to 4 elements per signed-in page (text
over a gradient or an image), and one Label in Name check on All Boards.
## Findings
### 1. Colour contrast of the header bars (WCAG 1.4.3, AA, serious)
On every signed-in page the header text is lighter than AA allows on the
default blue:
| Text | Background | Ratio | Needed | Where |
|---|---|---|---|---|
| `#ffffff` | `#2980b9` | 4.30 | 4.5 | page title in the header (14 px) |
| `#f2f2f2` | `#2980b9` | 3.84 | 4.5 | the labels of the header buttons ("Sort Boards", "Notifications", "Private", "Watch" and the rest) and the user name |
| `#f6f6f6` | `#2980b9` | 3.97 | 4.5 | text in a viewer inside a blue area |
| `#ffffff` | `#999999` | 2.84 | 4.5 | a card label chip (`.label`) |
| `#8c8c8c` | `#dedede` | 2.49 | 4.5 | the page heading on Global search (20 px) and an inline form placeholder |
This is the largest group: 11 elements on All Boards, 13 on a board and 20 with
a card open. Most share one cause, the near-white text on `#2980b9`, so a
change to that colour or to the text colour of the header buttons would likely
remove most of them in one place. The Apple glass
pastel theme was designed and measured against WCAG AA separately
(`tests/playwright/specs/theme-appleglasspastel-contrast.e2e.js`); this scan
used the default theme, so the two results do not contradict each other.
Other board colours were not scanned. Each has its own header colour, so each
needs the same check (the fix changed only the default theme, `belize`).
### 2. Label in Name (WCAG 2.5.3, A, serious)
Three header controls have an `aria-label` that does not contain the text a
sighted user sees, so a voice-control user who says what they see cannot
activate them:
- the **Starred** menu (`.js-open-starred-boards`): the label is "Starred",
the visible text is the star count (`0`);
- the **Member Settings** link (`.header-user-bar-name`): the label is
"Member Settings", but the visible text is something else (the user's name
or avatar);
- every **member avatar** (`a.member.js-member`): the label is the full name
and, for an admin, the role ("... Admin"); the visible text is different
(the initials).
Fix: make the accessible name start with, or contain, the visible text
(for example "0 Starred", or the user's name followed by "Member Settings").
It affects the header on every signed-in page (3 elements) and the avatar in
the card details (a 4th).
### 3. Form fields without a label (WCAG 4.1.2 and 1.3.1, A, critical)
On the card details, two controls have no label, no `aria-label` and no
`title`:
- the Pomodoro **work minutes** number input (`.js-pomodoro-work-minutes`);
- the new **comment** editor (`textarea.js-new-comment-input`).
A screen reader announces both as "edit text" with no purpose.
### 4. Interactive controls inside a button (WCAG 4.1.2, A, serious)
In the card details, the section headings `h3.js-toggle-card-section` for
**Custom Fields**, **Vote**, **Poker** and **Subtasks** carry `role="button"`
and also contain other focusable controls: the section menu or edit link
(`a.card-details-section-menu`, titled "Custom Fields", "Edit voting",
"Edit Planning Poker" and "Subtasks") and, for Custom Fields, the layout toggle
button. A button must not contain focusable descendants: the inner control
cannot be reached or announced reliably.
Fix: keep the toggle and the action links as siblings in a wrapper instead of
nesting them.
### 5. ARIA role not allowed on the element (best practice, minor)
All 15 section headings (`h3` with `role="button"`; the four of finding 4 are
among them) are flagged by `aria-allowed-role`: an `h3` should not take the
`button` role, because it loses its heading meaning. A `<button>` inside the
`<h3>`, as the WAI disclosure pattern does, keeps both the outline and the
control. The same change as finding 4 fixes this one too.
### 6. Page structure on sign in and sign up (best practice)
Both signed-out pages report the same four items:
- **`landmark-one-main`**: there is no `main` landmark;
- **`region`**: the title, the "Register" / "sign in" link and the language
picker are outside any landmark (3 elements);
- **`empty-heading`**: the logo is an empty `<h1 class="at-form-landing-logo">`,
so the page has a first-level heading with no text;
- **`heading-order`**: the visible title "Sign In" / "Create an Account" is an
`<h3>` directly after that empty `<h1>`.
The signed-in layout has landmarks (see the WCAG 1.3.1 test in
`tests/playwright/specs/19-accessibility.e2e.js`); the sign-in form does not.
Fix: wrap the form in `<main>`, give the title the `<h1>` and make the logo
an image with an `alt`, or hide it from assistive technology.
### 7. Accessibility page has no level-one heading (best practice)
`/accessibility` has no `<h1>` (`page-has-heading-one`). The page title is
shown in the header bar as a `span`.
## What passed
axe ran 48 distinct rules that passed on at least one of the pages, among
them: the page language (`html-has-lang`, `html-lang-valid`), the document
title, the skip link (`bypass`, `skip-link`), image alt text, link names,
button names, select names, `aria-*` attribute and role validity, required
ARIA attributes, `aria-hidden` on focusable elements, valid `tabindex`,
scrollable regions that are focusable, lists, and meta viewport scaling
(`meta-viewport`). A rule that passes on one page can still fail on another.
The existing end-to-end test
`19-accessibility.e2e.js` also checks the language, the skip link, the
landmarks, the visible focus and the dialog control names.
## What this audit does not cover
- Only Chromium, one viewport (1280 x 900), the default theme, light mode.
Not covered: the phone and tablet layouts, dark mode, high contrast, the
other board colours, the other themes, and right-to-left languages.
- Seven pages in one state. Not scanned: popups and menus (member menu, board
menu, card menu, filter, sort), the hamburger sidebars, the Admin Panel, the
card edit forms, the Gantt, Calendar and Table views, dragging, and the
import and export pages.
- Rules that need a person: reading order, whether a label makes sense, focus
order across a drag, error messages, time limits, motion, and the quality of
text alternatives. These need keyboard-only and screen-reader testing (NVDA,
JAWS, VoiceOver, TalkBack).
- No automated check for "target size" (WCAG 2.5.8, 2.2) or the other
WCAG 2.2 criteria; axe 4.14 was run against the 2.0 and 2.1 sets only.
## Suggested order of work
Steps 1 to 4 are done (see Status above), and the axe scan is in the Playwright
suite as `45-axe-accessibility.e2e.js`. What is left:
1. Repeat the scan for the states and pages this audit did not reach: popups
and menus, the hamburger sidebars, the Calendar, Gantt and Table views, the
card edit forms, and the rest of the Admin Panel.
2. Repeat it for the other board colours and themes, at a phone width, in dark
mode and with a right-to-left language. The header colour of every board
colour needs the 4.5 check that the default theme now passes.
3. Test with people: keyboard only, and a screen reader (NVDA, JAWS, VoiceOver,
TalkBack).
## How to repeat it
Run a WeKan server and a database it can reach, seed a user and a board (the
Playwright helpers in `tests/playwright/helpers/db.js` do this), then for each
page inject `axe.min.js` and call:
```js
await axe.run(document, {
runOnly: { type: 'tag', values: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'best-practice'] },
resultTypes: ['violations', 'incomplete'],
});
```
`tests/playwright/specs/45-axe-accessibility.e2e.js` does exactly this for the
pages above, with the `axe-core` package of `tests/playwright`, and signs in
with `loginWithToken` from `tests/playwright/helpers/auth.js`. Run it like the
other Playwright specs. The original trial used `axe-core` 4.14.0 and the
Chromium of the repository's Playwright cache (`.tools/ms-playwright`).
+6 -6
View File
@@ -116,7 +116,7 @@ were wrong once it moved:
- **clearblue** is a colour *slide*, and the slide was only ever on
`#header #header-main-bar`, the inner element of the second bar. The first bar
took the flat `background-color: #00aecc` from the rule beside it — which is
took the flat `background-color: #00798d` from the rule beside it — which is
**strongcyan's colour exactly**, so choosing clearblue painted the header
strongcyan. The slide is on `#header-quick-access` now.
- **natural** and **modern** each had a *later* rule repainting the first bar the
@@ -136,7 +136,7 @@ right for the eighteen flat themes. clearblue cannot be carried by it: a variabl
holds a colour, and a gradient is not one. So the rows that must *slide* are
named in clearblue's own rule — the Admin Panel's selected left-menu row, which
always was, and the All Boards one, which now is. Without that, All Boards' row
sat flat `#00aecc` beside an Admin Panel row that slid.
sat flat `#00798d` beside an Admin Panel row that slid.
`tests/themeAccents.test.cjs` pins all of it: every theme paints the first bar,
paints it its *own* colour, and **no two themes paint it the same** — which is
@@ -156,10 +156,10 @@ categories cover the same colour families:
| Theme | Slide | Base is |
| --- | --- | --- |
| `clearblue` | `#499bea` → `#00aecc` | (hand-tuned, the original) |
| `cleargreen` | `#8ad59f` → `#4bbf6b` | limegreen |
| `clearorange` | `#efab6f` → `#e67e22` | pumpkin |
| `clearpink` | `#df94b8` → `#cd5a91` | moderatepink |
| `clearblue` | `#499bea` → `#00798d` | (hand-tuned, the original) |
| `cleargreen` | `#8ad59f` → `#277c48` | limegreen |
| `clearorange` | `#efab6f` → `#a75919` | pumpkin |
| `clearpink` | `#df94b8` → `#bd3a79` | moderatepink |
| `clearpurple` | `#b685ca` → `#8e44ad` | wisteria |
| `clearred` | `#d67e75` → `#c0392b` | pomegranate |
@@ -10,8 +10,6 @@
# - There is hardcoded value of avatar URL example.com .
# Try to change it to use existing environment variables.
import os
import environs
import ldap
import hashlib
@@ -43,3 +43,20 @@ For developers: the switch is `profile.keepAddCardFormOpen`, toggled by the
`client/components/lists/listBody.js` closes the form after its last title,
pinned by `tests/keepAddCardFormOpen6827.test.cjs` and
`tests/playwright/specs/keep-add-card-form-open-6827.e2e.js`.
## The focus outline
**Show focus outline** is personal and **on** by default. The blue ring round the
focused control is an accessibility feature (WCAG 2.4.7, Focus Visible): it tells
a keyboard user where they are, and it also shows after a click on a minicard,
which some members find distracting
([#6755](https://github.com/wekan/wekan/issues/6755)). Untick it and the ring - the
outline and its halo - is hidden for you, on every board; other members keep
theirs. Field borders and the colour a button takes on focus are not the ring and
stay. If you use the keyboard to move through WeKan, leave it on.
For developers: the switch is `profile.hideFocusOutline` (stored as the exception,
so an account that never touched it keeps the ring), toggled by the
`toggleHideFocusOutline` method; `client/components/main/uiFont.js` puts the class
`has-hide-focus-outline` on `<body>` and `client/components/main/layouts.css` turns
it into no ring. Pinned by `tests/hideFocusOutline6755.test.cjs`.
@@ -10,7 +10,7 @@ This is without container (without Docker or Snap).
Right click and download files 1-4:
1. [wekan-12.78-win64.zip](https://github.com/wekan/wekan/releases/download/v12.78/wekan-12.78-win64.zip) or newest available at https://github.com/wekan/wekan/releases/
1. [wekan-12.92-win64.zip](https://github.com/wekan/wekan/releases/download/v12.92/wekan-12.92-win64.zip) or newest available at https://github.com/wekan/wekan/releases/
2. [node.exe](https://nodejs.org/dist/latest-v26.x/win-x64/node.exe)
@@ -22,7 +22,7 @@ Right click and download files 1-4:
6. Double click `mongodb-windows-x86_64-7.0.34-signed.msi` . In installer, uncheck downloading MongoDB compass.
7. Unzip `wekan-12.78-win64.zip` , inside it is directory `bundle`, to it copy other files:
7. Unzip `wekan-12.92-win64.zip` , inside it is directory `bundle`, to it copy other files:
```
bundle (directory)
+45
View File
@@ -31,6 +31,32 @@ Install WeKan's tools into `.tools` once, from a normal terminal **outside**
the sandbox (`./build.sh`, option 1). Inside the sandbox they are already on
`PATH`.
### Claude Code in the sandbox
Claude Code keeps its login in the macOS Keychain, which the sandbox cannot
reach, so signing in inside the sandbox does not stick: macOS asks for a
keychain to store "user" in, and Claude Code shows its login screen again.
Give it a long-lived token instead, once, from a normal terminal **outside**
the sandbox:
```bash
claude setup-token # sign in in the browser; prints a token valid for a year
docs/Security/Sandboxes/macOS/sandbox.sh --claude-token # copy the token first; saved to .tools/secrets/claude-oauth-token
```
The token is printed over several lines: copy all of them. `--claude-token`
reads it from the clipboard, joins the lines, and refuses a token that is
cut short, which Claude Code would answer with "401 Invalid bearer token".
In zsh, paste the commands without the `#` comments.
Then restart the sandboxed VSCodium. `sandbox.sh` passes the token as
`CLAUDE_CODE_OAUTH_TOKEN`, marks Claude Code's onboarding done in
`.tools/home/.claude.json`, and sets `"claudeCode.disableLoginPrompt": true`
in VSCodium's settings, so neither the extension nor the `claude` command in
the terminal asks for a login. Everything in the sandbox can read the token,
as it could read any login made there; revoke it at claude.ai under
Settings if it leaks, and make a new one when it expires.
## What the sandbox allows and refuses
`sandbox.sh --check` runs these checks inside the sandbox and prints the
@@ -73,6 +99,10 @@ with the checkout's real path:
`.tools/vscodium/user-data` and `.tools/vscodium/extensions`, given with
`--user-data-dir` and `--extensions-dir`. They are not in `~/Library` or
`~/.vscode-oss`, which the sandbox cannot reach.
- **Started in the checkout:** `sandbox.sh` changes to the checkout before it
starts anything. Finder opens the launcher in the home directory, which the
sandbox refuses, and VSCodium then stopped at once with "EPERM:
process.cwd failed with error operation not permitted, uv_cwd".
- **Temporary files:** `TMPDIR`, `TMP` and `TEMP` are `.tools/tmp`, as for every
WeKan script (`releases/tools-env.sh`).
- **A clean environment:** VSCodium starts with `env -i`. That drops
@@ -96,6 +126,21 @@ with the checkout's real path:
reach anything outside the checkout. That is the property this sandbox
exists for. The Flatpak build of VSCodium makes the same trade with
zypak.
- **No Keychain: `--use-mock-keychain`.** The login keychain is in
`~/Library/Keychains`, which the sandbox refuses. Without this switch
VSCodium's secret storage (Electron `safeStorage`) asks for a keychain to
keep "VSCodium Key" in, with the dialog "A keychain cannot be found to store
'VSCodium Key'" (in Finnish "Avainnippua ei löydy"). Never press **Reset To
Defaults** (*Palauta oletuksiin*) there: it would replace the login keychain
with a new, empty one. Press **Cancel** (*Kumoa*) and start VSCodium again
with the updated `sandbox.sh`. With the switch, Chromium uses a built-in
mock keychain with a fixed, publicly known key and does not touch the real
Keychain (Claude Code's own login also lives in the Keychain; see
"Claude Code in the sandbox" above); it is the macOS equivalent of `--password-store=basic`, which
works on Linux only. The price: what extensions store as secrets (sign-ins,
tokens) in `.tools/vscodium/user-data` is only obfuscated, readable by
anyone who can read the checkout. Keep long-lived credentials out of the
sandbox. Opening `~/Library/Keychains` to the sandbox instead would be worse.
- **`sandbox-exec` is marked deprecated** in its man page, but it has been
present and working in every macOS release since 10.5, including macOS 27,
where this was tested. Apple uses the same profiles for its own services.
+84 -7
View File
@@ -3,11 +3,13 @@
# sandbox.sh - install VSCodium and start it in a macOS sandbox that can read
# and write only this WeKan checkout (~/Documents/repos/wekan).
#
# Usage: docs/Security/Sandboxes/macOS/sandbox.sh [--setup-only] [--check]
# Usage: docs/Security/Sandboxes/macOS/sandbox.sh [--setup-only|--check|--claude-token]
#
# (no option) set up what is missing, then start VSCodium in the sandbox
# --setup-only set up, and write the Finder launcher, but do not start it
# --check show what the sandbox allows and refuses, then exit
# (no option) set up what is missing, then start VSCodium in the sandbox
# --setup-only set up, and write the Finder launcher, but do not start it
# --check show what the sandbox allows and refuses, then exit
# --claude-token save a token from `claude setup-token` for Claude Code
# in the sandbox, then set up (see 2b below)
#
# The macOS counterpart of the Flatpak sandbox on Linux
# (docs/Security/Sandboxes/vscodium). The design, what is and is not
@@ -28,7 +30,8 @@ case "${1:-}" in
'') ;;
--setup-only) MODE=setup ;;
--check) MODE=check ;;
*) echo "Usage: $0 [--setup-only] [--check]" >&2; exit 2 ;;
--claude-token) MODE=claude-token ;;
*) echo "Usage: $0 [--setup-only|--check|--claude-token]" >&2; exit 2 ;;
esac
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)"
@@ -40,6 +43,11 @@ TOOLS="$REPO/.tools"
APP="${VSCODIUM_APP:-/Applications/VSCodium.app}"
PROFILE="$TOOLS/sandbox/vscodium-macos.sb"
LAUNCHER="$TOOLS/sandbox/WeKan VSCodium.command"
# Work from the checkout. Finder starts the launcher in the home directory,
# which the sandbox refuses: VSCodium (Node.js) then fails at its first
# process.cwd() with "EPERM: process.cwd failed with error operation not
# permitted, uv_cwd", and so would any other program started in the sandbox.
cd "$REPO"
mkdir -p "$TOOLS/tmp" "$TOOLS/home" "$TOOLS/sandbox" \
"$TOOLS/vscodium/user-data" "$TOOLS/vscodium/extensions"
@@ -68,6 +76,65 @@ for key in user.name user.email; do
fi
done
# 2b) Claude Code's sign-in. Claude Code keeps its login in the macOS Keychain,
# which the sandbox cannot reach ("A keychain cannot be found to store
# 'user'"), so a login made inside the sandbox is lost at once. Instead a
# long-lived token, made with `claude setup-token` outside the sandbox, is
# kept in .tools/secrets/claude-oauth-token and given to the sandbox as
# CLAUDE_CODE_OAUTH_TOKEN. Save it with: sandbox.sh --claude-token
CLAUDE_TOKEN_FILE="$TOOLS/secrets/claude-oauth-token"
if [ "$MODE" = claude-token ]; then
(umask 077; mkdir -p "$TOOLS/secrets")
# `claude setup-token` prints the token wrapped over several lines, so
# reading one pasted line would keep only the first part of it ("401
# Invalid bearer token"). Take it from the clipboard, or read lines until
# an empty one, and drop the line breaks.
token="$(pbpaste 2>/dev/null | tr -d '[:space:]' || true)"
case "$token" in
sk-ant-*) echo "Token taken from the clipboard." ;;
*)
echo 'Paste the token from `claude setup-token` (not shown), then press Return twice:'
token=""
while IFS= read -rs line && [ -n "$line" ]; do token="$token$line"; done
token="$(printf '%s' "$token" | tr -d '[:space:]')"
;;
esac
case "$token" in
sk-ant-oat*) ;;
*) echo "That is not a token from \`claude setup-token\` (it starts with sk-ant-oat); nothing saved." >&2; exit 1 ;;
esac
if [ "${#token}" -lt 90 ]; then
echo "The token is only ${#token} characters, so part of it is missing; nothing saved." >&2
echo "Copy all of it, every line, and run this again." >&2
exit 1
fi
(umask 077; printf '%s\n' "$token" > "$CLAUDE_TOKEN_FILE")
echo "Saved to $CLAUDE_TOKEN_FILE (readable only by you)."
MODE=setup
fi
claude_env=()
if [ -s "$CLAUDE_TOKEN_FILE" ]; then
claude_env=(CLAUDE_CODE_OAUTH_TOKEN="$(tr -d '[:space:]' < "$CLAUDE_TOKEN_FILE")")
# With a token Claude Code would still show its login screen until its
# onboarding is marked done, and the VS Code extension until its login
# prompt is turned off.
cj="$TOOLS/home/.claude.json"
if [ ! -s "$cj" ]; then
printf '{\n "hasCompletedOnboarding": true\n}\n' > "$cj"
elif [ "$(plutil -extract hasCompletedOnboarding raw -o - "$cj" 2>/dev/null)" != true ]; then
cp "$cj" "$cj.bak"
plutil -replace hasCompletedOnboarding -bool YES "$cj" 2>/dev/null ||
echo "Note: add \"hasCompletedOnboarding\": true to $cj by hand." >&2
fi
vs="$TOOLS/vscodium/user-data/User/settings.json"
mkdir -p "$(dirname "$vs")"
if [ ! -s "$vs" ]; then
printf '{\n "claudeCode.disableLoginPrompt": true\n}\n' > "$vs"
elif ! grep -q '"claudeCode.disableLoginPrompt"' "$vs"; then
echo "Note: add \"claudeCode.disableLoginPrompt\": true to VSCodium's settings ($vs)." >&2
fi
fi
# 3) The sandbox profile (Seatbelt, the language of sandbox-exec), written for
# this checkout's own path. Everything is allowed except the file system
# under /Users and /Volumes, of which only the checkout is opened again.
@@ -113,6 +180,7 @@ sandboxed() {
WEKAN_DIR="$REPO" WEKAN_TOOLS_DIR="$TOOLS" \
npm_config_cache="$TOOLS/npm-cache" PLAYWRIGHT_BROWSERS_PATH="$TOOLS/ms-playwright" \
PATH="$TOOLS/bin:$TOOLS/.meteor:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" \
${claude_env[@]+"${claude_env[@]}"} \
"$@"
}
@@ -141,8 +209,17 @@ echo "Sandbox ready. Finder launcher: $LAUNCHER"
# initialization failed: Operation not permitted"), and every process
# VSCodium starts is inside this sandbox instead. Its settings and
# extensions are in .tools/vscodium, not in ~/Library and ~/.vscode-oss.
sandboxed "$APP/Contents/MacOS/VSCodium" --no-sandbox \
# --use-mock-keychain: the login keychain is in ~/Library/Keychains, which
# the sandbox refuses, so Electron's safeStorage would ask for a keychain
# to store "VSCodium Key" in ("A keychain cannot be found", with a "Reset
# To Defaults" button that would replace the user's login keychain). With
# it, Chromium's OSCrypt uses a mock keychain with a fixed key and never
# touches the real one, so extension secrets in .tools/vscodium are only
# obfuscated. (--password-store=basic does the same on Linux only.)
# nohup and </dev/null: the launcher's Terminal window may close as soon
# as this script exits, and VSCodium must not go with it.
sandboxed /usr/bin/nohup "$APP/Contents/MacOS/VSCodium" --no-sandbox --use-mock-keychain \
--user-data-dir "$TOOLS/vscodium/user-data" \
--extensions-dir "$TOOLS/vscodium/extensions" \
"$REPO" >"$TOOLS/sandbox/vscodium.log" 2>&1 &
"$REPO" </dev/null >"$TOOLS/sandbox/vscodium.log" 2>&1 &
echo "VSCodium started in the sandbox (log: $TOOLS/sandbox/vscodium.log)."
+2
View File
@@ -1311,6 +1311,7 @@
"custom-login-logo-link-url": "URL tautan logo login khusus",
"custom-help-link-url": "URL Tautan Bantu Khusuih",
"text-below-custom-login-logo": "Teks di miyup logo login khusus",
"login-background-color": "Warna latar halaman login",
"automatic-linked-url-schemes": "Skema URL khusus nyang haroh jeuet geuklik ngon otomatis. Saboh skema URL bak tiep bareh",
"automatic-linked-url-schemes-hint": "Bak atôran awai mantöng soh, jadi nyang jeuet teuklik cit tautan web ngon imèl. Tiep jeunéh tautan nyang na lam dapeuta, lagee thunderlink atawa onenote, jipeuhah aplikasi nyang ka teudaftar keu jeunéh nyan. javascript, data, vbscript ngon jeunéh nyang saban hana tom jeuet keu tautan.",
"external-link-pattern": "Tautan Keudroe u Peuturi Masalah Luwa",
@@ -1991,6 +1992,7 @@
"title-bracket-label-description": "Watèe judul kad barô geupeuphôn ngon [Haba] lheuh nyan na haba laén, Haba jeut keu label kad nyan (geupeugöt meunyo papan hana label ngon nan nyan) dan geupeugadoh nibak judul. Mate: judul teutap lagée nyan.",
"keep-add-card-form-open": "Peutinggai formulir Tamah Kad teuhah lheueh kad ka geutamah",
"keep-add-card-form-open-description": "Udep: lheueh kad ka geutamah, formulir teutap teuhah ngon judul soh keu kad lheueh nyoe. Mate: formulir teutôp meunan kad ka geutamah.",
"show-focus-outline": "Peuĝah garis fokus",
"checklist-ding-sound": "Peubunyi su watèe item checklist geuseuleusoe",
"checklist-ding-sound-description": "Peubunyi su \"ding\" paneuk watèe droeneueh cek saboh item checklist. Matê meunurôt default.",
"assignee": "Ureueng nyang geubri buet",
+2
View File
@@ -1311,6 +1311,7 @@
"custom-login-logo-link-url": "Gepasmaakte aanmeldlogo-skakel-URL",
"custom-help-link-url": "Gepasmaakte hulpskakel-URL",
"text-below-custom-login-logo": "Teks onder Custom Login Logo",
"login-background-color": "Agtergrondkleur van aanmeldblad",
"automatic-linked-url-schemes": "Pasgemaakte URL-skemas wat outomaties klikbaar moet wees. Een URL-skema per reël",
"automatic-linked-url-schemes-hint": "By verstek leeg, sodat slegs web- en posskakels klikbaar is. Elke gelyste skema, byvoorbeeld thunderlink of onenote, open die toepassing wat daarvoor geregistreer is. javascript, data, vbscript en soortgelyke skemas word nooit skakels nie.",
"external-link-pattern": "Outomatiese skakel na eksterne kwessie-opspoorder",
@@ -1991,6 +1992,7 @@
"title-bracket-label-description": "Wanneer 'n nuwe kaart se titel begin met [Teks] gevolg deur meer teks, word Teks 'n etiket van die kaart (geskep as die raad nog geen etiket met daardie naam het nie) en uit die titel verwyder. Af: die titel bly soos dit is.",
"keep-add-card-form-open": "Hou die Voeg kaart by-vorm oop nadat 'n kaart bygevoeg is",
"keep-add-card-form-open-description": "Aan: nadat 'n kaart bygevoeg is, bly die vorm oop met 'n leë titel vir die volgende kaart. Af: die vorm maak toe sodra die kaart bygevoeg is.",
"show-focus-outline": "Wys fokusomlyn",
"checklist-ding-sound": "Speel 'n klank wanneer 'n kontrolelys-item voltooi word",
"checklist-ding-sound-description": "Speel 'n kort klokkie wanneer jy 'n kontrolelysitem afmerk. Standaard af.",
"assignee": "Gevolmagtigde",
+2
View File
@@ -1311,6 +1311,7 @@
"custom-login-logo-link-url": "Gepasmaakte aanmeldlogo-skakel-URL",
"custom-help-link-url": "Gepasmaakte hulpskakel-URL",
"text-below-custom-login-logo": "Teks onder Custom Login Logo",
"login-background-color": "Agtergrondkleur van aanmeldblad",
"automatic-linked-url-schemes": "Pasgemaakte URL-skemas wat outomaties klikbaar moet wees. Een URL-skema per reël",
"automatic-linked-url-schemes-hint": "By verstek leeg, sodat slegs web- en posskakels klikbaar is. Elke gelyste skema, byvoorbeeld thunderlink of onenote, open die toepassing wat daarvoor geregistreer is. javascript, data, vbscript en soortgelyke skemas word nooit skakels nie.",
"external-link-pattern": "Outomatiese skakel na eksterne kwessie-opspoorder",
@@ -1991,6 +1992,7 @@
"title-bracket-label-description": "Wanneer 'n nuwe kaart se titel begin met [Teks] gevolg deur meer teks, word Teks 'n etiket van die kaart (geskep as die raad nog geen etiket met daardie naam het nie) en uit die titel verwyder. Af: die titel bly soos dit is.",
"keep-add-card-form-open": "Hou die Voeg kaart by-vorm oop nadat 'n kaart bygevoeg is",
"keep-add-card-form-open-description": "Aan: nadat 'n kaart bygevoeg is, bly die vorm oop met 'n leë titel vir die volgende kaart. Af: die vorm maak toe sodra die kaart bygevoeg is.",
"show-focus-outline": "Wys fokusomlyn",
"checklist-ding-sound": "Speel 'n klank wanneer 'n kontrolelys-item voltooi word",
"checklist-ding-sound-description": "Speel 'n kort klokkie wanneer jy 'n kontrolelysitem afmerk. Standaard af.",
"assignee": "Gevolmagtigde",
+2
View File
@@ -1311,6 +1311,7 @@
"custom-login-logo-link-url": "Wo ankasa kɔ-mu agyiraehyɛde link URL",
"custom-help-link-url": "W’ankasa mmoa link URL",
"text-below-custom-login-logo": "Nsɛm a ɛwɔ wo ankasa kɔ-mu agyiraehyɛde no ase",
"login-background-color": "Login kratafa no akyi ahosu",
"automatic-linked-url-schemes": "URL akwan a woahyɛ da ayɛ a ɛsɛ sɛ wotumi mia so ankasa. URL kwan biako wɔ nkyerɛw no ɔfa biara so",
"automatic-linked-url-schemes-hint": "Mfitiaseɛ no, ɛyɛ hunu, enti wɛb ne ɛmɛl nkitahodie nko ara na wobɛtumi aklika so. Scheme biara a ɛwɔ din no mu, sɛ nhwɛsoɔ no thunderlink anaa onenote, bue app a wɔakyerɛw ne din ama no no. Wɔnyɛ javascript, data, vbscript ne scheme a ɛte saa no nkitahodie da.",
"external-link-pattern": "Ntoatoaso ahomaso a ɛkɔ nsɛnnennennee foforo so",
@@ -1991,6 +1992,7 @@
"title-bracket-label-description": "Sɛ kaad foforɔ asɛmti fi ase de [Nkyerɛwee] na nkyerɛwee foforɔ di akyi a, Nkyerɛwee no bɛyɛ kaad no nsɛnkyerɛnne (wɔbɛyɛ no sɛ bɔɔd no nni nsɛnkyerɛnne a ne din saa) na wɔayi afi asɛmti no mu. Mmom: asɛmti no tena sɛdeɛ ɛte.",
"keep-add-card-form-open": "Ma Fa kaad ka ho krataa no nkɔ so mmue wɔ kaad bi a wode aka ho akyi",
"keep-add-card-form-open-description": "Bue: sɛ wode kaad aka ho a, krataa no kɔ so bue a ne din yɛ hwee ma kaad a edi hɔ. To mu: krataa no to mu bere a wode kaad no aka ho.",
"show-focus-outline": "Kyerɛ ahwɛyie ntwerɛ",
"checklist-ding-sound": "Bɔ nnyigyei bere a wɔawie nhwehwɛmu no mu ade bi",
"checklist-ding-sound-description": "Bɔ nnyigyei tiaa bi bere a wohyɛ nhwehwɛmu no mu ade bi agyirae sɛ wɔawie. Wɔadum fi mfiase.",
"assignee": "Nea wɔde adwuma ama no",
+2
View File
@@ -1311,6 +1311,7 @@
"custom-login-logo-link-url": "ብጁ የመግቢያ አርማ አገናኝ URL",
"custom-help-link-url": "ብጁ የእገዛ አገናኝ URL",
"text-below-custom-login-logo": "ከብጁ የመግቢያ አርማ በታች ያለ ጽሑፍ",
"login-background-color": "የመግቢያ ገጽ የጀርባ ቀለም",
"automatic-linked-url-schemes": "በራስ-ሰር ሊጫኑ የሚገባቸው ብጁ የURL መርሃግብሮች። በእያንዳንዱ መስመር አንድ የURL መርሃግብር",
"automatic-linked-url-schemes-hint": "በነባሪ ባዶ ነው፣ ስለዚህ የድር እና የኢሜይል አገናኞች ብቻ ጠቅ ሊደረጉ ይችላሉ። እያንዳንዱ የተዘረዘረ ስኪማ፣ ለምሳሌ thunderlink ወይም onenote፣ ለእሱ የተመዘገበውን መተግበሪያ ይከፍታል። javascript፣ data፣ vbscript እና ተመሳሳይ ስኪማዎች ፈጽሞ አገናኝ አይሆኑም።",
"external-link-pattern": "ወደ ውጫዊ ችግር መከታተያ ራስ-ሰር አገናኝ",
@@ -1991,6 +1992,7 @@
"title-bracket-label-description": "የአዲስ ካርድ ርዕስ በ[ጽሑፍ] ተጀምሮ ሌላ ጽሑፍ ከተከተለው፣ ጽሑፍ የካርዱ መለያ ይሆናል (ሰሌዳው በዚያ ስም መለያ ከሌለው ይፈጠራል) እና ከርዕሱ ይወገዳል። ጠፍቷል፦ ርዕሱ እንዳለ ይቆያል።",
"keep-add-card-form-open": "ካርድ ከታከለ በኋላ የካርድ ጨምር ቅጹ ክፍት ሆኖ ይቆይ",
"keep-add-card-form-open-description": "በርቷል: ካርድ ከታከለ በኋላ ቅጹ ለቀጣዩ ካርድ ባዶ ርዕስ ይዞ ክፍት ሆኖ ይቆያል። ጠፍቷል: ካርዱ እንደታከለ ቅጹ ይዘጋል።",
"show-focus-outline": "የትኩረት ዙሪያ መስመር አሳይ",
"checklist-ding-sound": "የማጣሪያ ዝርዝር ንጥል ሲጠናቀቅ ድምፅ አጫውት",
"checklist-ding-sound-description": "የማጣሪያ ዝርዝር ንጥል ሲያመልክቱ አጭር ድምፅ ያጫውቱ። ነባሪው ጠፍቷል።",
"assignee": "ኃላፊ",
+2
View File
@@ -1311,6 +1311,7 @@
"custom-login-logo-link-url": "URL de enlace d'o logo personalizado de inicio de sesión",
"custom-help-link-url": "URL d’o vinclo d’aduya personalizau",
"text-below-custom-login-logo": "Texto debajo d'o logo personalizado de inicio de sesión",
"login-background-color": "Color de fondo d'a pachina d'inicio de sesión",
"automatic-linked-url-schemes": "Esquemas d’URL personalizaus que han de poder clicar-se automaticament. Un esquema d’URL por linia.",
"automatic-linked-url-schemes-hint": "Vuedo por defecto, asinas que nomás se puede fer clic en os vinclos web y de correu. Cada esquema d'a lista, por eixemplo thunderlink u onenote, ubre l'aplicación rechistrada ta ell. javascript, data, vbscript y esquemas pareixius nunca no se tornan vinclos.",
"external-link-pattern": "Vinclo automatico ta un rastrexador de problemas externo",
@@ -1991,6 +1992,7 @@
"title-bracket-label-description": "Quan o titol d'una tarcheta nueva empecipia con [Texto] seguiu de mas texto, Texto se convierte en una etiqueta d'a tarcheta (se crea si o tablero no en tiene garra con ixe nombre) y se quita d'o titol. Desactivau: o titol se mantiene tal como ye.",
"keep-add-card-form-open": "Mantener ubierto o formulario d'adhibir tarcheta dimpués d'adhibir una tarcheta",
"keep-add-card-form-open-description": "Activau: dimpués d'adhibir una tarcheta, o formulario se mantién ubierto con un titol vuedo pa la siguient tarcheta. Desactivau: o formulario se zarra en cuanto s'adhibe la tarcheta.",
"show-focus-outline": "Amostrar o contorno de foco",
"checklist-ding-sound": "Sonar quan se completa un elemento d'a lista de verificación",
"checklist-ding-sound-description": "Sonar un tono curto quan marcas un elemento d'a lista de verificación. Desactivau por defecto.",
"assignee": "Asignau",
+2
View File
@@ -1311,6 +1311,7 @@
"custom-login-logo-link-url": "عنوان URL لرابط شعار تسجيل الدخول المخصص",
"custom-help-link-url": "رابط صفحة المساعدة المخصص URL",
"text-below-custom-login-logo": "النص أسفل شعار تسجيل الدخول المخصص",
"login-background-color": "لون خلفية صفحة تسجيل الدخول",
"automatic-linked-url-schemes": "مخططات URL المخصصة التي يجب أن تكون قابلة للنقر تلقائياً. مخطط URL واحد لكل سطر",
"automatic-linked-url-schemes-hint": "تكون فارغة افتراضيًا، لذا يمكن النقر على روابط الويب والبريد فقط. يفتح كل مخطط مدرج، مثل thunderlink أو onenote، التطبيق المسجل له. لا تُحوَّل مخططات javascript وdata وvbscript والمخططات المشابهة إلى روابط أبدًا.",
"external-link-pattern": "الربط التلقائي بمتتبع مشاكل خارجي",
@@ -1991,6 +1992,7 @@
"title-bracket-label-description": "عندما يبدأ عنوان بطاقة جديدة بـ [نص] متبوعًا بنص آخر، يصبح النص علامة للبطاقة (تُنشأ إذا لم تكن في اللوحة علامة بهذا الاسم) ويُحذف من العنوان. عند الإيقاف: يبقى العنوان كما هو.",
"keep-add-card-form-open": "إبقاء نموذج إضافة بطاقة مفتوحًا بعد إضافة بطاقة",
"keep-add-card-form-open-description": "تشغيل: بعد إضافة بطاقة، يبقى النموذج مفتوحًا بعنوان فارغ للبطاقة التالية. إيقاف: يُغلق النموذج بمجرد إضافة البطاقة.",
"show-focus-outline": "إظهار حدود التركيز",
"checklist-ding-sound": "تشغيل صوت عند إتمام عنصر في قائمة المهام",
"checklist-ding-sound-description": "تشغيل نغمة قصيرة عند تحديد عنصر في قائمة المهام. معطّل افتراضيًا.",
"assignee": "المكلّف",
+2
View File
@@ -1311,6 +1311,7 @@
"custom-login-logo-link-url": "عنوان URL لرابط شعار تسجيل الدخول المخصص",
"custom-help-link-url": "رابط صفحة المساعدة المخصص URL",
"text-below-custom-login-logo": "النص أسفل شعار تسجيل الدخول المخصص",
"login-background-color": "لون خلفية صفحة تسجيل الدخول",
"automatic-linked-url-schemes": "مخططات URL المخصصة التي يجب أن تكون قابلة للنقر تلقائياً. مخطط URL واحد لكل سطر",
"automatic-linked-url-schemes-hint": "تكون فارغة افتراضيًا، لذا يمكن النقر على روابط الويب والبريد فقط. يفتح كل مخطط مدرج، مثل thunderlink أو onenote، التطبيق المسجل له. لا تُحوَّل مخططات javascript وdata وvbscript والمخططات المشابهة إلى روابط أبدًا.",
"external-link-pattern": "الربط التلقائي بمتتبع مشاكل خارجي",
@@ -1991,6 +1992,7 @@
"title-bracket-label-description": "عندما يبدأ عنوان بطاقة جديدة بـ [نص] متبوعًا بنص آخر، يصبح النص علامة للبطاقة (تُنشأ إذا لم تكن في اللوحة علامة بهذا الاسم) ويُحذف من العنوان. عند الإيقاف: يبقى العنوان كما هو.",
"keep-add-card-form-open": "إبقاء نموذج إضافة بطاقة مفتوحًا بعد إضافة بطاقة",
"keep-add-card-form-open-description": "تشغيل: بعد إضافة بطاقة، يبقى النموذج مفتوحًا بعنوان فارغ للبطاقة التالية. إيقاف: يُغلق النموذج بمجرد إضافة البطاقة.",
"show-focus-outline": "إظهار حدود التركيز",
"checklist-ding-sound": "تشغيل صوت عند إتمام عنصر في قائمة المهام",
"checklist-ding-sound-description": "تشغيل نغمة قصيرة عند تحديد عنصر في قائمة المهام. معطّل افتراضيًا.",
"assignee": "المكلّف",
+2
View File
@@ -1311,6 +1311,7 @@
"custom-login-logo-link-url": "عنوان URL لرابط شعار تسجيل الدخول المخصص",
"custom-help-link-url": "رابط صفحة المساعدة المخصص URL",
"text-below-custom-login-logo": "النص أسفل شعار تسجيل الدخول المخصص",
"login-background-color": "لون خلفية صفحة تسجيل الدخول",
"automatic-linked-url-schemes": "مخططات URL المخصصة التي يجب أن تكون قابلة للنقر تلقائياً. مخطط URL واحد لكل سطر",
"automatic-linked-url-schemes-hint": "تكون فارغة افتراضيًا، لذا يمكن النقر على روابط الويب والبريد فقط. يفتح كل مخطط مدرج، مثل thunderlink أو onenote، التطبيق المسجل له. لا تُحوَّل مخططات javascript وdata وvbscript والمخططات المشابهة إلى روابط أبدًا.",
"external-link-pattern": "الربط التلقائي بمتتبع مشاكل خارجي",
@@ -1991,6 +1992,7 @@
"title-bracket-label-description": "عندما يبدأ عنوان بطاقة جديدة بـ [نص] متبوعًا بنص آخر، يصبح النص علامة للبطاقة (تُنشأ إذا لم تكن في اللوحة علامة بهذا الاسم) ويُحذف من العنوان. عند الإيقاف: يبقى العنوان كما هو.",
"keep-add-card-form-open": "إبقاء نموذج إضافة بطاقة مفتوحًا بعد إضافة بطاقة",
"keep-add-card-form-open-description": "تشغيل: بعد إضافة بطاقة، يبقى النموذج مفتوحًا بعنوان فارغ للبطاقة التالية. إيقاف: يُغلق النموذج بمجرد إضافة البطاقة.",
"show-focus-outline": "إظهار حدود التركيز",
"checklist-ding-sound": "تشغيل صوت عند إتمام عنصر في قائمة المهام",
"checklist-ding-sound-description": "تشغيل نغمة قصيرة عند تحديد عنصر في قائمة المهام. معطّل افتراضيًا.",
"assignee": "المكلّف",
+2
View File
@@ -1311,6 +1311,7 @@
"custom-login-logo-link-url": "آدرس لینک لوگوی سفارشی در لاگین",
"custom-help-link-url": "رابط صفحة المساعدة المخصص URL",
"text-below-custom-login-logo": "متن پایین لوگوی سفارشی در فرم لاگین",
"login-background-color": "لون خلفية صفحة تسجيل الدخول",
"automatic-linked-url-schemes": "مخططات URL المخصصة التي يجب أن تكون قابلة للنقر تلقائياً. مخطط URL واحد لكل سطر",
"automatic-linked-url-schemes-hint": "كتكون خاوية فالإعدادات الأصلية، وداكشي علاش غير روابط الويب والإيميل اللي كتقدر تكليكي عليها. كل نوع رابط فاللائحة، بحال thunderlink ولا onenote، كيفتح التطبيق المسجل ديالو. javascript وdata وvbscript والأنواع المشابهة ما كيتحوّلوش لروابط نهائياً.",
"external-link-pattern": "الربط التلقائي بمتتبع مشاكل خارجي",
@@ -1991,6 +1992,7 @@
"title-bracket-label-description": "ملي كيبدا عنوان بطاقة جديدة بـ [نص] ومن بعدو نص آخر، النص كيولّي علامة ديال البطاقة (كتتصاوب إلا ما كانتش فالبرد علامة بهاد السمية) وكيتحيّد من العنوان. مطفي: العنوان كيبقى كيف ما هو.",
"keep-add-card-form-open": "خلّي الفورمولير ديال إضافة بطاقة محلول من بعد ما تزيد بطاقة",
"keep-add-card-form-open-description": "مشعول: من بعد ما تتزاد بطاقة، الفورمولير كيبقى محلول بعنوان خاوي للبطاقة الجاية. مطفي: الفورمولير كيتسد غير تتزاد البطاقة.",
"show-focus-outline": "بين حدود التركيز",
"checklist-ding-sound": "شغّل صوت ملي كيتكمل عنصر ديال لائحة تاشيك",
"checklist-ding-sound-description": "شغّل نغمة قصيرة ملي تعلّم عنصر ديال لائحة تاشيك. راه مطفي من الأصل.",
"assignee": "محول‌شده",
+2
View File
@@ -1311,6 +1311,7 @@
"custom-login-logo-link-url": "স্বনিৰ্বাচিত লগইন ল'গ' সংযোগৰ URL",
"custom-help-link-url": "স্বনিৰ্বাচিত সহায় সংযোগৰ URL",
"text-below-custom-login-logo": "স্বনিৰ্বাচিত লগইন ল'গ'ৰ তলৰ পাঠ",
"login-background-color": "লগইন পৃষ্ঠাৰ পটভূমিৰ ৰং",
"automatic-linked-url-schemes": "স্বয়ংক্ৰিয়ভাৱে ক্লিক কৰিব পৰা স্বনিৰ্বাচিত URL আঁচনি। প্ৰতি শাৰীত এটা URL আঁচনি",
"automatic-linked-url-schemes-hint": "অবিকল্পিতভাৱে খালী থাকে, সেয়ে কেৱল ৱেব আৰু মেইল লিংকত ক্লিক কৰিব পাৰি। তালিকাভুক্ত প্ৰতিটো স্কীমে, যেনে thunderlink বা onenote, তাৰ বাবে পঞ্জীয়ন কৰা এপ্লিকেচন খোলে। javascript, data, vbscript আৰু একেধৰণৰ স্কীম কেতিয়াও লিংক কৰা নহয়।",
"external-link-pattern": "বাহিৰৰ সমস্যা ট্ৰেকাৰলৈ স্বয়ংক্ৰিয় লিংক",
@@ -1991,6 +1992,7 @@
"title-bracket-label-description": "নতুন কাৰ্ডৰ শিৰোনাম [লিখনি]ৰে আৰম্ভ হৈ তাৰ পিছত আৰু লিখনি থাকিলে, লিখনি কাৰ্ডখনৰ লেবেল হয় (ফলকত সেই নামৰ লেবেল নাথাকিলে সৃষ্টি কৰা হয়) আৰু শিৰোনামৰ পৰা আঁতৰোৱা হয়। বন্ধ: শিৰোনাম যেনে আছে তেনেকৈ থাকে।",
"keep-add-card-form-open": "কাৰ্ড যোগ কৰাৰ পিছত কাৰ্ড যোগ কৰক ফৰ্মখন খোলা ৰাখক",
"keep-add-card-form-open-description": "সক্ৰিয়: কাৰ্ড যোগ কৰাৰ পিছত পৰৱৰ্তী কাৰ্ডৰ বাবে খালী শিৰোনামৰ সৈতে ফৰ্মখন খোলা থাকে। নিষ্ক্ৰিয়: কাৰ্ডখন যোগ হোৱাৰ লগে লগে ফৰ্মখন বন্ধ হয়।",
"show-focus-outline": "ফোকাছ আউটলাইন দেখুৱাওক",
"checklist-ding-sound": "চেকলিষ্ট বস্তু সম্পূৰ্ণ হ'লে শব্দ বজাওক",
"checklist-ding-sound-description": "চেকলিষ্ট বস্তু চেক কৰাৰ সময়ত চুটি শব্দ বজাওক। অবিকল্পিতভাৱে অফ।",
"assignee": "দায়িত্বপ্ৰাপ্ত",
+2
View File
@@ -1311,6 +1311,7 @@
"custom-login-logo-link-url": "URL del enllaz del logotipu personalizáu d’aniciu de sesión",
"custom-help-link-url": "URL del enllaz d’ayuda personalizáu",
"text-below-custom-login-logo": "Testu debaxo del logotipu personalizáu d’aniciu de sesión",
"login-background-color": "Color de fondu de la páxina d'aniciu de sesión",
"automatic-linked-url-schemes": "Esquemes d’URL personalizaos que tienen de poder calcase automáticamente. Un esquema d’URL per llinia.",
"automatic-linked-url-schemes-hint": "Balero por defeutu, polo que namás se pue calcar nos enllaces web y de corréu. Cada esquema llistáu, por exemplu thunderlink o onenote, abre l'aplicación rexistrada pa él. javascript, data, vbscript y esquemes asemeyaos nunca se convierten n'enllaces.",
"external-link-pattern": "Enllaz automáticu a un rastreador d'asuntos esternu",
@@ -1991,6 +1992,7 @@
"title-bracket-label-description": "Cuando'l títulu d'una tarxeta nueva entama con [Testu] siguíu de más testu, Testu conviértese nuna etiqueta de la tarxeta (críase si'l tableru nun tien nenguna con esi nome) y quítase del títulu. Desactiváu: el títulu caltiénse como ta.",
"keep-add-card-form-open": "Caltener abiertu'l formulariu d'amestar tarxeta dempués d'amestar una tarxeta",
"keep-add-card-form-open-description": "Activáu: dempués d'amestar una tarxeta, el formulariu sigue abiertu con un títulu baleru pa la tarxeta siguiente. Desactiváu: el formulariu ciérrase en cuantes s'amiesta la tarxeta.",
"show-focus-outline": "Amosar el contornu del focu",
"checklist-ding-sound": "Sonar cuando se completa un elementu de llista de verificación",
"checklist-ding-sound-description": "Sonar un tonu curtiu cuando marques un elementu de llista de verificación. Desactiváu por defeutu.",
"assignee": "Asignado",
+2
View File
@@ -1311,6 +1311,7 @@
"custom-login-logo-link-url": "Custom Login Logo chinu URL",
"custom-help-link-url": "Custom Help chinu URL",
"text-below-custom-login-logo": "qillqa below Custom Login Logo",
"login-background-color": "Login panka qhipa color",
"automatic-linked-url-schemes": "Aymar aruna: Custom URL Schemes which should automatically be clickable. One URL Scheme per line",
"automatic-linked-url-schemes-hint": "Qalltanxa chʼusawa, ukatwa web ukat correo electrónico chinuwinakaki limtʼasma. Listankir sapa esquema, sañäni thunderlink jan ukax onenote, jupatak qillqantat programa jistʼari. javascript, data, vbscript ukat ukham esquemanakax janipuniw chinuwir tukuyatäkiti.",
"external-link-pattern": "Autoenlace hacia jach'a tracker jan p'ampachaña",
@@ -1991,6 +1992,7 @@
"title-bracket-label-description": "Machaqa kartan p'iqi sutipax [Qillqa] ukamp qalltani ukatsti juk'amp qillqax arkani ukhaxa, Qillqax kartan unanchapaw tuku (pirqan uka sutin unanchax janiw utjkchi ukhax lurataw) ukatsti p'iqi sutit apsutaw. Jist'arata: p'iqi sutix kunjamatix ukhamaw qhipari.",
"keep-add-card-form-open": "Karta yapañ formulario jist'aratapa uñjaña, karta yapatatxa",
"keep-add-card-form-open-description": "Naktayata: karta yapatatxa, formulariox jist'aratakiw qhipa kartataki ch'usa sutimpi. Jiwt'ayata: formulariox jist'antasiwa karta yapatatxa.",
"show-focus-outline": "Qhanstayaña muyu uñachtʼayaña",
"checklist-ding-sound": "Checklist amtata phuqhasitaxa qhanañchaña uñstayaña",
"checklist-ding-sound-description": "Checklist amtata qillqasina taqi kunaru jisk'a qhanañcha uñstayi. Jan apnaqata.",
"assignee": "Lurañataki churata",
+2
View File
@@ -1311,6 +1311,7 @@
"custom-login-logo-link-url": "Fərdi Giriş Loqo Linki URL",
"custom-help-link-url": "Fərdi Yardım Linki URL",
"text-below-custom-login-logo": "Xüsusi Giriş Loqosunun altındakı mətn",
"login-background-color": "Giriş səhifəsinin fon rəngi",
"automatic-linked-url-schemes": "Avtomatik olaraq klik edilə bilən fərdi URL sxemləri. Hər sətir üçün bir URL sxemi",
"automatic-linked-url-schemes-hint": "Standart olaraq boşdur, buna görə yalnız veb və poçt keçidləri kliklənə bilər. Siyahıdakı hər sxem, məsələn, thunderlink və ya onenote, onun üçün qeydiyyatdan keçmiş tətbiqi açır. javascript, data, vbscript və oxşar sxemlər heç vaxt keçidə çevrilmir.",
"external-link-pattern": "Xarici xəta izləyicisinə avtomatik keçid",
@@ -1991,6 +1992,7 @@
"title-bracket-label-description": "Yeni kartın başlığı [Mətn] ilə başlayıb ardınca başqa mətn gəldikdə, Mətn kartın etiketi olur (lövhədə bu adda etiket yoxdursa, yaradılır) və başlıqdan silinir. Söndürülüb: başlıq olduğu kimi saxlanılır.",
"keep-add-card-form-open": "Kart əlavə edildikdən sonra Kart əlavə et formasını açıq saxla",
"keep-add-card-form-open-description": "Aktiv: kart əlavə edildikdən sonra forma növbəti kart üçün boş başlıqla açıq qalır. Deaktiv: kart əlavə edilən kimi forma bağlanır.",
"show-focus-outline": "Fokus konturunu göstər",
"checklist-ding-sound": "Yoxlama siyahısı elementi tamamlandıqda səs çal",
"checklist-ding-sound-description": "Yoxlama siyahısı elementini işarələyəndə qısa siqnal səsi çal. Standart olaraq söndürülüb.",
"assignee": "Təyin edilmiş şəxs",
+2
View File
@@ -1311,6 +1311,7 @@
"custom-login-logo-link-url": "Fərdi Giriş Loqo Linki URL",
"custom-help-link-url": "Fərdi Yardım Linki URL",
"text-below-custom-login-logo": "Xüsusi Giriş Loqosunun altındakı mətn",
"login-background-color": "Giriş səhifəsinin fon rəngi",
"automatic-linked-url-schemes": "Avtomatik olaraq klik edilə bilən fərdi URL sxemləri. Hər sətir üçün bir URL sxemi",
"automatic-linked-url-schemes-hint": "Standart olaraq boşdur, buna görə yalnız veb və poçt keçidləri kliklənə bilər. Siyahıdakı hər sxem, məsələn, thunderlink və ya onenote, onun üçün qeydiyyatdan keçmiş tətbiqi açır. javascript, data, vbscript və oxşar sxemlər heç vaxt keçidə çevrilmir.",
"external-link-pattern": "Xarici xəta izləyicisinə avtomatik keçid",
@@ -1991,6 +1992,7 @@
"title-bracket-label-description": "Yeni kartın başlığı [Mətn] ilə başlayıb ardınca başqa mətn gəldikdə, Mətn kartın etiketi olur (lövhədə bu adda etiket yoxdursa, yaradılır) və başlıqdan silinir. Söndürülüb: başlıq olduğu kimi saxlanılır.",
"keep-add-card-form-open": "Kart əlavə edildikdən sonra Kart əlavə et formasını açıq saxla",
"keep-add-card-form-open-description": "Aktiv: kart əlavə edildikdən sonra forma növbəti kart üçün boş başlıqla açıq qalır. Deaktiv: kart əlavə edilən kimi forma bağlanır.",
"show-focus-outline": "Fokus konturunu göstər",
"checklist-ding-sound": "Yoxlama siyahısı elementi tamamlandıqda səs çal",
"checklist-ding-sound-description": "Yoxlama siyahısı elementini işarələyəndə qısa siqnal səsi çal. Standart olaraq söndürülüb.",
"assignee": "Təyin edilmiş şəxs",
+2
View File
@@ -1311,6 +1311,7 @@
"custom-login-logo-link-url": "Fərdi Giriş Loqo Linki URL",
"custom-help-link-url": "Fərdi Yardım Linki URL",
"text-below-custom-login-logo": "Xüsusi Giriş Loqosunun altındakı mətn",
"login-background-color": "Giriş səhifəsinin fon rəngi",
"automatic-linked-url-schemes": "Avtomatik olaraq klik edilə bilən fərdi URL sxemləri. Hər sətir üçün bir URL sxemi",
"automatic-linked-url-schemes-hint": "Standart olaraq boşdur, buna görə yalnız veb və poçt keçidləri kliklənə bilər. Siyahıdakı hər sxem, məsələn, thunderlink və ya onenote, onun üçün qeydiyyatdan keçmiş tətbiqi açır. javascript, data, vbscript və oxşar sxemlər heç vaxt keçidə çevrilmir.",
"external-link-pattern": "Xarici xəta izləyicisinə avtomatik keçid",
@@ -1991,6 +1992,7 @@
"title-bracket-label-description": "Yeni kartın başlığı [Mətn] ilə başlayıb ardınca başqa mətn gəldikdə, Mətn kartın etiketi olur (lövhədə bu adda etiket yoxdursa, yaradılır) və başlıqdan silinir. Söndürülüb: başlıq olduğu kimi saxlanılır.",
"keep-add-card-form-open": "Kart əlavə edildikdən sonra Kart əlavə et formasını açıq saxla",
"keep-add-card-form-open-description": "Aktiv: kart əlavə edildikdən sonra forma növbəti kart üçün boş başlıqla açıq qalır. Deaktiv: kart əlavə edilən kimi forma bağlanır.",
"show-focus-outline": "Fokus konturunu göstər",
"checklist-ding-sound": "Yoxlama siyahısı elementi tamamlandıqda səs çal",
"checklist-ding-sound-description": "Yoxlama siyahısı elementini işarələyəndə qısa siqnal səsi çal. Standart olaraq söndürülüb.",
"assignee": "Təyin edilmiş şəxs",
+2
View File
@@ -1311,6 +1311,7 @@
"custom-login-logo-link-url": "Махсус инеү логотип һылтанмаһының URL-ы",
"custom-help-link-url": "Махсус ярҙам һылтанмаһының URL-ы",
"text-below-custom-login-logo": "Махсус инеү логотибы аҫтындағы текст",
"login-background-color": "Керү битенең фон төсө",
"automatic-linked-url-schemes": "Автоматик баҫыла торған махсус URL схемалары. Һәр юлға бер URL схемаһы",
"automatic-linked-url-schemes-hint": "Ғәҙәттә буш, шуға күрә веб һәм почта һылтанмаларына ғына баҫып була. Исемлектәге һәр схема, мәҫәлән thunderlink йәки onenote, уның өсөн теркәлгән ҡушымтаны аса. javascript, data, vbscript һәм оҡшаш схемалар бер ҡасан да һылтанмаға әйләндерелмәй.",
"external-link-pattern": "Тышҡы проблема күҙәтеүсеһенә автоматик һылтанма",
@@ -1991,6 +1992,7 @@
"title-bracket-label-description": "Яңы карточканың баш һүҙе [Текст] менән башланып, артынан башҡа текст килһә, Текст карточканың билдәһе була (тактала был исемле билдә булмаһа, булдырыла) һәм баш һүҙҙән алына. Һүндерелгән: баш һүҙ нисек бар, шулай ҡала.",
"keep-add-card-form-open": "Карточка өҫтәгәндән һуң «Карточка өҫтәү» формаһын асыҡ ҡалдырырға",
"keep-add-card-form-open-description": "Ҡабыҙылған: карточка өҫтәлгәндән һуң форма киләһе карточка өсөн буш исем менән асыҡ ҡала. Һүндерелгән: карточка өҫтәлеү менән форма ябыла.",
"show-focus-outline": "Фокус рамкаһын күрһәтергә",
"checklist-ding-sound": "Тикшереү исемлеге пункты тамамланғанда тауыш уйнат",
"checklist-ding-sound-description": "Тикшереү исемлегендәге пунктты билдәләгәндә кыска тауыш уйнат. Стандарт буйынса һүнгән.",
"assignee": "Яуаплы",
+2
View File
@@ -1311,6 +1311,7 @@
"custom-login-logo-link-url": "URL спасылкі карыстальніцкага лагатыпа ўваходу",
"custom-help-link-url": "URL карыстальніцкай спасылкі даведкі",
"text-below-custom-login-logo": "Тэкст пад карыстальніцкім лагатыпам уваходу",
"login-background-color": "Колер фону старонкі ўваходу",
"automatic-linked-url-schemes": "Карыстальніцкія схемы URL, якія павінны аўтаматычна станавіцца націскальнымі. Адна схема URL на радок",
"automatic-linked-url-schemes-hint": "Прадвызначана пустое, таму націскаць можна толькі на вэб-спасылкі і спасылкі электроннай пошты. Кожная пералічаная схема, напрыклад thunderlink або onenote, адкрывае зарэгістраваную для яе праграму. Для схем javascript, data, vbscript і падобных спасылкі ніколі не ствараюцца.",
"external-link-pattern": "Аўтаспасылка на знешні трэкер задач",
@@ -1991,6 +1992,7 @@
"title-bracket-label-description": "Калі назва новай карткі пачынаецца з [Тэкст], за якім ідзе іншы тэкст, Тэкст становіцца меткай карткі (ствараецца, калі на дошцы няма меткі з такой назвай) і выдаляецца з назвы. Выключана: назва застаецца як ёсць.",
"keep-add-card-form-open": "Пакідаць форму «Дадаць картку» адкрытай пасля дадання карткі",
"keep-add-card-form-open-description": "Уключана: пасля дадання карткі форма застаецца адкрытай з пустой назвай для наступнай карткі. Выключана: форма закрываецца, як толькі картка дададзена.",
"show-focus-outline": "Паказваць контур фокусу",
"checklist-ding-sound": "Прайграваць гук пры выкананні пункта спіса",
"checklist-ding-sound-description": "Прайграваць кароткі гук пры адзначэнні пункта спіса задач. Па змаўчанні выключана.",
"assignee": "Прызначаны",
+2
View File
@@ -1311,6 +1311,7 @@
"custom-login-logo-link-url": "URL на връзката за персонализираното лого при вход",
"custom-help-link-url": "URL на персонализираната връзка за помощ",
"text-below-custom-login-logo": "Текст под персонализираното лого при вход",
"login-background-color": "Цвят на фона на страницата за вход",
"automatic-linked-url-schemes": "Персонализирани URL схеми, които автоматично стават активни връзки. По една URL схема на ред",
"automatic-linked-url-schemes-hint": "По подразбиране е празно, така че могат да се отварят само уеб връзки и връзки за имейл. Всяка изброена схема, например thunderlink или onenote, отваря приложението, регистрирано за нея. javascript, data, vbscript и подобни схеми никога не стават активни връзки.",
"external-link-pattern": "Автоматично свързване с външна система за проследяване на проблеми",
@@ -1991,6 +1992,7 @@
"title-bracket-label-description": "Когато заглавието на нова карта започва с [Текст], последван от още текст, Текст става етикет на картата (създава се, ако таблото няма етикет с това име) и се премахва от заглавието. Изключено: заглавието се запазва както е.",
"keep-add-card-form-open": "Формата „Добави карта“ да остава отворена след добавяне на карта",
"keep-add-card-form-open-description": "Включено: след добавяне на карта формата остава отворена с празно заглавие за следващата карта. Изключено: формата се затваря веднага щом картата бъде добавена.",
"show-focus-outline": "Показване на контура на фокуса",
"checklist-ding-sound": "Изпълни звук, когато елемент от списъка за проверка е завършен",
"checklist-ding-sound-description": "Изпълни кратък звук при отбелязване на елемент от списъка за проверка. По подразбиране е изключено.",
"assignee": "Изпълнител",
+2
View File
@@ -1311,6 +1311,7 @@
"custom-login-logo-link-url": "मनपसंद लॉग इन लोगो कड़ी URL",
"custom-help-link-url": "मनपसंद मदद कड़ी URL",
"text-below-custom-login-logo": "मनपसंद लॉग इन लोगो के नीचे लिखाई",
"login-background-color": "लॉगिन पन्ना के पृष्ठभूमि रंग",
"automatic-linked-url-schemes": "मनपसंद URL योजना जे अपने-आप क्लिक करे लायक होखे। हर पाँति में एगो URL योजना",
"automatic-linked-url-schemes-hint": "डिफॉल्ट रूप से खाली बा, एहसे खाली वेब आ ईमेल के लिंक पर क्लिक कइल जा सकेला। सूची में दिहल हर लिंक स्कीम, जइसे thunderlink भा onenote, अपना खातिर पंजीकृत ऐप खोलेला। javascript, data, vbscript आ एह तरह के स्कीम के कबो क्लिक करे लायक लिंक ना बनावल जाला।",
"external-link-pattern": "बाहरी इश्यू ट्रैकर से खुदे-खुद लिंक",
@@ -1991,6 +1992,7 @@
"title-bracket-label-description": "जब नया कार्ड के शीर्षक [पाठ] से शुरू होखे आ ओकरा बाद अउरी लिखल होखे, त पाठ कार्ड के लेबल बन जाला (अगर बोर्ड में ओह नाम के लेबल ना होखे त बनावल जाला) आ शीर्षक से हटा दिहल जाला। बंद: शीर्षक जइसन बा ओइसने रहेला।",
"keep-add-card-form-open": "कार्ड जोड़ला के बाद कार्ड जोड़े वाला फॉर्म खुलल रखीं",
"keep-add-card-form-open-description": "चालू: कार्ड जोड़ला के बाद, अगिला कार्ड खातिर खाली शीर्षक के साथे फॉर्म खुलल रहेला। बंद: कार्ड जुड़ते फॉर्म बंद हो जाला।",
"show-focus-outline": "फोकस आउटलाइन देखाईं",
"checklist-ding-sound": "चेकलिस्ट आइटम पूरा होखला पर आवाज बजाईं",
"checklist-ding-sound-description": "चेकलिस्ट आइटम चेक करला पर छोट आवाज बजाईं। डिफॉल्ट रूप से बंद।",
"assignee": "जिम्मेदार",
+2
View File
@@ -1311,6 +1311,7 @@
"custom-login-logo-link-url": "URL blong Link blong Spesel Logo blong Go insaed",
"custom-help-link-url": "URL blong Spesel Link blong Help",
"text-below-custom-login-logo": "Raeting aninit long Spesel Logo blong Go insaed",
"login-background-color": "Kala blong baksaed blong pej blong login",
"automatic-linked-url-schemes": "Ol Spesel Fasin blong URL we yu save klik otomatik. Wan Fasin blong URL long wanwan laen",
"automatic-linked-url-schemes-hint": "Long fas seting, hem i emti, mekem se yu save klik long ol link blong web mo imel nomo. Evri kaen link long lis, olsem thunderlink o onenote, i openem program we oli rejistarem blong hem. javascript, data, vbscript mo ol kaen olsem oli neva kam ol link we yu save klik long olgeta.",
"external-link-pattern": "Otomatik link i go long wan aotsaed traka blong problem",
@@ -1991,6 +1992,7 @@
"title-bracket-label-description": "Taem taetol blong wan niu kad i stat wetem [Tekis] mo i gat moa tekis biaen, Tekis i kam wan mak blong kad (oli mekem sapos bod i no gat mak wetem nem ia) mo oli tekemaot long taetol. Of: taetol i stap olsem we i stap.",
"keep-add-card-form-open": "Livim fom blong Adem kad i stap open afta we yu adem wan kad",
"keep-add-card-form-open-description": "On: afta we yu adem wan kad, fom i stap open wetem wan taetel we i emti blong nekis kad. Of: fom i satem stret taem we kad i kam insaed.",
"show-focus-outline": "Soem aot-lain blong fokas",
"checklist-ding-sound": "Plei wan saon taem wan samting long jeklis i finis",
"checklist-ding-sound-description": "Plei wan sotfala saon taem yu makem wan samting long jeklis. Long stat, hem i of.",
"assignee": "Man we i gat wok",
+2
View File
@@ -1311,6 +1311,7 @@
"custom-login-logo-link-url": "Donni logo lasigiden URL kɛrɛnkɛrɛnnen",
"custom-help-link-url": "Dɛmɛ lasigiden URL kɛrɛnkɛrɛnnen",
"text-below-custom-login-logo": "Sɛbɛnni donni logo kɛrɛnkɛrɛnnen duguma",
"login-background-color": "Kuntilenni gafe jukɔrɔ kulɛri",
"automatic-linked-url-schemes": "URL cogoya kɛrɛnkɛrɛnnen minnu ka kan ka digi yɛrɛ ma. URL cogoya kelen sira kelen kelen bɛɛ la",
"automatic-linked-url-schemes-hint": "A daminɛ na, a bɛ to kolon, o la i bɛ se ka web ani imɛli siriliw dɔrɔn digi. Sɛbɛnni kɔnɔ sikiima kelen-kelen bɛɛ, misali la thunderlink walima onenote, bɛ porogaramu min tɔgɔ sɛbɛnna a kama, o dayɛlɛ. javascript, data, vbscript ani sikiimaw minnu bɛ i nʼa fɔ olu, olu tɛ kɛ siriliw ye abada.",
"external-link-pattern": "Sɛbɛnni ka baara kɛ ni kunbɛnjuru kunkan ka gafe ye kɔfɛ",
@@ -1991,6 +1992,7 @@
"title-bracket-label-description": "Ni karti kura kunnafoni kuntigi daminɛna ni [Sɛbɛn] ye ani sɛbɛn wɛrɛ b'a kɔfɛ, Sɛbɛn bɛ kɛ karti in taamasiyɛn ye (a bɛ dila ni walan tɛ taamasiyɛn si ye o tɔgɔ la) ani a bɛ bɔ kunnafoni kuntigi la. A dabilalen: kunnafoni kuntigi bɛ to i n'a fɔ a bɛ cogo min na.",
"keep-add-card-form-open": "Karti fara a kan fɔrimu to a dayɛlɛlen na karti farali kɔfɛ",
"keep-add-card-form-open-description": "A bɛ baara la: ni karti farala a kan, fɔrimu bɛ to a dayɛlɛlen na ni tɔgɔ lankolon ye karti nata kama. A tɛ baara la: fɔrimu bɛ datugu ni karti farala a kan dɔrɔn.",
"show-focus-outline": "Fokisi lamini jira",
"checklist-ding-sound": "Kanfa mankan tuma min na checklist fɛn dafara",
"checklist-ding-sound-description": "Kanfa mankan surunman tuma min na i bɛ checklist fɛn sɛbɛn. A tɛ baara la ka bɔ a bɔyɔrɔ la.",
"assignee": "Baarakɛla",
Loaded 100 of 458 files, more files were not shown because too many files have changed in this diff. Show more