Compare commits

..
250 Commits
Author SHA1 Message Date
github-actions[bot] 9c51391a75 Bump versions for v9.83 2026-07-09 11:57:12 +00:00
Lauri Ojansivu 2ed13a1593 Updated ChangeLog. 2026-07-09 14:48:08 +03:00
Lauri Ojansivu ca50c9d1aa Updated ChangeLog. 2026-07-09 14:45:09 +03:00
Lauri Ojansivu 548d7b483e Updated ChangeLog. 2026-07-09 14:42:48 +03:00
Lauri Ojansivu 71bbc9f67e Updated ChangeLog. 2026-07-09 14:09:14 +03:00
Lauri Ojansivu 05fd6fd3a5 Fix #3624 swimlane REST regression: await the async board.swimlanes()
The swimlanes CRUD e2e test (23-rest-api-more.e2e.js:209) regressed on all
browsers. Root cause: on the server ReactiveCache.getSwimlanes is async, so
board.swimlanes() returns a Promise in the REST handler. The #3624 change read
board.swimlanes().map(s => s.sort); .map on a Promise throws, the insert never
ran, the error was swallowed by the handler's catch (returned as 200 with no
_id), and the test read .title off a null swimlane.

The earlier "move the require to a top-level import" commit was not the real
cause (the same module.exports import pattern works server-side, e.g.
ruleDeletePermission in rulesButton.js). The actual fix is to await
board.swimlanes() before mapping. This also means API-created swimlanes now get
a real max(existing sort)+1 sort — previously the un-awaited `.length` yielded
undefined, so they were stored with no sort at all, which is the #3624 symptom.

Verified: the swimlane test passed in every kept local run through
2026-07-09_02-38-07 and failed starting 09-52-25 (right after the #3624 change),
confirming the regression window; the tests/swimlaneSort.test.cjs unit test
still passes.
2026-07-09 14:09:14 +03:00
github-actions[bot] e8939bc1e6 Bump versions for v9.82 2026-07-09 08:13:59 +00:00
Lauri Ojansivu bc8d58011c Prepare v9.82 release 2026-07-09 11:08:40 +03:00
Lauri Ojansivu 0bf8cae81c Updated ChangeLog. 2026-07-09 10:43:25 +03:00
Lauri Ojansivu 984ec160da Fix Playwright CI failures from #3624 and #4236
Two e2e tests failed across Chromium/Firefox/WebKit after this release's changes:

- 23-rest-api-more.e2e.js:209 (swimlanes: create, get, edit, delete): the
  #3624 change referenced models/lib/swimlaneSort via a function-scoped
  require('/models/lib/swimlaneSort'); the new module was not reliably bundled/
  resolved that way, so POST /api/boards/:id/swimlanes threw, was caught, and
  returned an error object with no _id — the test then read title off a null
  swimlane. Import nextSwimlaneSort at the module top level, matching every
  other import in server/models/swimlanes.js, so it is statically bundled.

- 02-cards-open-view.e2e.js:93 (editing the card title does not break the card):
  #4236 intentionally made plain Enter insert a newline in the card title
  (consistent with the description), with Ctrl/Cmd+Enter saving. The CardPage
  page object still saved with plain Enter, so the title never persisted and the
  read timed out. Save with Control+Enter (sends ctrlKey on every platform,
  which the save handler accepts).

Server-side Mocha, import regression, and Node E2E jobs already passed; this
makes the Playwright jobs green too.
2026-07-09 10:26:03 +03:00
Lauri Ojansivu a2df80e64d Updated ChangeLog. 2026-07-09 10:09:58 +03:00
Lauri Ojansivu 3aaf8f9b61 Fix #6444: RTL — typing a card title no longer garbles other lists' minicard titles
In an RTL language (e.g. Arabic) the board root is dir="rtl". Both the minicard
title (a dir="auto" .viewer) and the add-card composer textarea are dir="auto"
but had no bidirectional isolation, so they shared the surrounding bidi context.
Typing a strong RTL character into one list's composer re-resolved that shared
context and visibly reflowed the displayed minicard titles of the other lists
(no underlying data change — it reverted on refresh).

Each minicard title and the composer textarea now use `unicode-bidi: isolate`,
making them self-contained bidirectional runs that cannot perturb one another.
The isolation is locked in (positive + negative) by tests/minicardBidiIsolation.test.cjs;
full browser RTL behavior is covered by tests/playwright/specs/18-rtl-layout.e2e.js.

Thanks to xet7.
2026-07-09 10:09:32 +03:00
Lauri Ojansivu 1dfc223ca0 Updated translations 2026-07-09 09:48:44 +03:00
Lauri Ojansivu 604cc455e0 Updated ChangeLog. 2026-07-09 09:45:53 +03:00
Lauri Ojansivu e85072a14a Updated ChangeLog. 2026-07-09 09:45:53 +03:00
Lauri Ojansivu 1621f3784a Fix #5439: list scrollbar is now always visible on desktop and mobile browsers
List bodies used overflow-y:auto, so on platforms/browsers with overlay
scrollbars (macOS, iOS, Android, Firefox) the scrollbar auto-hid when idle and
there was no persistent affordance that a list is scrollable.

The list body now shows a scrollbar across all engines: overflow-y:scroll to
always reserve and show the track, ::-webkit-scrollbar width + thumb/track for
Chrome/Safari/Edge and mobile WebKit/Blink, scrollbar-width + scrollbar-color
for Firefox/Gecko (desktop and Android), and scrollbar-gutter:stable so content
does not shift. The cross-browser rules come from a Meteor-free builder
models/lib/scrollbarCss.js and are applied in client/components/lists/list.css;
both the builder contract and the applied CSS are unit tested (positive +
negative, including that no hide values sneak back in) in
tests/scrollbarCss.test.cjs.

Thanks to xet7.
2026-07-09 09:45:53 +03:00
Lauri Ojansivu ba6ad20a30 Updated ChangeLog. 2026-07-09 09:45:53 +03:00
Lauri Ojansivu 3847157753 Fix #3185: copying a card (or template) now copies its subtasks' checklists
When a card was copied — including generating a card from a template — each
subtask was inserted as a bare card document. The parent card's checklists were
copied, but the subtasks' checklists (and their items) were never copied, so
the new card's subtasks came out empty.

Card.copy() now copies each subtask's checklists (via Checklist.copy, which also
copies the checklist items) onto the newly inserted subtask. The subtask copy
document is built by a Meteor-free helper models/lib/subtaskCopy.js
(buildCopiedSubtaskFields — re-homes the subtask under the new parent and onto
the destination board; subtaskCopyChildKinds declares 'checklists' must be
reproduced), unit tested (positive + negative) in tests/subtaskCopy.test.cjs.

Thanks to ramses345 and xet7.
2026-07-09 09:45:53 +03:00
Lauri Ojansivu 4e77133aca Updated ChangeLog. 2026-07-09 09:45:53 +03:00
Lauri Ojansivu 77c696e37b Updated ChangeLog. 2026-07-09 09:45:53 +03:00
Lauri Ojansivu e3f17be14d Fix #3624: new_swimlane REST API appends the swimlane last (and accepts a sort)
POST /api/boards/:boardId/swimlanes set the new swimlane's sort to
board.swimlanes().length (a count). When the existing swimlanes' sort values
were not a contiguous 0..n-1 run (after deletions/reordering, or swimlanes
created top-first with decreasing sorts), the count collided with or preceded
existing sorts and the new swimlane appeared FIRST instead of last.

The endpoint now computes the sort with nextSwimlaneSort(): max(existing sort)+1
for a reliable append (0 on an empty board), and honors an optional explicit
`sort` in the request body (the enhancement the reporter asked for). The pure
logic lives in models/lib/swimlaneSort.js, unit tested (positive + negative) in
tests/swimlaneSort.test.cjs.

Thanks to tamasberesoebb and xet7.
2026-07-09 09:45:53 +03:00
Lauri Ojansivu 5ca4ebe09a Updated ChangeLog. 2026-07-09 09:45:53 +03:00
Lauri Ojansivu 3117e419bb Fix #3606: activity feed no longer shows "edited/deleted comment undefined"
The activity feed rendered "edited comment undefined" / "deleted comment
undefined" for comment edits and deletions. The template passed the comment's
ID into the "%s" of activity-editComment / activity-deleteComment: on older
activities the id was absent (literally "undefined"), and a deleted comment's
CardComment document is already removed so it cannot be looked up live either.

The edit and delete activities now store the comment text (commentText), and a
new Activities.commentDisplayText() helper picks the stored text, else the live
comment text (edit case), else an empty string — so the feed never renders the
word "undefined". The template shows the (sanitized) comment text. The
text-selection logic is a Meteor-free helper models/lib/commentActivity.js,
unit tested (positive + negative) in tests/commentActivity.test.cjs.

Thanks to janchuelo and xet7.
2026-07-09 09:45:53 +03:00
Lauri Ojansivu 3eba4aa782 Updated ChangeLog. 2026-07-09 09:45:53 +03:00
Lauri Ojansivu 127f1fb04e Updated ChangeLog. 2026-07-09 09:45:53 +03:00
Lauri Ojansivu 14b4f5b5ed Fix #4249: filter by card title now works for renamed linked cards
Renaming a linked card (or linked-board card) wrote the new title only to the
linked target (via getRealId()/linkedId), leaving the linking card's OWN title
field stale. Filter-by-title queries each card document's own title field, so a
linked card silently dropped out of title filters after being renamed.

Card.setTitle() now also writes the linking card's own title (in addition to the
linked target), keeping it filterable while preserving the existing mirror
behavior. The set of documents to update is a Meteor-free helper
models/lib/linkedCardTitle.js (computeTitleUpdateTargets), unit tested
(positive + negative) in tests/linkedCardTitle.test.cjs.

Thanks to Ben0it-T and xet7.
2026-07-09 09:45:53 +03:00
Lauri Ojansivu e6a4abd394 Updated ChangeLog. 2026-07-09 09:45:53 +03:00
Lauri Ojansivu 25cdc4db0d Fix #4494: Creating a board from a template no longer breaks subtasks
Creating a board from a template copies the template board. The copy inherited
the template's subtasksDefaultBoardId / dateSettingsDefaultBoardId verbatim.
When those pointed at the template board itself, subtasks created on the NEW
board were dropped onto the TEMPLATE board, and the subtask card linked back
across boards to the parent.

Board.copy() now repoints any subtasks/date-settings default that referenced
the source board to the new copy, and clears the paired list id so it self-heals
to a list on the new board. Defaults that deliberately point at another (shared)
board are left untouched. The remap logic is a Meteor-free helper
models/lib/boardCopyDefaults.js, unit tested (positive + negative) in
tests/boardCopyDefaults.test.cjs.

Thanks to Xilef11 and xet7.
2026-07-09 09:45:53 +03:00
Lauri Ojansivu c062d93d57 Updated ChangeLog. 2026-07-09 09:45:53 +03:00
Lauri Ojansivu e014d5c816 Fix #4394: Register / Forgot Password links stay hidden after a failed login
Security: with "Self Registration" and "Forgot Password" disabled in the Admin
Panel, the Register (.at-signup-link) and Forgot Password (.at-pwd-link) links
were hidden only by a one-shot jQuery .hide() in userFormsLayout.onRendered.
When a login failed (notably the LDAP "User not Found" path), useraccounts
re-rendered the form and recreated those link nodes without the inline
display:none, so the links reappeared and exposed registration / password-reset
flows that were meant to be off.

The disable state is now also reflected as a class on the stable <body>
ancestor (registration-disabled / forgotpwd-disabled) with matching CSS in
userForm.css. Because the class sits on <body>, the rule re-hides every copy of
the links that useraccounts recreates after a failed login, instead of relying
on a single inline style that the re-render dropped.

Thanks to Alsterdetektive1 and xet7.
2026-07-09 09:45:53 +03:00
Lauri Ojansivu 84ab805ec1 Fix #4055: add ISO week-number regression test (already correct in current code)
#4055 reported the calendar/date-badge week number was one week too high
(Mon-Sat) and two too high (Sunday) for 2021-10-25..31 (ISO week 43). That was
the old moment-based calculation. The current native, DST-safe
dateUtils.getISOWeek() computes these correctly (verified across a DST timezone).
Adds tests/isoWeek.test.cjs pinning the exact reported dates so the off-by-one/
two cannot silently return.

Thanks to marcungeschikts and xet7.
2026-07-09 09:45:53 +03:00
Lauri Ojansivu a9a5ac7a07 Fix #4236: Enter adds a new line in the card title (consistent with description)
The card title textarea submitted on plain Enter (only Shift+Enter made a new
line), while the card description textarea and the shared inlinedForm insert a
new line on plain Enter and only save on Ctrl/Cmd+Enter. So Enter could not add
a new line in the title, an inconsistent experience.

The title keydown handler now uses a shared, Meteor-free rule isSubmitKey()
(models/lib/editorSubmitKey.js): submit ONLY on Enter together with Ctrl or Cmd;
plain Enter and Shift+Enter fall through to the textarea default (a new line).
This matches the description field exactly. Unit tested (positive + negative) in
tests/editorSubmitKey.test.cjs.

Thanks to listenerri and xet7.
2026-07-09 09:45:53 +03:00
Lauri Ojansivu 0119e62da5 Fix #4653: LDAP username with a hyphen no longer becomes a dot
With LDAP_UTF8_NAMES_SLUGIFY enabled, sync.js slugified the username with
limax(text, { separator: '.' }) to transliterate UTF-8 names. limax replaces
every run of non-alphanumeric characters — including hyphens — with the
separator, so an LDAP username like "p.parta-partb" was turned into
"p.parta.partb". The generated Wekan username no longer matched and the user
could not log in.

slug() now slugifies each hyphen-separated segment on its own and rejoins
them with '-', so hyphens (and existing dots) survive while UTF-8 characters
in each segment are still transliterated. The hyphen-preserving logic is a
Meteor/limax-free helper packages/wekan-ldap/server/usernameSlug.js
(slugifyPreservingHyphens, with the transliterate function injected), unit
tested (positive + negative) in tests/ldapUsernameSlug.test.cjs.

Thanks to RowhamD and xet7.
2026-07-09 09:45:53 +03:00
Lauri Ojansivu 284d4b25ea Updated ChangeLog. 2026-07-09 09:45:53 +03:00
Lauri Ojansivu 75df523ef0 Fix #4946: Calendar week numbers now respect the defined start day of week
In the board Calendar view the week-number column was numbered from Sunday
regardless of the user's "start day of week" setting: setting Monday moved
the grid (via firstDay) but the week numbers still referred to Sunday-start
weeks. FullCalendar's default weekNumberCalculation is locale-based, so it
ignored firstDay.

The calendar now passes weekNumberCalculation that computes the number with
weekNumberByFirstDay(date, firstDay) from the same firstDay used to lay out
the grid, so the number matches the displayed week. The Meteor-free week math
lives in models/lib/weekStart.js and is unit tested (positive + negative,
including a case where Monday-start and Sunday-start must differ) in
tests/weekStart.test.cjs.

Thanks to helioguardabaxo and xet7.
2026-07-09 09:45:53 +03:00
Lauri Ojansivu e5c844fca7 Fix #4881: Due this week / next week filter now respects the start day of week
The "Due this week" date filter selected the wrong week (it showed next
week's cards) and ignored the user's configured start day of week.

Root cause: relativeWeek() derived its window from startOf(now(), 'week'),
but the native dateUtils.startOf() has no 'week' case, so it returned the
date unchanged. The window therefore started at "today + startDayOfWeek"
(next week) instead of the beginning of the current week, and never took the
configured start weekday into account.

Fix:
- New Meteor-free helper models/lib/weekStart.js computes startOfWeek(),
  weekRange() and weekNumberByFirstDay() for an arbitrary start day of week
  (0=Sunday..6=Saturday, default Monday). Unit tested (positive + negative)
  in tests/weekStart.test.cjs, including a guard that "this week" must
  contain today.
- client/lib/filter.js relativeWeek() now uses weekRange(now(), weekStartDay,
  0|1) so "this week" and "next week" are the correct, non-overlapping
  Monday..Sunday (or configured) windows.
- The this/next-week buttons now toggle per week: clicking the active one
  clears it, clicking the other switches to it (the old code reset on either
  state, so you could not switch directly from this week to next week).

Thanks to mimZD and xet7.
2026-07-09 09:45:53 +03:00
Lauri Ojansivu 8a84707726 Fix #4978: Board background now updates when switching boards via favorites bar
Switching directly from one board to another using the favorites bar reused
the same boardBody template instance (currentBoard went A -> B, never null),
so boardBody.onRendered never fired again and the one-shot
Utils.setBackgroundImage() call never re-ran. The previous board's inline
background:url(...) on .board-wrapper stayed on screen. Switching via the
"all boards" list worked only because it destroys and re-creates boardBody.

Two-part fix:
- boardBody.onRendered now calls Utils.setBackgroundImage() inside a reactive
  this.autorun(), so it recomputes on every board switch (and background edit).
  It performs no DB writes, so there is no autorun write loop.
- setBackgroundImage() now clears any stale inline background when the new
  board has no image, instead of only ever setting one. The decision is a
  Meteor-free helper models/lib/boardBackground.js (computeBoardBackground),
  unit tested (positive + negative) in tests/boardBackground.test.cjs. Board
  color keeps being applied reactively via the .board-wrapper colorClass.

Thanks to dasarne and xet7.
2026-07-09 09:45:53 +03:00
Lauri Ojansivu 9f7eac158e Fix #5536: Automated rule can now move/link card to a different board
The rules move-card-to-top/bottom (on another board) and link-card-to-another-board
actions failed in two ways for boards the rule creator did not own:

- The action showed BLANK after creating the rule: the wizard inserted the
  trigger/action/rule with three optimistic client Collection.insert() calls
  that landed in minimongo limbo once the wizard subscription stopped (and the
  client allow/deny rules rejected them outright for non-owner members). These
  now create the rule through the server rules.createRule method, which keeps
  the action's own destination boardId.

- Execution crashed with a generic Internal Server Error: the destination
  swimlane fallback dereferenced ._id on a possibly-undefined swimlane titled
  exactly Default, which does not exist when the destination board's default
  swimlane was renamed, translated, or the board had no such swimlane.
  Resolution now uses the board's real default swimlane and a Meteor-free
  resolver (models/lib/ruleActionResolve.js) that returns empty string instead
  of throwing. The same guard is applied to the createCard and linkCard actions.
  Unit tested (positive + negative) in tests/ruleActionResolve.test.cjs.

Thanks to DarthKillian and xet7.
2026-07-09 09:45:53 +03:00
github-actions[bot] 9b729b502e Bump versions for v9.81 2026-07-09 00:17:50 +00:00
Lauri Ojansivu cef965f73f Updated ChangeLog. 2026-07-09 03:08:00 +03:00
Lauri Ojansivu 43dd98f8a1 Translate remaining English strings in fi.i18n.json to Finnish
Translate the strings that were still in English (mostly keys added this
release: the Rules Workflow trigger/action palette, the relative-date
unit selector, the custom color-wheel label, the Activities hide toggle
and the changed-description activity), using the terminology already used
in the existing Finnish translations.
2026-07-09 02:30:16 +03:00
Lauri Ojansivu 2cd772fcf5 Updated translations. 2026-07-09 02:25:49 +03:00
Lauri Ojansivu eaa1d823de Updated ChangeLog. 2026-07-09 02:19:52 +03:00
Lauri Ojansivu 1a41c72c21 Update CHANGELOG commit links to post-rebase hashes 2026-07-09 02:17:01 +03:00
Lauri Ojansivu 7943a17432 Updated ChangeLog. 2026-07-09 02:14:07 +03:00
Lauri Ojansivu d41e17d6a0 Fix #5351: auto-add users to organizations matching their email domain on sign-up.
The Organization setting "Automatically add users with the domain name"
(org.orgAutoAddUsersWithDomainName) was never read at sign-up, so a user whose
email domain matched an organization was never added to it.

Add a pure helper models/lib/orgAutoAddByDomain.js (orgsToAutoAddForEmail) that
returns the ids of orgs whose configured domain exactly matches an email domain
(case-insensitive, exact — no subdomain, empty domain matches nothing), and wire
it into Accounts.onCreateUser so every non-admin sign-up path (password, LDAP,
invitation, new OIDC user) gains the matching org memberships using the same
{ orgId, orgDisplayName } shape used elsewhere, without duplicating existing
memberships. Add tests/orgAutoAddByDomain.test.cjs.
2026-07-09 02:14:07 +03:00
Lauri Ojansivu 4ec2cbcd5a Updated ChangeLog. 2026-07-09 02:14:07 +03:00
Lauri Ojansivu 1ce2d8cc88 Fix #5369: Activities show/hide control is now a clear eye / eye-slash icon toggle.
Replace the counterintuitive unlabeled material toggle switch on the card
details Activities panel with an eye / eye-slash icon toggle: open eye means
activities are shown, crossed eye means they are hidden. Clicking flips the
state, the icon reflects the current visibility, and the tooltip states the
action (Show activities / Hide activities). The same eye pattern from the
login/register password-visibility toggle is reused for consistency.

For consistency the board sidebar Activities toggle (same showActivities
state) is switched from the check / empty-square icon to the same eye /
eye-slash icon toggle.
2026-07-09 02:14:07 +03:00
Lauri Ojansivu 8f76c7b2b0 Updated ChangeLog. 2026-07-09 02:14:07 +03:00
Lauri Ojansivu b07c4d0a7b Feature #5394: sort the Link-card popup Cards dropdown alphabetically by title.
The "Link to this card" popup Cards pull-down previously listed cards in
board sort order, making a card hard to find on boards with many cards.
Sort it alphabetically by title (case-insensitive, locale/numeric aware)
via a small pure helper sortCardsByTitle, with unit tests. Thanks to xet7.
2026-07-09 02:14:07 +03:00
Lauri Ojansivu 5b43b7159e Updated ChangeLog. 2026-07-09 02:14:07 +03:00
Lauri Ojansivu 7ce71dbb2e Feature #5396: edit Lists (title, color) via the REST API + api.py commands.
The REST endpoint PUT /api/boards/:boardId/lists/:listId already accepted
title, color, starred and wipLimit, but the color was stored unvalidated. It
now validates the color with normalizeListColor (a named palette color OR a
custom #rrggbb hex) and rejects an unknown color with a clear 400 instead of
silently storing None or hanging. The pure field/validation logic is extracted
to a Meteor-free helper models/lib/listApiUpdate.js so it is unit testable
without a running server (tests/listApiUpdate.test.cjs).

api.py gains two commands mirroring editcard/editcardcolor:
editlist BOARDID LISTID NEWLISTTITLE and
editlistcolor BOARDID LISTID COLOR, with usage lines in the help block.

Thanks to C0rn3j and xet7.
2026-07-09 02:14:07 +03:00
Lauri Ojansivu d615e599f4 Updated ChangeLog. 2026-07-09 02:14:07 +03:00
Lauri Ojansivu aad00cdba2 Fix #5442: outgoing webhooks now include the label name on add/remove label
Labels are embedded in the board document, not a separate collection, but the
Activities label() helper looked the label id up in the Cards collection and
always returned undefined. So the addedLabel/removedLabel webhook and
notification params never got a label value and the __label__ token in the
message text rendered as a bare, generic "label" with no name.

Resolve the label from the already-loaded board via getLabelById, and add a
pure labelDisplayName() helper (name -> color -> id fallback) so a nameless
label is shown by its color, matching the UI. Add a plain-Node unit test.
2026-07-09 02:14:07 +03:00
Lauri Ojansivu 07800da9af Updated ChangeLog. 2026-07-09 02:14:07 +03:00
Lauri Ojansivu da0d338e33 Fix #5482: adding/editing a card description fires the outgoing webhook
Card description changes did not create an activity, so no outgoing webhook
fired (unlike title/dueAt changes, whose Cards before.update hooks log an
activity). Log an a-changedDescription activity in the Cards before.update hook,
mirroring the title-change activity, so the existing Activities.after.insert
webhook hook fires. Fires on first-time set and later edits, not on no-op /
empty->empty saves. Pure descriptionChanged helper + tests, English i18n and
activity rendering.
2026-07-09 02:14:07 +03:00
Lauri Ojansivu c3c091635d Updated ChangeLog. 2026-07-09 02:14:07 +03:00
Lauri Ojansivu 8c499823cb Fix: the Rules Workflow view is now fully translatable via i18n.
The workflow trigger/action palette rendered hardcoded English labels
regardless of the UI language. Palette entries now carry an i18n labelKey
translated at render time via TAPi18n.__, reusing existing rule keys where
they fit and adding new r-w-* keys for workflow-only labels. Also routed
the slot clear tooltip through the existing r-remove key.
2026-07-09 02:14:07 +03:00
Lauri Ojansivu 1844425e8f Updated ChangeLog. 2026-07-09 02:14:07 +03:00
Lauri Ojansivu 68dd14140f Fix: deleting a board rule no longer fails with "Access denied [403]"
Deleting a rule ran three separate client-side Collection.remove() calls
(Rules + Triggers + Actions). Each is gated by a per-collection allow() rule
that resolves the board from that document own boardId via
Boards.findOneAsync(doc.boardId). When a trigger/action document has no
resolvable boardId (legacy rules created before boardId was denormalised, or
docs not published to the client), the board resolves to null and
allowIsBoardAdmin returns false, so Meteor rejects the mutation with 403
"Access denied" and the delete fails even for a legitimate board admin.

Route rule deletion through a new server Meteor method rules.deleteRule, which
authorizes ONCE (active board admin or site admin) and removes the rule, its
trigger and its action server-side (bypassing the brittle client allow/deny).
The permission decision is extracted into the Meteor-free helper
models/lib/ruleDeletePermission.js and covered by
tests/ruleDeletePermission.test.cjs. No permission is loosened: only board
admins and site admins may delete, matching the board-admin gate used to
create a rule.
2026-07-09 02:14:07 +03:00
Lauri Ojansivu 1ba32f5269 Updated ChangeLog. 2026-07-09 02:14:07 +03:00
Lauri Ojansivu 30f36a7f31 Feature #5514: custom color-wheel (RGB/hex) picker with automatic readable text contrast.
Add a native color wheel (<input type="color">) alongside the existing fixed
named-color swatches in the color pickers for card labels, swimlanes, lists and
cards, so any #rrggbb color can be chosen. The schema color fields accept a
custom hex in addition to the named palette (via a validation that allows the
named values or a ^#[0-9a-fA-F]{6}$ hex), and normalizeListColor now passes hex
through, so existing named-color data keeps working. When a stored value is a
hex, elements apply an inline background-color instead of the card-details- /
swimlane- / list-header- / minicard- CSS class.

A new pure, Meteor-free helper models/lib/contrastColor.js computes a readable
text color from sRGB relative luminance (white on dark, black on light),
maps the named palette to hex, and validates/normalizes hex; it is applied as
an inline text color wherever a chosen color is a background behind text (label
chips, swimlane/list headers, minicard, card details header). Covered by
tests/contrastColor.test.cjs (20 assertions).
2026-07-09 02:14:07 +03:00
Lauri Ojansivu 20b9c9ddd6 Updated ChangeLog. 2026-07-09 02:14:07 +03:00
Lauri Ojansivu 0e9aed5a4b Fix #5510: adding a board label via the REST API no longer errors/hangs
PUT /api/boards/:boardId/labels only wrote an HTTP response inside
`if (hasOwnProperty(req.body, "label"))` and read req.body.label.color/.name
unconditionally. Two consequences:

- A body without a `label` key fell through the `if` and NEVER sent a response,
  so the request hung until the client timed out (the reported "timeout").
- A body where `label` was a bare string (e.g. { "label": "labelName" }, the
  exact reported payload) produced name === undefined / color === undefined and
  pushed a schema-invalid label via Boards.direct, returning 200 for garbage.
- The catch returned { code: 200, data: error }, masking real failures as
  success (same anti-pattern already fixed for the board create/delete handlers).

The handler now returns a 404 for an unknown board, delegates input handling to
a new pure, Meteor-free helper models/lib/restLabel.js (buildBoardLabel), and
always sends a JSON response: 2xx for a well-formed create (documented contract
unchanged), 400 with a clear message for bad input, or the real error status in
the catch. buildBoardLabel accepts { label: { name, color } } (color defaulted
when absent, rejected when unknown) and, for convenience, a bare string taken as
the label name. Added tests/restLabel.test.cjs covering these cases.
2026-07-09 02:14:07 +03:00
Lauri Ojansivu 27d8468be0 Updated ChangeLog. 2026-07-09 02:14:07 +03:00
Lauri Ojansivu d01526bbff Fix #5604: CSV export no longer crashes on boards with dangling references.
Exporter.buildCsv resolved each card list/swimlane/owner/member/assignee/label/
customField by id and read a property directly on the result of Array.find(),
which is undefined when the referenced document was deleted. Large/old boards
often contain such dangling references, so the read threw "Cannot read property
title of undefined" and crashed the whole CSV export request. Extracted the
per-card row builder into a pure, null-safe helper models/lib/exporterCsvRow.js
(unit-testable without Meteor) that emits a blank cell for a missing reference
while keeping identical output for well-formed cards. Added
tests/exporterCsvRow.test.cjs.
2026-07-09 02:14:07 +03:00
Lauri Ojansivu ab3b48b334 Updated ChangeLog. 2026-07-09 02:14:07 +03:00
Lauri Ojansivu b0da9097c1 Fix #5656: Calendar view now honors the active board filters.
The Calendar view queried cards with only a board+date selector via
Board.cardsInInterval()/cardsDueInBetween() and ignored the active board
Filter (member / assignee / due-date / label / custom-field), so it showed
every card in the interval regardless of the sidebar filter, unlike the
Board / Lists / Swimlanes views.

Extract the calendar selector building into a pure, Meteor-free helper
(models/lib/calendarFilter.js) that ANDs the Filter selector into the
date-interval selector via combineWithFilter, so the Filter is always applied
and can never collide with the interval selectors top-level $or. The calendar
events() callback now passes Filter._getMongoSelector() when Filter.isActive(),
and the onRendered autorun reads Filter.isActive() so events are refetched when
the filter changes. Add tests/calendarFilter.test.cjs.

Thanks to kerier and xet7.
2026-07-09 02:14:07 +03:00
Lauri Ojansivu 158130cbfc Updated ChangeLog. 2026-07-09 02:14:07 +03:00
Lauri Ojansivu 53328f40c6 Add unit selector (minute/hour/day/week/month) to the Rules relative-date action (#5621) 2026-07-09 02:14:07 +03:00
Lauri Ojansivu e7eeaf0b10 Add 'Count amount of tests by category' menu option to rebuild-wekan scripts 2026-07-09 02:14:07 +03:00
Lauri Ojansivu 609b07a8db Add 'Count amount of tests by category' menu option to rebuild-wekan scripts 2026-07-09 00:02:17 +03:00
Lauri Ojansivu 7545bb5506 Add #6442 fix to Upcoming WeKan release CHANGELOG section 2026-07-08 23:58:21 +03:00
Lauri Ojansivu 457713968c Fix #6442: All Boards custom drag order — drop now persists the reorder
Follow-up to #6439, which restored the drag preview but left the drop a
no-op: dragging a board on the All Boards page in "Custom (drag order)"
mode showed the dashed-border preview, but releasing it snapped the board
back without reordering.

The drop handler built the current on-screen order from
`el.classList[0]` for each `.js-board`. But the board list item is
`li.js-board(class="{{_id}} …")`, and Jade emits the literal `js-board`
class FIRST, so classList[0] is the string "js-board" for every board —
never the board _id. The ordered ids were therefore
['js-board','js-board',…]; computeReorderedSortIndex could not find the
dragged/target ids in that array, returned null, and nothing was written
to profile.boardSortIndex. The preview still worked because it is driven
by the dragover CSS class, independent of the id.

Read each board's _id from its Blaze data context (the same source
dragstart already uses via `this._id`) instead of the literal class, so
the real display order reaches the (already correct, unit-tested) reorder
helper and the drop persists. Add a regression guard in
tests/boardSortReorder.test.cjs asserting the wrong-class extraction
("js-board" repeated) yields no mapping while real ids reorder.
2026-07-08 23:57:39 +03:00
Lauri Ojansivu 372a58c112 Updated ChangeLog. 2026-07-08 23:49:32 +03:00
Lauri Ojansivu 527962966b Add Upcoming WeKan release CHANGELOG section for #6443 fix
Link the fix commit under a new "Upcoming WeKan release" heading, in the
same style as previous release entries.
2026-07-08 23:44:10 +03:00
Lauri Ojansivu a37e7d1cc7 Fix #6443: cards on a deleted swimlane are invisible in swimlane view
On old boards a swimlane can be deleted while its cards keep the now
dangling swimlaneId (an "orphaned" card). Cards with no swimlane at all
(null / '' / missing) already surface in every swimlane, but an orphaned
card matched no existing swimlane and so appeared in NO swimlane in
swimlane view, while still showing in list view (which applies no
swimlane scope) — the reported "no swimlane" symptom where some boards
show no cards in swimlane mode but work in list mode.

Mirror the existing orphaned-LIST fallback (Swimlanes.orphanedSwimlaneLists,
which surfaces orphaned lists in the first swimlane) for cards: when a
list's cards are fetched for the board's FIRST swimlane, build the
swimlane-membership clause as a single { swimlaneId: { $nin:
<otherSwimlaneIds> } } (everything not owned by another existing
swimlane: own id, null/'', missing, or orphaned). This keeps it a single
field clause with no second $or, so the #6441 board-wide label filter
still holds. Orphaned cards therefore appear once, in the first swimlane,
without a database migration.

Threaded through the pure selector helpers (swimlaneMembershipSelector /
listCardsSelector / filteredListCardsSelector) and the in-memory sibling
filterCardsByListAndSwimlane, a new List.orphanedCardsSwimlaneIds helper,
and the cards()/cardsUnfiltered()/allCards() model methods plus the
listBody cardsWithLimit render helper. Covered by new cases in
tests/swimlaneFilter.test.cjs (18 assertions pass; #6441 guards intact).
2026-07-08 23:43:26 +03:00
Lauri Ojansivu 33ad40d012 Updated translations. 2026-07-08 23:12:53 +03:00
Lauri Ojansivu 44b5e12de9 Update README to reflect Meteor version change 2026-07-06 19:07:03 +03:00
github-actions[bot] 1207b573b5 Bump versions for v9.80 2026-07-06 15:40:14 +00:00
Lauri Ojansivu a26295a2fb Updated ChangeLog. 2026-07-06 18:35:50 +03:00
Lauri Ojansivu b2da9ac8d4 Admin Panel Domains table: server-side pagination, column sort and search (like Board Table view). Thanks to xet7. 2026-07-06 18:27:38 +03:00
Lauri Ojansivu 53dcf7379d Updated ChangeLog. 2026-07-06 18:13:31 +03:00
Lauri Ojansivu dfa6c78d4f Add regression test for board-invite email language. Refs #5664. 2026-07-06 16:02:56 +03:00
Lauri Ojansivu 795850ed05 Updated ChangeLog. 2026-07-06 15:25:54 +03:00
Lauri Ojansivu 9ef7f4a07a Thanks to 32Dexter and xet7 . Fixes #5666 2026-07-06 15:22:22 +03:00
Lauri Ojansivu cb2394a95d Updated ChangeLog. 2026-07-06 15:12:21 +03:00
Lauri Ojansivu ac368de06a Thanks to MarcusDger and xet7 . Fixes #5667 2026-07-06 15:12:21 +03:00
github-actions[bot] b26494766f Bump versions for v9.79 2026-07-06 11:46:51 +00:00
Lauri Ojansivu 68ab63c7fe Updated ChangeLog. 2026-07-06 14:43:27 +03:00
Lauri Ojansivu e3e71b254a Updated ChangeLog.
Link #6439/#6440/#6441 and the i18n test fix to their commits; wire the new unit tests into test:unit:node.

Thanks to jullbo and xet7.
2026-07-06 14:35:35 +03:00
Lauri Ojansivu fc4eadedc9 Fix flaky server-side Mocha i18n test (zh-CN "is not a spy")
The .loadLanguage suite intermittently reported "1 failing" (the failure bouncing between the zh-CN and fr cases). Two causes: stubbing the shared TAPi18n.i18n let state leak between tests, and asserting via sinon-chai routed the matcher through chai-as-promised so it could evaluate after afterEach's sinon.restore(). Give each test a fresh i18next instance via TAPi18n.init() (mirrors the reliable .setLanguage suite) and assert with synchronous sinon.assert.* instead of sinon-chai.

Thanks to xet7.
2026-07-06 14:35:34 +03:00
Lauri Ojansivu 9dca403782 Fix #6441: label filter now applies board-wide across all swimlanes
Thanks to jullbo and xet7.
2026-07-06 13:49:44 +03:00
Lauri Ojansivu dd90995db7 Fix #6440: '+' add-item button on minicard checklist now adds the item
Thanks to jullbo and xet7.
2026-07-06 13:49:44 +03:00
Lauri Ojansivu f8e31745d8 Fix #6439: Custom (drag order) sort on All Boards page now reorders via drag-and-drop
Thanks to jullbo and xet7.
2026-07-06 13:49:44 +03:00
github-actions[bot] 736d308860 Bump versions for v9.78 2026-07-06 09:03:57 +00:00
Lauri Ojansivu 9eca7a3941 Updated ChangeLog. 2026-07-06 11:54:17 +03:00
Lauri Ojansivu af436e16c4 Updated ChangeLog. 2026-07-06 11:52:49 +03:00
Lauri Ojansivu b1b414e850 - **Fix "Internal Server Error" when signing up despite the account being created**:
Registering a new account showed a red "Internal server error" on the sign-up form even
  though the account was created and could sign in — which typically happens when SMTP is not
  configured. Root cause: useraccounts' `ATCreateUserServer` creates the account and then
  calls `Accounts.sendVerificationEmail()` (because `sendVerificationEmail: true`); when SMTP
  is missing/misconfigured that send throws **after** the user row is inserted, and with no
  try/catch the exception leaves the createUser method as an opaque HTTP 500. The verification
  email is best-effort at sign-up, so `Accounts.sendVerificationEmail` is now wrapped to log
  and swallow a transport failure — registration completes and redirects to sign-in — while an
  "already verified" error is re-thrown so the resend-verification flow still reports it. This
  mirrors the [#5706](https://github.com/wekan/wekan/issues/5706) reset-password hardening.
  Covered by `tests/verificationEmail.test.cjs`.

Thanks to Firas-Git and xet7.

Fixes #5672
2026-07-06 11:50:28 +03:00
Lauri Ojansivu 57da3885a2 Updated ChangeLog. 2026-07-06 11:38:57 +03:00
Lauri Ojansivu cb9c897309 - **Fix can not add members to a Linked Card**:
A **linked card** (created by "Link card to this card") is only a placeholder on the board
  that links it — its members are stored on the **real card** it points at (`linkedId`), which
  lives on another board, and `Card.getMembers()` / `assignMember()` / `unassignMember()`
  already read and write that real card. But the member **picker** listed the members of the
  board you were *viewing* the linked card on, not the board the real card lives on. So on a
  board that links a card from another board, the picker offered the wrong set of members and
  toggling them did not behave as a consistent add/remove — "can not add members to the linked
  card". The picker now resolves a linked card to its real card's board and offers *that*
  board's active members (matching where the membership is actually stored), falling back to
  the current board for normal cards or when the real card isn't loaded yet. Extracted the
  target-card/target-board resolution into `models/lib/linkedCardMembers.js`, covered by
  `tests/linkedCardMembers.test.cjs`.

Thanks to ITT5 and xet7 !

Fixes #5676
2026-07-06 11:35:56 +03:00
Lauri Ojansivu 81f07baeb8 Updated ChangeLog. 2026-07-06 11:21:41 +03:00
Lauri Ojansivu 001c258f96 - **Fix can't search numbers in custom fields**:
Searching a board for the value of a **number** or **currency** custom field (e.g. a
  transaction number `2025001`, or a currency amount `123`) found nothing. Those field types
  store their value as a JS **Number** (the inputs save `parseInt(...)` / `Number(...)`), but
  `Board.searchCards()` only matched custom fields with `{ value: <regex> }` — and a MongoDB /
  Minimongo regex only matches **string** values, so it silently skipped every numeric custom
  field. The search now also adds an exact numeric-equality clause when the term is a plain
  number (a comma is accepted as a decimal separator, matching the currency input), so numeric
  custom fields match too; text/title/description matching is unchanged. Card search runs
  against Minimongo on the client, which — like MongoDB — cannot regex a numeric field, so
  equality is the correct cross-environment match. Covered by `tests/cardSearch.test.cjs`.

Thanks to MarcusDger and xet7.

Fixes #5680
2026-07-06 11:19:15 +03:00
Lauri Ojansivu 9d77b9d7b7 Updated ChangeLog. 2026-07-06 10:57:03 +03:00
Lauri Ojansivu 2242da4176 - **Fix "Removed nonexistent document" crash during notification_cleanup**:
The scheduled notification-tray cleanup logged `Exception in removed observeChanges
  callback: Error: Removed nonexistent document …`. The crash itself came from the old
  `cottz:publish-relations` package (since replaced by `reywood:publish-composite`), but the
  cleanup that provoked it still fired one **un-awaited** `removeNotification()` per expired
  notification — a separate `Users.update` `$pull` each time — so a user with K stale
  notifications produced K writes to the Users collection, and every publication that
  republishes user documents re-ran its observers K times in quick bursts (the churn that
  surfaced the removed-document error), while any rejected write went unhandled. The cleanup
  now scans only users that have notifications and prunes each user's stale notifications in a
  single awaited `$pull … $in`. It also removes an activity's notifications only when *every*
  entry for that activity is read and past its removal age (so a freshly re-created unread
  notification sharing an activity id is not dropped), and guards missing/invalid `read`
  timestamps. Covered by `tests/notificationCleanup.test.cjs`.

Thanks to xet7.

Fixes #5685
2026-07-06 10:55:29 +03:00
Lauri Ojansivu 0a3a28ef29 Updated ChangeLog. 2026-07-06 10:46:27 +03:00
Lauri Ojansivu dcf5aa65b0 - **Fix impossible to select another board in rules**:
In the IFTTT-Rules "Move card to the board" and "Link card to the board" actions, the
  board dropdown was empty for some users — the current board included — while colleagues
  in the same company could pick boards normally. Root cause: the dropdown filtered the
  (already access-scoped) client cache with `'members.userId': me`, i.e. it only kept boards
  where the user has a *direct* member entry. A user who reaches a board through an
  Organization, Team or email-domain share — but is not listed individually in
  `board.members` — matched nothing, so the whole selector came up empty. The dropdown now
  filters by the same visibility rule as `Boards.userBoards()` (public OR active member OR
  active org OR active team OR active domain), so org/team/domain-shared boards appear too,
  while archived boards, template containers, the user's templates board and internal helper
  boards stay excluded.

Thanks to Augustin356 and xet7 !

Fixes #5698
2026-07-06 10:43:26 +03:00
Lauri Ojansivu dd3be179ca Updated ChangeLog. 2026-07-06 10:33:15 +03:00
Lauri Ojansivu bc1f149ea3 - **Fix board disappeared after adding another user**:
A board admin adding another user could make the whole board silently vanish from a
  user's board list, with no archive and "nothing out of order" in the logs. Root cause:
  the `setBoardTeams` server method (used by the board Teams/Members management popups)
  blindly overwrote the board's entire `members` array with a snapshot sent by the client.
  When that client's board document was stale — e.g. a member had just been added via
  `inviteUserToBoard` on the server and the change had not yet propagated to the client —
  the overwrite dropped members, and in the worst case the board's own admin, so the board
  no longer matched the board-list publication (which requires an active membership) and
  disappeared. Because a wholesale `$set: { members }` never passes through
  `foreachRemovedMember()`, no `removeBoardMember` activity was logged and no
  card/watcher/star cleanup ran, which is why the logs looked normal. `setBoardTeams` now
  reconciles against the authoritative server-side members instead of trusting the client
  snapshot: it never drops an active admin, keeps every existing member the client still
  lists, adds the members the client introduces, and only removes non-admin members the
  client explicitly omitted (an intentional team-leave) — logging and cleaning up each such
  removal so it is auditable rather than silent.

Thanks to DVNBLMHC and xet7 !

Fixes #5730
2026-07-06 10:33:15 +03:00
github-actions[bot] 0dd11ea573 Bump versions for v9.77 2026-07-06 00:01:08 +00:00
Lauri Ojansivu 5feb83d919 Updated ChangeLog. 2026-07-06 02:51:25 +03:00
Lauri Ojansivu 8980668686 Updated ChangeLog. 2026-07-06 02:47:52 +03:00
Lauri Ojansivu 0d4e48ddd6 Retry snapcraft install in the release-all.yml snap job
The Snap Store intermittently rejects `sudo snap install snapcraft --classic`
with "error: cannot install snapcraft: too many requests" (a 429-style rate
limit) that instantly failed the whole snap job before any build ran. Retry up
to 5 times with 30s backoff so one transient store throttle no longer fails the
release, matching the remote-build retry already in the same job.
2026-07-06 02:47:32 +03:00
Lauri Ojansivu a01b175895 Updated ChangeLog. 2026-07-06 02:37:29 +03:00
Lauri Ojansivu 83f63c9efa Updated Code of Conduct.
Thanks to xet7 !
2026-07-06 02:37:28 +03:00
Lauri Ojansivu 3b031bd901 Updated Code of Conduct.
Thanks to xet7 !
2026-07-06 02:37:28 +03:00
github-actions[bot] e2a7e300c9 Bump versions for v9.76 2026-07-05 23:07:35 +00:00
Lauri Ojansivu 844f59e12e Updated ChangeLog. 2026-07-06 01:57:54 +03:00
Lauri Ojansivu 1a3d7b5da4 Updated translations. 2026-07-06 01:57:00 +03:00
Lauri Ojansivu 3d2bdf4f22 Updated ChangeLog. 2026-07-06 01:50:59 +03:00
Lauri Ojansivu b9fe1f1639 Updated ChangeLog. 2026-07-06 01:35:30 +03:00
Lauri Ojansivu a39a7b90b1 Updated ChangeLog.
Repoint the #6427 upcoming entry to its on-main commit (the old link was
malformed and pointed at an orphaned pre-rebase commit).
2026-07-06 01:28:24 +03:00
Lauri Ojansivu 5d1c979e3b Updated ChangeLog.
Link the #5808 and board-not-found-flicker upcoming entries to their commits.
2026-07-06 01:26:43 +03:00
Lauri Ojansivu d00dc6056b Fix the "Board not found" flicker (stale-while-revalidate for the client board cache)
The client board cache (imports/lib/dataCache.js) re-fetches its value inside a
reactive computation. When the board doc is briefly absent from minimongo (a
subscription stops and restarts, momentarily removing the doc), the empty value
was surfaced immediately, flashing the "Board not found" shell — and on WebKit
throwing a Blaze "Can't select in removed DomRange" error tearing down the card
view. It self-recovers when the doc is re-delivered, so it presents as a flicker,
reliably hit only on Firefox/WebKit timing (which is why it surfaced as
browser-specific Playwright failures in 14-voting-watchers and 24-feature-issues).

Fixed with an opt-in stale-while-revalidate mode on DataCache, enabled only for
getBoard: a transient miss over an already-cached board keeps the last value and
re-checks after a short delay, surfacing an empty result only if the board is
still gone then (a genuine deletion / access loss). First-ever loads and caches
that did not opt in are unchanged. Core decision extracted to the pure
shouldDeferCacheMiss helper with unit tests.

Also hardens the related Playwright specs: a retry in CardPage.openActionsMenu, a
longer visibility budget for the WebKit slow card-render in spec 16, and removes a
reload race in the move-list-right spec (moveListBy fires two un-awaited
updateListSort calls, so an immediate reload read a half-applied order).
2026-07-06 01:26:05 +03:00
Lauri Ojansivu 878a24f586 Fix #5808: linking a card to another linked card made both cards inaccessible
The "Link to this card" target picker only excluded template cards, so an
existing linked card (or a card that links back to the current board) could be
chosen as a link target. That builds a chain of linkedId pointers, but the card
helpers (getTitle/getBoardTitle/getRealId) resolve linkedId only one hop, so
such a card renders as an empty/broken pointer and becomes inaccessible (the
reported freeze). As the reporter suggested, prevent the configuration: only a
real card — not a linked card/linked board, not one of the linking board's own
cards, and not a card that links back to one of them — may be a link target.
Enforced in the picker query and re-checked at creation time via the pure
isLinkableCardTarget guard, mirroring the #3328 parent/subtask cycle guard.
2026-07-06 01:25:39 +03:00
Lauri Ojansivu 6d08181edd Updated ChangeLog. 2026-07-05 23:24:30 +03:00
Lauri Ojansivu d5596f05b7 - **Fix DEFAULT_AUTHENTICATION_METHOD env var ignored, and Admin Panel Layout save hanging.
Two related problems with the default login authentication method.
  - **Env var ignored:** the stored setting was only ever seeded as `password` and
    `DEFAULT_AUTHENTICATION_METHOD` was never applied, so operators setting it (e.g. Kubernetes/Helm
    `DEFAULT_AUTHENTICATION_METHOD: ldap`) saw no effect. Startup now applies the env var
    authoritatively: it seeds the value on a fresh install and, on existing installs, keeps the
    stored `defaultAuthenticationMethod` in sync with the env var on every boot (the operator's env
    is the source of truth), so the method can be configured entirely by env without the Admin Panel.
    The value is normalized (trimmed + lower-cased), so `DEFAULT_AUTHENTICATION_METHOD=LDAP` works.
  - **Layout save hanging / not persisting:** the authentication-method `<select>` is populated by an
    async `Meteor.call`, so clicking **Admin Panel > Layout > Save** before it loaded sent an empty
    value for the **required** `defaultAuthenticationMethod` field, which silently failed validation —
    the save looked stuck and nothing changed. The save now falls back to the currently stored method
    when the select is empty, so a real value is never overwritten by `''`.
  Both paths share one pure helper (`resolveDefaultAuthenticationMethod`) that never resolves to an
  empty string.

Thanks to joe-speedboat and xet7 !

Fixes #5879
2026-07-05 23:22:16 +03:00
Lauri Ojansivu 22b280a18e Updated ChangeLog. 2026-07-05 23:13:36 +03:00
Lauri Ojansivu 2e7c4ede2a - **[Reduce card flicker on drag by only writing changed fields on a card move](https://github.com/wekan/wekan/issues/6430)** (#6430):
`Card.move()` wrote `boardId`/`swimlaneId`/`listId` into the update unconditionally, even for a
  same-board drag to another list (the reported case). Keeping `boardId` in the `$set` on every drag
  re-ran the `boardId`-gated `Cards.after.update` hook that re-syncs the card's checklists and
  checklist items via `multi` updates, ran the cross-board consistency guard and the
  `denyCrossBoardMove` deny-rule DB lookup, and invalidated more reactive dependents than
  necessary — server work and reactivity churn that contributed to a ~1s card flicker on large
  boards. `Card.move()` now writes **only the fields that actually change** (via the pure
  `computeCardMoveModifier` helper) and skips the write entirely when a card is dropped back in the
  same place, so a same-board move no longer touches boardId or the cross-board hooks. The
  `moveCard`/`moveCardBoard` activity generators already re-check `doc.X !== oldX`, so trimming the
  modifier does not drop any activity. Note: this reduces the drag-time work behind the flicker; the
  residual reactive re-render cost on very large boards is a separate performance topic.

Thanks to mimZD and xet7 !

Fixes #6430
2026-07-05 23:12:16 +03:00
Lauri Ojansivu ed77abd90d Updated ChangeLog. 2026-07-05 22:56:01 +03:00
Lauri Ojansivu 45fb547947 - **Fix thousands of unsolicited empty "Default" swimlanes created on some boards**:
`Board.getDefaultSwimline()`/`getDefaultSwimlineAsync()` self-heal a missing default swimlane by
  reading the board's swimlanes and inserting one if none exist. That check-then-insert is a race:
  concurrent or repeated server calls for a swimlane-less board each saw zero swimlanes and each
  inserted a new one, so some boards accumulated 30 000+ empty "Default" swimlanes and became
  unloadable (the `key 'default (en)' returned an object instead of string` log is a harmless i18n
  side effect — the title correctly falls back to the string `Default`). Follow-up to the
  client-side [#6382](https://github.com/wekan/wekan/issues/6382) fix, which only stopped the
  browser from auto-creating them. Fixed by making the server self-heal **idempotent**: the default
  swimlane is now upserted with a deterministic `_id` (`<boardId>-default`), so the `_id` unique
  index guarantees at most one default swimlane per board no matter how many times, or how
  concurrently, the getters run. `archived`/`type` are set explicitly in the `$setOnInsert` because
  their schema autoValue/defaultValue only fire on insert, not upsert. Note: this prevents new
  duplicates; boards that already accumulated thousands still need a one-off cleanup.

Thanks to brlin-tw and xet7 !

Fixes #6429
2026-07-05 22:54:13 +03:00
Lauri Ojansivu 477ad737d3 Updated ChangeLog. 2026-07-05 22:30:37 +03:00
Lauri Ojansivu a2802a850b - **Release workflow: publish the Helm chart only after the Docker image is pushed**:
In `.github/workflows/release-all.yml` the `charts` job now depends on the `docker` job
  (`needs: docker`) instead of running in parallel right after `bump`. GitHub Actions runs a job
  only when all of its `needs` jobs succeed, so the wekan/charts Helm chart is published only after
  the multi-arch image is live on Docker Hub / Quay.io / GHCR (and is skipped if `docker` fails).
  This prevents ArtifactHub from scanning a freshly published chart whose image tag does not exist
  yet and emailing the maintainer about the missing Docker image.

Thanks to xet7 !
2026-07-05 22:29:23 +03:00
Lauri Ojansivu 5cbe27be7e Updated ChangeLog. 2026-07-05 22:23:36 +03:00
Lauri Ojansivu 8b21158736 - **Fix [#6427](https://github.com/wekan/wekan/issues/6427): notification emails linked to
`/b/undefined/board/<cardId>` instead of the real board.** On the server
  `ReactiveCache.getBoard()` is async and returns a Promise, but `Cards.board()` did not await it,
  so the synchronous `Card.originRelativeUrl()`/`absoluteUrl()` interpolated a Promise —
  `board._id` and `board.slug` were `undefined`, producing `/b/undefined/board/<cardId>` in
  card activity notification emails (the client UI was unaffected because `this.board()` is
  synchronous there). Fixed by making `Card.originRelativeUrl(board)`/`absoluteUrl(board)` accept an
  already-resolved board and fall back to `this.boardId` (always available synchronously) when the
  board is a Promise, and by passing the awaited board from `server/models/activities.js` so the
  correct board id and slug are used.

Thanks to titver968 and xet7 !

Fixes #6427
2026-07-05 22:23:36 +03:00
github-actions[bot] 651c6f8fb6 Bump versions for v9.75 2026-07-05 19:05:05 +00:00
Lauri Ojansivu 5b67312f0f Updated ChangeLog. 2026-07-05 21:55:15 +03:00
Lauri Ojansivu 1a222c4477 - **[ScannerBleed](https://github.com/wekan/wekan/security/advisories/GHSA-x3xm-pxrv-jg7p)
— shell injection (RCE) via a malicious upload filename in the external antivirus scanner command path**
  ([GHSA-x3xm-pxrv-jg7p](https://github.com/wekan/wekan/security/advisories/GHSA-x3xm-pxrv-jg7p),
  CWE-78 OS Command Injection). Same RCE class as
  [AvatarBleed](https://wekan.fi/hall-of-fame/avatarbleed/) (CVE-2026-52891, GHSA-35j7-h385-2q9g)
  and its follow-up regression (CVE-2026-53447 / GHSA-qfqv-42qw-vvwh area), but in a code path that
  was **never** covered by those fixes.
  In `models/fileValidation.js`, when an admin has configured an external scanner (antivirus)
  command line with a `{file}` placeholder, the uploaded file path was interpolated into the command
  and run through `asyncExec` (`promisify(exec)`), which spawns `/bin/sh -c` and interprets **all**
  shell metacharacters:
  ```js
  await asyncExec(externalCommandLine.replace("{file}", '"' + fileObj.path + '"'));
  ```
  Wrapping the path in double quotes is not a shell boundary — inside double quotes the shell still
  expands `$(...)`, backticks and `\`, so a filename such as `` a`id`.png `` or `$(touch /tmp/pwn).png`
  escaped the argument and executed as the Wekan server process. Any **authenticated** user who can
  upload an attachment could trigger it, on servers that have an external scanner configured. Unlike
  the sibling MIME-detection path (`detectMimeFromFile`, which already uses `execFile` with no shell)
  and unlike the AvatarBleed fix (which strips non-alphanumeric characters from the filename), this
  scanner path had **zero** sanitization.
  - **Fixed** by POSIX single-quote-escaping the interpolated file path via a new `shellQuote()`
    helper (wrap in single quotes, escape embedded `'` as `'\''`). Inside single quotes the shell
    interprets no metacharacters, so a malicious filename can no longer break out of the argument or
    inject commands, while the admin's arbitrary command line and the exact on-disk path are both
    preserved. CVSS:3.1 9.9 Critical (AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H).
  - Affected Wekan v9.06 and earlier through the current release; fixed at the upcoming WeKan release.
    Reported by **DavidCarliez**.

Thanks to DavidCarliez and xet7 !
2026-07-05 21:55:15 +03:00
github-actions[bot] ca9bcfa4ee Bump versions for v9.74 2026-07-05 15:08:00 +00:00
Lauri Ojansivu 4da5b4eb96 Updated ChangeLog. 2026-07-05 17:55:45 +03:00
Lauri Ojansivu 8c4044ce6c Updated ChangeLog. 2026-07-05 17:53:28 +03:00
Lauri Ojansivu b46d9588c7 Fix translations at Login and Register pages.
Thanks to xet7 !
2026-07-05 17:52:05 +03:00
Lauri Ojansivu 96773e7b1d Updated ChangeLog. 2026-07-05 16:51:28 +03:00
Lauri OjansivuandClaude Opus 4.8 5a80f4f6ce Updated ChangeLog.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 16:49:23 +03:00
Lauri OjansivuandClaude Opus 4.8 b9f9390d04 Server-side Mocha suite: fixed a startup crash and the 44 latent failures it had been hiding
- imports/i18n/i18n.test.js crashed the whole run at load: chai 6.x plugins (sinon-chai,
  chai-as-promised) are ESM-only, so use(require('sinon-chai')) handed chai.use() a module
  namespace instead of the plugin function ("fn is not a function"). Now imports the default
  export.
- That unmasked a second load crash - PositionHistory.helpers is not a function: the
  meteor test entry (server/lib/tests/index.js) never ran the .helpers / .attachSchema shim
  that server/main.js bootstraps, so the first model to call Collection.helpers({...}) threw.
  The shim is now imported first in the test entry.
- With the suite finally running, 44 server tests failed because meteor test only loads what
  the specs import (not the app's /server/imports). The specs now import the files that
  register the methods / globals under test (cards.vote / cards.pokerVote, api.attachment.*,
  cloneBoard, getBackgroundImageURL, applyListWidth, updateListSort, moveChecklist,
  userPositionHistory.*, archiveBoard, sendSMTPTestEmail, and the Attachments global). Also
  fixed genuine test bugs: the cards.vote / cards.pokerVote specs were written synchronously
  against async methods; the header-login trust specs restored env vars with
  process.env.X = undefined (which stores the string "undefined" and shadowed the trusted-IP
  allowlist, making every trusted source read as untrusted); the DnsBleed decimal-loopback
  matcher assumed the integer host survived URL normalisation; and the dependencies-OpenAPI
  spec could not locate its source file from the built bundle. The two cards.archive /
  cards.move specs tested Meteor methods that do not exist (archive / move are Minimongo
  document helpers secured by Cards.allow / Cards.deny, already covered by the cards security
  tests) and were removed. Server-side Mocha is now 409 passing, 0 failing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 16:48:58 +03:00
Lauri OjansivuandClaude Opus 4.8 f5a4ece294 Playwright E2E: fixed three cross-process-contention flakes in the parallel run
The Chromium / Firefox / WebKit browser jobs run as separate processes against ONE
shared server + DB, so specs that used fixed identifiers or global cleanups raced
each other:

- 26-shared-templates.e2e.js seeded a fixed email domain (usera@acme-e2e.invalid) in
  all three browsers, hitting E11000 duplicate key on the unique emails.address index.
  Now uses a unique-per-run token for the org / team / domain and template titles.
- 38-impersonation.e2e.js cleaned up with a global
  deleteMany({ reason: 'clickedImpersonate' }) that deleted another browser's in-flight
  audit record mid-poll. Scoped the cleanup to the test's own adminId.
- 32-org-team-feature-toggles.e2e.js exercises setAllOrgsFeature / setAllTeamsFeature,
  which do a global updateMany({}) across every org / team, so two browsers clobbered
  each other's rows. These browser-agnostic server-method tests now run in a single
  project (Chromium); Firefox / WebKit skip them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 16:48:50 +03:00
Lauri Ojansivu c1ddf989a2 Updated ChangeLog. 2026-07-05 14:52:10 +03:00
Lauri Ojansivu c48ecb1d88 Fix tests.
Thanks to xet7 !
2026-07-05 14:50:59 +03:00
Lauri Ojansivu c664fe863c Updated translations. 2026-07-05 14:11:44 +03:00
Lauri Ojansivu 38f571b388 Updated ChangeLog. 2026-07-05 14:10:42 +03:00
Lauri Ojansivu 1e7de6d776 Card Settings popup options combined to same lines, and added more options.
Thanks to xet7 !
2026-07-05 14:09:38 +03:00
Lauri Ojansivu 6d47dc3f4a Updated translations. 2026-07-05 13:30:49 +03:00
Lauri Ojansivu 4756ca4226 Updated ChangeLog. 2026-07-05 13:27:46 +03:00
Lauri Ojansivu bb440b0d7b Board Settings / Card Settings / Checklist item count (0/0) on minicard. Default: Off.
Thanks to carl-unique and xet7 !

Fixes #6431
2026-07-05 13:25:00 +03:00
Lauri Ojansivu 89cfe1529f Updated ChangeLog. 2026-07-05 12:50:30 +03:00
Lauri Ojansivu 236fdf9338 Each run all tests now has logs at ../log/YYYY-MM-DD_HH-MM-SS/
Thanks to xet7 !
2026-07-05 12:48:21 +03:00
Lauri Ojansivu 5c36b2292b Updated ChangeLog. 2026-07-05 12:29:14 +03:00
Lauri Ojansivu 4eebdb4278 Fix tests.
Thanks to xet7 !
2026-07-05 12:21:47 +03:00
Lauri Ojansivu 4533268418 Update vscode sandbox.
Thanks to xet7 !
2026-07-05 12:12:24 +03:00
Lauri Ojansivu c997c12f88 Updated tests 2026-07-05 11:52:12 +03:00
Lauri Ojansivu 36efd9c988 Fix parallel tests.
Thanks to xet7 !
2026-07-05 11:30:26 +03:00
Lauri Ojansivu 267bfc7229 Script to be executeable 2026-07-05 11:03:13 +03:00
Lauri Ojansivu 405a7b72e9 Updated ChangeLog. 2026-07-05 11:02:01 +03:00
Lauri Ojansivu 7d416cc7cc Run all tests at parallel or sequential.
Thanks to xet7 !
2026-07-05 11:00:43 +03:00
Lauri Ojansivu 7929ab27cb Updated ChangeLog. 2026-07-05 10:21:20 +03:00
Lauri Ojansivu 7bbd1a3fad Fix ExcelBleed.
Thanks to sec-reex and xet7 !
2026-07-05 10:20:08 +03:00
Lauri Ojansivu 0f9bd4f9f9 Reverted JQuery 4.0.0 that did not work back to JQuery 3.7.1.
Thanks to xet7 !
2026-07-05 10:02:37 +03:00
Lauri Ojansivu a7ce6690a5 Merge pull request #6415 from wekan/dependabot/npm_and_yarn/babel/runtime-8.0.0
build(deps): bump @babel/runtime from 7.29.7 to 8.0.0
2026-07-05 09:56:51 +03:00
dependabot[bot] 82bd4ff39d build(deps): bump @babel/runtime from 7.29.7 to 8.0.0
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.29.7 to 8.0.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v8.0.0/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-version: 8.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-05 06:55:17 +00:00
Lauri Ojansivu 721c61bef5 Merge pull request #6417 from wekan/dependabot/npm_and_yarn/jquery-4.0.0
Bump jquery from 3.7.1 to 4.0.0
2026-07-05 09:54:49 +03:00
Lauri Ojansivu 7a25546da7 Merge pull request #6435 from wekan/dependabot/npm_and_yarn/i18next-26.3.4
build(deps): bump i18next from 25.10.10 to 26.3.4
2026-07-05 09:53:13 +03:00
Lauri Ojansivu 2c1d3a3922 Merge pull request #6414 from wekan/dependabot/npm_and_yarn/tweedegolf/sab-adapter-amazon-s3-3.0.4
Bump @tweedegolf/sab-adapter-amazon-s3 from 1.0.20 to 3.0.4
2026-07-05 09:52:52 +03:00
Lauri Ojansivu b4d6cc741c Merge pull request #6438 from wekan/dependabot/npm_and_yarn/azure/storage-blob-12.33.0
build(deps): bump @azure/storage-blob from 12.32.0 to 12.33.0
2026-07-05 09:49:31 +03:00
Lauri Ojansivu 5311f80711 Merge pull request #6436 from wekan/dependabot/npm_and_yarn/tests/playwright/playwright/test-1.61.1
build(deps-dev): bump @playwright/test from 1.61.0 to 1.61.1 in /tests/playwright
2026-07-05 09:48:19 +03:00
Lauri Ojansivu abed969a96 Merge pull request #6437 from wekan/dependabot/npm_and_yarn/tests/playwright/mongodb-7.4.0
build(deps): bump mongodb from 7.3.0 to 7.4.0 in /tests/playwright
2026-07-05 09:47:58 +03:00
Lauri Ojansivu 36e3cf30d3 Merge pull request #6428 from wekan/dependabot/github_actions/actions/cache-6
build(deps): bump actions/cache from 5 to 6
2026-07-05 09:47:10 +03:00
Lauri Ojansivu eaadbc6dd1 Merge pull request #6434 from wekan/dependabot/github_actions/docker/build-push-action-7.3.0
build(deps): bump docker/build-push-action from 7.2.0 to 7.3.0
2026-07-05 09:46:41 +03:00
Lauri Ojansivu 0aacc989b7 Merge pull request #6433 from wekan/dependabot/github_actions/docker/metadata-action-6.2.0
build(deps): bump docker/metadata-action from 6.1.0 to 6.2.0
2026-07-05 09:46:32 +03:00
Lauri Ojansivu 77adfb28c9 Merge pull request #6432 from wekan/dependabot/github_actions/docker/login-action-4.4.0
build(deps): bump docker/login-action from 4.2.0 to 4.4.0
2026-07-05 09:46:23 +03:00
Lauri Ojansivu a9d8261d80 Updated ChangeLog. 2026-07-05 09:38:48 +03:00
Lauri Ojansivu df3f519e99 Update to Meteor 3.5.
Thanks to Meteor developers !
2026-07-05 09:36:34 +03:00
Lauri Ojansivu d68364befe Updated translations. 2026-07-05 09:35:02 +03:00
Lauri Ojansivu 6094979d4e Updated ChangeLog. 2026-07-05 09:33:14 +03:00
Lauri Ojansivu ef845fe4a0 Fix DnsBleed.
Thanks to 4n207 and xet7 !
2026-07-05 09:30:12 +03:00
dependabot[bot] ce8fc8e392 build(deps): bump @azure/storage-blob from 12.32.0 to 12.33.0
Bumps [@azure/storage-blob](https://github.com/Azure/azure-sdk-for-js/tree/HEAD/sdk/storage/storage-blob) from 12.32.0 to 12.33.0.
- [Release notes](https://github.com/Azure/azure-sdk-for-js/releases)
- [Changelog](https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/storage/storage-blob/CHANGELOG.md)
- [Commits](https://github.com/Azure/azure-sdk-for-js/commits/@azure/storage-blob_12.33.0/sdk/storage/storage-blob)

---
updated-dependencies:
- dependency-name: "@azure/storage-blob"
  dependency-version: 12.33.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-04 20:32:53 +00:00
dependabot[bot] 402b79c340 build(deps): bump mongodb from 7.3.0 to 7.4.0 in /tests/playwright
Bumps [mongodb](https://github.com/mongodb/node-mongodb-native) from 7.3.0 to 7.4.0.
- [Release notes](https://github.com/mongodb/node-mongodb-native/releases)
- [Changelog](https://github.com/mongodb/node-mongodb-native/blob/main/HISTORY.md)
- [Commits](https://github.com/mongodb/node-mongodb-native/compare/v7.3.0...v7.4.0)

---
updated-dependencies:
- dependency-name: mongodb
  dependency-version: 7.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-04 20:32:51 +00:00
dependabot[bot] 08ba9b02f2 build(deps-dev): bump @playwright/test in /tests/playwright
Bumps [@playwright/test](https://github.com/microsoft/playwright) from 1.61.0 to 1.61.1.
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](https://github.com/microsoft/playwright/compare/v1.61.0...v1.61.1)

---
updated-dependencies:
- dependency-name: "@playwright/test"
  dependency-version: 1.61.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-04 20:32:44 +00:00
dependabot[bot] 3a016be97d build(deps): bump i18next from 25.10.10 to 26.3.4
Bumps [i18next](https://github.com/i18next/i18next) from 25.10.10 to 26.3.4.
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v25.10.10...v26.3.4)

---
updated-dependencies:
- dependency-name: i18next
  dependency-version: 26.3.4
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-04 20:32:38 +00:00
dependabot[bot] 56b09f9102 build(deps): bump docker/build-push-action from 7.2.0 to 7.3.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 7.2.0 to 7.3.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/f9f3042f7e2789586610d6e8b85c8f03e5195baf...53b7df96c91f9c12dcc8a07bcb9ccacbed38856a)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 7.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-04 20:32:31 +00:00
dependabot[bot] dccf16647a build(deps): bump docker/metadata-action from 6.1.0 to 6.2.0
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 6.1.0 to 6.2.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9...dc802804100637a589fabce1cb79ff13a1411302)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-version: 6.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-04 20:32:23 +00:00
dependabot[bot] 9c2ff81b12 build(deps): bump docker/login-action from 4.2.0 to 4.4.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 4.2.0 to 4.4.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/650006c6eb7dba73a995cc03b0b2d7f5ca915bee...af1e73f918a031802d376d3c8bbc3fe56130a9b0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-04 20:32:19 +00:00
dependabot[bot] af8bba27c0 build(deps): bump actions/cache from 5 to 6
Bumps [actions/cache](https://github.com/actions/cache) from 5 to 6.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-27 20:32:19 +00:00
github-actions[bot] 8f83ece2ed Bump versions for v9.73 2026-06-26 03:18:47 +00:00
Lauri Ojansivu ac8e39daca Updated ChangeLog. 2026-06-26 06:13:08 +03:00
Lauri OjansivuandClaude Opus 4.8 3c07777722 docs(changelog): repoint #6426/dragscroll links to post-rebase hashes
A git pull --rebase inserted upstream commit 17c62ccdf (Bump versions for
v9.72) under this session's work, rewriting its commit hashes. The CHANGELOG
still linked the pre-rebase hashes (37ac65e37, a4cce9a53), now orphaned and
not on main. Repoint to the rebased commits 219ee659e and 88f353dc1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 05:08:35 +03:00
Lauri OjansivuandClaude Opus 4.8 0e10d24bef docs(changelog): add login/register mobile-scroll fix entry (4a7df3745)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 05:07:38 +03:00
Lauri OjansivuandClaude Opus 4.8 4a7df37459 fix: login/register page not scrollable on mobile — sign-up link unreachable
On phones body.mobile-mode pins <body> to position:fixed + height:100vh (an
iOS board-view anti-bounce fix). The auth pages (userFormsLayout) have no
inner #content scroller, and .auth-layout/.auth-dialog were sized to the
viewport height, so the page was trapped at 100vh: the content below the
form — the sign-up/sign-in link, legal notice and language selector — could
not be scrolled into view, and the on-screen keyboard covered it.

userFormsLayout now tags <body> with .userform-layout while mounted, and CSS
lets those pages scroll as a normal document on mobile (position:static,
height:auto, min-height:100vh) with the auth layout/dialog growing to their
content. Scoped to .userform-layout so the board-view mobile lock is
unchanged. Removed on destroy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 05:07:22 +03:00
Lauri OjansivuandClaude Opus 4.8 f9b5de0e8e docs(changelog): link all-pages dragscroll entry to fix commit a4cce9a53
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 04:55:26 +03:00
Lauri OjansivuandClaude Opus 4.8 88f353dc1f fix: drag-to-scroll on all pages via central route-aware autorun
Page drag-to-scroll was wired into only boardsList (All Boards), myCards and
userFormsLayout (Login/Register). Every other defaultLayout page — Due Cards,
Global Search, Public, Bookmarks, Broken Cards, Settings, People, Admin
Reports, Attachments, Translation, Import, board Rules, Not Found — had no
page drag-scroll, and the per-template enable/disable calls fought each other
when navigating between two non-board pages (one template's onDestroyed
removed the class the next template's onRendered expected).

Replace them with a single route-aware autorun in defaultLayout (the
persistent shell that owns #content): non-board routes tag the real scroll
container (<body> + #content, so it works in both desktop and mobile mode),
and board-canvas routes (board/board-short/card) leave page tagging off so
their own .board-canvas.dragscroll handles dragging without double-scrolling
#content. Login/Register keep their own enable/disable (userFormsLayout has
no #content).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 04:55:26 +03:00
Lauri OjansivuandClaude Opus 4.8 50525f6fd1 docs(changelog): link Upcoming #6426 + dragscroll entries to fix commit 37ac65e37
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 04:55:26 +03:00
Lauri OjansivuandClaude Opus 4.8 219ee659e9 fix: finish reverting oversized mobile UI (#6426) + fix page dragscroll
#6426: the first revert only cleaned boardsList.css. The same forced
"2x/3x bigger on mobile" rules remained in header.css (#header-quick-access
logo/quick-access bar) and boardBody.css (#content — which the All Boards
list and the board view render inside) plus the iPhone card-details body
text. All neutralized to normal size; the iPhone-12-mini header blocks that
had grown tall/wrapped to fit 3x text are back to a compact single row. The
#6419 mobile touch-target icon sizes in the card-details header are kept.

dragscroll: enablePageDragscroll/disablePageDragscroll tagged only <body>,
but on phones body.mobile-mode is position:fixed and the real scroll
container becomes #content, so Login/Register/All Boards could not drag-
scroll. Now tag whichever element actually scrolls — both <body> and
#content when present — so only the overflowing one scrolls. The touch
helper already prefers the nearer #content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 04:55:26 +03:00
github-actions[bot] 17c62ccdf6 Bump versions for v9.72 2026-06-25 23:26:37 +00:00
Lauri Ojansivu 14de82ad2d Updated ChangeLog. 2026-06-26 02:15:49 +03:00
Lauri Ojansivu e92f4f9819 Updated translations. 2026-06-26 02:12:33 +03:00
Lauri Ojansivu 08aac777fb docs(changelog): generalize #6426 wording to mobile UI scope 2026-06-26 02:10:26 +03:00
Lauri Ojansivu b84caaad26 docs(changelog): link #6426 entry to fix commit\n\nFixes #6426 2026-06-26 02:09:06 +03:00
Lauri Ojansivu f7a2a3ace5 fix: revert oversized mobile All Boards/iPhone 12 mini UI\n\nFixes #6426 2026-06-26 02:08:49 +03:00
Lauri OjansivuandClaude Opus 4.8 88a2e96db3 CHANGELOG: Upcoming — api.py addcustomfieldtoboard empty-settings fix
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 03:05:02 +03:00
Lauri OjansivuandClaude Opus 4.8 487cf3f963 api.py: fix addcustomfieldtoboard crash on empty settings
json.loads('') on an empty settings argument raised JSONDecodeError before the
request was sent (a bug api.py's own header flagged). Default empty settings to
{} and send it as valid JSON via json.dumps instead of str(dict) (which produced
invalid JSON with single quotes). Verified: both empty and object settings create
the custom field.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 03:03:10 +03:00
github-actions[bot] 6b3ae817fe Bump versions for v9.71 2026-06-23 15:54:59 +00:00
Lauri OjansivuandClaude Opus 4.8 f74c658552 i18n: Finnish translations for fixed-list-width strings (#5729)
Translate the 4 newly-added fixed-list-width keys in fi.i18n.json to Finnish,
matching existing terminology (Lista/listoille, leveys, 'vain sinua varten'
as in list-width-personal-note). The remaining strings identical to English
are technical terms, abbreviations and placeholder-only templates (poker
numbers, LDAP/OAuth2/CAS/OK, Gantt, S3/GridFS, operator abbrevs, __board__)
that are correctly the same in Finnish.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 18:33:42 +03:00
Lauri Ojansivu 58fa52a4ac Updated translations. 2026-06-23 18:32:01 +03:00
Lauri Ojansivu 468a1e3d1d Updated ChangeLog. 2026-06-23 18:29:27 +03:00
Lauri OjansivuandClaude Opus 4.8 945b634d32 fix: full-width sidebar at top of board on phones (mobile + desktop mode)
On iPhone 12 mini the right board sidebar showed as a narrow strip pinned to
the far right. The sidebar lives inside .board-wrapper, which is wider than
the viewport on a phone, so the absolutely-positioned sidebar
(inset-inline-end: 0) anchored to the right edge of the wide board instead of
the screen. Under the @media (max-width: 800px) breakpoint (which matches the
viewport in both mobile and desktop mode), pin the sidebar to the VIEWPORT
with position: fixed + 100vw so it is a full-width panel at the top of the
board. Also normalise the sidebar text sizes so the headings are not
oversized and small text not tiny, reading consistently with the board.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 18:24:01 +03:00
Lauri OjansivuandClaude Opus 4.8 c70a36d89e fix: vertically center swimlane header buttons (mobile)
The swimlane header buttons (collapse, swimlane menu, + new swimlane, drag
handle) hugged the top of the swimlane header on small screens (e.g. iPhone
12 mini). The menu was absolutely positioned at top:0 and each icon used a
top: calc(50% + Npx) offset that centered against the menu's own small
height, not the header. Stretch .swimlane-header-menu to the full header
height and flex-center its children vertically (align-items: center), and
drop the now-unnecessary per-icon top offsets, so the buttons sit in the
middle of the swimlane header.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 18:15:59 +03:00
Lauri OjansivuandClaude Opus 4.8 19deee64c6 test: add test:unit:all npm script to run the full node test suite
Runs every plain-Node (non-browser) test in one command: builds on
test:unit:node and adds the sticker, link-card-popup (#5715), Trello/Wekan
importer and oauth2 admin-group suites. All green.

Browser-driven suites (Meteor client mocha + Playwright e2e) are not
included here — they need a running app + a working browser, which an x86
Chromium cannot provide on an ARM host under qemu; run those on x86 CI.
rtl.test.js is intentionally excluded (2 pre-existing CSS lint failures).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 18:05:09 +03:00
Lauri OjansivuandClaude Opus 4.8 0ddc47a0db fix: changing UI language had no effect for region/script-tagged languages
The custom tap:i18n reimplementation registered each language's resource
bundle under the raw Wekan tag, but i18next (cleanCode: true) resolves and
looks translations up under a normalised code. For region/script-tagged and
legacy underscore tags (zh-CN, zh-Hans, ar-DZ, af_ZA, en_AU, ...) the stored
code and the lookup code disagreed, so the bundle was never found and the UI
silently fell back to English while simple tags (de, fr) kept working.

Normalise every i18next boundary (supportedLngs, addResourceBundle,
changeLanguage, t) through one helper (toI18nCode): underscores -> hyphens
and i18next's formatLanguageCode, so storage and lookup always agree.
Safely unwrap ES-module-namespace JSON imports without mistaking the data's
own "default" translation key for a module default export. Surface failed
language switches in the user header / login form instead of leaving the UI
silently English. Add positive and negative regression tests.

Closes #5756

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 16:51:56 +03:00
Lauri OjansivuandClaude Opus 4.8 f456a1b8f5 fix: forgot-password 500 — make reset-password email build/send graceful
Forgot Password -> "Email reset link" could return a raw HTTP 500 instead of
sending the email / showing success (#5706). When SMTP is unconfigured (no
MAIL_URL / MAIL_FROM, or a bad mail server), Meteor's Email.sendAsync throws and
the exception propagated unhandled out of the forgotPassword method as an opaque
500.

Harden the reset-password / verify-email / enroll-account email-template builders
so they never throw (guarded user name/language lookups, safe fallback if
translation throws), and wrap Accounts.sendResetPasswordEmail so a send failure
surfaces as a clean Meteor.Error('email-fail', ...) instead of a 500. Logic
extracted to server/lib/resetPasswordEmail.js with positive + negative unit tests
in tests/unit/resetPasswordEmail.test.js (wired into the test:unit:node script).

Note: this makes the failure graceful; receiving reset emails still requires SMTP
to be configured.

Closes #5706

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 16:50:24 +03:00
Lauri OjansivuandClaude Opus 4.8 5d74057c3a fix: link to a whole board even when that board already has cards
The link-card popup (linkCardPopup in client/components/lists/listBody.js)
used to auto-populate the List/Card sub-selects when a board was chosen,
leaving no way to clear them. Once the chosen board had cards, you could
only link to a card and never to the whole board.

The Swimlane/List/Card selects now default to a blank "(none)" option and
are not auto-selected on board change (this part was already merged from
seve12 / PR #5967). In addition, the popup's confirm ("link") button now
falls back to creating a board-level link when a board is selected but the
Card field is left blank, so a whole board can be linked with the single
bottom button regardless of whether the board has cards. The duplicate
board-link guard is preserved and a blank selection never creates a
phantom/undefined linked card.

Added tests/linkCardPopup.test.js (standalone Node, like
tests/cards.stickers.test.js) covering positive and negative cases.

Closes #5715

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 16:48:59 +03:00
Lauri OjansivuandClaude Opus 4.8 4edfcb0cf6 fix: due date fails when locale uses non-Latin (Persian/Arabic-Indic) digits
In locales such as Persian/Farsi or Arabic, dates and times can be rendered
with non-Latin digits (Persian/Extended Arabic-Indic ۰-۹ U+06F0–U+06F9, or
Arabic-Indic ٠-٩ U+0660–U+0669). The native JavaScript Date constructor only
understands ASCII digits, so such a string fails to parse and produces an
Invalid Date when setting/updating or comparing dates.

Added a normalizeDigits() helper in imports/lib/dateUtils.js that converts
those non-Latin digits to ASCII, applied it at every date-string parse
boundary in dateUtils.js (via an internal toDate() wrapper) and in the
date/time picker submit/change handlers in client/lib/datepicker.js. The
normal ASCII path is unchanged.

Added tests/dateUtils.normalizeDigits.test.cjs (positive and negative cases)
and wired it into the test:unit:node script.

Closes #5752

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 16:47:00 +03:00
Lauri OjansivuandClaude Opus 4.8 9a5dd98389 feat: fixed (same) width for all lists (#5729)
Add a per-viewer, per-board "Same width for all lists" (fixed width) mode.
When enabled, every list renders at one shared width and drag-resizing any
list updates that single value so all lists change together. Works for both
logged-in users (profile.fixedListWidthBoards / profile.fixedListWidths via
new setFixedListWidthEnabled / setFixedListWidth Meteor methods) and
anonymous public-board users (localStorage). Enabling fixed width turns off
auto-width; widths below 270px are rejected.

Adds Playwright spec 38 covering positive and negative cases for logged-in
(profile/DDP + rendered widths) and anonymous (localStorage) viewers.

Closes #5729

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 16:46:24 +03:00
Lauri OjansivuandClaude Opus 4.8 01271b79e8 test: add tests + negative tests for #5799, #6423, #6424
- tests/playwright/specs/39-allboards-sort-search-pagination.e2e.js (#5799):
  UI sort (A-Z/Z-A) + search (positive + non-matching negative); server
  getAllBoardsPage paging (page boundaries, search narrowing, empty result,
  and a negative that boards the user can't access never leak).
- tests/playwright/specs/40-rest-card-move-list.e2e.js (#6423): PUT card with
  listId returns 200 (not 500) and moves the card + records a moveCard
  activity; repeated moves never 500; a user without write access cannot move.
- tests/minicardLabelHeight.test.cjs (#6424): CSS regression guard that the
  minicard label viewer/label min-height overrides stay in place; wired into
  test:unit:node.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 16:36:14 +03:00
Lauri OjansivuandClaude Opus 4.8 1be9b98559 i18n: Finnish translations for All Boards sort/search strings (#5799)
Translate the new sort-boards / search-boards strings in fi.i18n.json to
Finnish, matching existing terminology (taulu/taulut, Etsi, Lajittele,
Otsikko). The other strings that remain identical to English are technical
terms and abbreviations (LDAP, OAuth2, S3, GridFS, poker numbers, operator
abbreviations) that are correctly the same in Finnish.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 16:18:14 +03:00
Lauri OjansivuandClaude Opus 4.8 71c0c2b582 test: impersonation UI tests + ChangeLog for All Boards sort/search/pagination (#5799)
Adds Playwright specs covering GlobalAdmin impersonation: impersonating a
user from Admin > People makes the session act as that user (Meteor.userId,
audit record, board data visibility), and a non-admin cannot impersonate.
Documents #5799 in the ChangeLog.

Refs #5799

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 16:16:11 +03:00
Lauri Ojansivu 98800d61cc Updated translations. 2026-06-23 16:14:47 +03:00
Lauri Ojansivu 2cf94c2fd3 Updated translations. 2026-06-23 16:12:16 +03:00
Lauri OjansivuandClaude Opus 4.8 c1da7a4a33 feature: All Boards search + server-side pagination of board icons (#5799)
Builds on the alphabetical sort: add a board-name search box and paginated
board icons to the All Boards page, with the search box and pagination
controls placed like Admin Panel > People.

- Search by board name spans every category (Starred, Templates, Remaining
  and all (sub)workspaces) from one box; Esc / a clear button reset it.
- In the sorted (A->Z / Z->A) modes the current page of board icons is
  computed on the server (getAllBoardsPage) and only that page is rendered,
  so the page scales to thousands of boards. Custom (manual drag order)
  stays unpaginated client-side so drag-reordering keeps working.
- getAllBoardsPage resolves visibility, menu/workspace filtering, search and
  sort against the effective current user (this.userId), so it also works
  when a GlobalAdmin impersonates a user.

Refs #5799

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 16:04:56 +03:00
Lauri OjansivuandClaude Opus 4.8 f74ccbc8fa feature: sort All Boards alphabetically (A-Z / Z-A)
The All Boards page only offered the per-user manual drag order, with no
obvious way to sort boards by name (#5799). Add a "Sort" button to the All
Boards header that opens a popup to choose Custom (manual drag order, the
default), Title A->Z, or Title Z->A. The choice is stored per user in
profile.allBoardsSortBy and applied in sortBoardsForUser(), so it persists
and reorders the grid live.

Closes #5799

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 15:44:46 +03:00
Lauri OjansivuandClaude Opus 4.8 7436b9d06a Updated ChangeLog.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 15:38:17 +03:00
Lauri OjansivuandClaude Opus 4.8 22f996903b fix: notification emails ignored the user's language and were sent in English
On the server only the default (English) translation bundle is loaded at
startup. Translating a notification to a user's language called
TAPi18n.__(key, params, lang) with lng=lang, but that resource bundle was
never added on the server, so i18next fell back to English — every
notification email went out in English regardless of the user's profile
language. (The client works because switching language calls setLanguage,
which loads the bundle.)

Add TAPi18n.ensureLanguageLoaded(language), which loads the bundle on
demand if i18next does not already have it, and await it before translating
in the email notification, EmailLocalization.sendEmail and outgoing webhook
paths.

Closes #5875

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 15:37:56 +03:00
Lauri OjansivuandClaude Opus 4.8 e5edb54a53 Updated ChangeLog.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 15:34:08 +03:00
Lauri OjansivuandClaude Opus 4.8 69c899f1f9 fix: REST card move to another list returned HTTP 500
The PUT .../cards/:cardId list-move path called cardMove() with
{ fieldName: 'listId' } as its fieldNames argument. cardMove() does
fieldNames.includes('boardId'), and a plain object has no .includes, so
moving a card to another list over the REST API threw
"fieldNames.includes is not a function" and returned HTTP 500. Pass the
changed field names as an array (['listId']) like the board-move path does.

Closes #6423

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 15:33:52 +03:00
Lauri Ojansivu 0f86a81bd8 Updated ChangeLog. 2026-06-23 15:31:37 +03:00
Lauri OjansivuandClaude Opus 4.8 338adb5ba9 Updated ChangeLog.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 15:29:57 +03:00
Lauri OjansivuandClaude Opus 4.8 afbabccd26 fix: card labels took two lines / double height on minicards
Each minicard label's name is rendered inside a `.viewer`, whose global
`min-height: 2.5vh` (intended for the full content editor) forced every
label to roughly double height, wasting vertical space on the board.

Scope overrides to minicard text labels: reset that `min-height`, render
the viewer/paragraph inline, and keep each label on a single compact line.

Closes #6424

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 15:29:57 +03:00
github-actions[bot] cf5560b41f Bump versions for v9.70 2026-06-23 07:20:48 +00:00
Lauri Ojansivu 5c53b8611b Updated ChangeLog. 2026-06-23 10:16:57 +03:00
Lauri Ojansivu 6aa514f359 Updated translations. 2026-06-23 10:12:46 +03:00
Lauri OjansivuandClaude Opus 4.8 ac9efbf402 fix: cross-board card copy throws on a board with no labels
Card.copy()'s cross-board label-remap did oldBoard.labels.filter(...) and
filterCopiedLabelIds(newBoard.labels, ...) without guarding a missing labels
array. Boards created via the REST API (POST /api/boards) have no labels array,
so copying a card to/from such a board — or copying the board itself (which
copies its cards cross-board) — threw "Cannot read properties of undefined
(reading 'filter')" and returned HTTP 500. Both lookups now fall back to [].

Also fixes the subtask cross-board-copy test seed (real cards always have a
userId; the seed lacked one, hitting "User ID is required in cards insertAsync").

Validated locally: full tests/playwright/specs/17-rest-api.e2e.js passes
(24/24, chromium) against a running dev server, and npm run test:unit:node
passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 09:55:34 +03:00
Lauri OjansivuandClaude Opus 4.8 1f344879fb fix: client bundle crash — move server-only card helpers out of the client bundle
models/lib/cardBoardConsistency.js and models/lib/restArrayParam.js are
CommonJS (module.exports), but being under models/ they were eagerly bundled
into the client by rspack, which classifies a no-import/require .js as an ES
module and threw at load time:

  Uncaught Error: ES Modules may not assign module.exports or exports.*
  ... ./models/lib/cardBoardConsistency.js -> ./models/cards.js

This aborted the client bundle on every page. (filenameSanitizer.js uses the
same module.exports pattern without issue only because it is never imported
into the client bundle.)

Both helpers are server-only logic (a REST handler and a Cards.before.update
hook), so they are moved to server/lib/ (excluded from the client rspack
bundle, CommonJS is fine on the Node server) and enforceCardBoardConsistency
is relocated from the isomorphic models/cards.js into server/models/cards.js.
The isomorphic models/cards.js no longer imports either helper.

Verified in a real headless Chromium against a running dev server: the page
now loads with window.Meteor present and zero console/page errors (previously
one PAGEERROR from cardBoardConsistency.js). Node unit tests updated to the new
paths and pass (npm run test:unit:node, 11 passing).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 08:50:29 +03:00
Lauri OjansivuandClaude Opus 4.8 85841a1c43 fix: cross-board card copy re-homes subtasks onto the destination board
Card.copy() re-pointed each subtask's parentId to the copied card but left its
boardId/swimlaneId/listId pointing at the source board, so copying a card to
another board orphaned its subtasks on the original board (parent on the new
board, subtask on the old one). It also mutated the cached source docs in place.
Copied subtasks are now re-homed onto the destination board/swimlane/list via a
fresh object (no cache mutation) — same class as the #5166/#5874 cross-board
consistency fixes.

Cross-board subtask copy regression in tests/playwright/specs/17-rest-api.e2e.js.
This fixes the orphaned-cross-board-subtask data symptom noted in #5347; the
separate "Maximum call stack" error there is not root-caused, so #5347 stays
open.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 08:19:05 +03:00
Lauri OjansivuandClaude Opus 4.8 c2c6bdaedd fix(#5325,#5390,#5565): notification crash, custom-field delete crash, minicard checklists
#5325: a notification referencing a deleted activity (card/board gone) had a
null activityObj, and the notifications drawer dereferences it — one orphaned
entry threw and broke the whole popup. user.notifications() now drops entries
whose activity can't be resolved.

#5390: removing a multi-board custom field from one board logged a
setCustomField activity via (await getActivity()).value with no null guard, so
a field that never had a value set threw and aborted the removal. Guarded.

#5565: the sidebar "show checklists on minicard" toggle writes
board.allowsChecklistsOnMinicard, but the minicard checked a different UI-less
field (allowsChecklistAtMinicard), so the toggle had no effect. Minicard now
reads the field the toggle actually sets.

(No automated regressions: #5325/#5565 are client-display logic and #5390's
trigger needs a multi-board custom field; all three are small, self-evident
guards/field-name corrections.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 08:07:09 +03:00
Lauri OjansivuandClaude Opus 4.8 39e8cc11a4 perf(#5688): copy a card's checklists/items without per-item activity churn
Checklist.copy() duplicated each checklist item with the hooked insertAsync, so
collection-hooks fired each item's after.insert — every one doing a getCard and
inserting an addChecklistItem activity. Copying a card with ~100 items meant
~100+ activity inserts plus per-item DB round-trips, taking minutes and spiking
CPU. The copy now uses .direct for the checklist and its items (skipping the
per-item activity hooks, which are pure churn for a wholesale copy) and sets
boardId itself — the same approach as the cardRemover delete path.

Copy regression (checklist + all items duplicated, correct boardId/checklistId)
in tests/playwright/specs/17-rest-api.e2e.js. Also documents #5388 (list
collapse now per-user) as verified-resolved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 07:59:35 +03:00
Lauri OjansivuandClaude Opus 4.8 0a206c85cf docs(changelog): note #5411 verified-resolved (board admins can invite)
Board admins already see the add-member + via canInviteToBoard (board.hasAdmin
returns true; default invite roles include board-admin) — resolved by the
Allow-Invite-to-Board roles feature (c956ab5a4), which postdates the report.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 07:39:39 +03:00
Lauri OjansivuandClaude Opus 4.8 0ed4d1556c fix(#4965,#5809): reactive card members popup; surface linked-card edit errors
#4965: the card "add members" popup snapshotted board.activeMembers() once on
open, so a member added to the board afterwards (or whose user doc loaded just
after open) was missing until the popup was reopened. The popup now stores only
the filter term and derives its candidate list reactively from
board.activeMembers() on each render, so newly-added members appear without
reopening. activeMembers() itself is unchanged.

#5809: editing a linked card whose target is on a board the user cannot write
to was rejected by the server allow rule, but the title/description submit
handlers had no error handling, so the edit vanished silently. They now catch
the failure and show the error (mirroring the existing label-color handler).

Also documents #3894 (import with a member missing from users) as
verified-resolved — already guarded in the importers with a dedicated test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 07:36:30 +03:00
Lauri OjansivuandClaude Opus 4.8 4edbab4402 fix(#4847): allow removing a deleted user from a card's members
When a user account is deleted its entry stays in a card's members and renders
as a blank avatar. Clicking it opened cardMemberPopup, whose template
dereferenced the missing user document and failed to render, so the orphaned
member could not be removed. The popup now detects a missing user, shows a
"Deleted user" entry with the raw id, and keeps the Remove from Card control
(the remove handler keys off the member's userId, not the user doc). Adds the
deleted-user i18n string.

No automated regression — a self-evident Blaze template guard; the underlying
unassignMember removal already worked. The board-members list still hides such
entries because activeMembers() also filters not-yet-loaded users, so
surfacing board-side orphans cleanly is a separate follow-up (noted in
changelog).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 07:27:29 +03:00
Lauri OjansivuandClaude Opus 4.8 0f45c0e392 fix(#5122): boards report lists only active members
Removing a member from a board marks the entry isActive:false (kept in
board.members for role history / re-activation), but the Admin Panel boards
report listed all member entries, so removed users still showed as members.
The report's userNames helper now filters to active members. The raw
GET /api/boards/:boardId response intentionally still returns the full members
array with isActive flags so API consumers can filter as needed.

Also documents #5630 (cannot save Layout settings) and #5117 (TeX rendered both
SVG and a math tag) as verified-resolved — both already fixed in current code
(Layout save handler no longer reads the moved accessibility fields; math
rendering migrated from markdown-it-mathjax3 to Temml/MathML).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 07:12:47 +03:00
Lauri OjansivuandClaude Opus 4.8 ba62449323 fix(#5592,#1894): copy board webhooks; block disabled users from boards
#5592: board.copy() duplicated swimlanes/lists/cards, custom fields and
rules/triggers/actions but had no loop for Integrations (outgoing webhooks),
so a copied board lost all of them (they are per-board children — boardRemover
deletes them by boardId). copy() now also copies the board's Integrations,
remapping boardId.

#1894: none of the add-member paths checked the target account's loginDisabled
flag, so a deactivated user could be added to a board and assigned to cards.
inviteUserToBoard now throws error-user-disabled and the REST add-member
endpoint returns HTTP 400 when the target account is disabled (added the
error-user-disabled i18n string).

Also locks in #5627 (rules ARE copied on board copy — already-fixed code that
the old report predates) with the same copyBoard regression. Regressions in
tests/playwright/specs/17-rest-api.e2e.js; changelog updated under Upcoming.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 06:52:15 +03:00
dependabot[bot] f188572b70 Bump jquery from 3.7.1 to 4.0.0
Bumps [jquery](https://github.com/jquery/jquery) from 3.7.1 to 4.0.0.
- [Release notes](https://github.com/jquery/jquery/releases)
- [Changelog](https://github.com/jquery/jquery/blob/main/changelog.md)
- [Commits](https://github.com/jquery/jquery/compare/3.7.1...4.0.0)

---
updated-dependencies:
- dependency-name: jquery
  dependency-version: 4.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-22 09:06:11 +00:00
dependabot[bot] fb9380c0d0 Bump @tweedegolf/sab-adapter-amazon-s3 from 1.0.20 to 3.0.4
Bumps [@tweedegolf/sab-adapter-amazon-s3](https://github.com/tweedegolf/storage-abstraction) from 1.0.20 to 3.0.4.
- [Release notes](https://github.com/tweedegolf/storage-abstraction/releases)
- [Changelog](https://github.com/tweedegolf/storage-abstraction/blob/master/changelog.md)
- [Commits](https://github.com/tweedegolf/storage-abstraction/commits/v3.0.4)

---
updated-dependencies:
- dependency-name: "@tweedegolf/sab-adapter-amazon-s3"
  dependency-version: 3.0.4
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-22 08:33:08 +00:00
382 changed files with 19742 additions and 2225 deletions

No files matched your search

+3 -3
View File
@@ -38,7 +38,7 @@ jobs:
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -48,14 +48,14 @@ jobs:
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
+6 -6
View File
@@ -27,14 +27,14 @@ jobs:
cache: 'npm'
- name: Cache Meteor installation
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: ~/.meteor
key: meteor-${{ hashFiles('.meteor/release') }}-${{ hashFiles('.meteor/packages') }}
restore-keys: meteor-
- name: Cache Meteor build (.meteor/local)
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: .meteor/local
key: meteor-local-unit-${{ hashFiles('.meteor/versions') }}
@@ -98,14 +98,14 @@ jobs:
cache: 'npm'
- name: Cache Meteor installation
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: ~/.meteor
key: meteor-${{ hashFiles('.meteor/release') }}-${{ hashFiles('.meteor/packages') }}
restore-keys: meteor-
- name: Cache Meteor build (.meteor/local)
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: .meteor/local
key: meteor-local-e2e-${{ hashFiles('.meteor/versions') }}
@@ -227,14 +227,14 @@ jobs:
cache: 'npm'
- name: Cache Meteor installation
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: ~/.meteor
key: meteor-${{ hashFiles('.meteor/release') }}-${{ hashFiles('.meteor/packages') }}
restore-keys: meteor-
- name: Cache Meteor build (.meteor/local)
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: .meteor/local
key: meteor-local-regression-${{ hashFiles('.meteor/versions') }}
+25 -8
View File
@@ -22,7 +22,9 @@ name: Release All Platforms
# commits and pushes the bump to wekan/wekan main
# - prepare: tags the bumped main as v<new_version> and extracts release notes
# - website: updates the wekan/wekan.fi repo (install page + API docs) and pushes
# - charts: updates and publishes the wekan/charts Helm chart and pushes
# - charts: updates and publishes the wekan/charts Helm chart and pushes,
# only AFTER the `docker` job has published the image (skipped if
# docker fails)
# - build-*: builds the amd64 bundle (full Meteor 3 build), then rebuilds native
# modules for arm64 from that amd64 base
# - release: creates the GitHub Release with the newest CHANGELOG.md notes and
@@ -210,12 +212,14 @@ jobs:
cd ..
./wekan/releases/release-website.sh "${{ inputs.old_version }}" "${{ inputs.new_version }}"
# ─── 1c. Update and publish the Helm chart repo (parallel) ──────────────────
# ─── 1c. Update and publish the Helm chart repo (after docker) ──────────────
# Bumps the WeKan version in wekan/charts, packages the chart and publishes it
# to the gh-pages Helm index, then pushes both branches. Runs in parallel with
# the platform builds; packaging the chart does not need the built artifacts.
# to the gh-pages Helm index, then pushes both branches. Runs only AFTER the
# `docker` job succeeds: the chart points users at the freshly published
# multi-arch image, so we don't advertise a new chart version until that image
# is actually on the registries. If `docker` fails, this job is skipped.
charts:
needs: bump
needs: docker
runs-on: ubuntu-latest
steps:
- name: Checkout wekan repo (for releases/release-charts.sh + version refs)
@@ -265,14 +269,14 @@ jobs:
ref: refs/tags/v${{ needs.prepare.outputs.version }}
- name: Cache Meteor installation
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: ~/.meteor
key: meteor-${{ hashFiles('.meteor/release') }}-${{ hashFiles('.meteor/packages') }}
restore-keys: meteor-
- name: Cache npm packages
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}
@@ -796,7 +800,20 @@ jobs:
fetch-depth: 0
- name: Install snapcraft
run: sudo snap install snapcraft --classic
# The Snap Store intermittently rejects the install with
# 'error: cannot install "snapcraft": too many requests' (a 429-style
# rate limit) that instantly fails the whole snap job before any build.
# Retry a few times so one transient store throttle does not fail the
# release (same spirit as the remote-build retry below).
run: |
set -uo pipefail
for i in 1 2 3 4 5; do
sudo snap install snapcraft --classic && exit 0
echo "snapcraft install attempt $i/5 failed (Snap Store throttle?); retrying in 30s..."
sleep 30
done
echo "::error::snapcraft install failed after 5 attempts (Snap Store rate limit)."
exit 1
- name: Provide Launchpad credentials for remote-build
env:
+8 -8
View File
@@ -6,7 +6,7 @@
meteor-base@1.5.2
# Build system
ecmascript@0.18.1-rc350.0
ecmascript@0.18.1
standard-minifier-js@3.2.0
rspack@1.1.0
@@ -16,11 +16,11 @@ blaze@3.0.2
aldeed:collection2
aldeed:schema-index@4.0.0-beta.5
reywood:publish-composite
mongo@2.4.0-rc350.2
mongo@2.4.0
# Account system
accounts-password@3.3.0-rc350.2
accounts-express@1.0.0-rc350.2
accounts-password@3.3.0
accounts-express@1.0.0
communitypackages:core
communitypackages:unstyled
communitypackages:flow-routing
@@ -37,7 +37,7 @@ wekan-accounts-oidc
check@1.5.0
random@1.2.2
reactive-dict@1.3.2
session@1.2.3-rc350.2
session@1.2.3
tracker@1.3.4
audit-argument-checks@1.0.8
@@ -45,7 +45,7 @@ audit-argument-checks@1.0.8
ostrio:i18n
reactive-var@1.0.13
shell-server@0.7.0
email@3.2.0-rc350.2
email@3.2.0
dynamic-import@0.7.4
wekan-markdown
quave:synced-cron
@@ -55,8 +55,8 @@ ostrio:files
meteortesting:mocha@3.0.0
matb33:collection-hooks
service-configuration@1.3.5
minifier-css@2.0.2-rc350.2
ejson@1.2.0-rc350.2
minifier-css@2.0.2
ejson@1.2.0
logging@1.3.6
wekan-fullcalendar
wekan-fontawesome
+1 -1
View File
@@ -1 +1 @@
METEOR@3.5-rc.2
METEOR@3.5
+28 -28
View File
@@ -1,44 +1,44 @@
accounts-base@3.3.0-rc350.2
accounts-express@1.0.0-rc350.2
accounts-oauth@1.4.7-rc350.2
accounts-password@3.3.0-rc350.2
accounts-base@3.3.0
accounts-express@1.0.0
accounts-oauth@1.4.7
accounts-password@3.3.0
aldeed:collection2@4.2.0
aldeed:schema-index@4.0.0
aldeed:simple-schema@1.13.1
allow-deny@2.1.0
audit-argument-checks@1.0.8
autoupdate@2.0.1
babel-compiler@7.14.1-rc350.0
babel-compiler@7.14.1
babel-runtime@1.5.2
base64@1.0.14-rc350.2
binary-heap@1.0.13-rc350.2
base64@1.0.14
binary-heap@1.0.13
blaze@3.0.3
blaze-tools@2.0.1
boilerplate-generator@2.1.0
caching-compiler@2.0.1
caching-html-compiler@2.0.1
callback-hook@1.8.0-rc350.2
callback-hook@1.8.0
check@1.5.0
communitypackages:core@2.0.3
communitypackages:flow-routing@2.1.0
communitypackages:unstyled@2.0.1
core-runtime@1.0.0
ddp@1.4.2
ddp-client@3.3.0-rc350.2
ddp-common@1.4.5-rc350.2
ddp-rate-limiter@1.3.0-rc350.2
ddp-server@3.3.0-rc350.2
ddp-client@3.3.0
ddp-common@1.4.5
ddp-rate-limiter@1.3.0
ddp-server@3.3.0
diff-sequence@1.1.3
dynamic-import@0.7.4
ecmascript@0.18.1-rc350.0
ecmascript@0.18.1
ecmascript-runtime@0.8.3
ecmascript-runtime-client@0.13.0
ecmascript-runtime-server@0.11.1
ejson@1.2.0-rc350.2
email@3.2.0-rc350.2
ejson@1.2.0
email@3.2.0
es5-shim@4.8.1
facts-base@1.0.2
fetch@0.2.0-rc350.2
fetch@0.2.0
geojson-utils@1.0.12
hot-code-push@1.0.5
html-tools@2.0.1
@@ -53,23 +53,23 @@ lai:collection-extensions@1.0.0
localstorage@1.2.1
logging@1.3.6
matb33:collection-hooks@2.1.0
meteor@2.3.1-rc350.2
meteor@2.3.1
meteor-base@1.5.2
meteortesting:browser-tests@1.8.0
meteortesting:mocha@3.3.0
meteortesting:mocha-core@8.2.0
minifier-css@2.0.2-rc350.2
minifier-js@3.2.1-rc350.0
minimongo@2.2.0-rc350.2
minifier-css@2.0.2
minifier-js@3.2.1
minimongo@2.2.0
modern-browsers@0.2.3
modules@0.20.3
modules-runtime@0.13.2
mongo@2.4.0-rc350.2
mongo@2.4.0
mongo-decimal@0.2.0
mongo-dev-server@1.1.1
mongo-id@1.0.9
npm-mongo@6.16.1
oauth@3.0.4-rc350.2
oauth@3.0.4
oauth2@1.3.3
observe-sequence@2.0.1
ordered-dict@1.2.0
@@ -82,7 +82,7 @@ promise@1.0.0
quave:synced-cron@2.4.0
raix:eventemitter@1.0.0
random@1.2.2
rate-limit@1.2.0-rc350.2
rate-limit@1.2.0
react-fast-refresh@0.3.0
reactive-dict@1.3.2
reactive-var@1.0.13
@@ -92,10 +92,10 @@ reywood:publish-composite@1.9.0
routepolicy@1.1.2
rspack@1.1.0
service-configuration@1.3.5
session@1.2.3-rc350.2
session@1.2.3
sha@1.0.10
shell-server@0.7.0
socket-stream-client@0.7.0-rc350.2
socket-stream-client@0.7.0
spacebars@2.0.1
spacebars-compiler@2.0.1
standard-minifier-js@3.2.0
@@ -105,9 +105,9 @@ templating-runtime@2.0.2
templating-tools@2.0.1
tools-core@1.1.0
tracker@1.3.4
typescript@5.10.1-rc350.0
url@1.3.6-rc350.2
webapp@2.2.0-rc350.2
typescript@5.10.1
url@1.3.6
webapp@2.2.0
webapp-hashing@1.1.2
wekan-accounts-cas@0.2.0
wekan-accounts-lockout@1.1.0
+8 -1
View File
@@ -1,2 +1,9 @@
npm-packages/
tests/
# Root tests/ holds Playwright specs, Node E2E and node-based unit tests - none
# of it is Meteor code, and Meteor's file watcher otherwise churns on the
# tests/playwright/test-results/ tree (inotify errors). The leading slash anchors
# this to the repo root only, so it does NOT match server/lib/tests/ or
# client/lib/tests/ (the Mocha suites, loaded via meteor.testModule in
# package.json). A naked "tests/" would match a dir named tests at any depth and
# would hide those suites, making `meteor test` report "0 passing".
/tests/
+1227 -144
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -2,10 +2,10 @@
For all code at WeKan GitHub Organization https://github.com/wekan
- For pull requests, mention only those participants that are **human**.
- All code in pull requests need to have permission already to add it to WeKan with MIT license, and will become MIT license.
- For any dependencies, permissive licenses like https://copyfree.org are preferred
- For anything currently that is non-permissive (like GPL, AGPL, SSPL), those will be replaced with permissive-licensed alternatives
- Any intelligence allowed: Human, AI or any other. No discrimination, please have world peace. Mentioning participants optional.
- All code is checked and validated by xet7 and many different security AI. If code is real working fix or feature, it usually will be merged.
- All known vulnerabilities have been fixed at newest WeKan, please upgrade https://wekan.fi/upgrade/
- There is no known backdoors. If WeKan is installed by someone else, first registered user is Admin and has Admin Panel access.
+2 -2
View File
@@ -7,14 +7,14 @@ LABEL org.opencontainers.image.source="https://github.com/wekan/wekan"
# TARGETARCH and TARGETVARIANT are automatically provided by Docker Buildx
ARG TARGETARCH
ARG TARGETVARIANT
ARG VERSION=9.69
ARG VERSION=9.83
ARG DEBIAN_FRONTEND=noninteractive
ENV BUILD_DEPS="apt-utils gnupg wget bzip2 g++ curl libarchive-tools build-essential git ca-certificates python3 unzip"
ENV \
DEBUG=false \
NODE_VERSION=v24.17.0 \
NODE_VERSION=v24.18.0 \
METEOR_RELEASE=METEOR@3.5-rc.2 \
USE_EDGE=false \
NPM_VERSION=11.12.1 \
+1 -1
View File
@@ -103,7 +103,7 @@ By working directly with WeKan ® you get the benefit of active maintenance and
## Getting Started with Development
The default branch uses Meteor 3.5-rc.2 with Node.js 24.x.
The main branch uses Meteor 3.5 with Node.js 24.x.
See [CHANGELOG.md](https://github.com/wekan/wekan/blob/main/CHANGELOG.md) for the latest runtime updates.
To contribute, [create a fork](https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Build-and-Create-Pull-Request.md#2-create-fork-of-httpsgithubcomwekanwekan-at-github-web-page) and run `./rebuild-wekan.sh` (or `./rebuild-wekan.bat` on Windows) as detailed [here](https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Build-and-Create-Pull-Request.md#3-select-option-1-to-install-dependencies-and-then-enter). Once you're ready, please test your code and [submit a pull request (PR)](https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Build-and-Create-Pull-Request.md#7-test).
+1 -1
View File
@@ -1,5 +1,5 @@
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
appVersion: "v9.69.0"
appVersion: "v9.83.0"
files:
userUploads:
- README.md
+49 -5
View File
@@ -37,6 +37,8 @@ If *nix: chmod +x api.py => ./api.py users
python3 api.py lists BOARDID # Lists of BOARDID
python3 api.py list BOARDID LISTID # Info of LISTID
python3 api.py createlist BOARDID LISTTITLE # Create list
python3 api.py editlist BOARDID LISTID NEWLISTTITLE # Edit list title
python3 api.py editlistcolor BOARDID LISTID COLOR (Color available: `white`, `green`, `yellow`, `orange`, `red`, `purple`, `blue`, `sky`, `lime`, `pink`, `black`, `silver`, `peachpuff`, `crimson`, `plum`, `darkgreen`, `slateblue`, `magenta`, `gold`, `navy`, `gray`, `saddlebrown`, `paleturquoise`, `mistyrose`, `indigo`) # Edit list color
python3 api.py addcard AUTHORID BOARDID SWIMLANEID LISTID CARDTITLE CARDDESCRIPTION
python3 api.py editcard BOARDID LISTID CARDID NEWCARDTITLE NEWCARDDESCRIPTION
python3 api.py getcard BOARDID LISTID CARDID # Get card info
@@ -265,10 +267,9 @@ if arguments == 10:
boardid = sys.argv[3]
name = sys.argv[4]
type1 = sys.argv[5]
settings = str(json.loads(sys.argv[6]))
# There is error: Settings must be object. So this does not work yet.
#settings = {'currencyCode': 'EUR'}
print(type(settings))
settings_arg = sys.argv[6]
# settings is optional; an empty arg -> {} (avoids json.loads('') crashing).
settings = json.loads(settings_arg) if settings_arg.strip() else {}
showoncard = sys.argv[7]
automaticallyoncard = sys.argv[8]
showlabelonminicard = sys.argv[9]
@@ -276,7 +277,7 @@ if arguments == 10:
customfieldtoboard = wekanurl + apiboards + boardid + s + cf
# Add Custom Field to Board
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
post_data = {'authorId': '{}'.format(authorid), 'name': '{}'.format(name), 'type': '{}'.format(type1), 'settings': '{}'.format(settings), 'showoncard': '{}'.format(showoncard), 'automaticallyoncard': '{}'.format(automaticallyoncard), 'showlabelonminicard': '{}'.format(showlabelonminicard), 'showsumattopoflist': '{}'.format(showsumattopoflist)}
post_data = {'authorId': '{}'.format(authorid), 'name': '{}'.format(name), 'type': '{}'.format(type1), 'settings': json.dumps(settings), 'showoncard': '{}'.format(showoncard), 'automaticallyoncard': '{}'.format(automaticallyoncard), 'showlabelonminicard': '{}'.format(showlabelonminicard), 'showsumattopoflist': '{}'.format(showsumattopoflist)}
body = requests.post(customfieldtoboard, data=post_data, headers=headers)
print(body.text)
# ------- ADD CUSTOM FIELD TO BOARD END -----------
@@ -433,6 +434,49 @@ if arguments == 5:
if arguments >= 4:
if sys.argv[1] == 'editlist':
# ------- EDIT LIST START -----------
boardid = sys.argv[2]
listid = sys.argv[3]
newlisttitle = sys.argv[4]
edlist = wekanurl + apiboards + boardid + s + l + s + listid
print(edlist)
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
put_data = {'title': '{}'.format(newlisttitle)}
body = requests.put(edlist, data=put_data, headers=headers)
print("=== EDIT LIST ===\n")
body = requests.get(edlist, headers=headers)
data2 = body.text.replace('}', "}\n")
print(data2)
# ------- EDIT LIST END -----------
if sys.argv[1] == 'editlistcolor':
# ------- EDIT LIST COLOR START -----------
boardid = sys.argv[2]
listid = sys.argv[3]
newcolor = sys.argv[4]
valid_colors = ['white', 'green', 'yellow', 'orange', 'red', 'purple', 'blue', 'sky', 'lime', 'pink', 'black',
'silver', 'peachpuff', 'crimson', 'plum', 'darkgreen', 'slateblue', 'magenta', 'gold', 'navy',
'gray', 'saddlebrown', 'paleturquoise', 'mistyrose', 'indigo']
if newcolor not in valid_colors:
print("Invalid color. Choose a color from the list.")
sys.exit(1)
edlist = wekanurl + apiboards + boardid + s + l + s + listid
print(edlist)
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
put_data = {'color': '{}'.format(newcolor)}
body = requests.put(edlist, data=put_data, headers=headers)
print("=== EDIT LIST COLOR ===\n")
body = requests.get(edlist, headers=headers)
data2 = body.text.replace('}', "}\n")
print(data2)
# ------- EDIT LIST COLOR END -----------
if sys.argv[1] == 'newuser':
# ------- CREATE NEW USER START -----------
@@ -15,6 +15,20 @@
.reactions-popup .add-comment-reaction:hover {
background-color: #b0c4de;
}
/* Eye / eye-slash icon toggle for showing/hiding activities. Open eye
(.eye-icon) = activities shown; crossed eye (.eye-slash-icon) = hidden.
Matches the password-visibility toggle used on the login/register pages. */
.activities-eye-toggle {
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 0.4em;
text-decoration: none;
}
.activities-eye-toggle .eye-icon,
.activities-eye-toggle .eye-slash-icon {
line-height: 1;
}
.activities {
clear: both;
}
+5 -2
View File
@@ -106,10 +106,10 @@ template(name="activity")
//- comment activity ----------------------------------------------------
if($eq activity.activityType 'deleteComment')
| {{{_ 'activity-deleteComment' activity.commentId}}}.
| {{{_ 'activity-deleteComment' (sanitize activity.commentDisplayText)}}}.
if($eq activity.activityType 'editComment')
| {{{_ 'activity-editComment' activity.commentId}}}.
| {{{_ 'activity-editComment' (sanitize activity.commentDisplayText)}}}.
if($eq activity.activityType 'addComment')
| {{{_ 'activity-on' cardLink}}}
@@ -133,6 +133,9 @@ template(name="activity")
if($eq activity.activityType 'a-changedTitle')
| {{{_ 'activity-changedTitle' (sanitize activity.value) cardLink}}}.
if($eq activity.activityType 'a-changedDescription')
| {{{_ 'activity-changedDescription' cardLink}}}.
//- customField activity ------------------------------------------------
if($eq activity.activityType 'createCustomField')
| {{_ 'activity-customfield-created' customField}}.
+6 -6
View File
@@ -107,17 +107,17 @@
overflow-y: scroll !important;
}
/* Mobile - make all text 2x bigger inside #content by default (icons stay same size) */
/* Mobile - keep #content text at normal size (reverted #6426 oversizing) */
@media screen and (max-width: 800px),
screen and (max-device-width: 800px),
screen and (-webkit-min-device-pixel-ratio: 2) and (max-width: 800px),
screen and (max-width: 800px) and (orientation: portrait),
screen and (max-width: 800px) and (orientation: landscape) {
#content {
font-size: 2em !important; /* 2x bigger base font size for content area */
font-size: 1em !important; /* Normal size (reverted #6426 oversizing) */
}
/* Make all text elements 2x bigger */
/* Keep all text elements at normal size */
#content h1, #content h2, #content h3, #content h4, #content h5, #content h6,
#content p, #content span, #content div, #content a, #content button,
#content .minicard, #content .list-header-name, #content .board-header-btn,
@@ -186,7 +186,7 @@
/* Fallback for iPhone devices using JavaScript detection */
.iphone-device #content {
font-size: 2em !important; /* 2x bigger base font size for content area */
font-size: 1em !important; /* Normal size (reverted #6426 oversizing) */
}
.iphone-device #content h1, .iphone-device #content h2, .iphone-device #content h3, .iphone-device #content h4, .iphone-device #content h5, .iphone-device #content h6,
@@ -231,9 +231,9 @@
font-size: 1em !important; /* Keep original icon size */
}
/* Mobile iPhone: scale card details text and icons to 2x */
/* Mobile iPhone: keep card details text/icons at normal size (reverted #6426 oversizing) */
body.mobile-mode.iphone-device .card-details {
font-size: 2em !important;
font-size: 1em !important;
}
body.mobile-mode.iphone-device .card-details .fa,
body.mobile-mode.iphone-device .card-details .icon,
+30 -3
View File
@@ -6,11 +6,13 @@ import '/client/lib/dragscrollTouch';
import { boardConverter } from '/client/lib/boardConverter';
import { formatDateByUserPreference } from '/imports/lib/dateUtils';
import { toFullCalendarFirstDay } from '/client/lib/calendarFirstDay';
import { weekNumberByFirstDay } from '/models/lib/weekStart';
import Swimlanes from '/models/swimlanes';
import Lists from '/models/lists';
import TableVisibilityModeSettings from '/models/tableVisibilityModeSettings';
import { EscapeActions } from '/client/lib/escapeActions';
import { Utils } from '/client/lib/utils';
import { Filter } from '/client/lib/filter';
// SubsManager removed for Meteor 3 migration
const { calculateIndex } = Utils;
@@ -578,7 +580,15 @@ Template.boardBody.onRendered(function () {
});
dragscroll.reset();
Utils.setBackgroundImage();
// #4978: apply the board background inside a reactive autorun instead of a
// one-shot call. Switching directly between boards (e.g. the favorites bar)
// reuses this template instance and never re-fires onRendered, so the
// background used to stay on the previously shown board. Reading the current
// board inside setBackgroundImage() tracks both board switches and background
// edits; it performs no DB writes, so there is no autorun write loop.
this.autorun(() => {
Utils.setBackgroundImage();
});
});
Template.boardBody.onDestroyed(function () {
@@ -929,6 +939,11 @@ Template.calendarView.onRendered(function () {
document.documentElement.dir = TAPi18n.getLanguageDirection();
this.autorun(function () {
// #5656: refetch calendar events whenever the active board Filter changes
// so the Calendar view stays in sync with the other views. Reading the
// Filter's reactive state here establishes the dependency; the events()
// callback below applies the actual selector.
Filter.isActive();
const calendarEl = document.getElementById('calendar-view');
if (calendarEl && calendarEl._wekanCalendar) {
calendarEl._wekanCalendar.refetchEvents();
@@ -959,6 +974,12 @@ Template.calendarView.helpers({
editable: true,
selectable: true,
weekNumbers: true,
// #4946: FullCalendar's default weekNumberCalculation is locale-based
// (Sunday-anchored for many locales), so the week-number column ignored
// the user's start-day-of-week setting even though `firstDay` moved the
// grid. Compute the number from the SAME firstDay so the label matches the
// way the week is laid out.
weekNumberCalculation: date => weekNumberByFirstDay(date, firstDay),
// Use non-localized AM/PM time format to avoid confusing notations like 上/下/中
// Use full 'am'/'pm' instead of single-letter 'a'/'p' for clarity
eventTimeFormat: {
@@ -1002,6 +1023,12 @@ Template.calendarView.helpers({
isRTL: TAPi18n.isRTL(),
events(fetchInfo, callback) {
const currentBoard = Utils.getCurrentBoard();
// #5656: apply the active board Filter (member / assignee / due-date /
// label / custom-field) so the Calendar view shows the same scoped set
// of cards as the Board / Lists / Swimlanes views.
const filterSelector = Filter.isActive()
? Filter._getMongoSelector()
: undefined;
const events = [];
const pushEvent = function (card, title, start, end, extraCls) {
start = start || card.startAt;
@@ -1026,12 +1053,12 @@ Template.calendarView.helpers({
});
};
currentBoard
.cardsInInterval(fetchInfo.start, fetchInfo.end)
.cardsInInterval(fetchInfo.start, fetchInfo.end, filterSelector)
.forEach(function (card) {
pushEvent(card);
});
currentBoard
.cardsDueInBetween(fetchInfo.start, fetchInfo.end)
.cardsDueInBetween(fetchInfo.start, fetchInfo.end, filterSelector)
.forEach(function (card) {
pushEvent(
card,
+64 -246
View File
@@ -297,6 +297,62 @@
gap: 8px;
}
/* #5799: board-name search box in the All Boards header */
.boards-path-header .board-search {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 8px;
background: #fff;
border: 1px solid #ddd;
border-radius: 4px;
}
.boards-path-header .board-search input {
border: none;
outline: none;
background: transparent;
font-size: 14px;
min-width: 140px;
}
.boards-path-header .board-search .board-search-clear {
cursor: pointer;
color: #999;
}
.boards-path-header .board-search .board-search-clear:hover {
color: #333;
}
/* #5799: board grid pagination controls, mirroring Admin Panel > People */
.boards-pagination {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 12px;
}
.boards-pagination button {
min-width: 32px;
height: 30px;
border: 1px solid #bbb;
background: #fff;
border-radius: 0.4vw;
cursor: pointer;
}
.boards-pagination button.disabled {
opacity: 0.5;
pointer-events: none;
}
.boards-page-info {
min-width: 72px;
text-align: center;
color: #333;
}
.boards-path-header .path-icon {
font-size: 18px;
}
@@ -946,21 +1002,13 @@ body.grey-icons-enabled .checkmark-no-grey {
}
}
/* Mobile - make all text and icons 2x bigger above #content on All Boards page */
/* Mobile All Boards layout fixes */
@media screen and (max-width: 800px),
screen and (max-device-width: 800px),
screen and (-webkit-min-device-pixel-ratio: 2) and (max-width: 800px),
screen and (max-width: 800px) and (orientation: portrait),
screen and (max-width: 800px) and (orientation: landscape),
screen and (max-device-width: 932px) and (-webkit-min-device-pixel-ratio: 3) {
.wrapper {
font-size: 2em !important; /* 2x bigger base font size for All Boards page */
}
.wrapper * {
font-size: inherit !important; /* Inherit the 2x scaling */
}
/* Stack the two-column All Boards layout on small screens. The fixed
"260px 1fr" grid had no mobile collapse, so on narrow non-iPhone browsers
(Fairphone Firefox ESR / Ubuntu Touch Morph, narrow desktop) it overflowed
@@ -976,243 +1024,6 @@ body.grey-icons-enabled .checkmark-no-grey {
padding-inline-end: 0 !important;
padding-bottom: 12px;
}
.wrapper .fa, .wrapper .icon {
font-size: 2em !important; /* 2x bigger icons */
}
.board-list-header {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.board-list-header h1 {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.AllBoardTeamsOrgs {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.AllBoardTeamsOrgs select {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.AllBoardTeamsOrgs input {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.AllBoardTeamsOrgs .fa {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.board-list {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.board-list .board-list-item {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.board-list .board-list-item-name {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.board-list .board-list-item-desc {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.board-list .minicard-members {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.board-list .minicard-lists {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.board-list .fa {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.board-list .board-handle {
font-size: 1em !important; /* Use inherited 2x scaling */
}
}
/* Fallback for iPhone devices using JavaScript detection - All Boards page */
.iphone-device .wrapper {
font-size: 2em !important; /* 2x bigger base font size for All Boards page */
}
.iphone-device .wrapper * {
font-size: inherit !important; /* Inherit the 2x scaling */
}
.iphone-device .wrapper .fa, .iphone-device .wrapper .icon {
font-size: 2em !important; /* 2x bigger icons */
}
.iphone-device .board-list-header {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.iphone-device .board-list-header h1 {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.iphone-device .AllBoardTeamsOrgs {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.iphone-device .AllBoardTeamsOrgs select {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.iphone-device .AllBoardTeamsOrgs input {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.iphone-device .AllBoardTeamsOrgs .fa {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.iphone-device .board-list {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.iphone-device .board-list .board-list-item {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.iphone-device .board-list .board-list-item-name {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.iphone-device .board-list .board-list-item-desc {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.iphone-device .board-list .minicard-members {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.iphone-device .board-list .minicard-lists {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.iphone-device .board-list .fa {
font-size: 1em !important; /* Use inherited 2x scaling */
}
.iphone-device .board-list .board-handle {
font-size: 1em !important; /* Use inherited 2x scaling */
}
/* iPhone 12 Mini and very small screens - make everything much larger */
@media screen and (max-width: 400px) and (max-height: 900px) {
.board-list {
height: calc(100vh - 120px) !important;
overflow-y: scroll !important;
overflow-x: hidden !important;
-webkit-overflow-scrolling: touch;
padding: 0 0.5rem;
}
.board-list li {
width: 100% !important;
float: none !important;
display: block !important;
margin-bottom: 1.5rem !important;
}
.board-list .board-list-item {
height: 12rem !important; /* Much taller */
width: 100% !important;
margin: 0 !important;
padding: 1rem !important; /* More padding */
font-size: 18px !important; /* Much larger text */
line-height: 1.4 !important;
}
.board-list .board-list-item .board-list-item-name {
font-size: 20px !important; /* Larger board names */
font-weight: bold !important;
margin-bottom: 0.5rem !important;
}
.board-list .board-list-item .board-list-item-desc {
font-size: 16px !important; /* Larger descriptions */
line-height: 1.3 !important;
}
.board-list .board-list-item .minicard-members {
font-size: 14px !important; /* Larger member avatars */
}
.board-list .board-list-item .minicard-lists {
font-size: 14px !important; /* Larger list counters */
}
.board-list .board-handle {
position: absolute;
padding: 10px !important;
top: 50%;
transform: translateY(-50%);
inset-inline-end: 15px !important;
font-size: 28px !important; /* Much larger drag handle */
color: #fff;
background: rgba(0,0,0,0.4) !important;
border-radius: 50%;
width: 50px !important; /* Larger handle */
height: 50px !important;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
transition: background-color 0.2s ease;
}
.board-list .board-handle:hover {
background: rgba(255, 255, 0, 0.8) !important; /* Yellow hover */
}
/* Force scrollbar to be visible and larger */
.board-list::-webkit-scrollbar {
width: 16px !important; /* Much wider scrollbar */
display: block !important;
visibility: visible !important;
}
.board-list::-webkit-scrollbar-track {
background: #f1f1f1 !important;
border-radius: 8px !important;
display: block !important;
visibility: visible !important;
}
.board-list::-webkit-scrollbar-thumb {
background: #666 !important; /* Darker for better visibility */
border-radius: 8px !important;
display: block !important;
visibility: visible !important;
min-height: 50px !important; /* Minimum thumb size */
}
.board-list::-webkit-scrollbar-thumb:hover {
background: #333 !important;
}
/* Ensure scrollbar is always visible */
.board-list {
scrollbar-gutter: stable;
scrollbar-width: auto !important;
min-height: 100vh !important;
}
.board-list::after {
content: '';
display: block;
height: 200px !important; /* More space to ensure scrolling */
}
}
.AllBoardTeamsOrgs {
list-style-type: none;
@@ -1267,6 +1078,13 @@ body.grey-icons-enabled .checkmark-no-grey {
margin: 8px; /* Space between board items */
}
/* #6439: highlight the board a dragged board will be dropped before, when
reordering in the Custom (drag order) sort mode. */
.js-board.board-reorder-over {
outline: 2px dashed #3498db;
outline-offset: 2px;
}
/* Reset background for add-board button */
.js-add-board {
background-color: transparent !important;
+37
View File
@@ -85,6 +85,18 @@ template(name="boardList")
i.fa.fa-thumb-tack
| {{_ 'multi-selection-active'}}
.path-right
.board-search
span.emoji-icon
i.fa.fa-search
input.js-board-search-input(type="text" placeholder="{{_ 'search-boards'}}" value="{{boardSearch}}" autocomplete="off")
if boardSearch
a.board-search-clear.js-board-search-clear(title="{{_ 'filter-clear'}}")
span.emoji-icon
i.fa.fa-times
a.board-header-btn.js-open-boards-sort(title="{{_ 'sort-boards'}}" class="{{#unless isBoardsSort 'custom'}}emphasis{{/unless}}")
span.emoji-icon
i.fa.fa-sort-alpha-asc
span {{_ 'sort'}}
if canModifyBoards
if hasBoardsSelected
button.js-archive-selected-boards.board-header-btn
@@ -104,6 +116,13 @@ template(name="boardList")
a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}")
span.emoji-icon
i.fa.fa-times
if boardsPaginationActive
.boards-pagination
button.js-boards-prev-page(class="{{#if hasBoardsPrevPage}}{{else}}disabled{{/if}}")
| &lt;
span.boards-page-info {{boardsCurrentPage}} / {{boardsTotalPages}}
button.js-boards-next-page(class="{{#if hasBoardsNextPage}}{{else}}disabled{{/if}}")
| &gt;
ul.board-list.clearfix.js-boards.dragscroll(class="{{#if isMiniScreen}}mobile-view{{/if}} {{#if BoardMultiSelection.isActive}}is-multiselection-active{{/if}}")
li.js-add-board
if isSelectedMenu 'templates'
@@ -205,6 +224,24 @@ template(name="boardList")
span.emoji-icon
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
template(name="boardsSortPopup")
ul.pop-over-list
li
a.js-boards-sort(data-sort="custom" class="{{#if isBoardsSort 'custom'}}active{{/if}}")
span.emoji-icon
i.fa.fa-arrows
| &nbsp;{{_ 'sort-boards-custom'}}
li
a.js-boards-sort(data-sort="title-asc" class="{{#if isBoardsSort 'title-asc'}}active{{/if}}")
span.emoji-icon
i.fa.fa-sort-alpha-asc
| &nbsp;{{_ 'sort-boards-title-asc'}}
li
a.js-boards-sort(data-sort="title-desc" class="{{#if isBoardsSort 'title-desc'}}active{{/if}}")
span.emoji-icon
i.fa.fa-sort-alpha-desc
| &nbsp;{{_ 'sort-boards-title-desc'}}
template(name="boardListHeaderBar")
h1 {{_ title }}
//.board-header-btns.right
+259 -29
View File
@@ -2,12 +2,14 @@ import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import getSlug from 'limax';
import '/client/lib/dragscrollTouch';
import { enablePageDragscroll, disablePageDragscroll } from '/client/lib/pageDragscroll';
import TableVisibilityModeSettings from '/models/tableVisibilityModeSettings';
import { BoardMultiSelection } from '/client/lib/boardMultiSelection';
import { EscapeActions } from '/client/lib/escapeActions';
import { Utils } from '/client/lib/utils';
import {
isDragReorderEnabled,
computeReorderedSortIndex,
} from '/models/lib/boardSortReorder';
// SubsManager removed for Meteor 3 migration
@@ -57,6 +59,19 @@ function shareBoardWith(boardId, shareType, name, id) {
const DEFAULT_WORKSPACE_ICON = '📁';
// #5799: how many board icons to show per page in the sorted (non-custom) modes.
// Matches the Admin Panel > People page size.
const BOARDS_PER_PAGE = 25;
// #6439: the effective All Boards sort mode for the current user, defaulting to
// 'custom' (manual drag order) when unknown. Used to gate board drag-reordering.
function currentAllBoardsSortBy() {
const cu = ReactiveCache.getCurrentUser();
return cu && typeof cu.getAllBoardsSortBy === 'function'
? cu.getAllBoardsSortBy()
: 'custom';
}
function getCurrentWorkspacesTree() {
const currentUser = ReactiveCache.getCurrentUser();
const tree =
@@ -164,6 +179,13 @@ Template.boardList.onCreated(function () {
this.selectedMenu = new ReactiveVar(Session.get('boardListMenu') || 'starred');
this.selectedWorkspaceIdVar = new ReactiveVar(null);
this.workspacesTreeVar = new ReactiveVar([]);
// #5799: free-text search by board name. When non-empty it searches across
// ALL the user's boards (Starred, Templates, Remaining and every workspace),
// ignoring the selected-menu filter.
this.boardSearchVar = new ReactiveVar('');
// #5799: server-side pagination state for the sorted (non-custom) modes.
this.boardsPageVar = new ReactiveVar(1);
this.pagedBoardsVar = new ReactiveVar({ ids: [], total: 0 });
// #5850: the user's orgs/teams that have the per-org/team Shared Templates
// flag set (plus email domains), fetched via a non-admin server method since
// the org/team publications are admin-only. Gates the drag-to-share targets.
@@ -262,6 +284,50 @@ Template.boardList.onCreated(function () {
}
});
// #5799: reset to the first page whenever the active filter changes (search
// text, selected menu/workspace or sort mode). Depends only on those signals,
// not on boardsPageVar, so paging next/prev does not reset itself.
this.autorun(() => {
this.boardSearchVar.get();
this.selectedMenu.get();
const cu = ReactiveCache.getCurrentUser();
if (cu && typeof cu.getAllBoardsSortBy === 'function') {
cu.getAllBoardsSortBy();
}
this.boardsPageVar.set(1);
});
// #5799: fetch the current page of boards from the server for the sorted
// (non-custom) modes, so only that page of icons is rendered. Custom (manual
// drag order) stays unpaginated client-side so drag-reordering keeps working.
// Uses the effective current user server-side, so it also works under
// GlobalAdmin impersonation.
this.autorun(() => {
const cu = ReactiveCache.getCurrentUser();
const sortBy =
cu && typeof cu.getAllBoardsSortBy === 'function'
? cu.getAllBoardsSortBy()
: 'custom';
if (sortBy === 'custom') {
this.pagedBoardsVar.set({ ids: [], total: 0 });
return;
}
const search = (this.boardSearchVar.get() || '').trim();
const menu = this.selectedMenu.get();
const page = this.boardsPageVar.get();
Meteor.call(
'getAllBoardsPage',
{ search, sortBy, menu, page, perPage: BOARDS_PER_PAGE },
(err, res) => {
if (err) {
console.error('getAllBoardsPage failed:', err);
return;
}
if (res) this.pagedBoardsVar.set(res);
},
);
});
// The templates-container board is no longer auto-created at signup (#2339,
// #5850), and it is no longer auto-created when the user merely opens the
// Templates sub-view either: opening the view must not leave behind an empty
@@ -274,12 +340,9 @@ Template.boardList.onCreated(function () {
});
Template.boardList.onRendered(function () {
// Enable drag-to-scroll on the All Boards page the same way the board
// canvas/swimlanes do. On desktop the page scroll container is <body>, so we
// tag it via enablePageDragscroll(); the .board-list.dragscroll element only
// becomes a scroll container in mobile view, where its own class handles it.
// Finger drag-scrolling is handled by dragscrollTouch.js.
enablePageDragscroll();
// Drag-to-scroll on the All Boards page is handled centrally for all
// non-board pages by defaultLayout's route-aware autorun
// (see client/components/main/layouts.js).
// jQuery sortable is disabled in favor of HTML5 drag-and-drop for space management
// The old sortable code has been removed to prevent conflicts
@@ -325,13 +388,6 @@ Template.boardList.onRendered(function () {
*/
});
Template.boardList.onDestroyed(function () {
// Stop tagging <body> as drag-scrollable when leaving the All Boards page so
// other layouts (e.g. a board view, whose canvas does its own dragscroll) are
// not affected.
disablePageDragscroll();
});
Template.boardList.helpers({
userHasTeams() {
if (ReactiveCache.getCurrentUser()?.teams?.length > 0) return true;
@@ -486,27 +542,53 @@ Template.boardList.helpers({
const boards = ReactiveCache.getBoards(query, {});
const currentUser = ReactiveCache.getCurrentUser();
// #5799: in a sorted (non-custom) mode the server already computed the
// current page (filtered by menu/search and sorted), so render exactly that
// ordered page of board icons. Custom (manual drag order) falls through to
// the unpaginated client-side path below so drag-reordering keeps working.
const sortMode =
currentUser && typeof currentUser.getAllBoardsSortBy === 'function'
? currentUser.getAllBoardsSortBy()
: 'custom';
if (sortMode !== 'custom') {
const paged = tpl.pagedBoardsVar.get();
return (paged.ids || [])
.map((id) => ReactiveCache.getBoard(id))
.filter(Boolean);
}
let list = boards;
// Apply left menu filtering
const sel = tpl.selectedMenu.get();
const assignments =
(currentUser &&
currentUser.profile &&
currentUser.profile.boardWorkspaceAssignments) ||
{};
if (sel === 'starred') {
// Starred boards are always visible in Starred.
list = list.filter((b) => currentUser && currentUser.hasStarred(b._id));
} else if (sel === 'templates') {
list = list.filter((b) => b.type === 'template-container');
} else if (sel === 'remaining') {
// Remaining only shows boards not assigned to any workspace.
list = list.filter(
(b) => !assignments[b._id] && b.type !== 'template-container',
);
// #5799: when a board-name search is active, search across ALL the user's
// boards (every menu/workspace) by title and skip the menu filter, so a
// board in any category — Starred, Templates, Remaining or a (sub)workspace
// — is found from a single search box.
const search = (tpl.boardSearchVar.get() || '').trim().toLowerCase();
if (search) {
list = list.filter((b) => (b.title || '').toLowerCase().includes(search));
} else {
// Workspace view includes all boards in that workspace, including starred.
list = list.filter((b) => assignments[b._id] === sel);
// Apply left menu filtering
const sel = tpl.selectedMenu.get();
if (sel === 'starred') {
// Starred boards are always visible in Starred.
list = list.filter((b) => currentUser && currentUser.hasStarred(b._id));
} else if (sel === 'templates') {
list = list.filter((b) => b.type === 'template-container');
} else if (sel === 'remaining') {
// Remaining only shows boards not assigned to any workspace.
list = list.filter(
(b) => !assignments[b._id] && b.type !== 'template-container',
);
} else {
// Workspace view includes all boards in that workspace, including starred.
list = list.filter((b) => assignments[b._id] === sel);
}
}
if (currentUser && typeof currentUser.sortBoardsForUser === 'function') {
@@ -650,6 +732,46 @@ Template.boardList.helpers({
const currentUser = ReactiveCache.getCurrentUser();
return currentUser && !currentUser.isCommentOnly();
},
// #5799: current board-name search text (for the input value and the clear button).
boardSearch() {
return Template.instance().boardSearchVar.get();
},
// #5799: pagination controls (only shown in the sorted, non-custom modes).
boardsPaginationActive() {
const currentUser = ReactiveCache.getCurrentUser();
const sortMode =
currentUser && typeof currentUser.getAllBoardsSortBy === 'function'
? currentUser.getAllBoardsSortBy()
: 'custom';
if (sortMode === 'custom') return false;
const total = Template.instance().pagedBoardsVar.get().total || 0;
return total > BOARDS_PER_PAGE;
},
boardsCurrentPage() {
return Template.instance().boardsPageVar.get();
},
boardsTotalPages() {
const total = Template.instance().pagedBoardsVar.get().total || 0;
return Math.max(1, Math.ceil(total / BOARDS_PER_PAGE));
},
hasBoardsPrevPage() {
return Template.instance().boardsPageVar.get() > 1;
},
hasBoardsNextPage() {
const tpl = Template.instance();
const total = tpl.pagedBoardsVar.get().total || 0;
const totalPages = Math.max(1, Math.ceil(total / BOARDS_PER_PAGE));
return tpl.boardsPageVar.get() < totalPages;
},
// #5799: current All Boards sort mode ('custom' | 'title-asc' | 'title-desc').
isBoardsSort(mode) {
const currentUser = ReactiveCache.getCurrentUser();
const current =
currentUser && typeof currentUser.getAllBoardsSortBy === 'function'
? currentUser.getAllBoardsSortBy()
: 'custom';
return current === mode;
},
hasBoardsSelected() {
return BoardMultiSelection.count() > 0;
},
@@ -745,6 +867,41 @@ Template.boardList.events({
Popup.open('createBoard')(evt);
}
},
// #5799: choose how the All Boards page is sorted.
'click .js-open-boards-sort': Popup.open('boardsSort'),
// #5799: search boards by name across all categories.
'input .js-board-search-input'(evt, tpl) {
tpl.boardSearchVar.set(evt.currentTarget.value);
},
'keydown .js-board-search-input'(evt, tpl) {
// Esc clears the search.
if (evt.keyCode === 27) {
tpl.boardSearchVar.set('');
evt.currentTarget.value = '';
}
},
'click .js-board-search-clear'(evt, tpl) {
evt.preventDefault();
tpl.boardSearchVar.set('');
const input = tpl.find('.js-board-search-input');
if (input) {
input.value = '';
input.focus();
}
},
// #5799: board grid pagination (sorted modes only).
'click .js-boards-prev-page'(evt, tpl) {
evt.preventDefault();
const page = tpl.boardsPageVar.get();
if (page > 1) tpl.boardsPageVar.set(page - 1);
},
'click .js-boards-next-page'(evt, tpl) {
evt.preventDefault();
const total = tpl.pagedBoardsVar.get().total || 0;
const totalPages = Math.max(1, Math.ceil(total / BOARDS_PER_PAGE));
const page = tpl.boardsPageVar.get();
if (page < totalPages) tpl.boardsPageVar.set(page + 1);
},
'click .js-star-board'(evt) {
evt.preventDefault();
evt.stopPropagation();
@@ -829,6 +986,55 @@ Template.boardList.events({
document.querySelectorAll('.workspace-node.board-drag-hint, .js-select-menu.board-drag-hint').forEach((el) => {
el.classList.remove('board-drag-hint');
});
document.querySelectorAll('.js-board.board-reorder-over').forEach((el) => {
el.classList.remove('board-reorder-over');
});
},
// #6439: reorder boards on the All Boards page by dropping one board onto
// another. Only active in the "custom" (manual drag order) sort mode — in the
// sorted modes the order comes from the title, so a board is not a valid drop
// target (which is why, before this, the browser showed the not-allowed cursor
// and nothing persisted). The reorder decision/index math lives in the pure,
// unit-tested models/lib/boardSortReorder.js.
'dragover .js-board'(evt, tpl) {
if (!isDragReorderEnabled(currentAllBoardsSortBy())) return;
// A multi-board drag is a share/assign gesture, not a reorder.
const dt = evt.originalEvent.dataTransfer;
if (dt && dt.getData('application/x-board-multi') === 'true') return;
evt.preventDefault();
evt.stopPropagation();
if (dt) dt.dropEffect = 'move';
evt.currentTarget.classList.add('board-reorder-over');
},
'dragleave .js-board'(evt) {
evt.currentTarget.classList.remove('board-reorder-over');
},
'drop .js-board'(evt, tpl) {
if (!isDragReorderEnabled(currentAllBoardsSortBy())) return;
const dt = evt.originalEvent.dataTransfer;
if (dt && dt.getData('application/x-board-multi') === 'true') return;
const draggedId = dt ? dt.getData('text/plain') : '';
if (!draggedId) return;
evt.preventDefault();
evt.stopPropagation();
evt.currentTarget.classList.remove('board-reorder-over');
const targetId = this._id;
// #6442: current display order of the board grid. Read each board's _id from
// its Blaze data context (the same source dragstart uses via `this._id`) —
// NOT el.classList[0]. On `li.js-board(class="{{_id}} …")` Jade emits the
// literal `js-board` class FIRST, so classList[0] is the string "js-board"
// for every board; the old code therefore produced ['js-board','js-board',…],
// computeReorderedSortIndex could not find the dragged/target ids among them,
// returned null, and the drop silently did nothing (board snapped back).
const orderedIds = Array.from(tpl.findAll('.js-board'))
.map((el) => Blaze.getData(el)?._id)
.filter(Boolean);
const mapping = computeReorderedSortIndex(orderedIds, draggedId, targetId);
if (!mapping) return;
const currentUser = ReactiveCache.getCurrentUser();
if (currentUser && typeof currentUser.setBoardSortIndexes === 'function') {
currentUser.setBoardSortIndexes(mapping);
}
},
'click .js-clone-board'(evt) {
if (confirm(TAPi18n.__('duplicate-board-confirm'))) {
@@ -1157,6 +1363,30 @@ Template.boardList.events({
},
});
// #5799: All Boards sort popup — pick custom (manual drag order) or
// alphabetical A→Z / Z→A. The choice is stored per user.
Template.boardsSortPopup.helpers({
isBoardsSort(mode) {
const currentUser = ReactiveCache.getCurrentUser();
const current =
currentUser && typeof currentUser.getAllBoardsSortBy === 'function'
? currentUser.getAllBoardsSortBy()
: 'custom';
return current === mode;
},
});
Template.boardsSortPopup.events({
'click .js-boards-sort'(evt) {
evt.preventDefault();
const mode = evt.currentTarget.getAttribute('data-sort');
if (mode) {
Meteor.call('setAllBoardsSortBy', mode);
}
Popup.back();
},
});
Template.workspaceActionsPopup.helpers({
workspaceName() {
return this.name || '';
+1 -1
View File
@@ -66,7 +66,7 @@ template(name="tableView")
td.table-view-cell-labels
.div
each label in row.labels
span.card-label(class="card-label-{{label.color}}" title="{{label.name}}")
span.card-label(class="{{labelColorClass label.color}}" style="{{labelColorStyle label.color}}" title="{{label.name}}")
+viewer
= label.name
if showReceivedColumn
+11
View File
@@ -1087,3 +1087,14 @@ body.mobile-mode .card-details .card-details-header .close-card-details-mobile-w
line-height: 1;
box-sizing: border-box;
}
/* #6431 follow-up: cover image on the opened card (Card Settings > Cover image
on card, opt-in). Mirrors the minicard cover but taller. */
.card-details-cover {
background-position: center;
background-repeat: no-repeat;
background-size: contain;
height: 200px;
user-select: none;
margin: 8px 0;
}
+27 -8
View File
@@ -6,7 +6,7 @@ template(name="cardDetails")
+attachmentViewer
section.card-details.js-card-details.nodragscroll(class='{{#if cardMaximized}}card-details-maximized{{/if}}' class='{{#if isPopup}}card-details-popup{{/if}}' class='{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}' class='{{#if cardCollapsed}}card-details-collapsed{{/if}}'): .card-details-canvas
.card-details-header(class='{{#if colorClass}}card-details-{{colorClass}}{{/if}}')
.card-details-header(class='{{#if colorClass}}card-details-{{colorClass}}{{/if}}' style='{{colorStyle}}')
+inlinedForm(classNames="js-card-details-title")
+editCardTitleForm
else
@@ -112,6 +112,11 @@ template(name="cardDetails")
i.fa.fa-check
span {{_ 'upload-completed'}}
//- #6431 follow-up: cover image on the opened card (opt-in, OFF by default).
if currentBoard.allowsCoverAttachmentOnCard
if cover
.card-details-cover(style="background-image: url('{{cover.link 'original'}}');")
.card-details-left
.card-details-items
@@ -122,7 +127,7 @@ template(name="cardDetails")
| {{_ 'labels'}}
a(class="{{#if canModifyCard}}js-add-labels{{else}}is-disabled{{/if}}" title="{{_ 'card-labels-title'}}")
each labels
span.card-label(class="card-label-{{color}}" title=name)
span.card-label(class="{{labelColorClass color}}" style="{{labelColorStyle color}}" title=name)
+viewer
= name
if canModifyCard
@@ -684,6 +689,14 @@ template(name="cardDetails")
.card-checklist-attachmentGallery.card-checklists
if currentBoard.allowsChecklists
hr
//- Total checklist item count (finished/total) shown above all
checklists. The board flag is a cheap boolean, so when it is off
the checklist counting below is never evaluated.
if currentBoard.allowsChecklistCountBadgeOnCard
if checklists.length
h3.card-details-item-title
i.fa.fa-check-square-o
| {{checklistFinishedCount}}/{{checklistItemCount}}
+checklists(cardId = _id card = this)
if currentBoard.allowsSubtasks
hr
@@ -693,6 +706,8 @@ template(name="cardDetails")
h3.card-details-item-title
i.fa.fa-paperclip
| {{_ 'attachments'}}
if currentBoard.allowsAttachmentCountOnCard
| ({{attachments.length}})
if Meteor.settings.public.attachmentsUploadMaxSize
| {{_ 'max-upload-filesize'}} {{Meteor.settings.public.attachmentsUploadMaxSize}}
br
@@ -726,12 +741,12 @@ template(name="cardDetails")
i.fa.fa-history
| {{ _ 'activities'}}
if currentUser.isBoardMember
.material-toggle-switch(title="{{_ 'show-activities'}}")
if showActivities
input.toggle-switch(type="checkbox" id="toggleShowActivitiesCard" checked="checked")
else
input.toggle-switch(type="checkbox" id="toggleShowActivitiesCard")
label.toggle-label(for="toggleShowActivitiesCard")
if showActivities
a.activities-eye-toggle.js-toggle-show-activities-card(title="{{_ 'hide-activities'}}" aria-label="{{_ 'hide-activities'}}")
i.fa.fa-eye.eye-icon
else
a.activities-eye-toggle.js-toggle-show-activities-card(title="{{_ 'show-activities'}}" aria-label="{{_ 'show-activities'}}")
i.fa.fa-eye-slash.eye-slash-icon
if currentUser.isBoardAdmin
if isLoaded.get
@@ -1184,6 +1199,10 @@ template(name="setCardColorPopup")
span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
if(isSelected color)
i.fa.fa-check
// #5514: native color wheel for picking a custom RGB / hex color
.color-wheel-wrap
label(for="cardColorWheel") {{_ 'custom-color'}}
input.js-card-color-wheel#cardColorWheel(type="color" value=currentColorHex title="{{_ 'custom-color'}}")
button.primary.confirm.js-submit {{_ 'save'}}
button.js-remove-color.negate.wide.right {{_ 'unset-color'}}
+61 -16
View File
@@ -50,7 +50,10 @@ import Users from '/models/users';
import Lists from '/models/lists';
import CardComments from '/models/cardComments';
import { ALLOWED_COLORS } from '/config/const';
import { isHexColor, toHex } from '/models/lib/contrastColor';
import { uniqBy } from '/imports/lib/collectionHelpers';
import { memberTargetBoardId } from '/models/lib/linkedCardMembers';
import { isSubmitKey } from '/models/lib/editorSubmitKey';
import { UserAvatar } from '../users/userAvatar';
import { Filter } from '/client/lib/filter';
import { BoardSwimlaneListCardDialog } from '/client/lib/dialogWithBoardSwimlaneListCard';
@@ -859,17 +862,25 @@ Template.cardDetails.events({
event.preventDefault();
const description = tpl.find('.js-new-description-input').value;
const card = Template.currentData();
await card.setDescription(description);
// #5809: surface a visible error instead of failing silently — e.g. editing
// a linked card whose target is on a board the user cannot write to gets a
// permission denial that otherwise vanished with no feedback.
try {
await card.setDescription(description);
} catch (error) {
alert(error?.reason || error?.message || 'Failed to save description');
}
},
async 'submit .js-card-details-title'(event, tpl) {
event.preventDefault();
const titleInput = tpl.find('.js-edit-card-title');
const title = titleInput ? titleInput.value.trim() : '';
const card = Template.currentData();
if (title) {
await card.setTitle(title);
} else {
await card.setTitle('');
// #5809: surface a visible error instead of failing silently (see above).
try {
await card.setTitle(title || '');
} catch (error) {
alert(error?.reason || error?.message || 'Failed to save title');
}
},
'submit .js-card-details-assigner'(event, tpl) {
@@ -1037,7 +1048,7 @@ Template.cardDetails.events({
const card = Template.currentData();
await card.toggleHideCheckedChecklistItems();
},
async 'change #toggleShowActivitiesCard'() {
async 'click .js-toggle-show-activities-card'() {
const card = Template.currentData();
await card.toggleShowActivities();
},
@@ -1489,18 +1500,24 @@ Template.editCardTitleForm.events({
Utils.showCopied(promise, $tooltip);
},
'keydown .js-edit-card-title'(event) {
// If enter key was pressed, submit the data
// Unless the shift key is also being pressed
if (event.keyCode === 13 && !event.shiftKey) {
// #4236: match the card description field — Ctrl/Cmd+Enter saves; plain
// Enter (and Shift+Enter) insert a new line instead of submitting. The old
// handler submitted on any Enter without Shift, so Enter could not add a
// newline in the title.
if (isSubmitKey(event)) {
$('.js-submit-edit-card-title-form').click();
}
},
});
Template.cardMembersPopup.onCreated(function () {
let currBoard = Utils.getCurrentBoard();
let members = currBoard.activeMembers();
this.members = new ReactiveVar(members);
// #4965: only the filter term is stored reactively; the candidate list is
// derived reactively in the members() helper (via filterMembers, which reads
// board.activeMembers() on each call). Previously the list was snapshotted
// once here, so a member added to the board after the popup opened — or whose
// user document finished loading after open — was missing until the popup was
// reopened.
this.filterTerm = new ReactiveVar('');
});
Template.cardMembersPopup.events({
@@ -1512,8 +1529,7 @@ Template.cardMembersPopup.events({
event.preventDefault();
},
'keyup .card-members-filter'(event) {
const members = filterMembers(event.target.value);
Template.instance().members.set(members);
Template.instance().filterTerm.set(event.target.value);
}
});
@@ -1527,7 +1543,7 @@ Template.cardMembersPopup.helpers({
},
members() {
const members = Template.instance().members.get();
const members = filterMembers(Template.instance().filterTerm.get());
const uniqueMembers = uniqBy(members, 'userId');
return [...uniqueMembers].sort((a, b) => {
const userA = ReactiveCache.getUser(a.userId);
@@ -1646,7 +1662,25 @@ Template.cardLocationsPopup.events({
});
const filterMembers = (filterTerm) => {
let currBoard = Utils.getCurrentBoard();
// #5676: for a linked card the members live on the REAL card it points at,
// which may be on another board (that is what getMembers()/toggleMember() act
// on). Offer that board's members instead of the viewing board's, so the
// picker is consistent with where membership is actually stored. Falls back to
// the current board for normal cards or when the real card isn't loaded yet.
const card = getCurrentCardFromContext();
let currBoard = null;
if (card) {
const boardId = memberTargetBoardId(card, id => ReactiveCache.getCard(id));
if (boardId) {
currBoard = ReactiveCache.getBoard(boardId);
}
}
if (!currBoard) {
currBoard = Utils.getCurrentBoard();
}
if (!currBoard) {
return [];
}
let members = currBoard.activeMembers();
if (filterTerm) {
@@ -2002,6 +2036,10 @@ Template.setCardColorPopup.helpers({
}
return tpl.currentColor.get() === color;
},
// #5514: current color as a '#rrggbb' hex for the native color wheel <input>.
currentColorHex() {
return toHex(Template.instance().currentColor.get()) || '#0079bf';
},
});
Template.setCardColorPopup.events({
@@ -2009,6 +2047,13 @@ Template.setCardColorPopup.events({
const paletteData = Blaze.getData(event.currentTarget);
tpl.currentColor.set(paletteData?.color);
},
// #5514: picking from the native color wheel stores a custom hex.
'input .js-card-color-wheel'(event, tpl) {
const value = event.currentTarget.value;
if (isHexColor(value)) {
tpl.currentColor.set(value);
}
},
async 'click .js-submit'(event, tpl) {
event.preventDefault();
const card = Cards.findOne(getCardId());
+21
View File
@@ -1,3 +1,24 @@
/* #5514: native color-wheel (custom RGB / hex) picker wrapper, shared by the
label / list / swimlane / card color popups. */
.color-wheel-wrap {
display: flex;
align-items: center;
gap: 8px;
margin: 6px 0 10px 0;
}
.color-wheel-wrap label {
margin: 0;
}
.color-wheel-wrap input[type="color"] {
width: 44px;
height: 28px;
padding: 0;
border: 1px solid #ccc;
border-radius: 4px;
cursor: pointer;
background: none;
}
.card-label {
border: 1px solid #000;
border-radius: 4px;
+6 -3
View File
@@ -4,9 +4,12 @@ template(name="formLabel")
label {{_ "select-color"}}
.palette-colors: each labels
span.card-label.palette-color.js-palette-color(class="card-label-{{color}}")
span.card-label.palette-color.js-palette-color(class="{{labelColorClass color}}" style="{{labelColorStyle color}}")
if(isSelected color)
i.fa.fa-check
.color-wheel-wrap
label(for="labelColorWheel") {{_ 'custom-color'}}
input.js-label-color-wheel#labelColorWheel(type="color" value=currentColorHex title="{{_ 'custom-color'}}")
template(name="createLabelPopup")
form.create-label
@@ -31,8 +34,8 @@ template(name="cardLabelsPopup")
a.card-label-edit-button.fa.fa-pencil.js-edit-label
if isTouchScreenOrShowDesktopDragHandles
span.fa.label-handle(class="fa-arrows" title="{{_ 'dragLabel'}}")
span.card-label.card-label-selectable.js-select-label.card-label-wrapper(class="card-label-{{color}}"
class="{{# if isLabelSelected ../_id }}active{{/if}}")
span.card-label.card-label-selectable.js-select-label.card-label-wrapper(class="{{labelColorClass color}}"
class="{{# if isLabelSelected ../_id }}active{{/if}}" style="{{labelColorStyle color}}")
+viewer
= name
if(isLabelSelected ../_id)
+33 -2
View File
@@ -1,5 +1,6 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { LABEL_COLORS } from '/models/metadata/colors';
import { isHexColor, toHex } from '/models/lib/contrastColor';
import { EscapeActions } from '/client/lib/escapeActions';
import { Utils } from '/client/lib/utils';
@@ -28,6 +29,11 @@ Template.formLabel.helpers({
isSelected(color) {
return Template.instance().currentColor.get() === color;
},
// #5514: current color as a '#rrggbb' hex for the color-wheel <input>, which
// only accepts hex. Named colors map to their palette hex; fall back to green.
currentColorHex() {
return toHex(Template.instance().currentColor.get()) || '#3cb500';
},
});
Template.formLabel.events({
@@ -36,6 +42,13 @@ Template.formLabel.events({
const selectedColor = paletteData?.color || Template.currentData()?.color || getFallbackLabelColor();
tpl.currentColor.set(selectedColor);
},
// #5514: picking a color from the native color wheel stores a custom hex.
'input .js-label-color-wheel'(event, tpl) {
const value = event.currentTarget.value;
if (isHexColor(value)) {
tpl.currentColor.set(value);
}
},
});
Template.createLabelPopup.helpers({
@@ -131,7 +144,16 @@ Template.createLabelPopup.events({
? selectedColorIcon.closest('.js-palette-color')
: null;
const selectedColorData = selectedPaletteNode && Blaze.getData(selectedPaletteNode);
const color = selectedColorData?.color || getFallbackLabelColor();
// #5514: a named swatch (has the check icon) wins; otherwise fall back to
// the custom color-wheel hex, then to the default palette color.
let color = selectedColorData?.color;
if (!color) {
const wheel = templateInstance.find('.js-label-color-wheel');
if (wheel && isHexColor(wheel.value)) {
color = wheel.value;
}
}
color = color || getFallbackLabelColor();
board.addLabel(name, color);
Popup.back();
},
@@ -158,7 +180,16 @@ Template.editLabelPopup.events({
? selectedColorIcon.closest('.js-palette-color')
: null;
const selectedColorData = selectedPaletteNode && Blaze.getData(selectedPaletteNode);
const color = selectedColorData?.color || getFallbackLabelColor();
// #5514: a named swatch (has the check icon) wins; otherwise fall back to
// the custom color-wheel hex, then to the default palette color.
let color = selectedColorData?.color;
if (!color) {
const wheel = templateInstance.find('.js-label-color-wheel');
if (wheel && isHexColor(wheel.value)) {
color = wheel.value;
}
}
color = color || getFallbackLabelColor();
board.editLabel(this._id, name, color);
Popup.back();
},
+27
View File
@@ -119,6 +119,22 @@
margin-inline-end: 0.4vw;
margin-bottom: 4px;
}
/* Keep text labels on a single compact line. The label name is rendered
inside a `.viewer`, whose global `min-height: 2.5vh` otherwise forces every
label to roughly double height on the minicard. */
.minicard .minicard-labels .card-label {
min-height: 0;
padding: 1px 6px;
line-height: 1.3;
white-space: nowrap;
}
.minicard .minicard-labels .card-label .viewer {
min-height: 0;
display: inline;
}
.minicard .minicard-labels .card-label .viewer p {
display: inline;
}
.minicard .minicard-labels-no-text {
display: flex;
flex-wrap: wrap;
@@ -167,6 +183,13 @@
}
.minicard .minicard-title {
margin-inline-end: 1.5vw;
/* #6444: isolate each title's bidirectional context. The title is a
dir="auto" .viewer; under a global dir="rtl" document, non-isolated
dir="auto" runs share bidi state, so typing a strong RTL (e.g. Arabic)
character into another list's dir="auto" composer re-resolved the shared
context and visibly reflowed OTHER lists' minicard titles (no data change,
reverts on refresh). Isolation makes each title a self-contained run. */
unicode-bidi: isolate;
}
.minicard .minicard-title .card-number {
color: #b3b3b3;
@@ -432,6 +455,10 @@
min-height: 50px;
margin-bottom: 20px;
overflow-y: auto;
/* #6444: isolate the composer's dir="auto" bidi run so typing a strong RTL
character here cannot perturb the rendering of the surrounding minicard
titles under a dir="rtl" document. */
unicode-bidi: isolate;
}
.parent-prefix {
color: #b3b3b3;
+13 -3
View File
@@ -3,7 +3,8 @@ template(name="minicard")
class="{{#if isLinkedCard}}linked-card{{/if}}"
class="{{#if isLinkedBoard}}linked-board{{/if}}"
class="{{agingClass}}"
class="{{#if colorClass}}minicard-{{colorClass}}{{/if}}")
class="{{#if colorClass}}minicard-{{colorClass}}{{/if}}"
style="{{colorStyle}}")
if canMoveCard
if isTouchScreenOrShowDesktopDragHandles
.handle
@@ -93,11 +94,11 @@ template(name="minicard")
.minicard-labels(class="{{#if hiddenMinicardLabelText}}minicard-labels-no-text{{/if}}")
each labels
unless hiddenMinicardLabelText
span.js-card-label.card-label(class="card-label-{{color}}" title=name)
span.js-card-label.card-label(class="{{labelColorClass color}}" style="{{labelColorStyle color}}" title=name)
+viewer
= name
if hiddenMinicardLabelText
.minicard-label(class="card-label-{{color}}" title="{{name}}")
.minicard-label(class="{{labelColorClass color}}" style="{{labelColorStyle color}}" title="{{name}}")
.minicard-custom-fields
each customFieldsWD
@@ -215,6 +216,15 @@ template(name="minicard")
i.fa.fa-globe
span.badge-text.check-list-text {{subtasksFinishedCount}}/{{allSubtasksCount}}
//{{subtasksFinishedCount}}/{{subtasksCount}} does not work because when a subtaks is archived, the count goes down
//- #6431: compact checklist item-count badge (finished/total). The outer
guard is a cheap boolean, so when the setting is off the checklist
counting below never runs.
if showChecklistCountBadge
if checklists.length
.badge(class="{{#if checklistFinished}}is-finished{{/if}}" title="{{_ 'checklists'}}")
span.badge-icon
i.fa.fa-check-square-o
span.badge-text.check-list-text {{checklistFinishedCount}}/{{checklistItemCount}}
if currentBoard.allowsCardSortingByNumber
if currentBoard.allowsCardSortingByNumberOnMinicard
.badge
+62 -18
View File
@@ -6,6 +6,11 @@ import uploadProgressManager from '../../lib/uploadProgressManager';
import { Utils } from '/client/lib/utils';
import ChecklistItems from '/models/checklistItems';
import Cards from '/models/cards';
import { resolveCoverId } from '/models/lib/linkedCardCover';
import {
parseChecklistItemTitles,
buildChecklistItemPayload,
} from '/models/lib/checklistItemTitles';
import { normalizeDependencies } from '/models/metadata/dependencies';
function getMinicardFlag(board, onMinicardField, legacyField, defaultValue) {
@@ -144,6 +149,14 @@ Template.minicard.helpers({
const board = this.board();
return getMinicardFlag(board, 'allowsSubtasksOnMinicard', 'allowsSubtasks', true);
},
// #6431: compact checklist item-count badge (finished/total) on the minicard.
// Opt-in, OFF by default. Only a cheap boolean read here; the actual checklist
// counting (checklistFinishedCount/checklistItemCount) is gated behind this in
// the template, so a board with the badge off spends no resources counting.
showChecklistCountBadge() {
const board = this.board();
return !!(board && board.allowsChecklistCountBadgeOnMinicard);
},
hiddenMinicardLabelText() {
const currentUser = ReactiveCache.getCurrentUser();
@@ -156,8 +169,12 @@ Template.minicard.helpers({
}
},
cover() {
if (!this.coverId) return null;
const attachment = ReactiveCache.getAttachment(this.coverId);
// #5666: for a linked card the cover lives on the real card it points at, so
// resolve the cover id through it (mirroring getTitle/getDue/...); a plain
// card resolves to its own coverId.
const coverId = resolveCoverId(this, id => ReactiveCache.getCard(id));
if (!coverId) return null;
const attachment = ReactiveCache.getAttachment(coverId);
if (!attachment) return null;
const coverLink = typeof attachment.link === 'function' ? attachment.link() : '';
if (!coverLink) return null;
@@ -209,7 +226,11 @@ Template.minicard.helpers({
// Show checklist if either:
// 1. Board-wide setting is enabled, OR
// 2. This specific checklist has the setting enabled
if (currentBoard.allowsChecklistAtMinicard || checklist.showChecklistAtMinicard) {
// #5565: use the same board field the sidebar toggle writes
// (`allowsChecklistsOnMinicard`); this previously checked a different,
// UI-less field (`allowsChecklistAtMinicard`), so the board toggle to show
// checklists on minicards had no effect.
if (currentBoard.allowsChecklistsOnMinicard || checklist.showChecklistAtMinicard) {
visibleChecklists.push(checklist);
}
});
@@ -364,6 +385,28 @@ Template.minicardChecklist.helpers({
},
});
// #6440: create the checklist item(s) for the minicard add-item form. Shared by
// the Save-button click handler and the (rarely reached) form-submit handler.
// The Save button needs an explicit click handler because the minicard — and so
// the add-item <form> — is rendered inside the `a.minicard-wrapper` anchor, where
// the native form `submit` event never reaches the Blaze event map; without this
// the "+" add-item on the minicard did nothing. The blank-input guard and the
// title parsing live in the pure, unit-tested models/lib/checklistItemTitles.js.
function addMinicardChecklistItems(textarea, checklist) {
if (!textarea || !checklist) return;
const titles = parseChecklistItemTitles(textarea.value);
if (titles.length === 0) return;
const items = checklist.items();
const lastItem = items[items.length - 1] || null;
let sortIndex = Utils.calculateIndexData(lastItem, null).base;
titles.forEach(title => {
ChecklistItems.insert(buildChecklistItemPayload(title, checklist, sortIndex));
sortIndex += 1;
});
textarea.value = '';
textarea.focus();
}
Template.minicardChecklist.events({
'click .js-convert-checklist-item-to-card'(event) {
event.stopPropagation();
@@ -405,25 +448,26 @@ Template.minicardChecklist.events({
}
$form.find('.js-close-inlined-form').trigger('click');
},
// #6440: explicit click handler for the minicard "+" add-item Save button.
// The native form `submit` event does not fire on the minicard (the form is
// inside the `a.minicard-wrapper` anchor), so — unlike card-detail — we cannot
// rely on `submit .js-add-checklist-item`. This mirrors the already-working
// `click .js-submit-edit-checklist-item-form` above.
'click .js-submit-add-checklist-item-form'(event) {
event.preventDefault();
event.stopPropagation();
const $form = $(event.currentTarget).closest('form');
const textarea = $form.find('textarea.js-add-checklist-item')[0];
const formData = Blaze.getData($form[0]) || Template.currentData() || {};
addMinicardChecklistItems(textarea, formData.checklist);
},
// Kept for completeness (e.g. card-detail-style contexts); on the minicard this
// rarely fires, so the click handler above is the real path. See #6440.
'submit .js-add-checklist-item'(event, tpl) {
event.preventDefault();
const textarea = tpl.find('textarea.js-add-checklist-item');
if (!textarea) return;
const title = textarea.value.trim();
const data = Template.currentData() || {};
const checklist = data.checklist;
if (!checklist || !title) return;
const items = checklist.items();
const lastItem = items[items.length - 1] || null;
const sortIndex = Utils.calculateIndexData(lastItem, null).base;
ChecklistItems.insert({
title,
checklistId: checklist._id,
cardId: checklist.cardId,
sort: sortIndex,
});
textarea.value = '';
textarea.focus();
addMinicardChecklistItems(textarea, data.checklist);
},
'click .js-delete-checklist-item': Popup.afterConfirm('checklistItemDelete', function () {
Popup.back();
+20 -1
View File
@@ -736,9 +736,28 @@ form.inlined-form .list-composer {
flex: 1 1 auto;
flex-direction: column;
display: flex;
overflow-y: auto;
/* #5439: always show a scrollbar on the list body across desktop and mobile
browsers (overlay scrollbars on macOS/iOS/Android/Firefox otherwise auto-
hide it). Keep these declarations in sync with models/lib/scrollbarCss.js
(alwaysVisibleScrollbarCss), which is unit tested. */
overflow-y: scroll;
scrollbar-width: thin;
scrollbar-color: rgba(0, 0, 0, 0.35) rgba(0, 0, 0, 0.08);
scrollbar-gutter: stable;
padding: 5px 11px;
}
.list-body::-webkit-scrollbar {
width: 10px;
height: 10px;
-webkit-appearance: none;
}
.list-body::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.08);
}
.list-body::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.35);
border-radius: 5px;
}
.list-body .minicards {
flex-grow: 1;
flex-shrink: 0;
+85
View File
@@ -54,8 +54,78 @@ function readAnonListWidth(boardId, listId) {
return null;
}
// ---------------------------------------------------------------------------
// #5729 Fixed (same) width for all lists.
//
// A per-viewer, per-board toggle that makes EVERY list render at a single
// shared value, and makes dragging any list update that one value (so all lists
// change together). Stored exactly like the personal width:
// * logged-in -> profile.fixedListWidthBoards / profile.fixedListWidths
// * anonymous -> localStorage (mirrors readAnonListWidth)
// ---------------------------------------------------------------------------
function readAnonFixedListWidthEnabled(boardId) {
try {
const stored = localStorage.getItem('wekan-fixed-list-width-enabled');
if (stored) {
const flags = JSON.parse(stored);
return flags[boardId] === true;
}
} catch (e) {
console.warn('Error reading fixed list width flag from localStorage:', e);
}
return false;
}
function readAnonFixedListWidth(boardId) {
try {
const stored = localStorage.getItem('wekan-fixed-list-width');
if (stored) {
const widths = JSON.parse(stored);
const w = widths[boardId];
if (typeof w === 'number' && w >= MIN_LIST_WIDTH) return w;
}
} catch (e) {
console.warn('Error reading fixed list width from localStorage:', e);
}
return DEFAULT_LIST_WIDTH;
}
// Whether the current viewer has fixed width mode enabled for this board.
function isFixedListWidth(boardId) {
const user = ReactiveCache.getCurrentUser();
if (user) {
return !!user.isFixedListWidth(boardId);
}
return readAnonFixedListWidthEnabled(boardId);
}
// The single width every list should use when fixed width mode is enabled.
function fixedListWidthValue(boardId) {
const user = ReactiveCache.getCurrentUser();
if (user) {
return user.getFixedListWidth(boardId);
}
return readAnonFixedListWidth(boardId);
}
// Persist the single fixed width for the whole board (anon localStorage).
function saveAnonFixedListWidth(boardId, width) {
try {
const stored = localStorage.getItem('wekan-fixed-list-width');
const widths = stored ? JSON.parse(stored) : {};
widths[boardId] = width;
localStorage.setItem('wekan-fixed-list-width', JSON.stringify(widths));
} catch (e) {
console.warn('Error saving fixed list width to localStorage:', e);
}
}
function effectiveListWidth(list) {
if (!list) return DEFAULT_LIST_WIDTH;
// #5729 In fixed width mode every list renders at the single shared value.
if (isFixedListWidth(list.boardId)) {
return fixedListWidthValue(list.boardId);
}
const shared =
typeof list.width === 'number' && list.width >= MIN_LIST_WIDTH
? list.width
@@ -80,6 +150,9 @@ function effectiveListWidth(list) {
function canResizeList(list) {
if (!list) return false;
if (effectiveAutoWidth(list.boardId)) return false;
// #5729 Fixed width is the viewer's own per-board setting, so any viewer
// (logged-in or anonymous) may drag-resize; the change applies to all lists.
if (isFixedListWidth(list.boardId)) return true;
if (isPersonalListWidth(list.boardId)) return true;
return Utils.canModifyBoard();
}
@@ -90,6 +163,18 @@ function saveListWidth(list, width) {
const boardId = list.boardId;
const listId = list._id;
const user = ReactiveCache.getCurrentUser();
// #5729 In fixed width mode, persist to the single per-board value instead of
// the per-list width, so EVERY list re-renders at the new width.
if (isFixedListWidth(boardId)) {
if (user) {
Meteor.call('setFixedListWidth', boardId, width, error => {
if (error) console.error('Error saving fixed list width:', error);
});
} else {
saveAnonFixedListWidth(boardId, width);
}
return;
}
if (isPersonalListWidth(boardId)) {
if (user) {
Meteor.call('applyListWidthToStorage', boardId, listId, width, width);
+1 -1
View File
@@ -46,7 +46,7 @@ template(name="addCardForm")
if getLabels
.minicard-labels
each getLabels
.minicard-label(class="card-label-{{color}}" title="{{name}}")
.minicard-label(class="{{labelColorClass color}}" style="{{labelColorStyle color}}" title="{{name}}")
textarea.minicard-composer-textarea.js-card-title(autofocus dir="auto")
if members.get
.minicard-members.js-minicard-composer-members
+63 -16
View File
@@ -9,6 +9,9 @@ import Swimlanes from '/models/swimlanes';
import { Filter } from '/client/lib/filter';
import { MultiSelection } from '/client/lib/multiSelection';
import { Utils } from '/client/lib/utils';
import { isLinkableCardTarget } from '/models/lib/linkedCardTarget';
import { listCardsSelector } from '/models/lib/swimlaneFilter';
import { sortCardsByTitle } from '/models/lib/sortCardsByTitle';
import autosize from 'autosize';
// SubsManager removed for Meteor 3 migration
@@ -338,19 +341,21 @@ Template.listBody.helpers({
if (!sortBy) {
sortBy = defaultSort;
}
const selector = {
listId: Template.currentData()._id,
archived: false,
};
if (swimlaneId) {
// Fallback: also show cards with no swimlaneId (null/empty) so that
// pre-migration / orphaned cards are always visible without a DB migration.
selector.$or = [
{ swimlaneId },
{ swimlaneId: null }, // null covers null AND missing field
{ swimlaneId: '' }, // empty string from shared-lists era
];
}
// #6441: build the swimlane-membership fallback as a single `swimlaneId:
// { $in: [...] }` clause (via the shared, unit-tested helper) instead of a
// bare top-level `$or`, so it never competes with the board Filter's own
// top-level `$or` (labels/members/exceptions) and the label filter applies
// board-wide across every swimlane.
// #6443: in the first swimlane also surface orphaned cards (a swimlaneId
// pointing at a deleted swimlane) so they are not invisible in swimlane view.
const list = Template.currentData();
const selector = listCardsSelector(
list._id,
swimlaneId,
list.orphanedCardsSwimlaneIds
? list.orphanedCardsSwimlaneIds(swimlaneId)
: undefined,
);
const ret = ReactiveCache.getCards(Filter.mongoSelector(selector), {
// sort: ['sort'],
sort: sortBy,
@@ -706,14 +711,19 @@ Template.linkCardPopup.helpers({
archived: false,
linkedId: { $nin: ownCardsIds },
_id: { $nin: ownCardsIds },
type: { $nin: ['template-card'] },
// #5808: never offer an existing linked card/board as a link target —
// linking to one builds a chain of linkedId pointers that renders the
// card inaccessible. Only real cards may be linked.
type: { $nin: ['template-card', 'cardType-linkedCard', 'cardType-linkedBoard'] },
};
if (tpl.selectedBoardId.get()) selector.boardId = tpl.selectedBoardId.get();
if (tpl.selectedSwimlaneId.get()) selector.swimlaneId = tpl.selectedSwimlaneId.get();
if (tpl.selectedListId.get()) selector.listId = tpl.selectedListId.get();
const ret = ReactiveCache.getCards(selector, { sort: { sort: 1 } });
return ret;
// #5394: sort the Cards dropdown alphabetically by title
// (case-insensitive) so a card can be found on boards with many cards.
return sortCardsByTitle(ret);
},
isTitleDefault(title) {
@@ -756,11 +766,48 @@ Template.linkCardPopup.events({
tpl.selectedListId.set($(evt.currentTarget).val());
},
async 'click .js-done'(evt, tpl) {
// LINK CARD
// LINK CARD (or, when no card is chosen, fall back to a board-level link
// so a whole board can be linked even when that board already has cards).
// https://github.com/wekan/wekan/issues/5715
evt.stopPropagation();
evt.preventDefault();
const linkedId = $('.js-select-cards option:selected').val();
if (!linkedId) {
const boardId = $('.js-select-boards option:selected').val();
// No board and no card selected: nothing to link.
if (!boardId) {
Popup.back();
return;
}
// A linkedBoard card for this board already exists; avoid duplicates.
if (ReactiveCache.getCard({ linkedId: boardId, archived: false })) {
Popup.back();
return;
}
const boardCardNumber = await tpl.board.getNextCardNumber();
const boardSortIndex = tpl.getSortIndex();
const boardCardId = Cards.insert({
title: $('.js-select-boards option:selected').text(), //dummy
listId: tpl.listId,
swimlaneId: tpl.swimlaneId,
boardId: tpl.boardId,
sort: boardSortIndex,
type: 'cardType-linkedBoard',
linkedId: boardId,
cardNumber: boardCardNumber,
});
Filter.addException(boardCardId);
Popup.back();
return;
}
// #5808: refuse to link to a linked card/board or to a card that links back
// to this board — those build a chain/cycle of linkedId pointers that renders
// the cards inaccessible. The <select> already filters these out, but its
// options can be stale, so re-check the resolved target here.
const targetCard = ReactiveCache.getCard(linkedId);
const ownCardsIds = tpl.board.cards().map(card => card.getRealId());
if (!isLinkableCardTarget(targetCard, ownCardsIds)) {
alert(TAPi18n.__('error-linked-card-not-allowed'));
Popup.back();
return;
}
+9 -1
View File
@@ -1,7 +1,8 @@
template(name="listHeader")
.list-header.js-list-header.nodragscroll(
class="{{#if limitToShowCardsCount}}list-header-card-count{{/if}}"
class=colorClass)
class=colorClass
style=colorStyle)
//- #459: keyboard/screen-reader list reordering (visually hidden, focusable).
.list-move-buttons.sr-only
a.js-list-move-left(href="#" role="button" aria-label="{{_ 'move-list-left'}}") {{_ 'move-list-left'}}
@@ -262,6 +263,9 @@ template(name="setListWidthPopup")
a.js-toggle-auto-width(title="{{#if isAutoWidth}}{{_ 'click-to-disable-auto-width'}}{{else}}{{_ 'click-to-enable-auto-width'}}{{/if}}")
| {{#if isAutoWidth}}🗜️{{else}}📏{{/if}}
span {{_ 'auto-list-width'}}
a.js-toggle-fixed-list-width(title="{{#if isFixedListWidth}}{{_ 'click-to-disable-fixed-list-width'}}{{else}}{{_ 'click-to-enable-fixed-list-width'}}{{/if}}")
| {{#if isFixedListWidth}}✅{{else}}⬜{{/if}}
span {{_ 'fixed-list-width'}}
p.list-width-scope-note {{ listWidthScopeNote }}
template(name="listWidthErrorPopup")
@@ -276,6 +280,10 @@ template(name="setListColorPopup")
span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
if(isSelected color)
i.fa.fa-check
// #5514: native color wheel for picking a custom RGB / hex color
.color-wheel-wrap
label(for="listColorWheel") {{_ 'custom-color'}}
input.js-list-color-wheel#listColorWheel(type="color" value=currentColorHex title="{{_ 'custom-color'}}")
button.primary.confirm.js-submit {{_ 'save'}}
button.js-remove-color.negate.wide.right {{_ 'unset-color'}}
+118
View File
@@ -5,6 +5,7 @@ import dragscroll from '@wekanteam/dragscroll';
import { BoardSwimlaneListDialog } from '/client/lib/dialogWithBoardSwimlaneList';
import Cards from '/models/cards';
import { LIST_COLORS } from '/models/metadata/colors';
import { isHexColor, toHex } from '/models/lib/contrastColor';
import { MultiSelection } from '/client/lib/multiSelection';
import { Utils } from '/client/lib/utils';
@@ -476,6 +477,10 @@ Template.setListColorPopup.helpers({
return tpl.currentColor.get() === color;
}
},
// #5514: current color as a '#rrggbb' hex for the native color wheel <input>.
currentColorHex() {
return toHex(Template.instance().currentColor.get()) || '#0079bf';
},
});
Template.setListColorPopup.events({
@@ -483,6 +488,13 @@ Template.setListColorPopup.events({
const paletteData = Blaze.getData(event.currentTarget);
tpl.currentColor.set(paletteData?.color);
},
// #5514: picking from the native color wheel stores a custom hex.
'input .js-list-color-wheel'(event, tpl) {
const value = event.currentTarget.value;
if (isHexColor(value)) {
tpl.currentColor.set(value);
}
},
async 'submit form'(event, tpl) {
event.preventDefault();
try {
@@ -520,9 +532,77 @@ function isPersonalListWidth(boardId) {
return !!(board && board.allowsPersonalListWidth);
}
// #5729 Fixed (same) width for all lists is a per-viewer/per-board setting.
// Logged-in users store it in their profile; anonymous (public board) users in
// localStorage (mirrors the per-list anon storage in list.js).
function readAnonFixedListWidthEnabled(boardId) {
try {
const stored = localStorage.getItem('wekan-fixed-list-width-enabled');
if (stored) {
const flags = JSON.parse(stored);
return flags[boardId] === true;
}
} catch (e) {
console.warn('Error reading fixed list width flag from localStorage:', e);
}
return false;
}
function readAnonFixedListWidth(boardId) {
try {
const stored = localStorage.getItem('wekan-fixed-list-width');
if (stored) {
const widths = JSON.parse(stored);
const w = widths[boardId];
if (typeof w === 'number' && w >= 270) return w;
}
} catch (e) {
console.warn('Error reading fixed list width from localStorage:', e);
}
return 272;
}
function isFixedListWidth(boardId) {
const user = ReactiveCache.getCurrentUser();
if (user) return !!user.isFixedListWidth(boardId);
return readAnonFixedListWidthEnabled(boardId);
}
function fixedListWidthValue(boardId) {
const user = ReactiveCache.getCurrentUser();
if (user) return user.getFixedListWidth(boardId);
return readAnonFixedListWidth(boardId);
}
function setAnonFixedListWidthEnabled(boardId, enabled) {
try {
const stored = localStorage.getItem('wekan-fixed-list-width-enabled');
const flags = stored ? JSON.parse(stored) : {};
flags[boardId] = !!enabled;
localStorage.setItem('wekan-fixed-list-width-enabled', JSON.stringify(flags));
} catch (e) {
console.warn('Error saving fixed list width flag to localStorage:', e);
}
}
function setAnonFixedListWidth(boardId, width) {
try {
const stored = localStorage.getItem('wekan-fixed-list-width');
const widths = stored ? JSON.parse(stored) : {};
widths[boardId] = width;
localStorage.setItem('wekan-fixed-list-width', JSON.stringify(widths));
} catch (e) {
console.warn('Error saving fixed list width to localStorage:', e);
}
}
Template.setListWidthPopup.helpers({
listWidthValue() {
const list = Template.currentData();
// #5729 In fixed width mode the input edits the single per-board value.
if (isFixedListWidth(list.boardId)) {
return fixedListWidthValue(list.boardId);
}
const shared =
typeof list.width === 'number' && list.width >= 270 ? list.width : 272;
if (!isPersonalListWidth(list.boardId)) {
@@ -539,6 +619,10 @@ Template.setListWidthPopup.helpers({
listWidthScopeNote() {
const list = Template.currentData();
// #5729 In fixed width mode the note explains the all-lists behaviour.
if (isFixedListWidth(list.boardId)) {
return TAPi18n.__('fixed-list-width-note');
}
return isPersonalListWidth(list.boardId)
? TAPi18n.__('list-width-personal-note')
: TAPi18n.__('list-width-shared-note');
@@ -564,6 +648,12 @@ Template.setListWidthPopup.helpers({
}
return Utils.canModifyBoard();
},
// #5729 Whether "same width for all lists" mode is on for the current viewer.
isFixedListWidth() {
const list = Template.currentData();
return isFixedListWidth(list.boardId);
},
});
Template.setListWidthPopup.events({
@@ -581,6 +671,24 @@ Template.setListWidthPopup.events({
}
Popup.back();
},
// #5729 Toggle "same width for all lists" (fixed width) for the current
// viewer. Enabling it turns off auto-width (the two modes are exclusive).
'click .js-toggle-fixed-list-width'(event) {
event.preventDefault();
const list = Template.currentData();
const boardId = list.boardId;
const enabled = !isFixedListWidth(boardId);
const user = ReactiveCache.getCurrentUser();
if (user) {
if (enabled && isPersonalListWidth(boardId) && user.isAutoWidth(boardId)) {
user.toggleAutoWidth(boardId);
}
Meteor.call('setFixedListWidthEnabled', boardId, enabled);
} else {
setAnonFixedListWidthEnabled(boardId, enabled);
}
Popup.back();
},
'click .list-width-apply'(event, tpl) {
const list = Template.currentData();
const boardId = list.boardId;
@@ -591,6 +699,16 @@ Template.setListWidthPopup.events({
return;
}
const user = ReactiveCache.getCurrentUser();
// #5729 In fixed width mode the input applies to the single per-board value.
if (isFixedListWidth(boardId)) {
if (user) {
Meteor.call('setFixedListWidth', boardId, width);
} else {
setAnonFixedListWidth(boardId, width);
}
Popup.back();
return;
}
if (isPersonalListWidth(boardId)) {
if (user) {
Meteor.call('applyListWidthToStorage', boardId, list._id, width, width);
+37 -37
View File
@@ -558,7 +558,7 @@ body:not(.board-view) #header #header-main-bar h1 {
}
}
/* Mobile - make all text and icons 2x bigger above #content by default */
/* Mobile - keep header text/icons at normal size (reverted #6426 oversizing) */
@media screen and (max-width: 800px),
screen and (max-device-width: 800px),
screen and (-webkit-min-device-pixel-ratio: 2) and (max-width: 800px),
@@ -573,7 +573,7 @@ body:not(.board-view) #header #header-main-bar h1 {
overflow: hidden !important; /* Prevent content overflow */
}
#header-quick-access {
font-size: 2em !important; /* 2x bigger base font size */
font-size: 1em !important; /* Normal size (reverted #6426 oversizing) */
}
#header-quick-access * {
@@ -582,7 +582,7 @@ body:not(.board-view) #header #header-main-bar h1 {
#header-quick-access .fa,
#header-quick-access .icon {
font-size: 2em !important; /* 2x bigger icons */
font-size: 1em !important; /* Normal icon size (reverted #6426 oversizing) */
}
#header-quick-access .home-icon a {
@@ -652,7 +652,7 @@ body:not(.board-view) #header #header-main-bar h1 {
}
}
/* Mobile All Boards page - make logo row elements 2x bigger */
/* Mobile All Boards page - keep logo row at normal size (reverted #6426 oversizing) */
@media screen and (max-width: 800px),
screen and (max-device-width: 800px),
screen and (-webkit-min-device-pixel-ratio: 2) and (max-width: 800px),
@@ -660,16 +660,16 @@ body:not(.board-view) #header #header-main-bar h1 {
screen and (max-width: 800px) and (orientation: landscape) {
.wrapper ~ #header-quick-access,
body:not(.board-view) #header-quick-access {
font-size: 2em !important; /* 2x bigger base font size for logo row */
font-size: 1em !important; /* Normal size (reverted #6426 oversizing) */
}
/* iPhone 12 Mini specific - 3x bigger for All Boards page */
/* iPhone 12 Mini specific - keep All Boards logo row at normal size (reverted #6426) */
@media screen and (device-width: 375px) and (device-height: 812px), /* iPhone 12 Mini exact */
screen and (max-width: 375px) and (max-height: 812px), /* iPhone 12 Mini viewport */
screen and (-webkit-min-device-pixel-ratio: 3) and (max-width: 375px) /* iPhone 12 Mini Retina */ {
.wrapper ~ #header-quick-access,
body:not(.board-view) #header-quick-access {
font-size: 3em !important; /* 3x bigger base font size for iPhone 12 Mini All Boards page */
font-size: 1em !important; /* Normal size (reverted #6426 oversizing) */
}
}
@@ -682,7 +682,7 @@ body:not(.board-view) #header #header-main-bar h1 {
.wrapper ~ #header-quick-access .icon,
body:not(.board-view) #header-quick-access .fa,
body:not(.board-view) #header-quick-access .icon {
font-size: 2em !important; /* 2x bigger icons in logo row */
font-size: 1em !important; /* Normal icon size (reverted #6426 oversizing) */
}
.wrapper ~ #header-quick-access .home-icon a,
@@ -741,19 +741,19 @@ body:not(.board-view) #header #header-main-bar h1 {
}
}
/* iPhone 12 Mini specific - make header elements 3x bigger */
/* iPhone 12 Mini specific - keep the header a normal compact single row */
@media screen and (device-width: 375px) and (device-height: 812px), /* iPhone 12 Mini exact */
screen and (max-width: 375px) and (max-height: 812px), /* iPhone 12 Mini viewport */
screen and (-webkit-min-device-pixel-ratio: 3) and (max-width: 375px), /* iPhone 12 Mini Retina */
screen and (max-width: 375px) and (orientation: portrait), /* iPhone 12 Mini portrait */
screen and (max-width: 375px) and (orientation: landscape) /* iPhone 12 Mini landscape */ {
#header-quick-access {
font-size: 3em !important; /* 3x bigger base font size for iPhone 12 Mini */
height: auto !important; /* Allow height to grow */
min-height: 84px !important; /* Much taller minimum height for iPhone 12 Mini */
flex-wrap: wrap !important; /* Force wrapping */
align-items: flex-start !important; /* Align to top when wrapping */
padding: 18px 0px !important; /* More padding for iPhone 12 Mini */
font-size: 1em !important; /* Normal size (reverted #6426 oversizing) */
height: 48px !important;
min-height: 48px !important;
flex-wrap: nowrap !important;
align-items: center !important;
padding: 8px 0px !important;
}
#header-quick-access * {
@@ -762,7 +762,7 @@ body:not(.board-view) #header #header-main-bar h1 {
#header-quick-access .fa,
#header-quick-access .icon {
font-size: 3em !important; /* 3x bigger icons for iPhone 12 Mini */
font-size: 1em !important; /* Normal icon size (reverted #6426 oversizing) */
}
#header-quick-access .home-icon a {
@@ -848,7 +848,7 @@ body:not(.board-view) #header #header-main-bar h1 {
}
}
/* iPhone 12 Mini and very small screens - make header elements much larger */
/* iPhone 12 Mini and very small screens - header element sizing */
@media screen and (max-width: 400px) and (max-height: 900px),
screen and (max-device-width: 400px) and (max-device-height: 900px),
screen and (-webkit-min-device-pixel-ratio: 2) and (max-width: 400px),
@@ -912,12 +912,12 @@ body:not(.board-view) #header #header-main-bar h1 {
/* Fallback for iPhone devices using JavaScript detection */
.iphone-device #header-quick-access {
font-size: 2em !important; /* 2x bigger base font size */
height: auto !important; /* Allow height to grow */
min-height: 48px !important; /* Minimum height for mobile */
flex-wrap: wrap !important; /* Force wrapping */
align-items: flex-start !important; /* Align to top when wrapping */
padding: 8px 0px !important; /* Adjust padding for mobile */
font-size: 1em !important; /* Normal size (reverted #6426 oversizing) */
height: 48px !important;
min-height: 48px !important;
flex-wrap: nowrap !important;
align-items: center !important;
padding: 8px 0px !important;
}
.iphone-device #header-quick-access * {
@@ -926,7 +926,7 @@ body:not(.board-view) #header #header-main-bar h1 {
.iphone-device #header-quick-access .fa,
.iphone-device #header-quick-access .icon {
font-size: 2em !important; /* 2x bigger icons */
font-size: 1em !important; /* Normal icon size (reverted #6426 oversizing) */
}
.iphone-device #header-quick-access .home-icon a {
@@ -995,14 +995,14 @@ body:not(.board-view) #header #header-main-bar h1 {
width: auto !important;
}
/* iPhone 12 Mini specific - JavaScript detection fallback */
/* iPhone 12 Mini specific - JavaScript detection fallback (normal compact row) */
.iphone-device #header-quick-access {
font-size: 3em !important; /* 3x bigger base font size for iPhone 12 Mini */
height: auto !important; /* Allow height to grow */
min-height: 84px !important; /* Much taller minimum height for iPhone 12 Mini */
flex-wrap: wrap !important; /* Force wrapping */
align-items: flex-start !important; /* Align to top when wrapping */
padding: 18px 0px !important; /* More padding for iPhone 12 Mini */
font-size: 1em !important; /* Normal size (reverted #6426 oversizing) */
height: 48px !important;
min-height: 48px !important;
flex-wrap: nowrap !important;
align-items: center !important;
padding: 8px 0px !important;
}
.iphone-device #header-quick-access * {
@@ -1011,7 +1011,7 @@ body:not(.board-view) #header #header-main-bar h1 {
.iphone-device #header-quick-access .fa,
.iphone-device #header-quick-access .icon {
font-size: 3em !important; /* 3x bigger icons for iPhone 12 Mini */
font-size: 1em !important; /* Normal icon size (reverted #6426 oversizing) */
}
.iphone-device #header-quick-access .home-icon a {
@@ -1096,10 +1096,10 @@ body:not(.board-view) #header #header-main-bar h1 {
width: auto !important;
}
/* iPhone 12 Mini All Boards page - make logo row elements 3x bigger */
/* iPhone 12 Mini All Boards page - keep logo row at normal size (reverted #6426) */
.iphone-device .wrapper ~ #header-quick-access,
.iphone-device body:not(.board-view) #header-quick-access {
font-size: 3em !important; /* 3x bigger base font size for logo row */
font-size: 1em !important; /* Normal size (reverted #6426 oversizing) */
}
.iphone-device .wrapper ~ #header-quick-access *,
@@ -1111,7 +1111,7 @@ body:not(.board-view) #header #header-main-bar h1 {
.iphone-device .wrapper ~ #header-quick-access .icon,
.iphone-device body:not(.board-view) #header-quick-access .fa,
.iphone-device body:not(.board-view) #header-quick-access .icon {
font-size: 2em !important; /* 2x bigger icons in logo row */
font-size: 1em !important; /* Normal icon size (reverted #6426 oversizing) */
}
.iphone-device .wrapper ~ #header-quick-access .home-icon a,
@@ -1209,7 +1209,7 @@ body:not(.board-view) #header #header-main-bar h1 {
.iphone-device #header-quick-access,
.iphone-device .wrapper ~ #header-quick-access,
.iphone-device body:not(.board-view) #header-quick-access {
font-size: 1.2em !important;
font-size: 1em !important;
}
body:not(.board-view) #header-quick-access .fa,
body:not(.board-view) #header-quick-access .icon,
@@ -1221,7 +1221,7 @@ body:not(.board-view) #header #header-main-bar h1 {
.iphone-device .wrapper ~ #header-quick-access .icon,
.iphone-device body:not(.board-view) #header-quick-access .fa,
.iphone-device body:not(.board-view) #header-quick-access .icon {
font-size: 1.5em !important;
font-size: 1.2em !important;
}
/* Zoom (100%), drag-handle toggle and mobile/desktop toggle 30% smaller than
the other quick-access controls at small width — for all browsers. */
+48 -1
View File
@@ -52,6 +52,13 @@ Template.userFormsLayout.onRendered(() => {
// gesture works the same as on the board swimlanes view.
enablePageDragscroll();
// Mark the auth pages so CSS can let <body> scroll as a normal document.
// body.mobile-mode locks <body> to position:fixed/100vh (an iOS board-view
// anti-bounce fix); without an inner #content scroller that traps these pages
// at viewport height, hiding the sign-up link / language selector below the
// form (and they sit behind the on-screen keyboard). See userForm.css.
document.body.classList.add('userform-layout');
Meteor.call('getAuthenticationsEnabled', (_, result) => {
let enabledAuthenticationMethods = ['password']; // we show/hide this based on isPasswordLoginEnabled
@@ -76,14 +83,23 @@ Template.userFormsLayout.onRendered(() => {
})();
});
// #4394 (security): a one-shot $('.at-signup-link').hide() does not survive
// the useraccounts form re-rendering that happens after a failed login (e.g.
// an LDAP "User not Found") — the links were recreated without the inline
// display:none and reappeared even though registration / forgot-password
// were disabled in the Admin Panel. Toggle a class on the stable <body>
// ancestor and hide the links via CSS (userForm.css) so the rule keeps
// applying to every freshly re-rendered copy of the links.
Meteor.call('isDisableRegistration', (_, result) => {
if (result) {
document.body.classList.add('registration-disabled');
$('.at-signup-link').hide();
}
});
Meteor.call('isDisableForgotPassword', (_, result) => {
if (result) {
document.body.classList.add('forgotpwd-disabled');
$('.at-pwd-link').hide();
}
});
@@ -200,6 +216,7 @@ Template.userFormsLayout.onDestroyed(() => {
// Stop drag-scrolling <body> after leaving login / register (e.g. once logged
// in) so other layouts keep their normal behaviour.
disablePageDragscroll();
document.body.classList.remove('userform-layout');
});
Template.userFormsLayout.helpers({
@@ -267,7 +284,12 @@ Template.main.helpers({
Template.userFormsLayout.events({
'change .js-userform-set-language'(event) {
const tag = $(event.currentTarget).val();
TAPi18n.setLanguage(tag);
// setLanguage is async; surface a failed load instead of silently leaving
// the UI in English (#5756).
Promise.resolve(TAPi18n.setLanguage(tag)).catch(error => {
// eslint-disable-next-line no-console
console.error(`Failed to switch language to ${tag}:`, error);
});
event.preventDefault();
},
'click #at-btn'(event, templateInstance) {
@@ -309,6 +331,31 @@ Template.defaultLayout.onRendered(function () {
}
});
});
// Drag-to-scroll for every whole-page layout rendered inside defaultLayout
// (All Boards, My Cards, Due Cards, Global Search, Public, Bookmarks, Broken
// Cards, Settings, People, Admin, Import, board Rules, …). These pages scroll
// <body> on desktop and #content in mobile mode (where <body> is
// position:fixed), so enablePageDragscroll() tags whichever element actually
// scrolls. The board-canvas pages are the exception: their own
// `.board-canvas.dragscroll` handles dragging, and the canvas does its own
// vertical/horizontal scroll, so page-level tagging is turned off there to
// avoid scrolling #content and the canvas at the same time. defaultLayout is
// persistent (only its `content` swaps), so this single autorun covers every
// route without per-template wiring. Login / Register use userFormsLayout and
// keep their own enable/disable. Works the same in mobile and desktop mode.
const boardCanvasRoutes = ['board', 'board-short', 'card'];
this.autorun(() => {
FlowRouter.watchPathChange();
const onBoardCanvas = boardCanvasRoutes.includes(FlowRouter.getRouteName());
Tracker.afterFlush(() => {
if (onBoardCanvas) {
disablePageDragscroll();
} else {
enablePageDragscroll();
}
});
});
});
async function authentication(event, templateInstance) {
+1 -1
View File
@@ -94,7 +94,7 @@ template(name="myCards")
td
.div
each label in card.labelIds
span.card-label(class="card-label-{{labelColor board label}}" title=name)
span.card-label(class="{{labelColorClass (labelColor board label)}}" style="{{labelColorStyle (labelColor board label)}}" title=name)
| {{labelName board label}}
td
if card.dueAt
+2 -10
View File
@@ -1,6 +1,5 @@
import { CardSearchPaged } from '../../lib/cardSearch';
import { Utils } from '/client/lib/utils';
import { enablePageDragscroll, disablePageDragscroll } from '/client/lib/pageDragscroll';
Template.myCardsHeaderBar.helpers({
myCardsSort() {
@@ -41,15 +40,8 @@ Template.myCards.onCreated(function () {
Meteor.subscribe('setting');
});
Template.myCards.onRendered(function () {
// My Cards renders into <body>'s page scroll area (.wrapper inside #content),
// so enable drag-to-scroll on <body> like the All Boards page does.
enablePageDragscroll();
});
Template.myCards.onDestroyed(function () {
disablePageDragscroll();
});
// Drag-to-scroll is handled centrally for all non-board pages by
// defaultLayout's route-aware autorun (see client/components/main/layouts.js).
Template.myCards.helpers({
userId() {
+38 -49
View File
@@ -4,6 +4,7 @@ import Actions from '/models/actions';
import Rules from '/models/rules';
import Triggers from '/models/triggers';
import { Utils } from '/client/lib/utils';
import { canSelectBoardInRules } from '/models/lib/rulesBoardSelector';
Template.boardActions.onCreated(function () {
this.subscribe('boards');
@@ -11,19 +12,30 @@ Template.boardActions.onCreated(function () {
Template.boardActions.helpers({
boards() {
const ret = ReactiveCache.getBoards(
{
archived: false,
'members.userId': Meteor.userId(),
_id: {
$ne: ReactiveCache.getCurrentUser().getTemplatesBoardId(),
},
},
// #5698: don't restrict to boards where the user is a DIRECT member — that
// hid every board reached through an Organization / Team / email-domain
// share (the reporter's whole board dropdown was empty, current board
// included). Filter the already access-scoped client cache with the same
// visibility rule as Boards.userBoards() so org/team/domain boards show too.
const user = ReactiveCache.getCurrentUser();
if (!user) return [];
const ctx = {
userId: user._id,
orgIds: typeof user.orgIds === 'function' ? user.orgIds() : [],
teamIds: typeof user.teamIds === 'function' ? user.teamIds() : [],
emailDomains:
typeof user.emailDomains === 'function' ? user.emailDomains() : [],
};
const templatesBoardId = user.getTemplatesBoardId();
const boards = ReactiveCache.getBoards(
{ archived: false },
{
sort: { sort: 1 /* boards default sorting */ },
},
);
return ret;
return boards.filter(board =>
canSelectBoardInRules(board, ctx, templatesBoardId),
);
},
loadingBoardsLabel() {
@@ -94,38 +106,19 @@ Template.boardActions.events({
const boardId = Session.get('currentBoard');
const destBoardId = tpl.find('#board-id').value;
const desc = Utils.getTriggerActionDesc(event, tpl);
if (actionSelected === 'top') {
const triggerId = Triggers.insert(trigger);
const actionId = Actions.insert({
actionType: 'moveCardToTop',
listName,
swimlaneName,
boardId: destBoardId,
desc,
});
Rules.insert({
title: ruleName,
triggerId,
actionId,
boardId,
});
}
if (actionSelected === 'bottom') {
const triggerId = Triggers.insert(trigger);
const actionId = Actions.insert({
actionType: 'moveCardToBottom',
listName,
swimlaneName,
boardId: destBoardId,
desc,
});
Rules.insert({
title: ruleName,
triggerId,
actionId,
boardId,
});
}
// #5536: create the rule on the server (see server/rulesButton.js →
// rules.createRule) instead of three optimistic client Collection.insert()
// calls. The optimistic writes landed in minimongo limbo once the wizard
// subscription stopped, so the cross-board move action rendered BLANK; and
// the client allow/deny rules rejected them outright for non-owner members.
// The action keeps its own destination `boardId` (the board to move to).
Meteor.call('rules.createRule', boardId, ruleName, trigger, {
actionType: actionSelected === 'bottom' ? 'moveCardToBottom' : 'moveCardToTop',
listName,
swimlaneName,
boardId: destBoardId,
desc,
});
},
'click .js-add-gen-move-action'(event, tpl) {
const desc = Utils.getTriggerActionDesc(event, tpl);
@@ -191,20 +184,16 @@ Template.boardActions.events({
const boardId = Session.get('currentBoard');
const destBoardId = tpl.find('#board-id-link').value;
const desc = Utils.getTriggerActionDesc(event, tpl);
const triggerId = Triggers.insert(trigger);
const actionId = Actions.insert({
// #5536: create on the server so the cross-board link action does not land
// blank in minimongo limbo / get rejected by client allow/deny. The action
// keeps its own destination `boardId` (the board to link the card into).
Meteor.call('rules.createRule', boardId, ruleName, trigger, {
actionType: 'linkCard',
listName,
swimlaneName,
boardId: destBoardId,
desc,
});
Rules.insert({
title: ruleName,
triggerId,
actionId,
boardId,
});
},
'click .js-sort-list-action'(event, tpl) {
const data = Template.currentData();
@@ -100,8 +100,15 @@ template(name="cardActions")
option(value="receivedAt") {{_'r-df-received-at'}}
div.trigger-dropdown
input(id="reldate-days",type="number",value="0")
div.trigger-dropdown
select(id="reldate-unit")
option(value="minutes") {{_'r-unit-minutes'}}
option(value="hours") {{_'r-unit-hours'}}
option(value="days",selected="selected") {{_'r-unit-days'}}
option(value="weeks") {{_'r-unit-weeks'}}
option(value="months") {{_'r-unit-months'}}
div.trigger-text
| {{_'r-days-after'}}
| {{_'r-later'}}
div.trigger-button.js-set-reldate-action.js-goto-rules
i.fa.fa-plus
@@ -238,12 +238,16 @@ Template.cardActions.events({
const boardId = Session.get('currentBoard');
const desc = Utils.getTriggerActionDesc(event, tpl);
const dateField = tpl.find('#reldate-datefield').value;
// `days` keeps its name for backward compatibility: it is the numeric
// amount, interpreted together with `unit` (minutes/hours/days/weeks/months).
const days = parseInt(tpl.find('#reldate-days').value, 10) || 0;
const unit = tpl.find('#reldate-unit').value;
const triggerId = Triggers.insert(trigger);
const actionId = Actions.insert({
actionType: 'setDateRelative',
dateField,
days,
unit,
boardId,
desc,
});
+4 -5
View File
@@ -1,7 +1,5 @@
import { ReactiveCache } from '/imports/reactiveCache';
import Actions from '/models/actions';
import Rules from '/models/rules';
import Triggers from '/models/triggers';
function boardRuleIds() {
const boardId = Session.get('currentBoard');
@@ -62,9 +60,10 @@ Template.rulesList.events({
getSelected().forEach(ruleId => {
const rule = ReactiveCache.getRule(ruleId);
if (rule) {
Rules.remove(rule._id);
Actions.remove(rule.actionId);
Triggers.remove(rule.triggerId);
// Delete the rule + its trigger + action server-side (see
// server/rulesButton.js `rules.deleteRule`): three client-side
// Collection.remove() calls were rejected with 403 "Access denied".
Meteor.call('rules.deleteRule', rule._id);
}
});
setSelected([]);
+6 -6
View File
@@ -1,9 +1,6 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import { Utils } from '/client/lib/utils';
import Actions from '/models/actions';
import Rules from '/models/rules';
import Triggers from '/models/triggers';
Template.rulesMain.onCreated(function () {
this.rulesCurrentTab = new ReactiveVar('rulesList');
@@ -83,9 +80,12 @@ function sanitizeObject(obj) {
Template.rulesMain.events({
'click .js-delete-rule'() {
const rule = Template.currentData();
Rules.remove(rule._id);
Actions.remove(rule.actionId);
Triggers.remove(rule.triggerId);
// Delete the rule + its trigger + action in one server call. Doing this
// client-side as three separate Collection.remove() calls failed with 403
// "Access denied" whenever a trigger/action document's boardId did not
// resolve to a board in the allow() rule; the method authorizes once and
// removes all three server-side.
Meteor.call('rules.deleteRule', rule._id);
},
'click .js-goto-trigger'(event, tpl) {
event.preventDefault();
+2 -2
View File
@@ -12,7 +12,7 @@ template(name="rulesWorkflow")
.workflow-node.workflow-node-when
span.workflow-node-label {{_ 'r-when'}}
span.workflow-node-text= builderTriggerLabel
a.workflow-slot-clear.js-clear-when(title="{{_ 'remove'}}") &#10005;
a.workflow-slot-clear.js-clear-when(title="{{_ 'r-remove'}}") &#10005;
else
span.workflow-slot-placeholder {{_ 'r-when'}}: {{_ 'r-drop-trigger'}}
.workflow-arrow
@@ -22,7 +22,7 @@ template(name="rulesWorkflow")
.workflow-node.workflow-node-then
span.workflow-node-label {{_ 'r-action'}}
span.workflow-node-text= builderActionLabel
a.workflow-slot-clear.js-clear-then(title="{{_ 'remove'}}") &#10005;
a.workflow-slot-clear.js-clear-then(title="{{_ 'r-remove'}}") &#10005;
else
span.workflow-slot-placeholder {{_ 'r-action'}}: {{_ 'r-drop-action'}}
.workflow-builder-controls
+46 -32
View File
@@ -12,31 +12,44 @@ import Triggers from '/models/triggers';
// can be built entirely by dragging. Parameterized rules (specific list, label,
// member, schedule time, …) are still created with the form builder on the
// List view.
// Each palette entry carries an i18n `labelKey` (translated at render time via
// TAPi18n.__) instead of a hardcoded English string, so the whole workflow view
// follows the UI language. `labelParams` is passed to TAPi18n.__ for labels that
// need a placeholder (e.g. the scheduled time). Existing rule keys are reused
// where they fit; workflow-only labels use new `r-w-*` keys (see en.i18n.json).
const TRIGGER_PALETTE = [
{ label: 'Card is created', doc: { activityType: 'createCard', listName: '*', swimlaneName: '*', cardTitle: '*', userId: '*' } },
{ label: 'Card is moved', doc: { activityType: 'moveCard', listName: '*', oldListName: '*', swimlaneName: '*', cardTitle: '*', userId: '*' } },
{ label: 'Card is archived', doc: { activityType: 'archivedCard', userId: '*' } },
{ label: 'Card is unarchived', doc: { activityType: 'restoredCard', userId: '*' } },
{ label: 'Any label is added', doc: { activityType: 'addedLabel', labelId: '*', userId: '*' } },
{ label: 'Any label is removed', doc: { activityType: 'removedLabel', labelId: '*', userId: '*' } },
{ label: 'A member is added', doc: { activityType: 'joinMember', username: '*', userId: '*' } },
{ label: 'A member is removed', doc: { activityType: 'unjoinMember', username: '*', userId: '*' } },
{ label: 'A checklist is added', doc: { activityType: 'addChecklist', checklistName: '*', userId: '*' } },
{ label: 'An attachment is added', doc: { activityType: 'addAttachment', userId: '*' } },
{ label: 'Every day at 09:00', doc: { activityType: 'scheduledTrigger', scheduleKind: 'calendar', scheduleType: 'daily', atTime: '09:00', listName: '*', swimlaneName: '*' } },
{ labelKey: 'r-w-card-created', doc: { activityType: 'createCard', listName: '*', swimlaneName: '*', cardTitle: '*', userId: '*' } },
{ labelKey: 'r-when-a-card-is-moved', doc: { activityType: 'moveCard', listName: '*', oldListName: '*', swimlaneName: '*', cardTitle: '*', userId: '*' } },
{ labelKey: 'r-w-card-archived', doc: { activityType: 'archivedCard', userId: '*' } },
{ labelKey: 'r-w-card-unarchived', doc: { activityType: 'restoredCard', userId: '*' } },
{ labelKey: 'r-w-label-added', doc: { activityType: 'addedLabel', labelId: '*', userId: '*' } },
{ labelKey: 'r-w-label-removed', doc: { activityType: 'removedLabel', labelId: '*', userId: '*' } },
{ labelKey: 'r-w-member-added', doc: { activityType: 'joinMember', username: '*', userId: '*' } },
{ labelKey: 'r-w-member-removed', doc: { activityType: 'unjoinMember', username: '*', userId: '*' } },
{ labelKey: 'r-w-checklist-added', doc: { activityType: 'addChecklist', checklistName: '*', userId: '*' } },
{ labelKey: 'r-w-attachment-added', doc: { activityType: 'addAttachment', userId: '*' } },
{ labelKey: 'r-w-every-day-at', labelParams: { time: '09:00' }, doc: { activityType: 'scheduledTrigger', scheduleKind: 'calendar', scheduleType: 'daily', atTime: '09:00', listName: '*', swimlaneName: '*' } },
];
const ACTION_PALETTE = [
{ label: 'Move card to top', doc: { actionType: 'moveCardToTop', listName: '*', swimlaneName: '*' } },
{ label: 'Move card to bottom', doc: { actionType: 'moveCardToBottom', listName: '*', swimlaneName: '*' } },
{ label: 'Archive card', doc: { actionType: 'archive' } },
{ label: 'Unarchive card', doc: { actionType: 'unarchive' } },
{ label: 'Mark card complete', doc: { actionType: 'markCardComplete' } },
{ label: 'Mark card incomplete', doc: { actionType: 'markCardIncomplete' } },
{ label: 'Remove all members', doc: { actionType: 'removeMember', username: '*' } },
{ label: 'Set received date to now', doc: { actionType: 'setDate', dateField: 'receivedAt' } },
{ labelKey: 'r-d-move-to-top-gen', doc: { actionType: 'moveCardToTop', listName: '*', swimlaneName: '*' } },
{ labelKey: 'r-d-move-to-bottom-gen', doc: { actionType: 'moveCardToBottom', listName: '*', swimlaneName: '*' } },
{ labelKey: 'r-d-archive', doc: { actionType: 'archive' } },
{ labelKey: 'r-d-unarchive', doc: { actionType: 'unarchive' } },
{ labelKey: 'r-mark-complete', doc: { actionType: 'markCardComplete' } },
{ labelKey: 'r-mark-incomplete', doc: { actionType: 'markCardIncomplete' } },
{ labelKey: 'r-remove-all', doc: { actionType: 'removeMember', username: '*' } },
{ labelKey: 'r-w-set-received-now', doc: { actionType: 'setDate', dateField: 'receivedAt' } },
];
// Translate a palette entry's label for display / storage in the current UI
// language (matches the classic Rules view, which also stores already-translated
// description text at creation time).
function paletteLabel(entry) {
if (!entry) return '';
return TAPi18n.__(entry.labelKey, entry.labelParams);
}
Template.rulesWorkflow.onCreated(function () {
this.builderTrigger = new ReactiveVar(null);
this.builderAction = new ReactiveVar(null);
@@ -52,24 +65,22 @@ Template.rulesWorkflow.helpers({
return Utils.getCurrentBoard();
},
triggerPalette() {
return TRIGGER_PALETTE.map((t, idx) => ({ idx, label: t.label }));
return TRIGGER_PALETTE.map((t, idx) => ({ idx, label: paletteLabel(t) }));
},
actionPalette() {
return ACTION_PALETTE.map((a, idx) => ({ idx, label: a.label }));
return ACTION_PALETTE.map((a, idx) => ({ idx, label: paletteLabel(a) }));
},
builderTrigger() {
return Template.instance().builderTrigger.get();
},
builderTriggerLabel() {
const t = Template.instance().builderTrigger.get();
return t ? t.label : '';
return paletteLabel(Template.instance().builderTrigger.get());
},
builderAction() {
return Template.instance().builderAction.get();
},
builderActionLabel() {
const a = Template.instance().builderAction.get();
return a ? a.label : '';
return paletteLabel(Template.instance().builderAction.get());
},
createDisabled() {
const tpl = Template.instance();
@@ -96,9 +107,11 @@ function persistRule(tpl) {
if (!t || !a) return;
const boardId = Session.get('currentBoard');
const titleField = tpl.find('.js-workflow-rule-title');
const title = (titleField.value || '').trim() || `${t.label} → ${a.label}`;
const triggerId = Triggers.insert({ ...t.doc, boardId, desc: t.label });
const actionId = Actions.insert({ ...a.doc, boardId, desc: a.label });
const tLabel = paletteLabel(t);
const aLabel = paletteLabel(a);
const title = (titleField.value || '').trim() || `${tLabel} → ${aLabel}`;
const triggerId = Triggers.insert({ ...t.doc, boardId, desc: tLabel });
const actionId = Actions.insert({ ...a.doc, boardId, desc: aLabel });
Rules.insert({ title, triggerId, actionId, boardId });
tpl.builderTrigger.set(null);
tpl.builderAction.set(null);
@@ -140,7 +153,7 @@ Template.rulesWorkflow.events({
const a = ACTION_PALETTE[tpl.dragItem.idx];
const boardId = Session.get('currentBoard');
const oldActionId = rule.actionId;
const newActionId = Actions.insert({ ...a.doc, boardId, desc: a.label });
const newActionId = Actions.insert({ ...a.doc, boardId, desc: paletteLabel(a) });
Rules.update(ruleId, { $set: { actionId: newActionId } });
if (oldActionId) Actions.remove(oldActionId);
tpl.dragItem = null;
@@ -163,8 +176,9 @@ Template.rulesWorkflow.events({
const ruleId = node && node.dataset.ruleId;
const rule = ReactiveCache.getRule(ruleId);
if (!rule) return;
Rules.remove(rule._id);
Actions.remove(rule.actionId);
Triggers.remove(rule.triggerId);
// Delete the rule + its trigger + action server-side (see
// server/rulesButton.js `rules.deleteRule`): three client-side
// Collection.remove() calls were rejected with 403 "Access denied".
Meteor.call('rules.deleteRule', rule._id);
},
});
@@ -364,6 +364,10 @@ Template.boardsReport.helpers({
},
userNames(members) {
const ret = (members || [])
// #5122: removing a member from a board marks it `isActive: false` (the
// entry stays in board.members for role history / re-activation). The
// boards report must not list those removed members as current members.
.filter(_member => _member.isActive !== false)
.map(_member => {
const _ret = ReactiveCache.getUser(_member.userId)?.username || _member.userId;
return _ret;
+59
View File
@@ -378,3 +378,62 @@ table td:first-child {
.roles-settings .roles-description {
margin-bottom: 1rem;
}
/* Admin Panel > People > Domains table: search + column sort + pagination,
matching the Board Table view / other admin tables. */
.domain-controls {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 8px;
}
.domain-controls input.js-domain-search {
flex: 1 1 auto;
height: 30px;
padding: 0 8px;
border: 1px solid #bbb;
border-radius: 0.4vw;
}
.domain-controls button.js-domain-search-button {
height: 30px;
padding: 0 12px;
border: 1px solid #bbb;
background: #fff;
border-radius: 0.4vw;
cursor: pointer;
}
.domain-pagination {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 12px;
}
.domain-pagination button {
min-width: 32px;
height: 30px;
padding: 0 10px;
border: 1px solid #bbb;
background: #fff;
border-radius: 0.4vw;
cursor: pointer;
}
.domain-pagination button.disabled {
opacity: 0.5;
pointer-events: none;
}
.domain-page-info {
min-width: 72px;
text-align: center;
color: #333;
}
th.js-domain-sort {
cursor: pointer;
user-select: none;
}
+10 -3
View File
@@ -122,7 +122,7 @@ template(name="people")
else if templatesSetting.get
+templatesGeneral
else if domainSetting.get
+domainGeneral(domainList=domainList)
+domainGeneral
template(name="orgGeneral")
@@ -239,11 +239,18 @@ template(name="selectAllUser")
input.allUserChkBox(type="checkbox", id="chkSelectAll")
template(name="domainGeneral")
.domain-controls
input.js-domain-search(type="text" placeholder="{{_ 'search'}}" aria-label="{{_ 'search'}}")
button.js-domain-search-button(type="button") {{_ 'search'}}
.domain-pagination
button.js-domain-prev-page(type="button" class="{{#unless hasPrevPage}}disabled{{/unless}}" aria-label="{{_ 'previous'}}") {{_ 'previous'}}
span.domain-page-info {{currentPage}} / {{totalPages}}
button.js-domain-next-page(type="button" class="{{#unless hasNextPage}}disabled{{/unless}}" aria-label="{{_ 'next'}}") {{_ 'next'}}
table
thead
tr
th {{_ 'domain'}}
th {{_ 'domain-user-count'}}
th.js-domain-sort(scope="col" data-sort="domain") {{_ 'domain'}} {{domainSortIndicator 'domain'}}
th.js-domain-sort(scope="col" data-sort="count") {{_ 'domain-user-count'}} {{domainSortIndicator 'count'}}
tbody
if domainList.length
each domainList
+99 -11
View File
@@ -14,6 +14,7 @@ import InviteToBoardRolesSettings, {
const orgsPerPage = 25;
const teamsPerPage = 25;
const usersPerPage = 25;
const domainsPerPage = 25;
let userOrgsTeamsAction = ""; //poosible actions 'addOrg', 'addTeam', 'removeOrg' or 'removeTeam' when adding or modifying a user
let selectedUserChkBoxUserIds = [];
@@ -28,9 +29,9 @@ Template.people.onCreated(function () {
this.lockedUsersSetting = new ReactiveVar(false);
this.rolesSetting = new ReactiveVar(false);
this.templatesSetting = new ReactiveVar(false);
// #5850: Admin Panel > People > Domains tab.
// #5850: Admin Panel > People > Domains tab. The domains table itself
// (domainGeneral) now owns its data via getDomainsWithUserCountsPage.
this.domainSetting = new ReactiveVar(false);
this.domains = new ReactiveVar([]);
this.subscribe('inviteToBoardRolesSettings');
this.findOrgsOptions = new ReactiveVar({});
this.findTeamsOptions = new ReactiveVar({});
@@ -205,12 +206,9 @@ Template.people.onCreated(function () {
this.templatesSetting.set('templates-setting' === targetID);
this.domainSetting.set('domains-setting' === targetID);
// #5850: load the domains + per-domain user counts when the tab opens.
if ('domains-setting' === targetID) {
Meteor.call('getDomainsWithUserCounts', (err, res) => {
if (!err) this.domains.set(res || []);
});
}
// The Domains table (domainGeneral) now fetches its own single page from
// getDomainsWithUserCountsPage (server-side search / sort / pagination), so
// the parent no longer eagerly loads every domain into the browser.
// When switching to locked users tab, refresh the locked users list
if ('locked-users-setting' === targetID) {
@@ -292,9 +290,6 @@ Template.people.helpers({
domainSetting() {
return Template.instance().domainSetting;
},
domainList() {
return Template.instance().domains.get();
},
orgList() {
const tpl = Template.instance();
// The 'org' publication already returns only the current page (server-side
@@ -1897,3 +1892,96 @@ Template.settingsUserPopup.helpers({
return Template.instance().errorMessage.get();
},
});
// Admin Panel > People > Domains table. Self-contained (like the Board Table
// view): it keeps its own search / sort / page state and fetches only ONE page
// from the server method getDomainsWithUserCountsPage, so the whole domain list
// is never loaded into the browser.
Template.domainGeneral.onCreated(function () {
this.searchQuery = new ReactiveVar('');
this.sortField = new ReactiveVar('domain'); // domain | count
this.sortDirection = new ReactiveVar(1); // 1 ascending, -1 descending
this.page = new ReactiveVar(1);
this.pageData = new ReactiveVar({ rows: [], total: 0, totalPages: 1 });
this.autorun(() => {
const params = {
search: this.searchQuery.get(),
sortField: this.sortField.get(),
sortDirection: this.sortDirection.get(),
page: this.page.get(),
perPage: domainsPerPage,
};
Meteor.call('getDomainsWithUserCountsPage', params, (err, res) => {
if (!err && res) {
this.pageData.set(res);
// Server clamps the page into range; mirror that so the controls agree.
if (typeof res.page === 'number' && res.page !== this.page.get()) {
this.page.set(res.page);
}
}
});
});
});
Template.domainGeneral.helpers({
domainList() {
return Template.instance().pageData.get().rows;
},
currentPage() {
return Template.instance().page.get();
},
totalPages() {
return Template.instance().pageData.get().totalPages || 1;
},
hasPrevPage() {
return Template.instance().page.get() > 1;
},
hasNextPage() {
const tpl = Template.instance();
return tpl.page.get() < (tpl.pageData.get().totalPages || 1);
},
// Excel-like sort arrow on the active column, matching the Board Table view.
domainSortIndicator(field) {
const tpl = Template.instance();
if (tpl.sortField.get() !== field) return '';
return tpl.sortDirection.get() === 1 ? '▲' : '▼';
},
});
Template.domainGeneral.events({
'click .js-domain-search-button'(event, tpl) {
event.preventDefault();
tpl.searchQuery.set(tpl.$('.js-domain-search').val() || '');
tpl.page.set(1);
},
'keydown .js-domain-search'(event, tpl) {
if (event.keyCode === 13) {
event.preventDefault();
tpl.searchQuery.set(tpl.$('.js-domain-search').val() || '');
tpl.page.set(1);
}
},
'click .js-domain-prev-page'(event, tpl) {
event.preventDefault();
const current = tpl.page.get();
if (current > 1) tpl.page.set(current - 1);
},
'click .js-domain-next-page'(event, tpl) {
event.preventDefault();
const current = tpl.page.get();
if (current < (tpl.pageData.get().totalPages || 1)) tpl.page.set(current + 1);
},
'click .js-domain-sort'(event, tpl) {
event.preventDefault();
const field = event.currentTarget.dataset.sort;
if (!field) return;
if (tpl.sortField.get() === field) {
tpl.sortDirection.set(tpl.sortDirection.get() * -1);
} else {
tpl.sortField.set(field);
tpl.sortDirection.set(1);
}
tpl.page.set(1);
},
});
+12 -1
View File
@@ -6,6 +6,7 @@ import AccessibilitySettings from '/models/accessibilitySettings';
import AccountSettings from '/models/accountSettings';
import Announcements from '/models/announcements';
import Settings from '/models/settings';
import { resolveDefaultAuthenticationMethod } from '/models/lib/authenticationMethod';
import TableVisibilityModeSettings from '/models/tableVisibilityModeSettings';
// import {
// cronMigrationProgress,
@@ -803,7 +804,17 @@ Template.setting.events({
$('input[name=hideBoardMemberList]:checked').val() === 'true';
const displayAuthenticationMethod =
$('input[name=displayAuthenticationMethod]:checked').val() === 'true';
const defaultAuthenticationMethod = $('#defaultAuthenticationMethod').val();
// #5879: the <select> options are populated by an async Meteor.call, so its
// value can still be '' / null when Save is clicked. Saving that empty value
// over the required `defaultAuthenticationMethod` string silently failed
// validation, so the Layout save looked like it hung / did nothing. Fall back
// to the currently stored method so a real value is never overwritten by ''.
const currentDefaultAuthenticationMethod =
ReactiveCache.getCurrentSetting()?.defaultAuthenticationMethod;
const defaultAuthenticationMethod = resolveDefaultAuthenticationMethod(
$('#defaultAuthenticationMethod').val(),
currentDefaultAuthenticationMethod,
);
const spinnerName = ($('#spinnerName').val() || '').trim();
try {
+41 -2
View File
@@ -82,6 +82,14 @@ body.grey-icons-enabled .boardSubtaskSettingsPopup .materialCheckBox.is-checked
grid-template-columns: 1fr 1fr 2fr;
gap: 10px;
margin-bottom: 10px;
/* Keep the "Show on Card / Show on Minicard / Description" titles visible
while the rows below scroll (the popup scrolls in .pop-over .content-wrapper). */
position: sticky;
top: 0;
z-index: 2;
background: #fff;
padding: 5px 0;
border-bottom: 2px solid #ddd;
}
.card-settings-row {
@@ -261,9 +269,40 @@ body.grey-icons-enabled .sidebar .sidebar-content ul.sidebar-list li > a .fa.fa-
padding: 0 10px 0 10px;
}
@media screen and (max-width: 800px) {
.board-sidebar {
width: 100%;
/* On phones (iPhone 12 mini, etc.) the right sidebar should be a full-width
panel at the top of the board, in BOTH mobile mode and desktop mode. The
sidebar lives inside .board-wrapper, which is wider than the viewport on a
phone, so an absolutely-positioned sidebar (inset-inline-end: 0) pins to the
far right of the wide board and shows as a narrow strip. Pin it to the
VIEWPORT instead (position: fixed + 100vw) so it spans the full screen
width. At 100% zoom .board-wrapper has no transform, so fixed resolves to
the viewport in desktop mode too. */
.board-sidebar.sidebar {
position: fixed;
top: 0;
bottom: 0;
inset-inline-start: 0;
inset-inline-end: 0;
width: 100vw;
max-width: 100vw;
z-index: 1000;
}
/* Normalise sidebar text on phones so the headings are not oversized and the
small text is not tiny — keep them close to the board's text size so board
and sidebar read at one consistent size. */
.board-sidebar.sidebar .sidebar-content {
font-size: 14px;
}
.board-sidebar.sidebar .sidebar-content h2,
.board-sidebar.sidebar .sidebar-content h3 {
font-size: 16px;
}
.board-sidebar.sidebar .sidebar-content ul.sidebar-list li > a,
.board-sidebar.sidebar .sidebar-content .card-label,
.board-sidebar.sidebar .sidebar-content .card-label .card-label-name,
.board-sidebar.sidebar .sidebar-content p,
.board-sidebar.sidebar .sidebar-content span {
font-size: 14px;
}
.board-sidebar .sidebar-content .hide-btn {
width: 40px;
+34 -43
View File
@@ -55,9 +55,14 @@ template(name='homeSidebar')
i.fa.fa-comment-o
| {{_ 'activities'}}
a.flex.js-toggle-show-activities(title="{{_ 'show-activities'}}")
i.fa(class="{{#if showActivities}}fa-check{{else}}fa-square-o{{/if}}")
span {{_ 'show-activities'}}
if showActivities
a.flex.activities-eye-toggle.js-toggle-show-activities(title="{{_ 'hide-activities'}}" aria-label="{{_ 'hide-activities'}}")
i.fa.fa-eye.eye-icon
span {{_ 'hide-activities'}}
else
a.flex.activities-eye-toggle.js-toggle-show-activities(title="{{_ 'show-activities'}}" aria-label="{{_ 'show-activities'}}")
i.fa.fa-eye-slash.eye-slash-icon
span {{_ 'show-activities'}}
+activities(mode="board")
template(name="membersWidget")
@@ -315,21 +320,12 @@ template(name="boardCardSettingsPopup")
a.flex.js-field-has-creator(title="{{_ 'creator'}}" class="{{#if allowsCreator}}is-checked{{/if}}")
i.fa(class="{{#if allowsCreator}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
.card-settings-column
span
i.fa.fa-user
| {{_ 'creator'}}
.card-settings-row
.card-settings-column
span
.card-settings-column
a.flex.js-field-has-creator-on-minicard(title="{{_ 'creator-on-minicard'}}" class="{{#if allowsCreatorOnMinicard}}is-checked{{/if}}")
a.flex.js-field-has-creator-on-minicard(title="{{_ 'creator'}}" class="{{#if allowsCreatorOnMinicard}}is-checked{{/if}}")
i.fa(class="{{#if allowsCreatorOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
i.fa.fa-user
| {{_ 'creator-on-minicard'}}
| {{_ 'creator'}}
.card-settings-row
.card-settings-column
a.flex.js-field-has-assignee(title="{{_ 'assignee'}}" class="{{#if allowsAssignee}}is-checked{{/if}}")
@@ -369,27 +365,19 @@ template(name="boardCardSettingsPopup")
a.flex.js-field-has-card-sorting-by-number(title="{{_ 'card-sorting-by-number'}}" class="{{#if allowsCardSortingByNumber}}is-checked{{/if}}")
i.fa(class="{{#if allowsCardSortingByNumber}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
a.flex.js-field-has-card-sorting-by-number-on-minicard(title="{{_ 'card-sorting-by-number'}}" class="{{#if allowsCardSortingByNumberOnMinicard}}is-checked{{/if}}")
i.fa(class="{{#if allowsCardSortingByNumberOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
i.fa.fa-sort-numeric-asc
| {{_ 'card-sorting-by-number'}}
.card-settings-row
.card-settings-column
span
.card-settings-column
a.flex.js-field-has-card-sorting-by-number-on-minicard(title="{{_ 'card-sorting-by-number-on-minicard'}}" class="{{#if allowsCardSortingByNumberOnMinicard}}is-checked{{/if}}")
i.fa(class="{{#if allowsCardSortingByNumberOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
i.fa.fa-sort-numeric-asc
| {{_ 'card-sorting-by-number-on-minicard'}}
.card-settings-row
.card-settings-column
a.flex.js-field-has-card-show-lists(title="{{_ 'card-show-lists'}}" class="{{#if allowsShowLists}}is-checked{{/if}}")
i.fa(class="{{#if allowsShowLists}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
a.flex.js-field-has-card-show-lists-on-minicard(title="{{_ 'card-show-lists'}}" class="{{#if allowsShowListsOnMinicard}}is-checked{{/if}}")
i.fa(class="{{#if allowsShowListsOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
i.fa.fa-list
@@ -405,16 +393,6 @@ template(name="boardCardSettingsPopup")
span
i.fa.fa-tag
| {{_ 'labels'}}
.card-settings-row
.card-settings-column
span
.card-settings-column
a.flex.js-field-has-card-show-lists-on-minicard(title="{{_ 'card-show-lists-on-minicard'}}" class="{{#if allowsShowListsOnMinicard}}is-checked{{/if}}")
i.fa(class="{{#if allowsShowListsOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
i.fa.fa-list
| {{_ 'card-show-lists-on-minicard'}}
.card-settings-row
.card-settings-column
a.flex.js-field-has-card-number(title="{{_ 'card'}} {{_ 'number'}}" class="{{#if allowsCardNumber}}is-checked{{/if}}")
@@ -462,6 +440,17 @@ template(name="boardCardSettingsPopup")
span
i.fa.fa-check
| {{_ 'checklists'}}
.card-settings-row
.card-settings-column
a.flex.js-field-has-checklist-count(title="{{_ 'checklist-count'}}" class="{{#if allowsChecklistCountBadgeOnCard}}is-checked{{/if}}")
i.fa(class="{{#if allowsChecklistCountBadgeOnCard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
a.flex.js-field-has-checklist-count-on-minicard(title="{{_ 'checklist-count'}}" class="{{#if allowsChecklistCountBadgeOnMinicard}}is-checked{{/if}}")
i.fa(class="{{#if allowsChecklistCountBadgeOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
i.fa.fa-check-square-o
| {{_ 'checklist-count'}}
.card-settings-row
.card-settings-column
a.flex.js-field-has-subtasks(title="{{_ 'subtasks'}}" class="{{#if allowsSubtasks}}is-checked{{/if}}")
@@ -486,24 +475,26 @@ template(name="boardCardSettingsPopup")
| {{_ 'attachments'}}
.card-settings-row
.card-settings-column
span
a.flex.js-field-has-attachment-count(title="{{_ 'attachment-count'}}" class="{{#if allowsAttachmentCountOnCard}}is-checked{{/if}}")
i.fa(class="{{#if allowsAttachmentCountOnCard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
a.flex.js-field-has-badge-attachment-on-minicard(title="{{_ 'badge-attachment-on-minicard'}}" class="{{#if allowsBadgeAttachmentOnMinicard}}is-checked{{/if}}")
a.flex.js-field-has-badge-attachment-on-minicard(title="{{_ 'attachment-count'}}" class="{{#if allowsBadgeAttachmentOnMinicard}}is-checked{{/if}}")
i.fa(class="{{#if allowsBadgeAttachmentOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
i.fa.fa-paperclip
| {{_ 'badge-attachment-on-minicard'}}
| {{_ 'attachment-count'}}
.card-settings-row
.card-settings-column
span
a.flex.js-field-has-cover-attachment-on-card(title="{{_ 'cover-image'}}" class="{{#if allowsCoverAttachmentOnCard}}is-checked{{/if}}")
i.fa(class="{{#if allowsCoverAttachmentOnCard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
a.flex.js-field-has-cover-attachment-on-minicard(title="{{_ 'cover-attachment-on-minicard'}}" class="{{#if allowsCoverAttachmentOnMinicard}}is-checked{{/if}}")
a.flex.js-field-has-cover-attachment-on-minicard(title="{{_ 'cover-image'}}" class="{{#if allowsCoverAttachmentOnMinicard}}is-checked{{/if}}")
i.fa(class="{{#if allowsCoverAttachmentOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
i.fa.fa-picture-o
| {{_ 'cover-attachment-on-minicard'}}
| {{_ 'cover-image'}}
//div.check-div
//
a.flex.js-field-has-comments(class="{{#if allowsComments}}is-checked{{/if}}")
@@ -837,7 +828,7 @@ template(name="labelsWidget")
| {{_ 'labels'}}
.board-widget-content
each currentBoard.labels
a.card-label(class="card-label-{{color}}"
a.card-label(class="{{labelColorClass color}}" style="{{labelColorStyle color}}"
class="{{#if currentUser.isNotCommentOnly}}{{#if currentUser.isNotWorker}}js-label{{/if}}{{/if}}")
span.card-label-name
+viewer
+42
View File
@@ -1496,6 +1496,28 @@ Template.boardCardSettingsPopup.helpers({
const currentBoard = ReactiveCache.getBoard(boardId);
return getMinicardSetting(currentBoard, 'allowsChecklistsOnMinicard', 'allowsChecklists', true);
},
// #6431: compact checklist count badge on minicard, opt-in and OFF by default.
allowsChecklistCountBadgeOnMinicard() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsChecklistCountBadgeOnMinicard : false;
},
// #6431 follow-up: new opt-in (OFF by default) card-side options.
allowsChecklistCountBadgeOnCard() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsChecklistCountBadgeOnCard : false;
},
allowsAttachmentCountOnCard() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsAttachmentCountOnCard : false;
},
allowsCoverAttachmentOnCard() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsCoverAttachmentOnCard : false;
},
allowsAttachments() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
@@ -1790,6 +1812,26 @@ Template.boardCardSettingsPopup.events({
const newValue = !tpl.currentBoard.allowsChecklistsOnMinicard;
Boards.update(tpl.currentBoard._id, { $set: { allowsChecklistsOnMinicard: newValue } });
},
'click .js-field-has-checklist-count-on-minicard'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsChecklistCountBadgeOnMinicard;
Boards.update(tpl.currentBoard._id, { $set: { allowsChecklistCountBadgeOnMinicard: newValue } });
},
'click .js-field-has-checklist-count'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsChecklistCountBadgeOnCard;
Boards.update(tpl.currentBoard._id, { $set: { allowsChecklistCountBadgeOnCard: newValue } });
},
'click .js-field-has-attachment-count'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsAttachmentCountOnCard;
Boards.update(tpl.currentBoard._id, { $set: { allowsAttachmentCountOnCard: newValue } });
},
'click .js-field-has-cover-attachment-on-card'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsCoverAttachmentOnCard;
Boards.update(tpl.currentBoard._id, { $set: { allowsCoverAttachmentOnCard: newValue } });
},
'click .js-field-has-attachments'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsAttachments;
@@ -29,7 +29,7 @@ template(name="filterSidebar")
each currentBoard.labels
li
a.name.js-toggle-label-filter(data-filter-id="{{_id}}")
span.card-label.square(class="card-label-{{color}}")
span.card-label.square(class="{{labelColorClass color}}" style="{{labelColorStyle color}}")
span.sidebar-list-item-description
if name
= name
@@ -188,7 +188,7 @@ template(name="multiselectionSidebar")
each currentBoard.labels
li
a.name.js-toggle-label-multiselection
span.card-label.square(class="card-label-{{color}}")
span.card-label.square(class="{{labelColorClass color}}" style="{{labelColorStyle color}}")
span.sidebar-list-item-description
if name
= name
@@ -1,5 +1,5 @@
template(name="swimlaneHeader")
.swimlane-header-wrap.js-swimlane-header(class=colorClass title="{{_ 'rename'}}")
.swimlane-header-wrap.js-swimlane-header(class=colorClass style=colorStyle title="{{_ 'rename'}}")
if this.isTemplateContainer
+swimlaneFixedHeader(this)
else
@@ -120,6 +120,10 @@ template(name="setSwimlaneColorPopup")
span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
if(isSelected color)
i.fa.fa-check
// #5514: native color wheel for picking a custom RGB / hex color
.flush-left.color-wheel-wrap
label(for="swimlaneColorWheel") {{_ 'custom-color'}}
input.js-swimlane-color-wheel#swimlaneColorWheel(type="color" value=currentColorHex title="{{_ 'custom-color'}}")
// Buttons aligned left too
.flush-left
button.primary.confirm.js-submit(style="margin-inline-start:0") {{_ 'save'}}
@@ -1,6 +1,7 @@
import { TAPi18n } from '/imports/i18n';
import { ReactiveCache } from '/imports/reactiveCache';
import { SWIMLANE_COLORS } from '/models/metadata/colors';
import { isHexColor, toHex } from '/models/lib/contrastColor';
import Swimlanes from '/models/swimlanes';
import { Utils } from '/client/lib/utils';
const { calculateIndexData } = Utils;
@@ -166,6 +167,10 @@ Template.setSwimlaneColorPopup.helpers({
isSelected(color) {
return Template.instance().currentColor.get() === color;
},
// #5514: current color as a '#rrggbb' hex for the native color wheel <input>.
currentColorHex() {
return toHex(Template.instance().currentColor.get()) || '#0079bf';
},
});
Template.setSwimlaneColorPopup.events({
@@ -178,6 +183,13 @@ Template.setSwimlaneColorPopup.events({
const paletteData = Blaze.getData(event.currentTarget);
tpl.currentColor.set(paletteData?.color);
},
// #5514: picking from the native color wheel stores a custom hex.
'input .js-swimlane-color-wheel'(event, tpl) {
const value = event.currentTarget.value;
if (isHexColor(value)) {
tpl.currentColor.set(value);
}
},
async 'click .js-submit'(event, tpl) {
event.preventDefault();
await tpl.currentSwimlane.setColor(tpl.currentColor.get());
+7 -7
View File
@@ -83,8 +83,15 @@
}
.swimlane .swimlane-header-wrap .swimlane-header-menu {
position: absolute;
/* Stretch the menu to the full height of the swimlane header and lay the
buttons (collapse, swimlane menu, + new swimlane, drag handle) in a row that
is vertically centered — instead of anchoring them to the top with per-icon
top: calc(50% + Npx) hacks that centered against the menu's own height. */
top: 0;
bottom: 0;
inset-inline-start: 0;
display: flex;
align-items: center;
padding: 0;
margin: 0;
font-size: 22px;
@@ -93,7 +100,6 @@
pointer-events: auto;
}
.swimlane .swimlane-header-wrap .swimlane-header-menu .js-open-swimlane-menu {
top: calc(50% + 6px);
padding: 5px;
display: inline-block;
margin-inline-start: 30px;
@@ -107,20 +113,16 @@
}
}
.swimlane .swimlane-header-wrap .swimlane-header-plus-icon {
top: calc(50% + 6px);
padding: 5px;
margin-inline-start: 20px;
font-size: 22px;
color: #a6a6a6;
}
.swimlane .swimlane-header-wrap .swimlane-header-menu-icon {
top: calc(50% + 6px);
padding-inline-start: 5px;
font-size: 22px;
}
.swimlane .swimlane-header-wrap .swimlane-header-handle {
position: relative;
top: calc(50% + 2px);
padding: 2px 5px;
font-size: clamp(16px, 3vw, 20px);
display: inline-block;
@@ -132,9 +134,7 @@
line-height: 1.2;
}
.swimlane .swimlane-header-wrap .swimlane-header-miniscreen-handle {
position: relative;
padding: 2px 5px;
top: calc(50% + 2px);
font-size: 24px;
display: inline-block;
vertical-align: middle;
+17 -7
View File
@@ -139,16 +139,26 @@ template(name="adminChangeAvatarPopup")
template(name="cardMemberPopup")
.board-member-menu
.mini-profile-info
+userAvatar(userId=user._id showEdit=true)
.info
h3= user.profile.fullname
p.quiet @{{ user.username }}
if user
+userAvatar(userId=user._id showEdit=true)
.info
h3= user.profile.fullname
p.quiet @{{ user.username }}
else
//- #4847: the member's user account was deleted. Still show the entry
//- (with its id) and the remove control so the orphaned member can be
//- removed from the card — the remove handler keys off userId, not the
//- user document.
.info
h3 {{_ 'deleted-user'}}
p.quiet {{ userId }}
ul.pop-over-list
unless noRemove
if currentUser.isNotCommentOnly
if currentUser.isNotWorker
li: a.js-remove-member {{_ 'remove-member-from-card'}}
if $eq currentUser._id user._id
with currentUser
li: a.js-edit-profile {{_ 'edit-profile'}}
if user
if $eq currentUser._id user._id
with currentUser
li: a.js-edit-profile {{_ 'edit-profile'}}
+36
View File
@@ -98,6 +98,19 @@ body.mobile-mode .auth-layout .auth-dialog .at-form .password-input-container .p
margin-top: 2vh;
color: #4d4d4d;
}
/* #4394 (security): when registration / forgot-password are disabled in the
Admin Panel, keep the corresponding links hidden even after useraccounts
re-renders the form on a failed login. The class lives on the stable <body>
ancestor (set in layouts.js), so this rule re-hides every recreated copy of
the link — unlike a one-shot inline display:none that the re-render dropped. */
body.registration-disabled .at-signup-link,
body.registration-disabled .at-signUp {
display: none !important;
}
body.forgotpwd-disabled .at-pwd-link,
body.forgotpwd-disabled .at-forgotPwd {
display: none !important;
}
.auth-layout .auth-dialog .at-form .at-signup-link .at-signUp,
.auth-layout .auth-dialog .at-form .at-signin-link .at-signUp,
.auth-layout .auth-dialog .at-form .at-forgotPwd .at-signUp,
@@ -113,6 +126,21 @@ body.mobile-mode .auth-layout .auth-dialog .at-form .password-input-container .p
width: 100%;
margin-top: 10px;
}
/* Auth pages (login / register) must scroll as a normal document on mobile so
the content below the form — the sign-up / sign-in link, legal notice and
language selector — is reachable, including when the on-screen keyboard is
open. body.mobile-mode otherwise locks <body> to position:fixed + 100vh (an
iOS board-view anti-bounce fix); the auth layout has no inner #content
scroller, so that lock traps the page at viewport height and the on-screen
keyboard hides the bottom. .userform-layout is added by userFormsLayout while
these pages are mounted (see layouts.js). */
body.mobile-mode.userform-layout {
position: static !important;
height: auto !important;
min-height: 100vh;
overflow-y: auto !important;
}
@media screen and (max-width: 800px) {
.auth-layout {
width: 100%;
@@ -120,6 +148,14 @@ body.mobile-mode .auth-layout .auth-dialog .at-form .password-input-container .p
margin: 0px;
padding: 0px;
}
/* Let the auth layout/dialog grow to their content instead of being pinned to
the viewport height, so everything below the form scrolls into view. */
body.userform-layout .auth-layout {
height: auto;
}
body.userform-layout .auth-layout .auth-dialog {
height: auto;
}
.auth-layout .at-form-landing-logo {
width: 125px;
position: absolute;
+6 -1
View File
@@ -309,7 +309,12 @@ Template.changeLanguagePopup.events({
'profile.language': this.tag,
},
});
TAPi18n.setLanguage(this.tag);
// setLanguage is async; surface a failed load instead of silently leaving
// the UI in English (#5756).
Promise.resolve(TAPi18n.setLanguage(this.tag)).catch(error => {
// eslint-disable-next-line no-console
console.error(`Failed to switch language to ${this.tag}:`, error);
});
event.preventDefault();
},
});
+21
View File
@@ -29,6 +29,27 @@ import {
calendar,
} from '/imports/lib/dateUtils';
import { Utils } from '/client/lib/utils';
import { isHexColor, contrastText } from '/models/lib/contrastColor';
// #5514: a color (label / list / swimlane / card) may be either a named palette
// color rendered via a CSS class (e.g. `card-label-green`) or a custom
// '#rrggbb' hex chosen from the color wheel. For a hex value there is no CSS
// class, so we emit an inline `background-color` plus an automatically readable
// text `color` (contrastText). These helpers let templates keep the class path
// for named colors and switch to inline styles for hex.
// Class for a label chip: `card-label-<name>` for named colors, empty for hex.
Blaze.registerHelper('labelColorClass', color =>
isHexColor(color) ? '' : `card-label-${color}`,
);
// Inline style for a label chip: background + readable text for a hex color,
// empty for a named color (its CSS class already sets both).
Blaze.registerHelper('labelColorStyle', color =>
isHexColor(color)
? `background-color:${color};color:${contrastText(color)};`
: '',
);
Blaze.registerHelper('currentBoard', () => {
const ret = Utils.getCurrentBoard();
+11 -6
View File
@@ -2,6 +2,7 @@ import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import { ReactiveCache } from '/imports/reactiveCache';
import { getCurrentCardFromContext } from '/client/lib/currentCard';
import { normalizeDigits } from '/imports/lib/dateUtils';
// Helper to check if a date is valid
function isValidDate(date) {
@@ -114,8 +115,9 @@ export function datePickerHelpers() {
export function datePickerEvents({ storeDate, deleteDate }) {
return {
'change .js-date-field'(evt, tpl) {
// Native HTML date input validation
const dateValue = tpl.find('#date').value;
// Native HTML date input validation. Normalize any non-Latin digits
// (e.g. Persian/Arabic-Indic) so parsing works in those locales (#5752).
const dateValue = normalizeDigits(tpl.find('#date').value);
if (dateValue) {
// HTML date input format is always YYYY-MM-DD
const dateObj = new Date(dateValue + 'T12:00:00');
@@ -127,8 +129,9 @@ export function datePickerEvents({ storeDate, deleteDate }) {
}
},
'change .js-time-field'(evt, tpl) {
// Native HTML time input validation
const timeValue = tpl.find('#time').value;
// Native HTML time input validation. Normalize any non-Latin digits
// (e.g. Persian/Arabic-Indic) so parsing works in those locales (#5752).
const timeValue = normalizeDigits(tpl.find('#time').value);
if (timeValue) {
// HTML time input format is always HH:mm
const timeObj = new Date(`1970-01-01T${timeValue}:00`);
@@ -142,8 +145,10 @@ export function datePickerEvents({ storeDate, deleteDate }) {
'submit .edit-date'(evt, tpl) {
evt.preventDefault();
const dateValue = evt.target.date.value;
const timeValue = evt.target.time.value || '12:00'; // Default to 12:00 if no time given
// Normalize any non-Latin digits (e.g. Persian/Arabic-Indic) before
// parsing so due/start/end dates work in those locales (#5752).
const dateValue = normalizeDigits(evt.target.date.value);
const timeValue = normalizeDigits(evt.target.time.value) || '12:00'; // Default to 12:00 if no time given
if (!dateValue) {
tpl.datePicker.error.set('invalid-date');
+19 -29
View File
@@ -19,8 +19,9 @@ import {
now,
createDate,
fromNow,
calendar
calendar
} from '/imports/lib/dateUtils';
import { weekRange } from '/models/lib/weekStart';
// Sidebar is imported late to avoid circular dependency (sidebar.js needs its
// jade template loaded first, but router.js → filter.js would load it too early)
let _Sidebar;
@@ -119,44 +120,33 @@ class DateFilter {
// weeks up to today +/- offset. This considers the user's preferred
// start of week day (as defined by Meteor).
relativeWeek(offset, week) {
if (this._filterState == 'thisweek') {
// #4881: toggle per-week so clicking the same button again clears it, but
// clicking the OTHER week switches to it instead of just resetting (the old
// code reset on either state, so you could not go straight from "this week"
// to "next week").
const targetState = week === 'next' ? 'nextweek' : 'thisweek';
if (this._filterState === targetState) {
this.reset();
return;
}
if (this._filterState == 'nextweek') {
this.reset();
return;
}
// getStartDayOfWeek returns the offset from Sunday of the user's
// preferred starting day of the week. This date should be added
// to the moment start of week to get the real start of week date.
// The default is 1, meaning Monday.
// #4881: the old window came from startOf(now(), 'week'), but native
// dateUtils.startOf() has no 'week' case, so it returned "now" unchanged and
// the window started at today + startDayOfWeek — i.e. it selected NEXT
// week's cards for "this week" and ignored the configured start weekday.
// weekRange() computes the correct window that CONTAINS today for the user's
// configured start day of week (0=Sunday..6=Saturday, default Monday).
const currentUser = ReactiveCache.getCurrentUser();
const weekStartDay = currentUser ? currentUser.getStartDayOfWeek() : 1;
const weekOffset = week === 'next' ? 1 : 0;
const { start: WeekStart, end: WeekEnd } = weekRange(now(), weekStartDay, weekOffset);
if (week === 'this') {
// Create week start and end dates
var WeekStart = startOf(add(startOf(now(), 'week'), weekStartDay, 'days'), 'day');
var WeekEnd = endOf(add(WeekStart, 6, 'days'), 'day');
this._updateState('thisweek');
} else if (week === 'next') {
// Create next week start and end dates
var WeekStart = startOf(add(startOf(now(), 'week'), weekStartDay + 7, 'days'), 'day');
var WeekEnd = endOf(add(WeekStart, 6, 'days'), 'day');
this._updateState('nextweek');
}
var startDate = WeekStart;
var endDate = WeekEnd;
this._updateState(targetState);
if (offset >= 0) {
this._filter = { $gte: startDate, $lte: endDate };
this._filter = { $gte: WeekStart, $lte: WeekEnd };
} else {
this._filter = { $lte: startDate, $gte: endDate };
this._filter = { $lte: WeekStart, $gte: WeekEnd };
}
}
+32 -14
View File
@@ -1,27 +1,45 @@
import dragscroll from '@wekanteam/dragscroll';
import '/client/lib/dragscrollTouch';
// Enable / disable mouse drag-to-scroll for whole-page layouts.
// Enable / disable mouse drag-to-scroll for whole-page layouts (All Boards,
// My Cards, Login, Register).
//
// On pages like All Boards, Login and Register the scroll container is the
// <body> element itself (it has `overflow-y: auto; height: 100vh`), not an
// inner board canvas or swimlane. The dragscroll library scrolls whichever
// element carries the `dragscroll` class and has a dedicated `el ==
// document.body` branch that also scrolls document.documentElement, so tagging
// <body> makes dragging empty page space scroll the page — the same gesture
// that already works on the board swimlanes view.
// The dragscroll library scrolls whichever element carries the `dragscroll`
// class AND is the actual scroll container. Which element that is depends on
// the layout and on mobile vs desktop mode:
//
// The class is toggled per template (onRendered / onDestroyed) so it only
// applies on those pages; dragscroll.reset() re-scans the DOM and (re)binds the
// mouse listeners after the class is added or removed. Touch (one-finger)
// scrolling is handled separately by client/lib/dragscrollTouch.js.
// * Login / Register (userFormsLayout) have no `#content`; the page scroll
// container is `<body>` in both modes (it has `overflow-y: auto` and a
// `100vh` height).
// * All Boards / My Cards (defaultLayout) render inside `#content`. On
// desktop the scroll container is `<body>`; in mobile mode `<body>` becomes
// `position: fixed` and `#content` becomes the scroller
// (`overflow-y: auto; height: calc(100vh - 48px)`), so tagging only
// `<body>` would never scroll on a phone.
//
// To cover every case we tag BOTH `<body>` and `#content` (when present). Only
// the element that actually overflows scrolls, so there is no double-scroll:
// on desktop `<body>` scrolls and `#content` (no vertical overflow) is a no-op;
// in mobile mode `#content` scrolls and `<body>` (fixed, no overflow) is a
// no-op. dragscroll has a dedicated `el == document.body` branch for the body
// case. dragscroll.reset() re-scans the DOM and (re)binds the mouse listeners.
// One-finger touch scrolling is handled by client/lib/dragscrollTouch.js, which
// picks the nearest scrollable `.dragscroll` ancestor (so `#content` wins over
// `<body>` when both are tagged).
function pageScrollEls() {
const els = [document.body];
const content = document.getElementById('content');
if (content) els.push(content);
return els;
}
export function enablePageDragscroll() {
document.body.classList.add('dragscroll');
pageScrollEls().forEach(el => el.classList.add('dragscroll'));
dragscroll.reset();
}
export function disablePageDragscroll() {
document.body.classList.remove('dragscroll');
pageScrollEls().forEach(el => el.classList.remove('dragscroll'));
dragscroll.reset();
}
+27 -101
View File
@@ -4,6 +4,7 @@ import { Tracker } from 'meteor/tracker';
import { TAPi18n } from '/imports/i18n';
import { EscapeActions } from '/client/lib/escapeActions';
import { Utils } from '/client/lib/utils';
import { computePopupOffset } from '/client/lib/popupOffset';
window.Popup = new (class {
constructor() {
@@ -222,108 +223,33 @@ window.Popup = new (class {
return () => {
Utils.windowResizeDep.depend();
if (Utils.isMiniScreen()) return { left: 0, top: 0 };
// #5667: read the current page scroll and the opener's document offset,
// then delegate the geometry to the pure, unit-tested computePopupOffset.
// It lays the popup out in VIEWPORT coordinates — so a date-picker opened
// low on a scrolled page stays fully visible instead of extending past the
// visible area — and returns document coordinates for the absolute style.
const hasOpener = !!($element && $element.length);
const offset = hasOpener ? $element.offset() : null;
const opener = hasOpener
? { top: offset.top, left: offset.left, height: $element.outerHeight() }
: null;
const viewportWidth = $(window).width();
const viewportHeight = $(window).height();
const viewportPadding = 10;
// Calculate actual popup width based on CSS: min(380px, 55vw)
const popupWidth = Math.min(380, viewportWidth * 0.55);
// Card details popup: dock it to the top of the viewport so it overlays
// the global and board header bars (its z-index already sits above them)
// instead of opening downward from the clicked minicard.
if (popupName === 'cardDetailsPopup') {
const cardWidth = Math.min(600, viewportWidth * 0.8);
const centeredLeft = (viewportWidth - cardWidth) / 2;
// top is forced to 0 in CSS (.pop-over[data-popup="cardDetailsPopup"])
// so the card overlays the header bars; use the full viewport height.
return {
left: Math.max(viewportPadding, centeredLeft),
top: 0,
maxHeight: viewportHeight,
};
}
// If the opener element is missing (e.g., programmatic open), fallback to viewport origin
if (!$element || $element.length === 0) {
return {
left: viewportPadding,
top: viewportPadding,
maxHeight: viewportHeight - viewportPadding * 2,
};
}
const offset = $element.offset();
const openerTop = offset.top;
const openerBottom = offset.top + $element.outerHeight();
const clampedLeft = Math.max(
viewportPadding,
Math.min(offset.left, viewportWidth - popupWidth - viewportPadding),
);
// Check if this is an admin panel edit popup
const isAdminEditPopup = $element.hasClass('edit-user') ||
$element.hasClass('edit-org') ||
$element.hasClass('edit-team');
if (isAdminEditPopup) {
// Center the popup horizontally and use full height
const centeredLeft = (viewportWidth - popupWidth) / 2;
return {
left: Math.max(viewportPadding, centeredLeft), // Ensure popup doesn't go off screen
top: viewportPadding, // Start from top with small margin
maxHeight: viewportHeight - viewportPadding * 2, // Use full height minus small margins
};
}
// Calculate available heights around the opener
const spaceBelow = viewportHeight - openerBottom - viewportPadding;
const spaceAbove = openerTop - viewportPadding;
const preferBelow = spaceBelow >= spaceAbove;
// For language popup, don't use dynamic height to avoid overlapping board
const isLanguagePopup = $element.hasClass('js-change-language');
let availableHeight;
if (isLanguagePopup) {
// For language popup, position content area below right vertical scrollbar
const languageTop = openerBottom;
const languageAvailable = Math.max(
0,
viewportHeight - languageTop - viewportPadding,
);
const calculatedHeight = Math.min(
languageAvailable,
viewportHeight * 0.5,
); // Max 50% of viewport
return {
left: clampedLeft,
top: languageTop,
maxHeight: Math.max(calculatedHeight, 0),
};
} else {
// For other popups, choose the side with more space and keep popup fully in viewport.
if (preferBelow) {
availableHeight = Math.max(0, Math.min(spaceBelow, viewportHeight * 0.8));
return {
left: clampedLeft,
top: openerBottom,
maxHeight: availableHeight,
};
}
availableHeight = Math.max(0, Math.min(spaceAbove, viewportHeight * 0.8));
const top = Math.max(viewportPadding, openerTop - availableHeight);
return {
left: clampedLeft,
top,
maxHeight: availableHeight,
};
}
return computePopupOffset({
viewportWidth: $(window).width(),
viewportHeight: $(window).height(),
scrollTop: $(window).scrollTop() || 0,
scrollLeft: $(window).scrollLeft() || 0,
opener,
popupName,
isMiniScreen: Utils.isMiniScreen(),
isAdminEditPopup:
hasOpener &&
($element.hasClass('edit-user') ||
$element.hasClass('edit-org') ||
$element.hasClass('edit-team')),
isLanguagePopup: hasOpener && $element.hasClass('js-change-language'),
viewportPadding: 10,
});
};
}
+118
View File
@@ -0,0 +1,118 @@
'use strict';
// Pure geometry for Popup._getOffset — extracted so the positioning math is
// unit-testable without a DOM / Meteor (see tests/popupOffset.test.cjs).
//
// #5667 ("Problem when scrolling in a map with date fields"): a date-picker
// calendar (due/start/end date, or a date custom field) opened low on the screen
// appeared partly outside the visible area, and — because the pop-over is
// `position: absolute` (document coordinates) — trying to scroll it into view
// moved it along with the page, so the calendar could never be seen fully.
//
// Root cause: _getOffset computed the space above / below the opener (and the
// clamped `top`) from the opener's DOCUMENT offset (`$el.offset()`) mixed with
// the VIEWPORT height, ignoring the page scroll. On a scrolled page that placed
// the anchored popup outside the visible viewport.
//
// Fix: do the space / clamp math in VIEWPORT coordinates (subtract
// scrollTop/scrollLeft), then convert the chosen top/left back to DOCUMENT
// coordinates (add the scroll back) for the absolute style. The popup box is then
// always laid out fully within the visible viewport. When the page is not
// scrolled (scrollTop === scrollLeft === 0) the output is identical to the
// previous behaviour, so nothing regresses in the common case.
function computePopupOffset(params) {
const {
viewportWidth,
viewportHeight,
scrollTop = 0,
scrollLeft = 0,
// Opener document offset { top, left, height }, or null for a programmatic
// open with no reference element.
opener = null,
popupName,
isMiniScreen = false,
isAdminEditPopup = false,
isLanguagePopup = false,
viewportPadding = 10,
} = params;
if (isMiniScreen) return { left: 0, top: 0 };
// Actual popup width from CSS: min(380px, 55vw).
const popupWidth = Math.min(380, viewportWidth * 0.55);
// Card details popup: docked to the top of the viewport (CSS also forces
// top:0) so it overlays the header bars instead of opening from the minicard.
if (popupName === 'cardDetailsPopup') {
const cardWidth = Math.min(600, viewportWidth * 0.8);
const centeredLeft = (viewportWidth - cardWidth) / 2;
return {
left: Math.max(viewportPadding, centeredLeft),
top: scrollTop, // viewport top in document coords
maxHeight: viewportHeight,
};
}
// No opener element: fall back to the top-left of the VISIBLE viewport.
if (!opener) {
return {
left: viewportPadding + scrollLeft,
top: viewportPadding + scrollTop,
maxHeight: viewportHeight - viewportPadding * 2,
};
}
// Opener position in viewport coordinates (this is the #5667 fix: the previous
// code used the raw document offset here).
const openerTopVp = opener.top - scrollTop;
const openerBottomVp = openerTopVp + opener.height;
const openerLeftVp = opener.left - scrollLeft;
const clampedLeftVp = Math.max(
viewportPadding,
Math.min(openerLeftVp, viewportWidth - popupWidth - viewportPadding),
);
const clampedLeft = clampedLeftVp + scrollLeft;
// Admin panel edit popups: horizontally centered, anchored to the viewport top.
if (isAdminEditPopup) {
const centeredLeft = (viewportWidth - popupWidth) / 2;
return {
left: Math.max(viewportPadding, centeredLeft),
top: viewportPadding + scrollTop,
maxHeight: viewportHeight - viewportPadding * 2,
};
}
const spaceBelow = viewportHeight - openerBottomVp - viewportPadding;
const spaceAbove = openerTopVp - viewportPadding;
const preferBelow = spaceBelow >= spaceAbove;
// Language popup: fixed-ish height below the opener, capped at 50% viewport.
if (isLanguagePopup) {
const languageTopVp = openerBottomVp;
const languageAvailable = Math.max(
0,
viewportHeight - languageTopVp - viewportPadding,
);
const calculatedHeight = Math.min(languageAvailable, viewportHeight * 0.5);
return {
left: clampedLeft,
top: languageTopVp + scrollTop,
maxHeight: Math.max(calculatedHeight, 0),
};
}
// Other popups: open on the side with more room and stay fully in the viewport.
if (preferBelow) {
const maxHeight = Math.max(0, Math.min(spaceBelow, viewportHeight * 0.8));
return { left: clampedLeft, top: openerBottomVp + scrollTop, maxHeight };
}
const maxHeight = Math.max(0, Math.min(spaceAbove, viewportHeight * 0.8));
const topVp = Math.max(viewportPadding, openerTopVp - maxHeight);
return { left: clampedLeft, top: topVp + scrollTop, maxHeight };
}
module.exports = { computePopupOffset };
+2
View File
@@ -1,3 +1,5 @@
// i18n (TAPi18n) tests, run on the client as the old tests/main.js did.
import '/imports/i18n/i18n.test.js';
import './Utils.tests';
import './filter.dependencies.tests';
import './dueDateColor.tests';
+16 -6
View File
@@ -4,16 +4,26 @@ import { Tracker } from 'meteor/tracker';
import { findWhere, where, uniqBy, groupBy, indexBy, debounce, once } from '/imports/lib/collectionHelpers';
import Settings from '/models/settings';
import Users from '/models/users';
import { computeBoardBackground } from '/models/lib/boardBackground';
export const Utils = {
async setBackgroundImage(url) {
const currentBoard = Utils.getCurrentBoard();
if (currentBoard.backgroundImageURL !== undefined) {
$(".board-wrapper").css({"background":"url(" + currentBoard.backgroundImageURL + ")","background-size":"cover"});
$(".swimlane,.swimlane .list,.swimlane .list .list-body,.swimlane .list:first-child .list-body").css({"background-color":"transparent"});
$(".minicard").css({"opacity": "0.9"});
} else if (currentBoard["background-color"]) {
await currentBoard.setColor(currentBoard["background-color"]);
const bg = computeBoardBackground(currentBoard);
const swimlaneSelector =
".swimlane,.swimlane .list,.swimlane .list .list-body,.swimlane .list:first-child .list-body";
if (bg.type === "image") {
$(".board-wrapper").css({ "background": "url(" + bg.url + ")", "background-size": "cover" });
$(swimlaneSelector).css({ "background-color": "transparent" });
$(".minicard").css({ "opacity": "0.9" });
} else {
// #4978: clear any inline background left over from a previously shown
// board, so switching image -> color or image -> plain (e.g. via the
// favorites bar) actually removes the old image. The board's color, when
// any, is applied reactively via the `.board-wrapper` colorClass.
$(".board-wrapper").css({ "background": "", "background-size": "" });
$(swimlaneSelector).css({ "background-color": "" });
$(".minicard").css({ "opacity": "" });
}
},
/** returns the current board id
+114 -11
View File
@@ -1,9 +1,28 @@
import { TAPi18n } from '/imports/i18n';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
// Readiness-aware, reactive translation — mirrors the `{{_}}` Blaze helper
// (imports/i18n/blaze.js). A bare TAPi18n.__() returns the raw key on the initial
// (English) render because the language bundle loads asynchronously, and it never
// re-runs once ready. Depending on TAPi18n.current / TAPi18n.ready makes the
// account-form labels/placeholders re-render as soon as i18n is loaded and on every
// language change.
function tr(key) {
TAPi18n.current.get();
TAPi18n.ready.get();
if (!TAPi18n.i18n || !TAPi18n.ready.get()) {
return key;
}
return TAPi18n.__(key);
}
const passwordField = AccountsTemplates.removeField('password');
passwordField.autocomplete = 'current-password';
passwordField.template = 'passwordInput';
// Translate the field label/placeholder via WeKan i18n — see the note below the
// addFields() call. Set before re-adding the field so it is present when useraccounts
// registers it.
passwordField.displayName = () => tr('password');
const emailField = AccountsTemplates.removeField('email');
// Don't add current_password to global fields - it should only be used for change password
@@ -75,7 +94,7 @@ AccountsTemplates.addFields([
{
_id: 'password_again',
type: 'password',
displayName: 'Password (again)',
displayName: () => tr('password-again'),
required: true,
minLength: 6,
autocomplete: 'new-password',
@@ -91,6 +110,57 @@ AccountsTemplates.addFields([
},
]);
// The sign-in / register form field labels and placeholders were rendered through
// accounts-t9n (`T9n.get`), whose per-language coverage is incomplete, and
// useraccounts' `getPlaceholder()` never invokes a function placeholder (unlike
// `getDisplayName()`) — so several placeholders (Username, Email, Password
// (again)) and the Email / Password (again) labels showed untranslated English.
// Translate the account-form fields through WeKan's own i18n (TAPi18n) instead,
// which has complete data/<lang>.i18n.json coverage.
//
// Password fields (password, password_again) use WeKan's passwordInput template,
// which renders {{displayName}} for both its label and its placeholder. Their
// field-level displayName functions (set above / in addFields) are invoked both by
// getDisplayName() and by Blaze, so they translate regardless of when useraccounts
// wires up per-template helpers. Username / email use the atTextInput override below.
if (Meteor.isClient) {
const { Template } = require('meteor/templating');
const { T9n } = require('meteor/communitypackages:core');
// Fields WeKan translates itself (complete data/<lang>.i18n.json coverage), keyed
// by field id. Other account-form text inputs — notably the sign-in combined
// "username_and_email" field and current_password — keep their built-in
// accounts-t9n translation, because WeKan has no key for them and accounts-t9n
// already ships one (e.g. "usernameOrEmail").
const WEKAN_FIELD_KEY = { username: 'username', email: 'email' };
function fieldText(field, original) {
TAPi18n.current.get();
TAPi18n.ready.get();
const key = WEKAN_FIELD_KEY[field._id];
if (key && TAPi18n.i18n && TAPi18n.ready.get()) {
const translated = TAPi18n.__(key);
if (translated !== key) {
return translated;
}
}
// Fall back to useraccounts' own accounts-t9n translation. The field's
// displayName/placeholder is an accounts-t9n key (e.g. "usernameOrEmail").
return T9n.get(original, false);
}
// The unstyled package registers these helpers on Template.atTextInput at load
// time, so this app-startup override wins.
Template.atTextInput.helpers({
displayName() {
return fieldText(this, this.getDisplayName());
},
placeholder() {
if (!AccountsTemplates.options.showPlaceholders) {
return undefined;
}
return fieldText(this, this.getPlaceholder());
},
});
}
AccountsTemplates.configure({
defaultLayout: 'userFormsLayout',
defaultContentRegion: 'content',
@@ -191,6 +261,16 @@ AccountsTemplates.configureRoute('changePwd', {
});
if (Meteor.isServer) {
// #5706: Internal Server Error (500) when attempting to reset a password.
// The reset-password email-template builders and the email send must never let
// an unhandled exception bubble up through Meteor's `forgotPassword` method (it
// surfaces to the client as an opaque HTTP 500). The pure helpers below are
// unit-tested in tests/unit/resetPasswordEmail.test.js.
const {
buildEmailTemplateField,
wrapSendResetPasswordEmail,
} = require('/server/lib/resetPasswordEmail');
[
'resetPassword-subject',
'resetPassword-text',
@@ -200,16 +280,39 @@ if (Meteor.isServer) {
'enrollAccount-text',
].forEach(str => {
const [templateName, field] = str.split('-');
Accounts.emailTemplates[templateName][field] = (user, url) => {
return TAPi18n.__(
`email-${str}`,
{
url,
user: user.getName(),
siteName: Accounts.emailTemplates.siteName,
},
user.getLanguage(),
Accounts.emailTemplates[templateName][field] = (user, url) =>
buildEmailTemplateField(
str,
(key, params, language) => TAPi18n.__(key, params, language),
Accounts.emailTemplates.siteName,
user,
url,
);
};
});
// #5706: When SMTP is not configured (no MAIL_URL / MAIL_FROM, or a bad mail
// server), Meteor's `Email.sendAsync` throws. That exception otherwise propagates
// out of the `forgotPassword` method as a raw HTTP 500 ("Internal Server Error").
// Wrap the reset-password email send so the failure surfaces as a clean,
// actionable `Meteor.Error('email-fail', ...)` instead, matching how the rest of
// Wekan reports email failures (see server/models/users.js, settings.js).
Accounts.sendResetPasswordEmail = wrapSendResetPasswordEmail(
Accounts.sendResetPasswordEmail,
(code, message) => new Meteor.Error(code, message),
);
// #5672: "Internal Server Error When Signing Up Despite Successful Account
// Creation". useraccounts' ATCreateUserServer creates the account and then
// calls Accounts.sendVerificationEmail(); when SMTP is not configured that
// send throws AFTER the user is inserted, so the createUser method returns a
// raw HTTP 500 even though the account exists and can sign in. The
// verification email is best-effort at sign-up, so swallow + log a transport
// failure (registration then completes and redirects to sign-in). An
// "already verified" error is re-thrown so ATResendVerificationEmail still
// reports it. Unit-tested in tests/verificationEmail.test.cjs.
const { wrapSendVerificationEmail } = require('/server/lib/verificationEmail');
Accounts.sendVerificationEmail = wrapSendVerificationEmail(
Accounts.sendVerificationEmail,
message => console.warn(message),
);
}
+2 -2
View File
@@ -10,7 +10,7 @@ This is without container (without Docker or Snap).
Right click and download files 1-4:
1. [wekan-9.69-win64.zip](https://github.com/wekan/wekan/releases/download/v9.69/wekan-9.69-win64.zip) or newest available at https://github.com/wekan/wekan/releases/
1. [wekan-9.83-win64.zip](https://github.com/wekan/wekan/releases/download/v9.83/wekan-9.83-win64.zip) or newest available at https://github.com/wekan/wekan/releases/
2. [node.exe](https://nodejs.org/dist/latest-v24.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-9.69-win64.zip` , inside it is directory `bundle`, to it copy other files:
7. Unzip `wekan-9.83-win64.zip` , inside it is directory `bundle`, to it copy other files:
```
bundle (directory)
+16
View File
@@ -0,0 +1,16 @@
# Reset previous attempts
sudo flatpak override --reset com.visualstudio.code
# Block EVERYTHING except the display and the wekan folder
sudo flatpak override com.visualstudio.code \
--nofilesystem=home \
--nofilesystem=host \
--nofilesystem=xdg-run/gvfs \
--nofilesystem=xdg-run/gvfsd \
--filesystem=~/repos/wekan:rw \
--filesystem=~/repos/log:rw \
--filesystem=~/repos/w/wekan.fi:rw \
--device=all \
--socket=wayland \
--socket=x11 \
--share=network
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "Menerima",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "Pintasan papan kekunci didayakan. Klik untuk batal.",
"keyboard-shortcuts-disabled": "Pintasan papan kekunci didayakan. Klik untuk batal.",
"setSwimlaneHeightPopup-title": "Atur Tinggi Swimlane ",
@@ -437,6 +442,7 @@
"createCustomField": "Cipta ruangan",
"createCustomFieldPopup-title": "Cipta ruangan",
"current": "sekarang",
"custom-color": "Custom color",
"custom-field-delete-pop": "Tidak boleh undur semula. Ianya akan menghapuskan ruangan khas daripada semua kad dan padamkan sejarahnya.",
"custom-field-checkbox": "kotak semak",
"custom-field-currency": "Matawang",
@@ -504,6 +510,8 @@
"error-csv-schema": "CSV anda(Nilai yang dipisahkan koma)/TSV (Nilai yang dipisahkan tab) tidak termasuk informasi yang tepat dalam format yang benar",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "Daftar ini tidak ada",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "Nama pengguna ini tidak ada",
"error-user-notAllowSelf": "Anda tidak bisa mengundang diri sendiri",
"error-user-notCreated": "Nama pengguna ini tidak dibuat",
@@ -576,6 +584,7 @@
"fullname": "Nama Lengkap",
"header-logo-title": "Kembali ke laman panel anda",
"show-activities": "Tunjukkan Aktiviti",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "Buat Panel",
"home": "Beranda",
"import": "Impor",
@@ -742,6 +751,7 @@
"remove-label": "Hapus Label",
"listDeletePopup-title": "Hapus Daftar ?",
"remove-member": "Hapus Anggota",
"deleted-user": "Deleted user",
"remove-member-from-card": "Hapus dari Kartu",
"remove-member-pop": "Hapus__nama__(__username__) dari __boardTitle__? Partisipan akan dihapus dari semua kartu di panel ini. Mereka akan diberi tahu",
"removeMemberPopup-title": "Hapus Anggota?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "Gambar sampul untuk kartu kecil",
"badge-attachment-on-minicard": "Menghitung lampiran pada kartu kecil",
"card-sorting-by-number-on-minicard": "Kartu disaring dengan nomor pada kartu kecil",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "rintisan dengan prefix",
"prefix-with-parent": "Induk dnegan prefix",
"subtext-with-full-path": "Subtext dengan rintisan lengkap",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "Picu",
"r-action": "Tindakan",
"r-when-a-card": "Ketika kartu",
@@ -1441,6 +1472,11 @@
"label-names": "Label Nama",
"archived-at": "diarkibkan pada",
"sort-cards": "Susun Kad",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "Urutan aktif",
"cardsSortPopup-title": "Susun Kad",
"due-date": "Tarikh Akhir",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "Aanvaar",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "Sleutelbordkortpaaie geaktiveer. Klik om te deaktiveer.",
"keyboard-shortcuts-disabled": "Sleutelbordkortpaaie gedeaktiveer. Klik om te aktiveer.",
"setSwimlaneHeightPopup-title": "Stel swembaanhoogte in",
@@ -437,6 +442,7 @@
"createCustomField": "Skep veld",
"createCustomFieldPopup-title": "Skep veld",
"current": "huidige",
"custom-color": "Custom color",
"custom-field-delete-pop": "Daar is geen ongedaan maak nie. Dit sal hierdie pasgemaakte veld van alle kaarte verwyder en sy geskiedenis vernietig.",
"custom-field-checkbox": "Merkblokkie",
"custom-field-currency": "Geldeenheid",
@@ -504,6 +510,8 @@
"error-csv-schema": "Jou CSV (Comma Separated Values)/TSV (Tab Separated Values) sluit nie die korrekte inligting in die korrekte formaat in nie",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "Hierdie lys bestaan ​​nie",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "Hierdie gebruiker bestaan ​​nie",
"error-user-notAllowSelf": "Jy kan nie jouself nooi nie",
"error-user-notCreated": "Hierdie gebruiker is nie geskep nie",
@@ -576,6 +584,7 @@
"fullname": "Volle Naam",
"header-logo-title": "Gaan terug na jou bordbladsy.",
"show-activities": "Wys aktiwiteite",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "Skep bord",
"home": "Tuis",
"import": "Invoer",
@@ -742,6 +751,7 @@
"remove-label": "Verwyder etiket",
"listDeletePopup-title": "Vee lys uit?",
"remove-member": "Verwyder lid",
"deleted-user": "Deleted user",
"remove-member-from-card": "Verwyder van kaart",
"remove-member-pop": "Verwyder __name__ (__username__) van __boardTitle__? Die lid sal van alle kaarte op hierdie bord verwyder word. Hulle sal 'n kennisgewing ontvang.",
"removeMemberPopup-title": "Verwyder lid?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "Omslagprent op minikaart",
"badge-attachment-on-minicard": "Telling van aanhegsels op minikaart",
"card-sorting-by-number-on-minicard": "Kaart sorteer volgens nommer op minikaart",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "Voorvoegsel met volle pad",
"prefix-with-parent": "Voorvoegsel met ouer",
"subtext-with-full-path": "Subteks met volle pad",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "Sneller",
"r-action": "Aksie",
"r-when-a-card": "Wanneer 'n kaart",
@@ -1441,6 +1472,11 @@
"label-names": "Etiket Name",
"archived-at": "geargiveer by",
"sort-cards": "Sorteer kaarte",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "Sorteer is aan",
"cardsSortPopup-title": "Sorteer kaarte",
"due-date": "Sperdatum",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "Aanvaar",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "Sleutelbordkortpaaie geaktiveer. Klik om te deaktiveer.",
"keyboard-shortcuts-disabled": "Sleutelbordkortpaaie gedeaktiveer. Klik om te aktiveer.",
"setSwimlaneHeightPopup-title": "Stel swembaanhoogte in",
@@ -437,6 +442,7 @@
"createCustomField": "Skep veld",
"createCustomFieldPopup-title": "Skep veld",
"current": "huidige",
"custom-color": "Custom color",
"custom-field-delete-pop": "Daar is geen ongedaan maak nie. Dit sal hierdie pasgemaakte veld van alle kaarte verwyder en sy geskiedenis vernietig.",
"custom-field-checkbox": "Merkblokkie",
"custom-field-currency": "Geldeenheid",
@@ -504,6 +510,8 @@
"error-csv-schema": "Jou CSV (Comma Separated Values)/TSV (Tab Separated Values) sluit nie die korrekte inligting in die korrekte formaat in nie",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "Hierdie lys bestaan ​​nie",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "Hierdie gebruiker bestaan ​​nie",
"error-user-notAllowSelf": "Jy kan nie jouself nooi nie",
"error-user-notCreated": "Hierdie gebruiker is nie geskep nie",
@@ -576,6 +584,7 @@
"fullname": "Volle Naam",
"header-logo-title": "Gaan terug na jou bordbladsy.",
"show-activities": "Wys aktiwiteite",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "Skep bord",
"home": "Tuis",
"import": "Invoer",
@@ -742,6 +751,7 @@
"remove-label": "Verwyder etiket",
"listDeletePopup-title": "Vee lys uit?",
"remove-member": "Verwyder lid",
"deleted-user": "Deleted user",
"remove-member-from-card": "Verwyder van kaart",
"remove-member-pop": "Verwyder __name__ (__username__) van __boardTitle__? Die lid sal van alle kaarte op hierdie bord verwyder word. Hulle sal 'n kennisgewing ontvang.",
"removeMemberPopup-title": "Verwyder lid?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "Omslagprent op minikaart",
"badge-attachment-on-minicard": "Telling van aanhegsels op minikaart",
"card-sorting-by-number-on-minicard": "Kaart sorteer volgens nommer op minikaart",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "Voorvoegsel met volle pad",
"prefix-with-parent": "Voorvoegsel met ouer",
"subtext-with-full-path": "Subteks met volle pad",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "Sneller",
"r-action": "Aksie",
"r-when-a-card": "Wanneer 'n kaart",
@@ -1441,6 +1472,11 @@
"label-names": "Etiket Name",
"archived-at": "geargiveer by",
"sort-cards": "Sorteer kaarte",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "Sorteer is aan",
"cardsSortPopup-title": "Sorteer kaarte",
"due-date": "Sperdatum",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "قبول",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "تم تفعيل اختصارات لوحة المفاتيح. انقر لتعطيلها.",
"keyboard-shortcuts-disabled": "اختصارات لوحة المفاتيح معطلة. انقر للتفعيل.",
"setSwimlaneHeightPopup-title": "ضبط ارتفاع مسار السباحة",
@@ -437,6 +442,7 @@
"createCustomField": "انشاء حقل",
"createCustomFieldPopup-title": "انشاء حقل",
"current": "الحالي",
"custom-color": "Custom color",
"custom-field-delete-pop": "این اقدام فیلد سفارشی را بهمراه تمامی تاریخچه آن از کارت ها پاک می کند و برگشت پذیر نمی باشد",
"custom-field-checkbox": "جعبه انتخابی",
"custom-field-currency": "عملة",
@@ -504,6 +510,8 @@
"error-csv-schema": "لا يتضمن ملف CSV (القيم المفصولة بفواصل)/TSV (القيم المفصولة بعلامات التبويب) الخاص بك المعلومات الصحيحة بالتنسيق الصحيح",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "این لیست موجود نیست",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "این کاربر وجود ندارد",
"error-user-notAllowSelf": "لا يمكنك دعوة نفسك",
"error-user-notCreated": "این کاربر ایجاد نشده است",
@@ -576,6 +584,7 @@
"fullname": "الإسم الكامل",
"header-logo-title": "الرجوع إلى صفحة اللوحات",
"show-activities": "عرض الأنشطة",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "إنشاء لوحة",
"home": "الرئيسية",
"import": "وارد کردن",
@@ -742,6 +751,7 @@
"remove-label": "إزالة التصنيف",
"listDeletePopup-title": "حذف القائمة ؟",
"remove-member": "حذف العضو",
"deleted-user": "Deleted user",
"remove-member-from-card": "حذف من البطاقة",
"remove-member-pop": "حذف __name__ (__username__) من __boardTitle__ ? سيتم حذف هذا العضو من جميع بطاقة اللوحة مع إرسال إشعار له بذاك.",
"removeMemberPopup-title": "حذف العضو ?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "صورة الغلاف على البطاقة الصغيرة",
"badge-attachment-on-minicard": "عدد المرفقات على البطاقة الصغيرة",
"card-sorting-by-number-on-minicard": "رز البطاقات حسب الرقم على البطاقة الصغيرة",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "پیشوند با مسیر کامل",
"prefix-with-parent": "پیشوند با خانواده",
"subtext-with-full-path": "زیرنویس با مسیر کامل",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "تریگر",
"r-action": "عملیات",
"r-when-a-card": "زمانی که کارت",
@@ -1441,6 +1472,11 @@
"label-names": "اسامی لیبل",
"archived-at": "بایگانی شده در",
"sort-cards": "مرتب‌سازی کارت‌ها",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "الفرز قيد التشغيل",
"cardsSortPopup-title": "مرتب‌سازی کارت‌ها",
"due-date": "تاریخ اجرا",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "قبول",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "تم تفعيل اختصارات لوحة المفاتيح. انقر لتعطيلها.",
"keyboard-shortcuts-disabled": "اختصارات لوحة المفاتيح معطلة. انقر للتفعيل.",
"setSwimlaneHeightPopup-title": "ضبط ارتفاع مسار السباحة",
@@ -437,6 +442,7 @@
"createCustomField": "انشاء حقل",
"createCustomFieldPopup-title": "انشاء حقل",
"current": "الحالي",
"custom-color": "Custom color",
"custom-field-delete-pop": "این اقدام فیلد سفارشی را بهمراه تمامی تاریخچه آن از کارت ها پاک می کند و برگشت پذیر نمی باشد",
"custom-field-checkbox": "جعبه انتخابی",
"custom-field-currency": "عملة",
@@ -504,6 +510,8 @@
"error-csv-schema": "لا يتضمن ملف CSV (القيم المفصولة بفواصل)/TSV (القيم المفصولة بعلامات التبويب) الخاص بك المعلومات الصحيحة بالتنسيق الصحيح",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "این لیست موجود نیست",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "این کاربر وجود ندارد",
"error-user-notAllowSelf": "لا يمكنك دعوة نفسك",
"error-user-notCreated": "این کاربر ایجاد نشده است",
@@ -576,6 +584,7 @@
"fullname": "الإسم الكامل",
"header-logo-title": "الرجوع إلى صفحة اللوحات",
"show-activities": "عرض الأنشطة",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "إنشاء لوحة",
"home": "الرئيسية",
"import": "وارد کردن",
@@ -742,6 +751,7 @@
"remove-label": "إزالة التصنيف",
"listDeletePopup-title": "حذف القائمة ؟",
"remove-member": "حذف العضو",
"deleted-user": "Deleted user",
"remove-member-from-card": "حذف من البطاقة",
"remove-member-pop": "حذف __name__ (__username__) من __boardTitle__ ? سيتم حذف هذا العضو من جميع بطاقة اللوحة مع إرسال إشعار له بذاك.",
"removeMemberPopup-title": "حذف العضو ?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "صورة الغلاف على البطاقة الصغيرة",
"badge-attachment-on-minicard": "عدد المرفقات على البطاقة الصغيرة",
"card-sorting-by-number-on-minicard": "رز البطاقات حسب الرقم على البطاقة الصغيرة",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "پیشوند با مسیر کامل",
"prefix-with-parent": "پیشوند با خانواده",
"subtext-with-full-path": "زیرنویس با مسیر کامل",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "تریگر",
"r-action": "عملیات",
"r-when-a-card": "زمانی که کارت",
@@ -1441,6 +1472,11 @@
"label-names": "اسامی لیبل",
"archived-at": "بایگانی شده در",
"sort-cards": "مرتب‌سازی کارت‌ها",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "الفرز قيد التشغيل",
"cardsSortPopup-title": "مرتب‌سازی کارت‌ها",
"due-date": "تاریخ اجرا",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "قبول",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "تم تفعيل اختصارات لوحة المفاتيح. انقر لتعطيلها.",
"keyboard-shortcuts-disabled": "اختصارات لوحة المفاتيح معطلة. انقر للتفعيل.",
"setSwimlaneHeightPopup-title": "ضبط ارتفاع مسار السباحة",
@@ -437,6 +442,7 @@
"createCustomField": "انشاء حقل",
"createCustomFieldPopup-title": "انشاء حقل",
"current": "الحالي",
"custom-color": "Custom color",
"custom-field-delete-pop": "این اقدام فیلد سفارشی را بهمراه تمامی تاریخچه آن از کارت ها پاک می کند و برگشت پذیر نمی باشد",
"custom-field-checkbox": "جعبه انتخابی",
"custom-field-currency": "عملة",
@@ -504,6 +510,8 @@
"error-csv-schema": "لا يتضمن ملف CSV (القيم المفصولة بفواصل)/TSV (القيم المفصولة بعلامات التبويب) الخاص بك المعلومات الصحيحة بالتنسيق الصحيح",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "این لیست موجود نیست",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "این کاربر وجود ندارد",
"error-user-notAllowSelf": "لا يمكنك دعوة نفسك",
"error-user-notCreated": "این کاربر ایجاد نشده است",
@@ -576,6 +584,7 @@
"fullname": "الإسم الكامل",
"header-logo-title": "الرجوع إلى صفحة اللوحات",
"show-activities": "عرض الأنشطة",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "إنشاء لوحة",
"home": "الرئيسية",
"import": "وارد کردن",
@@ -742,6 +751,7 @@
"remove-label": "إزالة التصنيف",
"listDeletePopup-title": "حذف القائمة ؟",
"remove-member": "حذف العضو",
"deleted-user": "Deleted user",
"remove-member-from-card": "حذف من البطاقة",
"remove-member-pop": "حذف __name__ (__username__) من __boardTitle__ ? سيتم حذف هذا العضو من جميع بطاقة اللوحة مع إرسال إشعار له بذاك.",
"removeMemberPopup-title": "حذف العضو ?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "صورة الغلاف على البطاقة الصغيرة",
"badge-attachment-on-minicard": "عدد المرفقات على البطاقة الصغيرة",
"card-sorting-by-number-on-minicard": "رز البطاقات حسب الرقم على البطاقة الصغيرة",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "پیشوند با مسیر کامل",
"prefix-with-parent": "پیشوند با خانواده",
"subtext-with-full-path": "زیرنویس با مسیر کامل",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "تریگر",
"r-action": "عملیات",
"r-when-a-card": "زمانی که کارت",
@@ -1441,6 +1472,11 @@
"label-names": "اسامی لیبل",
"archived-at": "بایگانی شده در",
"sort-cards": "مرتب‌سازی کارت‌ها",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "الفرز قيد التشغيل",
"cardsSortPopup-title": "مرتب‌سازی کارت‌ها",
"due-date": "تاریخ اجرا",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "قبول",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "تم تفعيل اختصارات لوحة المفاتيح. انقر لتعطيلها.",
"keyboard-shortcuts-disabled": "اختصارات لوحة المفاتيح معطلة. انقر للتفعيل.",
"setSwimlaneHeightPopup-title": "ضبط ارتفاع مسار السباحة",
@@ -437,6 +442,7 @@
"createCustomField": "انشاء حقل",
"createCustomFieldPopup-title": "انشاء حقل",
"current": "الحالي",
"custom-color": "Custom color",
"custom-field-delete-pop": "این اقدام فیلد سفارشی را بهمراه تمامی تاریخچه آن از کارت ها پاک می کند و برگشت پذیر نمی باشد",
"custom-field-checkbox": "جعبه انتخابی",
"custom-field-currency": "عملة",
@@ -504,6 +510,8 @@
"error-csv-schema": "لا يتضمن ملف CSV (القيم المفصولة بفواصل)/TSV (القيم المفصولة بعلامات التبويب) الخاص بك المعلومات الصحيحة بالتنسيق الصحيح",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "این لیست موجود نیست",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "این کاربر وجود ندارد",
"error-user-notAllowSelf": "لا يمكنك دعوة نفسك",
"error-user-notCreated": "این کاربر ایجاد نشده است",
@@ -576,6 +584,7 @@
"fullname": "الإسم الكامل",
"header-logo-title": "الرجوع إلى صفحة اللوحات",
"show-activities": "عرض الأنشطة",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "إنشاء لوحة",
"home": "الرئيسية",
"import": "وارد کردن",
@@ -742,6 +751,7 @@
"remove-label": "إزالة التصنيف",
"listDeletePopup-title": "حذف القائمة ؟",
"remove-member": "حذف العضو",
"deleted-user": "Deleted user",
"remove-member-from-card": "حذف من البطاقة",
"remove-member-pop": "حذف __name__ (__username__) من __boardTitle__ ? سيتم حذف هذا العضو من جميع بطاقة اللوحة مع إرسال إشعار له بذاك.",
"removeMemberPopup-title": "حذف العضو ?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "صورة الغلاف على البطاقة الصغيرة",
"badge-attachment-on-minicard": "عدد المرفقات على البطاقة الصغيرة",
"card-sorting-by-number-on-minicard": "رز البطاقات حسب الرقم على البطاقة الصغيرة",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "پیشوند با مسیر کامل",
"prefix-with-parent": "پیشوند با خانواده",
"subtext-with-full-path": "زیرنویس با مسیر کامل",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "تریگر",
"r-action": "عملیات",
"r-when-a-card": "زمانی که کارت",
@@ -1441,6 +1472,11 @@
"label-names": "اسامی لیبل",
"archived-at": "بایگانی شده در",
"sort-cards": "مرتب‌سازی کارت‌ها",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "الفرز قيد التشغيل",
"cardsSortPopup-title": "مرتب‌سازی کارت‌ها",
"due-date": "تاریخ اجرا",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "Aceptar",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "Atajos de teclado habilitados. Haz clic para deshabilitarlos.",
"keyboard-shortcuts-disabled": "Atajos de teclado deshabilitados. Haz clic para habilitarlos.",
"setSwimlaneHeightPopup-title": "Ajustar la altura del carril",
@@ -437,6 +442,7 @@
"createCustomField": "Crear un campo",
"createCustomFieldPopup-title": "Crear un campo",
"current": "actual",
"custom-color": "Custom color",
"custom-field-delete-pop": "Se eliminará este campo personalizado de todas las tarjetas y se destruirá su historial. Esta acción no puede deshacerse.",
"custom-field-checkbox": "Casilla de verificación",
"custom-field-currency": "Moneda",
@@ -504,6 +510,8 @@
"error-csv-schema": "Su CSV(Valores separados por coma)/TSV(Valores separados por tab) no incluyen la información apropiada en el formato correcto",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "La lista no existe",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "El usuario no existe",
"error-user-notAllowSelf": "No puedes invitarte a ti mismo",
"error-user-notCreated": "El usuario no ha sido creado",
@@ -576,6 +584,7 @@
"fullname": "Nombre completo",
"header-logo-title": "Volver a tu página de tableros",
"show-activities": "Mostrar actividades",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "Crear tablero",
"home": "Inicio",
"import": "Importar",
@@ -742,6 +751,7 @@
"remove-label": "Eliminar la etiqueta",
"listDeletePopup-title": "¿Eliminar la lista?",
"remove-member": "Eliminar miembro",
"deleted-user": "Deleted user",
"remove-member-from-card": "Eliminar de la tarjeta",
"remove-member-pop": "¿Eliminar __name__ (__username__) de __boardTitle__? El miembro será eliminado de todas las tarjetas de este tablero. En ellas se mostrará una notificación.",
"removeMemberPopup-title": "¿Eliminar miembro?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "Imagen de portada de la minitarjeta",
"badge-attachment-on-minicard": "Contador de adjuntos en la minitarjeta",
"card-sorting-by-number-on-minicard": "Orden de tarjetas por número de minitarjeta",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "Prefijo con ruta completa",
"prefix-with-parent": "Prefijo con el padre",
"subtext-with-full-path": "Subtexto con ruta completa",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "disparador",
"r-action": "acción",
"r-when-a-card": "Cuando una tarjeta",
@@ -1441,6 +1472,11 @@
"label-names": "Nombres de las etiquetas",
"archived-at": "archivado el",
"sort-cards": "Ordenar tarjetas",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "Ordenamiento activado",
"cardsSortPopup-title": "Ordenar tarjetas",
"due-date": "Fecha de Vencimiento",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "Qəbul edin",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "Klaviatura qısa yolları aktivləşdirildi. Deaktiv etmək üçün klikləyin.",
"keyboard-shortcuts-disabled": "Klaviatura qısa yolları deaktiv edilib. Aktivləşdirmək üçün klikləyin.",
"setSwimlaneHeightPopup-title": "Üzgüçülük Hündürlüyünü təyin edin",
@@ -437,6 +442,7 @@
"createCustomField": "Sahə yaradın",
"createCustomFieldPopup-title": "Sahə yaradın",
"current": "cari",
"custom-color": "Custom color",
"custom-field-delete-pop": "Geri qaytarma yoxdur. Bu, bu fərdi sahəni bütün kartlardan siləcək və onun tarixçəsini məhv edəcək.",
"custom-field-checkbox": "Yoxlama qutusu",
"custom-field-currency": "Valyuta",
@@ -504,6 +510,8 @@
"error-csv-schema": "Sizin CSV(Vergüllə Ayrılmış Dəyərlər)/TSV (Vergüllə Ayrılmış Dəyərlər) düzgün formatda düzgün məlumatları ehtiva etmir",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "Bu siyahı mövcud deyil",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "Bu istifadəçi mövcud deyil",
"error-user-notAllowSelf": "Özünüzü dəvət edə bilməzsiniz",
"error-user-notCreated": "Bu istifadəçi yaradılmayıb",
@@ -576,6 +584,7 @@
"fullname": "Tam adı",
"header-logo-title": "Lövhələr səhifəsinə qayıdın.",
"show-activities": "Fəaliyyətləri göstərin",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "Şura yaradın",
"home": "Ev",
"import": "İdxal",
@@ -742,6 +751,7 @@
"remove-label": "Etiketi çıxarın",
"listDeletePopup-title": "Siyahı silinsin?",
"remove-member": "Üzvü silin",
"deleted-user": "Deleted user",
"remove-member-from-card": "Kartdan çıxarın",
"remove-member-pop": "__name__ (__username__) __boardTitle__ ünvanından silinsin? Üzv bu lövhədəki bütün kartlardan silinəcək. Onlar bildiriş alacaqlar.",
"removeMemberPopup-title": "Üzv silinsin?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "Minikartda örtük şəkli",
"badge-attachment-on-minicard": "Minikartdakı qoşmaların sayı",
"card-sorting-by-number-on-minicard": "Minikartda nömrəyə görə kartın çeşidlənməsi",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "Tam yolu olan prefiks",
"prefix-with-parent": "Valideyn ilə prefiks",
"subtext-with-full-path": "Tam yolu olan alt mətn",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "Tətik",
"r-action": "Fəaliyyət",
"r-when-a-card": "Bir kart olanda",
@@ -1441,6 +1472,11 @@
"label-names": "Etiket Adları",
"archived-at": "ünvanında arxivləşdirilib",
"sort-cards": "Kartları çeşidləyin",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "Sort aktivdir",
"cardsSortPopup-title": "Kartları çeşidləyin",
"due-date": "Son tarix",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "Qəbul edin",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "Klaviatura qısa yolları aktivləşdirildi. Deaktiv etmək üçün klikləyin.",
"keyboard-shortcuts-disabled": "Klaviatura qısa yolları deaktiv edilib. Aktivləşdirmək üçün klikləyin.",
"setSwimlaneHeightPopup-title": "Üzgüçülük Hündürlüyünü təyin edin",
@@ -437,6 +442,7 @@
"createCustomField": "Sahə yaradın",
"createCustomFieldPopup-title": "Sahə yaradın",
"current": "cari",
"custom-color": "Custom color",
"custom-field-delete-pop": "Geri qaytarma yoxdur. Bu, bu fərdi sahəni bütün kartlardan siləcək və onun tarixçəsini məhv edəcək.",
"custom-field-checkbox": "Yoxlama qutusu",
"custom-field-currency": "Valyuta",
@@ -504,6 +510,8 @@
"error-csv-schema": "Sizin CSV(Vergüllə Ayrılmış Dəyərlər)/TSV (Vergüllə Ayrılmış Dəyərlər) düzgün formatda düzgün məlumatları ehtiva etmir",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "Bu siyahı mövcud deyil",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "Bu istifadəçi mövcud deyil",
"error-user-notAllowSelf": "Özünüzü dəvət edə bilməzsiniz",
"error-user-notCreated": "Bu istifadəçi yaradılmayıb",
@@ -576,6 +584,7 @@
"fullname": "Tam adı",
"header-logo-title": "Lövhələr səhifəsinə qayıdın.",
"show-activities": "Fəaliyyətləri göstərin",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "Şura yaradın",
"home": "Ev",
"import": "İdxal",
@@ -742,6 +751,7 @@
"remove-label": "Etiketi çıxarın",
"listDeletePopup-title": "Siyahı silinsin?",
"remove-member": "Üzvü silin",
"deleted-user": "Deleted user",
"remove-member-from-card": "Kartdan çıxarın",
"remove-member-pop": "__name__ (__username__) __boardTitle__ ünvanından silinsin? Üzv bu lövhədəki bütün kartlardan silinəcək. Onlar bildiriş alacaqlar.",
"removeMemberPopup-title": "Üzv silinsin?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "Minikartda örtük şəkli",
"badge-attachment-on-minicard": "Minikartdakı qoşmaların sayı",
"card-sorting-by-number-on-minicard": "Minikartda nömrəyə görə kartın çeşidlənməsi",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "Tam yolu olan prefiks",
"prefix-with-parent": "Valideyn ilə prefiks",
"subtext-with-full-path": "Tam yolu olan alt mətn",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "Tətik",
"r-action": "Fəaliyyət",
"r-when-a-card": "Bir kart olanda",
@@ -1441,6 +1472,11 @@
"label-names": "Etiket Adları",
"archived-at": "ünvanında arxivləşdirilib",
"sort-cards": "Kartları çeşidləyin",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "Sort aktivdir",
"cardsSortPopup-title": "Kartları çeşidləyin",
"due-date": "Son tarix",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "Qəbul edin",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "Klaviatura qısa yolları aktivləşdirildi. Deaktiv etmək üçün klikləyin.",
"keyboard-shortcuts-disabled": "Klaviatura qısa yolları deaktiv edilib. Aktivləşdirmək üçün klikləyin.",
"setSwimlaneHeightPopup-title": "Üzgüçülük Hündürlüyünü təyin edin",
@@ -437,6 +442,7 @@
"createCustomField": "Sahə yaradın",
"createCustomFieldPopup-title": "Sahə yaradın",
"current": "cari",
"custom-color": "Custom color",
"custom-field-delete-pop": "Geri qaytarma yoxdur. Bu, bu fərdi sahəni bütün kartlardan siləcək və onun tarixçəsini məhv edəcək.",
"custom-field-checkbox": "Yoxlama qutusu",
"custom-field-currency": "Valyuta",
@@ -504,6 +510,8 @@
"error-csv-schema": "Sizin CSV(Vergüllə Ayrılmış Dəyərlər)/TSV (Vergüllə Ayrılmış Dəyərlər) düzgün formatda düzgün məlumatları ehtiva etmir",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "Bu siyahı mövcud deyil",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "Bu istifadəçi mövcud deyil",
"error-user-notAllowSelf": "Özünüzü dəvət edə bilməzsiniz",
"error-user-notCreated": "Bu istifadəçi yaradılmayıb",
@@ -576,6 +584,7 @@
"fullname": "Tam adı",
"header-logo-title": "Lövhələr səhifəsinə qayıdın.",
"show-activities": "Fəaliyyətləri göstərin",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "Şura yaradın",
"home": "Ev",
"import": "İdxal",
@@ -742,6 +751,7 @@
"remove-label": "Etiketi çıxarın",
"listDeletePopup-title": "Siyahı silinsin?",
"remove-member": "Üzvü silin",
"deleted-user": "Deleted user",
"remove-member-from-card": "Kartdan çıxarın",
"remove-member-pop": "__name__ (__username__) __boardTitle__ ünvanından silinsin? Üzv bu lövhədəki bütün kartlardan silinəcək. Onlar bildiriş alacaqlar.",
"removeMemberPopup-title": "Üzv silinsin?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "Minikartda örtük şəkli",
"badge-attachment-on-minicard": "Minikartdakı qoşmaların sayı",
"card-sorting-by-number-on-minicard": "Minikartda nömrəyə görə kartın çeşidlənməsi",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "Tam yolu olan prefiks",
"prefix-with-parent": "Valideyn ilə prefiks",
"subtext-with-full-path": "Tam yolu olan alt mətn",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "Tətik",
"r-action": "Fəaliyyət",
"r-when-a-card": "Bir kart olanda",
@@ -1441,6 +1472,11 @@
"label-names": "Etiket Adları",
"archived-at": "ünvanında arxivləşdirilib",
"sort-cards": "Kartları çeşidləyin",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "Sort aktivdir",
"cardsSortPopup-title": "Kartları çeşidləyin",
"due-date": "Son tarix",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "Accept",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "Keyboard shortcuts enabled. Click to disable.",
"keyboard-shortcuts-disabled": "Keyboard shortcuts disabled. Click to enable.",
"setSwimlaneHeightPopup-title": "Set Swimlane Height",
@@ -437,6 +442,7 @@
"createCustomField": "Create Field",
"createCustomFieldPopup-title": "Create Field",
"current": "current",
"custom-color": "Custom color",
"custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.",
"custom-field-checkbox": "Checkbox",
"custom-field-currency": "Currency",
@@ -504,6 +510,8 @@
"error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format ",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "This list does not exist",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "This user does not exist",
"error-user-notAllowSelf": "You can not invite yourself",
"error-user-notCreated": "This user is not created",
@@ -576,6 +584,7 @@
"fullname": "Full Name",
"header-logo-title": "Go back to your boards page.",
"show-activities": "Show Activities",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "Create Board",
"home": "Home",
"import": "Import",
@@ -742,6 +751,7 @@
"remove-label": "Remove Label",
"listDeletePopup-title": "Delete List ?",
"remove-member": "Remove Member",
"deleted-user": "Deleted user",
"remove-member-from-card": "Remove from Card",
"remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.",
"removeMemberPopup-title": "Remove Member?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "Cover image on minicard",
"badge-attachment-on-minicard": "Count of attachments on minicard",
"card-sorting-by-number-on-minicard": "Card sorting by number on minicard",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "Prefix with full path",
"prefix-with-parent": "Prefix with parent",
"subtext-with-full-path": "Subtext with full path",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "Trigger",
"r-action": "Action",
"r-when-a-card": "When a card",
@@ -1441,6 +1472,11 @@
"label-names": "Label Names",
"archived-at": "archived at",
"sort-cards": "Sort Cards",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "Sort is on",
"cardsSortPopup-title": "Sort Cards",
"due-date": "Due Date",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "Приемам",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "Пречице са тастатуре су укључене.",
"keyboard-shortcuts-disabled": "Пречице са тастатуре су искључене.",
"setSwimlaneHeightPopup-title": "Подеси висину у овој врсти поступка",
@@ -437,6 +442,7 @@
"createCustomField": "Създай Поле",
"createCustomFieldPopup-title": "Създай Поле",
"current": "сегашен",
"custom-color": "Custom color",
"custom-field-delete-pop": "Опозив ове радње неће бити могућ. Радњом се уклања ова придружена рубрика из свих предмета и уништива њена претходна историја употребе.",
"custom-field-checkbox": "Чекбокс",
"custom-field-currency": "Валута",
@@ -504,6 +510,8 @@
"error-csv-schema": "Ваш CSV(Вредности раздвојене зарезом)/TSV (Вредности раздвојене табулатором) не укључује исправну информацију у предвиђеном облику",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "Този списък не съществува",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "Този потребител не съществува",
"error-user-notAllowSelf": "Не можете да поканите себе си",
"error-user-notCreated": "Този потребител не е създаден",
@@ -576,6 +584,7 @@
"fullname": "Име",
"header-logo-title": "Назад към страницата с Вашите табла.",
"show-activities": "Писани записник",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "Създай Табло",
"home": "Начало",
"import": "Импорт",
@@ -742,6 +751,7 @@
"remove-label": "Скини налепницу",
"listDeletePopup-title": "Желаете да изтриете списъка?",
"remove-member": "Премахни член",
"deleted-user": "Deleted user",
"remove-member-from-card": "Премахни от картата",
"remove-member-pop": "Изузећете __name__ (__username__) из списа __boardTitle__? Сарадник ће бити изузет са свих предмета у списима. Примиће обавештење о томе.",
"removeMemberPopup-title": "Желаете ли да премахнете членът?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "Мотив из предметне грађе",
"badge-attachment-on-minicard": "Број поред спајалице",
"card-sorting-by-number-on-minicard": "Бројчана ознака за редослед",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "Представка с пълен път",
"prefix-with-parent": "Представка с източник",
"subtext-with-full-path": "Под-текст с пълен път",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "Добави спусък",
"r-action": "Действие",
"r-when-a-card": "Когато карта",
@@ -1441,6 +1472,11 @@
"label-names": "Натписи на налепницама",
"archived-at": "датум и време архивирања",
"sort-cards": "Сложи предмете",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "Предмети су сложени",
"cardsSortPopup-title": "Сложи предмете",
"due-date": "По крајњем року",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "Asantiñ",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "Raccourcis clavier activés. Cliquer pour désactiver.",
"keyboard-shortcuts-disabled": "Raccourcis clavier désactivés. Cliquer pour activer.",
"setSwimlaneHeightPopup-title": "Définir la hauteur du couloir",
@@ -437,6 +442,7 @@
"createCustomField": "Créer un champ personnalisé",
"createCustomFieldPopup-title": "Créer un champ personnalisé",
"current": "actuel",
"custom-color": "Custom color",
"custom-field-delete-pop": "Cette action est irréversible. Elle supprimera ce champ personnalisé de toutes les cartes et détruira son historique.",
"custom-field-checkbox": "Case à cocher",
"custom-field-currency": "Devise",
@@ -504,6 +510,8 @@
"error-csv-schema": "Votre fichier CSV (valeurs séparées par des virgules) / TSV (valeurs séparées par des tabulations) ne contient pas d'informations au bon format",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "Cette liste n'existe pas",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "Cet utilisateur n'existe pas",
"error-user-notAllowSelf": "Vous ne pouvez pas vous inviter vous-même",
"error-user-notCreated": "Cet utilisateur n'a pas encore été créé",
@@ -576,6 +584,7 @@
"fullname": "Nom complet",
"header-logo-title": "Retourner à la page des tableaux",
"show-activities": "Afficher les activités",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "Créer un tableau",
"home": "Accueil",
"import": "Importer",
@@ -742,6 +751,7 @@
"remove-label": "Retirer l'étiquette",
"listDeletePopup-title": "Supprimer la liste ?",
"remove-member": "Supprimer le participant",
"deleted-user": "Deleted user",
"remove-member-from-card": "Supprimer de la carte",
"remove-member-pop": "Supprimer __name__ (__username__) de __boardTitle__ ? Ce participant sera supprimé de toutes les cartes du tableau et recevra une notification.",
"removeMemberPopup-title": "Supprimer le participant ?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "Vignette sur la mini-carte",
"badge-attachment-on-minicard": "Compteur de pièces jointes sur la mini-carte",
"card-sorting-by-number-on-minicard": "Tri numérique des cartes sur la mini-carte",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "Préfixer avec le chemin complet",
"prefix-with-parent": "Préfixer avec le parent",
"subtext-with-full-path": "Sous-titre avec le chemin complet",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "Déclencheur",
"r-action": "Action",
"r-when-a-card": "Quand une carte",
@@ -1441,6 +1472,11 @@
"label-names": "Noms d'étiquette",
"archived-at": "archivée le",
"sort-cards": "Trier les cartes",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "Le tri est activé",
"cardsSortPopup-title": "Trier les cartes",
"due-date": "Date d'échéance",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "Accepta",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "Keyboard shortcuts enabled. Click to disable.",
"keyboard-shortcuts-disabled": "Keyboard shortcuts disabled. Click to enable.",
"setSwimlaneHeightPopup-title": "Set Swimlane Height",
@@ -437,6 +442,7 @@
"createCustomField": "Crear campament",
"createCustomFieldPopup-title": "Crear campament",
"current": "Real",
"custom-color": "Custom color",
"custom-field-delete-pop": "No hi ha cap desfer. Això eliminarà aquest camp personalitzat de totes les cartes i destruirà el seu historial.",
"custom-field-checkbox": "casella de selecció",
"custom-field-currency": "Moneda",
@@ -504,6 +510,8 @@
"error-csv-schema": "El teu CSV no inclou la informació en el format correcte.",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "La llista no existeix",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "L'usuari no existeix",
"error-user-notAllowSelf": "No et pots convidar a la teva matriu",
"error-user-notCreated": "L'usuari no s'ha creat",
@@ -576,6 +584,7 @@
"fullname": "Nom complet",
"header-logo-title": "Torna a la teva pàgina de taulers",
"show-activities": "Show Activities",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "Crea tauler",
"home": "Inici",
"import": "importa",
@@ -742,6 +751,7 @@
"remove-label": "Elimina l'etiqueta",
"listDeletePopup-title": "Esborrar la llista?",
"remove-member": "Elimina membre",
"deleted-user": "Deleted user",
"remove-member-from-card": "Elimina de la fitxa",
"remove-member-pop": "Eliminar __name__ (__username__) de __boardTitle__? El membre serà eliminat de totes les fitxes d'aquest tauler. Ells rebran una notificació.",
"removeMemberPopup-title": "Vols suprimir el membre?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "Cover image on minicard",
"badge-attachment-on-minicard": "Count of attachments on minicard",
"card-sorting-by-number-on-minicard": "Card sorting by number on minicard",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "Prefix amb camí complet",
"prefix-with-parent": "Prefix amb el pare",
"subtext-with-full-path": "Subtext amb camí complet",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "Disparador",
"r-action": "Acció",
"r-when-a-card": "Quan una fitxa",
@@ -1441,6 +1472,11 @@
"label-names": "Noms d'etiquetes",
"archived-at": "arxivat a",
"sort-cards": "Ordenar fitxes",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "Sort is on",
"cardsSortPopup-title": "Ordenar fitxes",
"due-date": "Data de venciment",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "Acceptar",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "Atajos de teclado habilitados. Haz clic para deshabilitarlos.",
"keyboard-shortcuts-disabled": "Atajos de teclado deshabilitados. Haz clic para habilitarlos.",
"setSwimlaneHeightPopup-title": "Ajustar la altura del carril",
@@ -437,6 +442,7 @@
"createCustomField": "Crear campament",
"createCustomFieldPopup-title": "Crear campament",
"current": "Real",
"custom-color": "Custom color",
"custom-field-delete-pop": "No hi ha cap desfer. Això eliminarà aquest camp personalitzat de totes les cartes i destruirà el seu historial.",
"custom-field-checkbox": "casella de selecció",
"custom-field-currency": "Moneda",
@@ -504,6 +510,8 @@
"error-csv-schema": "El teu CSV no inclou la informació en el format correcte.",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "La llista no existeix",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "L'usuari no existeix",
"error-user-notAllowSelf": "No et pots convidar a la teva matriu",
"error-user-notCreated": "L'usuari no s'ha creat",
@@ -576,6 +584,7 @@
"fullname": "Nom complet",
"header-logo-title": "Torna a la teva pàgina de taulers",
"show-activities": "Mostrar actividades",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "Crea tauler",
"home": "Inici",
"import": "importa",
@@ -742,6 +751,7 @@
"remove-label": "Elimina l'etiqueta",
"listDeletePopup-title": "Esborrar la llista?",
"remove-member": "Elimina membre",
"deleted-user": "Deleted user",
"remove-member-from-card": "Elimina de la fitxa",
"remove-member-pop": "Eliminar __name__ (__username__) de __boardTitle__? El membre serà eliminat de totes les fitxes d'aquest tauler. Ells rebran una notificació.",
"removeMemberPopup-title": "Vols suprimir el membre?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "Imagen de portada de la minitarjeta",
"badge-attachment-on-minicard": "Contador de adjuntos en la minitarjeta",
"card-sorting-by-number-on-minicard": "Orden de tarjetas por número de minitarjeta",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "Prefix amb camí complet",
"prefix-with-parent": "Prefix amb el pare",
"subtext-with-full-path": "Subtext amb camí complet",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "Disparador",
"r-action": "Acció",
"r-when-a-card": "Quan una fitxa",
@@ -1441,6 +1472,11 @@
"label-names": "Noms d'etiquetes",
"archived-at": "arxivat a",
"sort-cards": "Ordenar fitxes",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "Ordenamiento activado",
"cardsSortPopup-title": "Ordenar fitxes",
"due-date": "Data de venciment",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "Accepta",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "Keyboard shortcuts enabled. Click to disable.",
"keyboard-shortcuts-disabled": "Keyboard shortcuts disabled. Click to enable.",
"setSwimlaneHeightPopup-title": "Set Swimlane Height",
@@ -437,6 +442,7 @@
"createCustomField": "Crear campament",
"createCustomFieldPopup-title": "Crear campament",
"current": "Real",
"custom-color": "Custom color",
"custom-field-delete-pop": "No hi ha cap desfer. Això eliminarà aquest camp personalitzat de totes les cartes i destruirà el seu historial.",
"custom-field-checkbox": "casella de selecció",
"custom-field-currency": "Moneda",
@@ -504,6 +510,8 @@
"error-csv-schema": "El teu CSV no inclou la informació en el format correcte.",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "La llista no existeix",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "L'usuari no existeix",
"error-user-notAllowSelf": "No et pots convidar a la teva matriu",
"error-user-notCreated": "L'usuari no s'ha creat",
@@ -576,6 +584,7 @@
"fullname": "Nom complet",
"header-logo-title": "Torna a la teva pàgina de taulers",
"show-activities": "Show Activities",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "Crea tauler",
"home": "Inici",
"import": "importa",
@@ -742,6 +751,7 @@
"remove-label": "Elimina l'etiqueta",
"listDeletePopup-title": "Esborrar la llista?",
"remove-member": "Elimina membre",
"deleted-user": "Deleted user",
"remove-member-from-card": "Elimina de la fitxa",
"remove-member-pop": "Eliminar __name__ (__username__) de __boardTitle__? El membre serà eliminat de totes les fitxes d'aquest tauler. Ells rebran una notificació.",
"removeMemberPopup-title": "Vols suprimir el membre?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "Cover image on minicard",
"badge-attachment-on-minicard": "Count of attachments on minicard",
"card-sorting-by-number-on-minicard": "Card sorting by number on minicard",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "Prefix amb camí complet",
"prefix-with-parent": "Prefix amb el pare",
"subtext-with-full-path": "Subtext amb camí complet",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "Disparador",
"r-action": "Acció",
"r-when-a-card": "Quan una fitxa",
@@ -1441,6 +1472,11 @@
"label-names": "Noms d'etiquetes",
"archived-at": "arxivat a",
"sort-cards": "Ordenar fitxes",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "Sort is on",
"cardsSortPopup-title": "Ordenar fitxes",
"due-date": "Data de venciment",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "接受",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "键盘快捷键已启用。单击以禁用。",
"keyboard-shortcuts-disabled": "键盘快捷键已禁用。单击以启用。",
"setSwimlaneHeightPopup-title": "设置泳道高度",
@@ -437,6 +442,7 @@
"createCustomField": "创建字段",
"createCustomFieldPopup-title": "创建字段",
"current": "当前的",
"custom-color": "Custom color",
"custom-field-delete-pop": "无法撤消。这将从所有卡中删除此自定义字段并销毁其历史记录。",
"custom-field-checkbox": "复选框",
"custom-field-currency": "货币",
@@ -504,6 +510,8 @@
"error-csv-schema": "您的 CSV(逗号分隔值)/TSV(制表符分隔值)未包含格式正确的正确信息",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "该列表不存在",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "该用户不存在",
"error-user-notAllowSelf": "您不能邀请自己",
"error-user-notCreated": "该用户尚未创建",
@@ -576,6 +584,7 @@
"fullname": "姓名",
"header-logo-title": "返回您的图板页面。",
"show-activities": "展会活动",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "创建板",
"home": "家",
"import": "进口",
@@ -742,6 +751,7 @@
"remove-label": "移除标签",
"listDeletePopup-title": "删除列表?",
"remove-member": "删除会员",
"deleted-user": "Deleted user",
"remove-member-from-card": "从卡中删除",
"remove-member-pop": "从__boardTitle__ 中删除__name__ (__username__) 吗?该成员将从该板上的所有卡中删除。他们将收到通知。",
"removeMemberPopup-title": "删除会员?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "迷你卡上的封面图片",
"badge-attachment-on-minicard": "迷你卡上的附件计数",
"card-sorting-by-number-on-minicard": "按迷你卡上的编号对卡片进行分类",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "带完整路径的前缀",
"prefix-with-parent": "带有父级的前缀",
"subtext-with-full-path": "带有完整路径的副文本",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "扳机",
"r-action": "行动",
"r-when-a-card": "当一张卡",
@@ -1441,6 +1472,11 @@
"label-names": "标签名称",
"archived-at": "存档于",
"sort-cards": "卡片排序",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "排序已开启",
"cardsSortPopup-title": "卡片排序",
"due-date": "到期日",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "Přijmout",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "Keyboard shortcuts enabled. Click to disable.",
"keyboard-shortcuts-disabled": "Keyboard shortcuts disabled. Click to enable.",
"setSwimlaneHeightPopup-title": "Nastavit výšku swimlane",
@@ -437,6 +442,7 @@
"createCustomField": "Vytvořit pole",
"createCustomFieldPopup-title": "Vytvořit pole",
"current": "Aktuální",
"custom-color": "Custom color",
"custom-field-delete-pop": "Nelze vrátit zpět. Toto odebere toto vlastní pole ze všech karet a zničí jeho historii.",
"custom-field-checkbox": "Checkbox",
"custom-field-currency": "Měna",
@@ -504,6 +510,8 @@
"error-csv-schema": "Váš CSV (Comma Separated Values)/TSV (Tab Separated Values) neobsahuje správné informace ve správném formátu.",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "Tento sloupec ;neexistuje",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "Tento uživatel neexistuje",
"error-user-notAllowSelf": "Nemůžeš pozvat sám sebe",
"error-user-notCreated": "Tento uživatel není vytvořen",
@@ -576,6 +584,7 @@
"fullname": "Celé jméno",
"header-logo-title": "Jit zpět na stránku s tably.",
"show-activities": "Show Activities",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "Vytvořit tablo",
"home": "Domů",
"import": "Import",
@@ -742,6 +751,7 @@
"remove-label": "Odstranit štítek",
"listDeletePopup-title": "Smazat sloupec?",
"remove-member": "Odebrat uživatele",
"deleted-user": "Deleted user",
"remove-member-from-card": "Odstranit z karty",
"remove-member-pop": "Odstranit __name__ (__username__) z __boardTitle__? Uživatel bude odebrán ze všech karet na tomto tablu. Na tuto skutečnost bude upozorněn.",
"removeMemberPopup-title": "Odstranit člena?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "Titulní obrázek na minikartě",
"badge-attachment-on-minicard": "Počet příloh na minikartě",
"card-sorting-by-number-on-minicard": "Řazení karet podle čísla na minikartě",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "Prefix s celou cestou",
"prefix-with-parent": "Prefix s předkem",
"subtext-with-full-path": "Podtext s celou cestou",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "Spouštěč",
"r-action": "Akce",
"r-when-a-card": "Pokud karta",
@@ -1441,6 +1472,11 @@
"label-names": "Štítek jména",
"archived-at": "archivováno",
"sort-cards": "Třídit",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "Řazení je zapnuté",
"cardsSortPopup-title": "Třídit",
"due-date": "Požadovaný termín",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "Přijmout",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "Keyboard shortcuts enabled. Click to disable.",
"keyboard-shortcuts-disabled": "Keyboard shortcuts disabled. Click to enable.",
"setSwimlaneHeightPopup-title": "Nastavit výšku swimlane",
@@ -437,6 +442,7 @@
"createCustomField": "Vytvořit pole",
"createCustomFieldPopup-title": "Vytvořit pole",
"current": "Aktuální",
"custom-color": "Custom color",
"custom-field-delete-pop": "Nelze vrátit zpět. Toto odebere toto vlastní pole ze všech karet a zničí jeho historii.",
"custom-field-checkbox": "Checkbox",
"custom-field-currency": "Měna",
@@ -504,6 +510,8 @@
"error-csv-schema": "Váš CSV (Comma Separated Values)/TSV (Tab Separated Values) neobsahuje správné informace ve správném formátu.",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "Tento sloupec ;neexistuje",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "Tento uživatel neexistuje",
"error-user-notAllowSelf": "Nemůžeš pozvat sám sebe",
"error-user-notCreated": "Tento uživatel není vytvořen",
@@ -576,6 +584,7 @@
"fullname": "Celé jméno",
"header-logo-title": "Jit zpět na stránku s tably.",
"show-activities": "Show Activities",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "Vytvořit tablo",
"home": "Domů",
"import": "Import",
@@ -742,6 +751,7 @@
"remove-label": "Odstranit štítek",
"listDeletePopup-title": "Smazat sloupec?",
"remove-member": "Odebrat uživatele",
"deleted-user": "Deleted user",
"remove-member-from-card": "Odstranit z karty",
"remove-member-pop": "Odstranit __name__ (__username__) z __boardTitle__? Uživatel bude odebrán ze všech karet na tomto tablu. Na tuto skutečnost bude upozorněn.",
"removeMemberPopup-title": "Odstranit člena?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "Titulní obrázek na minikartě",
"badge-attachment-on-minicard": "Počet příloh na minikartě",
"card-sorting-by-number-on-minicard": "Řazení karet podle čísla na minikartě",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "Prefix s celou cestou",
"prefix-with-parent": "Prefix s předkem",
"subtext-with-full-path": "Podtext s celou cestou",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "Spouštěč",
"r-action": "Akce",
"r-when-a-card": "Pokud karta",
@@ -1441,6 +1472,11 @@
"label-names": "Štítek jména",
"archived-at": "archivováno",
"sort-cards": "Třídit",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "Řazení je zapnuté",
"cardsSortPopup-title": "Třídit",
"due-date": "Požadovaný termín",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "Derbyn",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "Llwybrau byr bysellfwrdd wedi'u galluogi. Cliciwch i analluogi.",
"keyboard-shortcuts-disabled": "Analluogwyd llwybrau byr bysellfwrdd. Cliciwch i alluogi.",
"setSwimlaneHeightPopup-title": "Gosodwch Swimlane Uchder",
@@ -437,6 +442,7 @@
"createCustomField": "Creu Maes",
"createCustomFieldPopup-title": "Creu Maes",
"current": "presennol",
"custom-color": "Custom color",
"custom-field-delete-pop": "Does dim dadwneud. Bydd hyn yn dileu'r maes arfer hwn o bob cerdyn ac yn dinistrio ei hanes.",
"custom-field-checkbox": "Blwch ticio",
"custom-field-currency": "Arian cyfred",
@@ -504,6 +510,8 @@
"error-csv-schema": "Nid yw eich CSV(Comma Separated Values)/TSV (Tab Separated Values) yn cynnwys y wybodaeth gywir yn y fformat cywir",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "Nid yw'r rhestr hon yn bodoli",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "Nid yw'r defnyddiwr hwn yn bodoli",
"error-user-notAllowSelf": "Ni allwch wahodd eich hun",
"error-user-notCreated": "Nid yw'r defnyddiwr hwn wedi'i greu",
@@ -576,6 +584,7 @@
"fullname": "Enw Llawn",
"header-logo-title": "Ewch yn ôl i dudalen eich byrddau.",
"show-activities": "Gweithgareddau Sioe",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "Creu Bwrdd",
"home": "Cartref",
"import": "Mewnforio",
@@ -742,6 +751,7 @@
"remove-label": "Dileu Label",
"listDeletePopup-title": "Dileu Rhestr ?",
"remove-member": "Dileu Aelod",
"deleted-user": "Deleted user",
"remove-member-from-card": "Tynnu oddi ar Gerdyn",
"remove-member-pop": "Tynnu __name__ (__username__) o __boardTitle__? Bydd yr aelod yn cael ei dynnu oddi ar bob cerdyn ar y bwrdd hwn. Byddant yn derbyn hysbysiad.",
"removeMemberPopup-title": "Dileu Aelod?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "Llun clawr ar minicard",
"badge-attachment-on-minicard": "Cyfrif yr atodiadau ar miniard",
"card-sorting-by-number-on-minicard": "Didoli cardiau yn ôl rhif ar minicard",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "Rhagddodiad gyda llwybr llawn",
"prefix-with-parent": "Rhagddodiad gyda rhiant",
"subtext-with-full-path": "Is-destun gyda llwybr llawn",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "Sbardun",
"r-action": "Gweithred",
"r-when-a-card": "Pan fydd cerdyn",
@@ -1441,6 +1472,11 @@
"label-names": "Enwau Label",
"archived-at": "archifwyd yn",
"sort-cards": "Cardiau Didoli",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "Mae trefn ymlaen",
"cardsSortPopup-title": "Cardiau Didoli",
"due-date": "Dyddiad Cwblhau",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "Derbyn",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "Llwybrau byr bysellfwrdd wedi'u galluogi. Cliciwch i analluogi.",
"keyboard-shortcuts-disabled": "Analluogwyd llwybrau byr bysellfwrdd. Cliciwch i alluogi.",
"setSwimlaneHeightPopup-title": "Gosodwch Swimlane Uchder",
@@ -437,6 +442,7 @@
"createCustomField": "Creu Maes",
"createCustomFieldPopup-title": "Creu Maes",
"current": "presennol",
"custom-color": "Custom color",
"custom-field-delete-pop": "Does dim dadwneud. Bydd hyn yn dileu'r maes arfer hwn o bob cerdyn ac yn dinistrio ei hanes.",
"custom-field-checkbox": "Blwch ticio",
"custom-field-currency": "Arian cyfred",
@@ -504,6 +510,8 @@
"error-csv-schema": "Nid yw eich CSV(Comma Separated Values)/TSV (Tab Separated Values) yn cynnwys y wybodaeth gywir yn y fformat cywir",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "Nid yw'r rhestr hon yn bodoli",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "Nid yw'r defnyddiwr hwn yn bodoli",
"error-user-notAllowSelf": "Ni allwch wahodd eich hun",
"error-user-notCreated": "Nid yw'r defnyddiwr hwn wedi'i greu",
@@ -576,6 +584,7 @@
"fullname": "Enw Llawn",
"header-logo-title": "Ewch yn ôl i dudalen eich byrddau.",
"show-activities": "Gweithgareddau Sioe",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "Creu Bwrdd",
"home": "Cartref",
"import": "Mewnforio",
@@ -742,6 +751,7 @@
"remove-label": "Dileu Label",
"listDeletePopup-title": "Dileu Rhestr ?",
"remove-member": "Dileu Aelod",
"deleted-user": "Deleted user",
"remove-member-from-card": "Tynnu oddi ar Gerdyn",
"remove-member-pop": "Tynnu __name__ (__username__) o __boardTitle__? Bydd yr aelod yn cael ei dynnu oddi ar bob cerdyn ar y bwrdd hwn. Byddant yn derbyn hysbysiad.",
"removeMemberPopup-title": "Dileu Aelod?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "Llun clawr ar minicard",
"badge-attachment-on-minicard": "Cyfrif yr atodiadau ar miniard",
"card-sorting-by-number-on-minicard": "Didoli cardiau yn ôl rhif ar minicard",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "Rhagddodiad gyda llwybr llawn",
"prefix-with-parent": "Rhagddodiad gyda rhiant",
"subtext-with-full-path": "Is-destun gyda llwybr llawn",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "Sbardun",
"r-action": "Gweithred",
"r-when-a-card": "Pan fydd cerdyn",
@@ -1441,6 +1472,11 @@
"label-names": "Enwau Label",
"archived-at": "archifwyd yn",
"sort-cards": "Cardiau Didoli",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "Mae trefn ymlaen",
"cardsSortPopup-title": "Cardiau Didoli",
"due-date": "Dyddiad Cwblhau",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "Accepter",
"activity-changedTitle": "ændrede titel til %s af %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "slettede kortet __card__ fra listen __list__ i svømmebanen __swimlane__ på tavlen __board__",
"act-removeBoard": "slettede tavlen __board__",
"act-removeList": "slettede listen __list__ fra tavlen __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "Tastaturgenveje aktiverede. Klik for at deaktivere.",
"keyboard-shortcuts-disabled": "Tastaturgenveje deaktiverede. Klik for at aktivere.",
"setSwimlaneHeightPopup-title": "Angiv højde på svømmebane",
@@ -437,6 +442,7 @@
"createCustomField": "Opret felt",
"createCustomFieldPopup-title": "Opret felt",
"current": "nuværende",
"custom-color": "Custom color",
"custom-field-delete-pop": "Du kan ikke fortryde handlingen. Dette vil fjerne dette brugerdefinerede felt fra alle kort og tilintetgøre dens historik.",
"custom-field-checkbox": "Afkrydsningsfelt",
"custom-field-currency": "Valuta",
@@ -504,6 +510,8 @@
"error-csv-schema": "Din CSV (Comma Separated Values)/TSV (Tab Separated Values) indeholder ikke gyldig information i korrekt format",
"error-import-empty-board": "Denne eksport indeholder intet tavleindhold (ingen svømmebaner, lister eller kort). Den kan være oprettet af en ældre WeKan-version med en kendt eksportfejl. Eksporter tavlen igen med den aktuelle version, og importer den nye fil.",
"error-list-doesNotExist": "Listen findes ikke",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "Brugeren findes ikke",
"error-user-notAllowSelf": "Du kan ikke invitere dig selv",
"error-user-notCreated": "Brugeren er ikke oprettet",
@@ -576,6 +584,7 @@
"fullname": "Fuldt navn",
"header-logo-title": "Gå tilbage til siden med dine tavler",
"show-activities": "Visa aktiviteter",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "Opret tavle",
"home": "Hjem",
"import": "Importér",
@@ -742,6 +751,7 @@
"remove-label": "Fjern etikette",
"listDeletePopup-title": "Slet liste?",
"remove-member": "Fjern medlem",
"deleted-user": "Deleted user",
"remove-member-from-card": "Fjern fra kort",
"remove-member-pop": "Fjern __name__ (__username__) fra __boardTitle__? Medlemmet vil blive fjernet fra alle kort på denne tavle. De vil modtage en notifikation.",
"removeMemberPopup-title": "Fjern medlem?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "Omslagsbild på minikort",
"badge-attachment-on-minicard": "Antal bilagor på minikort",
"card-sorting-by-number-on-minicard": "Sortering av kort efter nummer på minikort",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "Præfiks med fuld sti",
"prefix-with-parent": "Præfiks med overordnede",
"subtext-with-full-path": "Undertekst med fuld sti",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Træk en trigger og en handling ind i byggeren, og tilføj derefter reglen. Træk en handling over på en eksisterende regel for at ændre, hvad den gør.",
"r-drop-trigger": "træk en udløser hertil",
"r-drop-action": "træk en handling hertil",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Eksporter regler til JSON",
"r-export-csv": "Eksporter regler til CSV",
"r-import-json": "Importer regler fra JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Markér kort som ikke fuldført",
"r-move-all-cards": "Flyt alle kort i en liste",
"r-set-date-relative": "Angiv dato i forhold til dags dato",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "Udløser",
"r-action": "Handling",
"r-when-a-card": "Når et kort",
@@ -1441,6 +1472,11 @@
"label-names": "Märkningsnamn",
"archived-at": "arkiverad den",
"sort-cards": "Sortera kort",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "Sortering er slået til",
"cardsSortPopup-title": "Sortera kort",
"due-date": "Förfallodatum",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "Akzeptieren",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "Tastaturkürzel sind aktiviert. Klicke, um sie zu deaktivieren.",
"keyboard-shortcuts-disabled": "Tastaturkürzel sind deaktiviert. Klicke, um sie zu aktivieren.",
"setSwimlaneHeightPopup-title": "Stelle die Höhe der Swimlane ein",
@@ -437,6 +442,7 @@
"createCustomField": "Feld erstellen",
"createCustomFieldPopup-title": "Feld erstellen",
"current": "aktuell",
"custom-color": "Custom color",
"custom-field-delete-pop": "Dies wird das Feld aus allen Karten entfernen und den dazugehörigen Verlauf löschen. Die Aktion kann nicht rückgängig gemacht werden.",
"custom-field-checkbox": "Kontrollkästchen",
"custom-field-currency": "Währung",
@@ -504,6 +510,8 @@
"error-csv-schema": "Deine CSV(Kommatgetrennte Werte)/TSV (Tab getrennte Werte) enthält keine korrekten Informationen im geforderten Format ",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "Diese Liste existiert nicht",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "Dieser Nutzer existiert nicht",
"error-user-notAllowSelf": "Sie können sich nicht selbst einladen.",
"error-user-notCreated": "Dieser Nutzer ist nicht angelegt",
@@ -576,6 +584,7 @@
"fullname": "Vollständiger Name",
"header-logo-title": "Zurück zur Board Seite.",
"show-activities": "Aktivitäten anzeigen",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "Board erstellen",
"home": "Home",
"import": "Importieren",
@@ -742,6 +751,7 @@
"remove-label": "Label entfernen",
"listDeletePopup-title": "Liste löschen?",
"remove-member": "Nutzer entfernen",
"deleted-user": "Deleted user",
"remove-member-from-card": "Von Karte entfernen",
"remove-member-pop": "__name__ (__username__) von __boardTitle__ entfernen? Das Mitglied wird von allen Karten auf diesem Board entfernt. Es erhält eine Benachrichtigung.",
"removeMemberPopup-title": "Mitglied entfernen?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "Cover Bild auf Minikarte",
"badge-attachment-on-minicard": "Anzahl der Anhänge auf Minikarte",
"card-sorting-by-number-on-minicard": "Kartensortierung nach Nummer auf Minikarte",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "Vollständiger Pfad über Titel",
"prefix-with-parent": "Über Titel",
"subtext-with-full-path": "Vollständiger Pfad unter Titel",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "Auslöser",
"r-action": "Aktion",
"r-when-a-card": "Wenn Karte",
@@ -1441,6 +1472,11 @@
"label-names": "Label-Namen",
"archived-at": "archiviert am",
"sort-cards": "Sortiere Karten",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "Sortierung ist aktiviert",
"cardsSortPopup-title": "Sortiere Karten",
"due-date": "Fälligkeitsdatum",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "Akzeptieren",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "Tastaturkürzel sind aktiviert. Klicke, um sie zu deaktivieren.",
"keyboard-shortcuts-disabled": "Tastaturkürzel sind deaktiviert. Klicke, um sie zu aktivieren.",
"setSwimlaneHeightPopup-title": "Stelle die Höhe der Swimlane ein",
@@ -437,6 +442,7 @@
"createCustomField": "Feld erstellen",
"createCustomFieldPopup-title": "Feld erstellen",
"current": "aktuell",
"custom-color": "Custom color",
"custom-field-delete-pop": "Dies wird das Feld aus allen Karten entfernen und den dazugehörigen Verlauf löschen. Die Aktion kann nicht rückgängig gemacht werden.",
"custom-field-checkbox": "Kontrollkästchen",
"custom-field-currency": "Währung",
@@ -504,6 +510,8 @@
"error-csv-schema": "Deine CSV(Kommatgetrennte Werte)/TSV (Tab getrennte Werte) enthält keine korrekten Informationen im geforderten Format ",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "Diese Liste existiert nicht",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "Dieser Nutzer existiert nicht",
"error-user-notAllowSelf": "Sie können sich nicht selbst einladen.",
"error-user-notCreated": "Dieser Nutzer ist nicht angelegt",
@@ -576,6 +584,7 @@
"fullname": "Vollständiger Name",
"header-logo-title": "Zurück zur Board Seite.",
"show-activities": "Aktivitäten anzeigen",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "Board erstellen",
"home": "Home",
"import": "Importieren",
@@ -742,6 +751,7 @@
"remove-label": "Label entfernen",
"listDeletePopup-title": "Liste löschen?",
"remove-member": "Nutzer entfernen",
"deleted-user": "Deleted user",
"remove-member-from-card": "Von Karte entfernen",
"remove-member-pop": "__name__ (__username__) von __boardTitle__ entfernen? Das Mitglied wird von allen Karten auf diesem Board entfernt. Es erhält eine Benachrichtigung.",
"removeMemberPopup-title": "Mitglied entfernen?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "Cover Bild auf Minikarte",
"badge-attachment-on-minicard": "Anzahl der Anhänge auf Minikarte",
"card-sorting-by-number-on-minicard": "Kartensortierung nach Nummer auf Minikarte",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "Vollständiger Pfad über Titel",
"prefix-with-parent": "Über Titel",
"subtext-with-full-path": "Vollständiger Pfad unter Titel",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "Auslöser",
"r-action": "Aktion",
"r-when-a-card": "Wenn Karte",
@@ -1441,6 +1472,11 @@
"label-names": "Label-Namen",
"archived-at": "archiviert am",
"sort-cards": "Sortiere Karten",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "Sortierung ist aktiviert",
"cardsSortPopup-title": "Sortiere Karten",
"due-date": "Fälligkeitsdatum",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "Akzeptieren",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "Tastaturkürzel sind aktiviert. Klicke, um sie zu deaktivieren.",
"keyboard-shortcuts-disabled": "Tastaturkürzel sind deaktiviert. Klicke, um sie zu aktivieren.",
"setSwimlaneHeightPopup-title": "Stelle die Höhe der Swimlane ein",
@@ -437,6 +442,7 @@
"createCustomField": "Feld erstellen",
"createCustomFieldPopup-title": "Feld erstellen",
"current": "aktuell",
"custom-color": "Custom color",
"custom-field-delete-pop": "Dies wird das Feld aus allen Karten entfernen und den dazugehörigen Verlauf löschen. Die Aktion kann nicht rückgängig gemacht werden.",
"custom-field-checkbox": "Kontrollkästchen",
"custom-field-currency": "Währung",
@@ -504,6 +510,8 @@
"error-csv-schema": "Deine CSV(Kommatgetrennte Werte)/TSV (Tab getrennte Werte) enthält keine korrekten Informationen im geforderten Format ",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "Diese Liste existiert nicht",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "Dieser Nutzer existiert nicht",
"error-user-notAllowSelf": "Sie können sich nicht selbst einladen.",
"error-user-notCreated": "Dieser Nutzer ist nicht angelegt",
@@ -576,6 +584,7 @@
"fullname": "Vollständiger Name",
"header-logo-title": "Zurück zur Board Seite.",
"show-activities": "Aktivitäten anzeigen",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "Board erstellen",
"home": "Home",
"import": "Importieren",
@@ -742,6 +751,7 @@
"remove-label": "Label entfernen",
"listDeletePopup-title": "Liste löschen?",
"remove-member": "Nutzer entfernen",
"deleted-user": "Deleted user",
"remove-member-from-card": "Von Karte entfernen",
"remove-member-pop": "__name__ (__username__) von __boardTitle__ entfernen? Das Mitglied wird von allen Karten auf diesem Board entfernt. Es erhält eine Benachrichtigung.",
"removeMemberPopup-title": "Mitglied entfernen?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "Cover Bild auf Minikarte",
"badge-attachment-on-minicard": "Anzahl der Anhänge auf Minikarte",
"card-sorting-by-number-on-minicard": "Kartensortierung nach Nummer auf Minikarte",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "Vollständiger Pfad über Titel",
"prefix-with-parent": "Über Titel",
"subtext-with-full-path": "Vollständiger Pfad unter Titel",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "Auslöser",
"r-action": "Aktion",
"r-when-a-card": "Wenn Karte",
@@ -1441,6 +1472,11 @@
"label-names": "Label-Namen",
"archived-at": "archiviert am",
"sort-cards": "Sortiere Karten",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "Sortierung ist aktiviert",
"cardsSortPopup-title": "Sortiere Karten",
"due-date": "Fälligkeitsdatum",
+36
View File
@@ -1,6 +1,7 @@
{
"accept": "Akzeptieren",
"activity-changedTitle": "changed title to %s of %s",
"activity-changedDescription": "changed description of %s",
"act-deleteCard": "deleted card __card__ from list __list__ at swimlane __swimlane__ at board __board__",
"act-removeBoard": "deleted board __board__",
"act-removeList": "deleted list __list__ at board __board__",
@@ -125,6 +126,10 @@
"list-width-personal-note": "This width is only for you.",
"personal-list-width": "Personal list widths",
"personal-list-width-description": "When on, each user keeps their own list widths. When off, list widths are shared with everyone on the board.",
"fixed-list-width": "Same width for all lists",
"click-to-enable-fixed-list-width": "Same width for all lists disabled. Click to enable.",
"click-to-disable-fixed-list-width": "Same width for all lists enabled. Click to disable.",
"fixed-list-width-note": "When on, all lists have the same width, and resizing one list resizes all lists. This setting is only for you.",
"keyboard-shortcuts-enabled": "Tastaturkürzel sind aktiviert. Klicke, um sie zu deaktivieren.",
"keyboard-shortcuts-disabled": "Tastaturkürzel sind deaktiviert. Klicke, um sie zu aktivieren.",
"setSwimlaneHeightPopup-title": "Stelle die Höhe der Swimlane ein",
@@ -437,6 +442,7 @@
"createCustomField": "Feld erstellen",
"createCustomFieldPopup-title": "Feld erstellen",
"current": "aktuell",
"custom-color": "Custom color",
"custom-field-delete-pop": "Dies wird das Feld aus allen Karten entfernen und den dazugehörigen Verlauf löschen. Die Aktion kann nicht rückgängig gemacht werden.",
"custom-field-checkbox": "Kontrollkästchen",
"custom-field-currency": "Währung",
@@ -504,6 +510,8 @@
"error-csv-schema": "Deine CSV(Kommatgetrennte Werte)/TSV (Tab getrennte Werte) enthält keine korrekten Informationen im geforderten Format ",
"error-import-empty-board": "This export contains no board content (no swimlanes, lists or cards). It may have been created by an older WeKan version with a known export bug. Please re-export the board with the current version and import the new file.",
"error-list-doesNotExist": "Diese Liste existiert nicht",
"error-linked-card-not-allowed": "You can only link to a normal card. Linking to another linked card, or to a card that links back to this board, is not allowed because it would make the cards inaccessible.",
"error-user-disabled": "This user account is disabled",
"error-user-doesNotExist": "Dieser Nutzer existiert nicht",
"error-user-notAllowSelf": "Sie können sich nicht selbst einladen.",
"error-user-notCreated": "Dieser Nutzer ist nicht angelegt",
@@ -576,6 +584,7 @@
"fullname": "Vollständiger Name",
"header-logo-title": "Zurück zur Board Seite.",
"show-activities": "Aktivitäten anzeigen",
"hide-activities": "Hide Activities",
"headerBarCreateBoardPopup-title": "Board erstellen",
"home": "Home",
"import": "Importieren",
@@ -742,6 +751,7 @@
"remove-label": "Label entfernen",
"listDeletePopup-title": "Liste löschen?",
"remove-member": "Nutzer entfernen",
"deleted-user": "Deleted user",
"remove-member-from-card": "Von Karte entfernen",
"remove-member-pop": "__name__ (__username__) von __boardTitle__ entfernen? Das Mitglied wird von allen Karten auf diesem Board entfernt. Es erhält eine Benachrichtigung.",
"removeMemberPopup-title": "Mitglied entfernen?",
@@ -968,6 +978,10 @@
"cover-attachment-on-minicard": "Cover Bild auf Minikarte",
"badge-attachment-on-minicard": "Anzahl der Anhänge auf Minikarte",
"card-sorting-by-number-on-minicard": "Kartensortierung nach Nummer auf Minikarte",
"checklist-count-on-minicard": "Checklist item count (0/0) on minicard",
"checklist-count": "Checklist item count (0/0)",
"attachment-count": "Count of attachments",
"cover-image": "Cover image",
"prefix-with-full-path": "Vollständiger Pfad über Titel",
"prefix-with-parent": "Über Titel",
"subtext-with-full-path": "Vollständiger Pfad unter Titel",
@@ -1005,6 +1019,17 @@
"r-workflow-help": "Drag a trigger and an action into the builder, then add the rule. Drag an action onto an existing rule to change what it does.",
"r-drop-trigger": "drop a trigger here",
"r-drop-action": "drop an action here",
"r-w-card-created": "Card is created",
"r-w-card-archived": "Card is archived",
"r-w-card-unarchived": "Card is unarchived",
"r-w-label-added": "Any label is added",
"r-w-label-removed": "Any label is removed",
"r-w-member-added": "A member is added",
"r-w-member-removed": "A member is removed",
"r-w-checklist-added": "A checklist is added",
"r-w-attachment-added": "An attachment is added",
"r-w-every-day-at": "Every day at __time__",
"r-w-set-received-now": "Set received date to now",
"r-export-json": "Export rules to JSON",
"r-export-csv": "Export rules to CSV",
"r-import-json": "Import rules from JSON",
@@ -1058,6 +1083,12 @@
"r-mark-incomplete": "Mark card incomplete",
"r-move-all-cards": "Move all cards in list",
"r-set-date-relative": "Set date relative to now",
"r-later": "later",
"r-unit-minutes": "minute(s)",
"r-unit-hours": "hour(s)",
"r-unit-days": "day(s)",
"r-unit-weeks": "week(s)",
"r-unit-months": "month(s)",
"r-trigger": "Auslöser",
"r-action": "Aktion",
"r-when-a-card": "Wenn Karte",
@@ -1441,6 +1472,11 @@
"label-names": "Label-Namen",
"archived-at": "archiviert am",
"sort-cards": "Sortiere Karten",
"sort-boards": "Sort Boards",
"search-boards": "Search boards",
"sort-boards-custom": "Custom (drag order)",
"sort-boards-title-asc": "Title (A → Z)",
"sort-boards-title-desc": "Title (Z → A)",
"sort-is-on": "Sortierung ist aktiviert",
"cardsSortPopup-title": "Sortiere Karten",
"due-date": "Fälligkeitsdatum",
Loaded 100 of 382 files, more files were not shown because too many files have changed in this diff. Show more