Compare commits

...
568 Commits
Author SHA1 Message Date
github-actions[bot] 9c1a5a3dcb Bump versions for v11.89 2026-09-20 20:53:06 +00:00
Lauri Ojansivu d1150280fd Prepare v11.89 release 2026-09-20 23:51:26 +03:00
Lauri Ojansivu 66ba7fde03 Document upcoming Snap Store upload retry fix 2026-09-20 23:51:05 +03:00
Lauri Ojansivu 986516e8ed Merge pull request #6708 from wekan/copilot/fix-snap-store-upload-failures
Retry transient Snap Store 500 upload failures across all snap publish workflows
2026-09-20 23:47:32 +03:00
copilot-swe-agent[bot]andxet7 62cccae192 Unify snap upload retries for transient store 500 errors
Co-authored-by: xet7 <15545+xet7@users.noreply.github.com>
2026-09-20 20:43:18 +00:00
copilot-swe-agent[bot]andxet7 ef5ccf3fb0 Retry Snap Store HTTP 500 upload failures
Co-authored-by: xet7 <15545+xet7@users.noreply.github.com>
2026-09-20 20:35:40 +00:00
copilot-swe-agent[bot] 0fb4408c57 Initial plan 2026-09-20 20:30:35 +00:00
github-actions[bot] a44adf6b8e Bump versions for v11.88 2026-09-20 18:18:43 +00:00
Lauri Ojansivu 452331c943 Prepare v11.88 release 2026-09-20 21:17:10 +03:00
Lauri Ojansivu 073fb8469a Document MIME detection across server platforms
Record the shared native/portable detector, platform packaging, focused regression coverage and unverified native builds.

Thanks to xet7 !
2026-09-20 21:07:17 +03:00
Lauri Ojansivu a990e7f202 Provide full MIME detection across server platforms
Share native file and bundled libmagic detection across uploads, extension correction and administrator file checks. Include the portable WASM database in runtime dependencies, stage native file for Snap and Linux setup, and verify release assets before smoke boot.

Cover portable detection with native file absent, missing dependencies/assets, isolated release layout and HTML diagnostics in Chromium, Firefox and WebKit.

Thanks to xet7 !
2026-09-20 21:06:30 +03:00
Lauri Ojansivu e5e38d881f Merge remote-tracking branch 'origin/main' 2026-09-20 20:46:20 +03:00
Lauri Ojansivu de7948b4e9 Document administrator file status and recovery checks
Thanks to xet7.
2026-09-20 19:36:08 +03:00
Lauri Ojansivu 1f5cb0d134 Add read-only file status checks to Admin Panel Problems
Add shared controls above Files, Filesystem integrity and Recovery for content/type checks, storage inventory and history correlation, with progress, cancellation and a downloadable bounded summary.

Report incomplete upload metadata separately from missing binaries. Compare filesystem and GridFS references, sizes, MIME/extension aliases, hashes and baseline evidence; correlate undo/activity history with candidate renamed or moved files. Preserve files and records, enforce administrator access and scan limits, and explicitly report unknown or unverified coverage.

Verify five focused Node suites and two browser scenarios in Chromium, Firefox and WebKit. Rerun header-first content detection in all three browsers. Document storage and verification limits.

Thanks to xet7.
2026-09-20 19:35:16 +03:00
Lauri Ojansivu a35f1cab3f Document production attachment dependency and MIME fixes
Thanks to xet7.
2026-09-20 19:05:18 +03:00
Lauri Ojansivu 9e1cf4c35b Resolve document and image dependencies from the release npm tree
Anchor runtime resolution at programs/server/npm so Office search indexing finds fflate, PDF indexing finds its worker and assets, and image conversion finds sharp. Parent resolution keeps source development working. Retain the file utility as a Docker runtime dependency for MIME detection.

Verify six focused regression suites and XLSX extraction plus PDF asset resolution in an isolated release container. Include negative archive and missing-dependency coverage.

Thanks to xet7.
2026-09-20 19:04:58 +03:00
Lauri Ojansivu 1760601457 Merge pull request #6707 from wekan/dependabot/github_actions/docker/build-push-action-7.4.0
Bump docker/build-push-action from 7.3.0 to 7.4.0
2026-09-20 13:18:18 +03:00
Lauri Ojansivu b6fcb6a3d3 Updated translations. 2026-09-20 12:53:04 +03:00
Lauri Ojansivu 3a339e1975 Document development startup and attachment fixture fixes
Thanks to xet7.
2026-09-20 12:26:22 +03:00
Lauri Ojansivu 721805b800 Fix source development startup warnings and browser fixture cleanup
Cache Meteor package transforms, make development DEBUG logging opt-in, and replace legacy CAS URL parsing while preserving callback encoding and custom HTTPS ports. Clean attachment metadata during browser fixture teardown, including failure paths.

Verify six focused Node suites, nine sequential browser checks across Chromium, Firefox and WebKit, and live HTTP requests with deprecation tracing. Document the package cache and verification limits.

Thanks to xet7.
2026-09-20 12:25:56 +03:00
Lauri Ojansivu 290aeae38b Document translation pull repairs and safeguards
Thanks to xet7 !
2026-09-20 11:43:09 +03:00
Lauri Ojansivu 3e38abc0d6 Reject recurring wrong-language translations during Transifex pulls
Repair 841 wrong-language or previously audited bad values from the pull while preserving 77 Persian and Portuguese updates. Record exact rejected locale/key/value fingerprints, validate fallback tokens and content, and reapply existing audited corrections without replacing newer human wording.

Separate adjacent interpolation tokens and clarify pre-merge diagnostics. Actual merge replay, 11 focused Node suites, 21 human-preference checks and Veps/Tamazight UI cases in Chromium, Firefox and WebKit pass. The Quechua width label uses attested kinraynin; full grammar remains low confidence pending native review.

Thanks to xet7 !
2026-09-20 11:42:30 +03:00
Lauri Ojansivu 62db6d74d1 Merge remote-tracking branch 'origin/main' 2026-09-20 11:16:36 +03:00
Lauri Ojansivu 2b94d87dcc Document source development server and verified browser regressions
Record sequential EVERYTHING coverage across Chromium, Firefox and WebKit, development database lifecycle checks, application fixes and the companion FerretDB test-runner fixes. State unsupported browser-specific coverage and the unconfirmed intermittent Firefox failure.

Thanks to xet7 !
2026-09-20 05:38:05 +03:00
Lauri Ojansivu b104edeef1 Add source-loaded development server and sequential EVERYTHING testing
Add Dev server nobuild to both build menus with shared URL and port options. Load checkout JavaScript and templates in memory using the installed Meteor toolchain; start its bundled MongoDB on the next port and wait for owned database shutdown.

Run server, Node, import, E2E and all supported Chromium, Firefox and WebKit regressions sequentially, stopping on failure. Preserve CommonJS semantics, native fetch, dynamic import namespaces and client source access boundaries. Document verified results and the remaining Windows and intermittent Firefox limits.

Thanks to xet7 !
2026-09-20 05:37:00 +03:00
Lauri Ojansivu 970ac9dc5f Tolerate partial user profiles when closing cards
Read the current user once and only offer description recovery when its published profile explicitly enables it. Session initialization can publish the user before the profile, which previously threw from the details-pane close handler.

Exercise missing users and profiles, recovery opt-in, unchanged drafts, save and discard from the actual handler. A browser regression removes the profile and closes a card without errors; it passes in Chromium, Firefox and WebKit.

Thanks to xet7 !
2026-09-20 05:06:28 +03:00
Lauri Ojansivu fc0c3c815b Exercise serial browser checks across all supported engines
Run portable drag-sort tests in Chromium, Firefox and WebKit and include global feature toggles in serial runs. Keep drag gestures inside the viewport and focus comment inputs with a real click before testing mention hit targets. Require the swimlane creation popup and its input instead of accepting an unrelated visible lane.

Affected tests passed in all three browsers. Raw CDP touch remains Chromium-only.

Thanks to xet7 !
2026-09-20 04:54:19 +03:00
Lauri Ojansivu f50b4786fe Wait for authenticated controls in report and mobile regressions
Use document-ready navigation for report permissions, board comment publication and phone board layout. Explicitly await the non-admin session before testing version lookup authorization. Positive and negative comment visibility and phone geometry assertions remain intact and pass in Chromium.

Thanks to xet7 !
2026-09-20 01:58:42 +03:00
Lauri Ojansivu 4c40bbf9f5 Filter document keyboard events against the focused control
The non-Latin keyboard bridge dispatches synthetic events on document. The shortcut filter previously called closest on that non-element target, throwing during Tab navigation on signed-out pages. Fall back to the focused element and safely reject missing focus.

Unit tests cover native and document-targeted events, form and interactive controls, editable content, selections, disabled shortcuts and Escape. The signed-out layout browser regression passes in Chromium.

Thanks to xet7 !
2026-09-20 01:51:22 +03:00
Lauri Ojansivu ef381207cc Wait for ready board views in template and pagination tests
Replace idle-network navigation waits with document readiness followed by the existing user, control, sorting, search and permission assertions. Template copying, board sort/search and server pagination regressions pass in Chromium on the source server.

Thanks to xet7 !
2026-09-20 01:15:14 +03:00
Lauri Ojansivu 8e9093154e Wait for card fields after document-ready reloads
Preserve Requested By and Assigned By accessibility checks and description persistence assertions without waiting for persistent Meteor connections to become idle. The card members and description browser group passes in Chromium.

Thanks to xet7 !
2026-09-20 00:46:27 +03:00
Lauri Ojansivu 0a1ad00b82 Wait for voting controls instead of idle Meteor connections
Keep voted-state, database membership and visible count assertions after document-ready reloads. Positive, negative and vote-count regressions pass against the source development server in Chromium.

Thanks to xet7 !
2026-09-20 00:41:35 +03:00
Lauri Ojansivu e3273cd2b5 Require the selected label badge and wait for usable date controls
Avoid idle-network reload waits with persistent Meteor connections. Require the seeded label to be selected and displayed instead of accepting an open popup as success.

The focused label regression and the remaining date-editor Chromium tests pass against the source development server.

Thanks to xet7 !
2026-09-20 00:31:20 +03:00
Lauri Ojansivu 24629d1752 Wait for rendered state in layout and stability regressions
Replace idle-network navigation waits with document readiness and the existing layout, session and persistence assertions. Meteor SockJS requests can stay active after the page is ready. Verify immediate comment persistence, list order, view changes and repeated sign-in page loads on the source development server.
2026-09-20 00:23:45 +03:00
Lauri Ojansivu a171ac0cb5 Restore protected initial URLs after cookie login
Remember the requested path before the sign-in guard redirects an initial page load. After authentication, restore that local URL instead of always opening All Boards, using the router redirect API to preserve deployment prefixes. Keep external targets rejected and sign-in enforcement unchanged. Verify direct My Cards navigation, assigned-card inclusion, unassigned-card exclusion and cookie/Sandstorm regressions.
2026-09-20 00:14:29 +03:00
Lauri Ojansivu 6d9a3a95b1 Show unread notification alerts before opening the drawer
Count unread profile records for the bell without requiring activity details that are subscribed only by the open drawer. Cover read and missing records, and verify a watching member receives a live mention alert in a separate browser session.
2026-09-20 00:03:51 +03:00
Lauri Ojansivu 01e985a6dd Verify keyboard-accessible password visibility controls
Align the sign-up browser regression with the adopted A05 accessibility behavior. Tab reaches both visibility buttons; Space and Enter reveal and hide passwords without submitting a valid form. Enter in the confirmation field still creates the account, and test accounts are cleaned up on failure.
2026-09-19 23:48:36 +03:00
Lauri Ojansivu 53b112c25f Wait for browser controls and fail when the test server is unavailable
Replace idle-network reload waits in membership and search regressions with document readiness and existing visible-control assertions. Verify member removal instead of silently bypassing missing controls. Bound HTTP and HTTPS readiness checks and reject failed, unhealthy or stalled servers rather than skipping all browser tests successfully.
2026-09-19 23:45:21 +03:00
dependabot[bot] 2282789661 Bump docker/build-push-action from 7.3.0 to 7.4.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 7.3.0 to 7.4.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/53b7df96c91f9c12dcc8a07bcb9ccacbed38856a...c3c9e263c25d99ce0380d002d59b67737d91b0dc)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-19 20:33:56 +00:00
Lauri Ojansivu c063877462 Select card destinations from ready subscription documents
Avoid a stale empty swimlane or list cache in the destination subscription ready callback. Cross-board moves now select a valid destination and enable Done without waiting for another user change. Cover delayed cache refresh alongside stale requests and rejected operations.
2026-09-19 23:31:18 +03:00
Lauri Ojansivu cbc6c20ba8 Measure delayed headers after Blaze renders them
Keep mobile sidebar controls below headers that appear after login or a route change. Cover delayed rendering, resize updates and destroyed templates, and wait for document readiness in the mobile Search browser regression.
2026-09-19 23:23:12 +03:00
github-actions[bot] 289ff9fb17 Bump versions for v11.87 2026-09-19 16:23:58 +00:00
Lauri Ojansivu 6bfceb80d6 Prepare v11.87 release 2026-09-19 19:22:24 +03:00
Lauri Ojansivu d0d9195c1c Document notification delivery verification for #6658. 2026-09-19 16:07:36 +03:00
Lauri Ojansivu fd238d34c3 Verify muted-board fixes through actual SMTP delivery.
The muted-assignment fix in 4be77e7189 and scoped-subscription/settings fix in 03e8a6bf66 are already present. Add browser regressions that capture SMTP email for board, list and card watching, comments, title changes and new cards; verify muted assignments and member email opt-outs stay silent. Correct the channel settings guide and document delivery prerequisites and test setup.

Ten SMTP scenarios and six existing tray/settings scenarios passed across Chromium and Firefox against the current Meteor bundle with MongoDB. Focused recipient and notification Node suites passed. External inbox delivery and FerretDB runtime were not verified; the latest reported delivery failure was not reproduced.

Thanks to Nissulya and xet7 !

Fixes #6658,
2026-09-19 16:07:09 +03:00
Lauri Ojansivu c6763f496c Document clean task board features and complete regression checks. 2026-09-19 15:59:41 +03:00
Lauri Ojansivu 16b1b307fc Add cleaner task board presentation and date-only formats.
Add board controls for minicard collapse, labels above the title, checklist due dates and the outer checklist heading. Keep previously collapsed content accessible when its control is hidden, and preserve stored content and existing defaults.

Add personal and admin date-only formats across calendars and exports. Extend the existing smaller font presets with compact checklist and settings spacing. Cover persistence, permissions, layout and date behavior with unit and browser tests.

Thanks to xet7 !
2026-09-19 15:56:01 +03:00
Lauri Ojansivu e13fc2028a Document retired editor removal in Upcoming. 2026-09-19 14:33:52 +03:00
Lauri Ojansivu 5ed9fc944a Remove obsolete Summernote integration and editor configuration.
Use the Markdown textarea for every editor. Remove the retired environment and Snap options from server settings, all platform launchers, build scripts and current documentation. Keep released and archived changelog entries intact.

Keep notification HTML escaping independent of editor configuration, preserve attachment metadata and mention behavior, and cover Markdown and emoji round trips in browser tests. Also import the translator used by attachment error handlers.

Thanks to xet7 !
2026-09-19 14:32:29 +03:00
Lauri Ojansivu 43b0a8de1e Document audited usability fixes and issue regressions in Upcoming. 2026-09-19 14:02:16 +03:00
Lauri Ojansivu 896f1222bd Implement audited board usability and accessibility fixes.
Repair relative ordering and destination dialog lifecycles, retain failed operations, and make checklist, rule and attachment controls keyboard accessible. Improve scaled layouts and reuse canonical translated labels.

Add source-execution and Chromium/Firefox regression coverage, including awaiting background persistence before reload. Record implementation decisions and validation limits.

Thanks to xet7 !
2026-09-19 14:00:48 +03:00
Lauri Ojansivu 684411f139 Keep only the English proposed-fix audit. 2026-09-19 13:59:21 +03:00
Lauri Ojansivu 193c799539 Restore visible, selectable comment mention suggestions.
Initialize autocomplete once per editor, place its styled menu above card windows, and fall back to the textarea when Summernote is unavailable. Verify pointer and keyboard selection for password and LDAP account fixtures.

Thanks to bbyszio and xet7 !

Fixes #6704.
2026-09-19 13:26:08 +03:00
Lauri Ojansivu 337f601852 Restore card dragging after expanding collapsed lists.
Bind sortable initialization and reactive work to the replaceable list body, and clean it up on destruction. Cover repeated collapse, expansion, reload and drag permissions.

Thanks to Nich01asFox and xet7 !

Fixes #6705.
2026-09-19 13:26:08 +03:00
Lauri Ojansivu 0d9744bb8d Add Finnish translation of the proposed-fix audit.
Thanks to xet7 !
2026-09-19 13:26:08 +03:00
Lauri Ojansivu 4ec273068e Audit September 18 fix proposals against source and browser behavior. 2026-09-19 12:37:52 +03:00
Lauri Ojansivu 2dbf9c7f39 Clean up spacing in September 18 fixes report. 2026-09-19 12:23:34 +03:00
Lauri Ojansivu e693fe5212 Document Launchpad build recovery fixes and verification
Thanks to xet7 !
2026-09-18 20:09:31 +03:00
Lauri Ojansivu 29bd491e85 Fix Launchpad recovery, ref indexing and artifact downloads
Recover the project repository used for submission, retry recipe creation while Launchpad indexes main, and reject incomplete snaps before remote cleanup. Run the compatibility wrapper inside Snapcraft's own environment.

Verified all 1173 Node suites sequentially, workflow lint and script syntax. Regression coverage exercises delayed refs, unrelated errors, recovery paths, invalid downloads and launcher argument handling without remote writes.

Thanks to xet7 !
2026-09-18 20:08:50 +03:00
github-actions[bot] e1e64e92b4 Bump versions for v11.86 2026-09-18 04:39:23 +00:00
Lauri Ojansivu 566190a97e Prepare v11.86 release 2026-09-18 07:37:51 +03:00
Lauri Ojansivu 1c3bb8448b Document Snap recovery and attachment verification 2026-09-18 07:36:46 +03:00
Lauri Ojansivu bc5881ff90 Recover interrupted Snap builds and bound release attachments
The v11.85 logs show ARMHF failing during Launchpad polling after TLS EOF,
PPC64EL and S390X reaching the six-hour job cap while queued, and AMD64
successfully publishing to the Snap Store before its GitHub attachment hung.

Recover existing recipes before considering a new submission, and share one
five-hour waiter budget across attempts on every architecture. Prepare an
isolated complete source snapshot with Git metadata and logs outside the
project hash, and distinguish architectures so matrix recipes stay separate.
Move downloaded artifacts out before retrying recovery.

Bound GitHub snap attachments and verify remote names and sizes. Exercise
these paths with fixture repositories and mocked network commands, covering
TLS failures, absent recipes, denied credentials, queue timeouts, invalid
artifacts and failed uploads. Workflow parsing, shell syntax and Actionlint
pass. The full 1,172 Node suite sweep is verified with corrected guards rerun.
Live remote builds and publishing have not been run.

Thanks to xet7 !
2026-09-18 07:36:20 +03:00
github-actions[bot] 1657a96943 Bump versions for v11.85 2026-09-17 15:17:56 +00:00
Lauri Ojansivu ee4eeedaa2 Prepare v11.85 release 2026-09-17 18:16:04 +03:00
Lauri Ojansivu 20b7dd33fa Merge remote-tracking branch 'origin/main' 2026-09-17 18:15:42 +03:00
Lauri Ojansivu 53a23258cf Document the global date format setting and verification 2026-09-17 18:14:28 +03:00
Lauri Ojansivu 46607d4299 Add an admin date format override for everyone
Place the global date format checkbox and selector between Board member
list and Wait Spinner in Visibility settings. While enabled, use the
selected format for members and guests, call the card section Date and
hide its personal format selector. Preserve personal preferences so they
return when the override is disabled.

Apply the same policy to date displays, filter parsing and browser export
requests. Validate supported formats and retain admin-only settings writes.
Add positive, negative and browser regression coverage and document the
setting. Translate the new label without changing existing translations.

Lower-confidence new translations include Acehnese, Aymara, Cherokee,
Inuktitut, Kashmiri, Nahuatl, Veps, Volapuk and Wolaytta; native-speaker
review would improve these labels.

Thanks to Mathia2 and xet7 !

Fixes #6703,
2026-09-17 18:13:20 +03:00
github-actions[bot] 4f5013ec48 Bump versions for v11.84 2026-09-17 14:04:58 +00:00
Lauri Ojansivu e2993617bc Prepare v11.84 release 2026-09-17 17:02:52 +03:00
Lauri Ojansivu f297cd989b Document the My Cards and My Attachments popup fix
Thanks to Mathia2 and xet7 !
2026-09-17 16:33:31 +03:00
Lauri Ojansivu d01b3a3d92 Open the selected card from My Cards and My Attachments
Bind card and board identifiers explicitly on links rendered under lexical each loops. Open the subscribed card popup from the link rather than the unrelated outer Blaze data context, while preserving modified-click navigation.

Regression tests cover outer-context mismatch, nested click targets, missing identifiers, modified clicks, and sequential card popup selection on both pages. The fresh bundle passes both page regressions in Chromium, Firefox and WebKit using polling reactivity.

Thanks to Mathia2 and xet7 !

Fixes #6702,
2026-09-17 16:32:53 +03:00
Lauri Ojansivu 4c1bcca5f7 Document verified translation repair and card visibility guard fixes
Thanks to xet7 !
2026-09-17 14:21:01 +03:00
Lauri Ojansivu 5dd0b11f7c Keep translation repairs and card visibility guards aligned with reviewed changes
Preserve the newer Traditional Chinese wording in historical repair records. Locate the card read-only branch by its matching conditional rather than fixed indentation after optional field grouping.

Verified all 1169 Node suites pass, translation pull/fill protection passes, and missing requester or assigner avatars still fail the repaired guard.

Thanks to xet7 !
2026-09-17 14:20:35 +03:00
Lauri Ojansivu d23e666aec Document verified issue fixes and the remaining login investigation.
Record notification, empty-card-section, WIP-group and duplicate-checklist fixes with their regression coverage. Link the tested FerretDB oplog changes and retain #6701 in TODO Later until the affected snap's database is confirmed. Verify the exact resumed user in the session regression.

Thanks to Nissulya, fabiosalles, Mahgozar, rmb82 and xet7 !
2026-09-17 13:47:31 +03:00
Lauri Ojansivu fd41207c54 Cover LDAP session restoration around large directory changes.
Resume an LDAP account with 1600 directory users, verify reloads before and after a bulk deletion, keep the directory out of client publications, and reject invalid cookies. The test passes against MongoDB and patched FerretDB; retain #6701 pending confirmation of the reported deployment's backend.

Thanks to Nissulya and xet7 !

Refs #6701,
2026-09-17 13:44:10 +03:00
Lauri Ojansivu 783733623e Authorize and persist edits to saved WIP limit groups.
Route group edits through a validated server method with board/site-admin authorization. Compare the saved group array before updating so concurrent edits are not overwritten and database backends need no positional array update. Unit and Chromium tests cover editing, invalid lists and limits, and unauthorized users.

Thanks to Mahgozar and xet7 !

Fixes #6699,
2026-09-17 13:43:01 +03:00
Lauri Ojansivu 762e742b24 Verify muted-board and explicit-list notification delivery.
Check the persisted profile notification array and exercise comments delivered to explicit list subscribers on otherwise muted boards.

Thanks to Nissulya and xet7 !

Fixes #6658,
2026-09-17 13:34:50 +03:00
Lauri Ojansivu 5c9e5045f8 Preserve checklist insert validation while awaiting submission.
Keep the existing client insert path, wait for its server callback, and retain the editor on failure. Verified click, Enter and Ctrl+Enter creation and persistence in Chromium, plus duplicate-submit and retry unit tests.

Thanks to rmb82 and xet7 !

Fixes #6700,
2026-09-17 13:34:50 +03:00
Lauri Ojansivu 0a8ee08d56 Submit checklist creation once per user action.
Stop handled Enter events before the enclosing checklist submits them again. Await the insert, scope the textarea to its form, and reject duplicate submits while the write is pending. Keep failed edits available for retry.

Thanks to rmb82 and xet7 !

Fixes #6700,
2026-09-17 13:12:32 +03:00
Lauri Ojansivu 87d69358dc Make WIP group list checkboxes visible and editable.
Restore native checkbox visibility and keyboard interaction inside the WIP group editor, and resolve list rows through the template helper when editing saved groups.

Thanks to Mahgozar and xet7 !

Fixes #6699,
2026-09-17 13:08:06 +03:00
Lauri Ojansivu 5c7f455285 Hide empty card field groups and their separators.
Render Dates, Members and Sort groups only when their fields can display content. Keep a group when any independently enabled field remains visible.

Thanks to fabiosalles and xet7 !

Fixes #6696,
2026-09-17 13:07:01 +03:00
Lauri Ojansivu 03e8a6bf66 Honor scoped notification subscriptions and settings.
Keep muted board assignments silent while honoring explicit list and card subscriptions. Resolve notification controls against their owning settings scope and show the selected option.

Thanks to Nissulya and xet7 !

Fixes #6658,
2026-09-17 13:05:45 +03:00
Lauri Ojansivu 959f15cbe0 Merge pull request #6697 from AmigaAbattoir/patch-1
fix(docs): images missing in multiple lines to checklist page
2026-09-16 21:52:36 +03:00
Christopher Pollati d87ec3954b fix(docs): images missing in multiple lines to checklist page 2026-09-16 10:26:41 -04:00
github-actions[bot] 97f74ea3b5 Bump versions for v11.83 2026-09-15 23:48:36 +00:00
Lauri Ojansivu 278f7b869a Prepare v11.83 release 2026-09-16 02:47:17 +03:00
Lauri Ojansivu e71e20036c Merge remote-tracking branch 'origin/main' 2026-09-16 02:47:00 +03:00
Lauri Ojansivu 9aeeac51a8 Bring upcoming changelog into release format 2026-09-16 02:38:41 +03:00
Lauri Ojansivu 522a7e88e8 Document custom logo loading and uploads in upcoming changelog 2026-09-16 02:26:43 +03:00
Lauri Ojansivu 593cab5911 Load configured logos without stock logo flash and allow uploads 2026-09-16 02:26:25 +03:00
Lauri Ojansivu a4eda40c89 Document live-only mirror links in upcoming changelog 2026-09-16 02:07:37 +03:00
Lauri Ojansivu f7da598ad2 Stop using archive.org for mirror linked files 2026-09-16 02:07:22 +03:00
github-actions[bot] f32a0f7e10 Bump versions for v11.82 2026-09-15 22:40:21 +00:00
Lauri Ojansivu e478ce7309 Prepare v11.82 release 2026-09-16 01:38:35 +03:00
Lauri Ojansivu f60df9b0be Merge remote-tracking branch 'origin/main' 2026-09-16 01:38:07 +03:00
Lauri Ojansivu b68dd01893 Document release preflight and keep changelog checks current 2026-09-16 01:37:35 +03:00
Lauri Ojansivu 7489120eb9 Validate translation languages before release tagging 2026-09-16 01:35:55 +03:00
github-actions[bot] f238c02af7 Bump versions for v11.81 2026-09-15 22:23:51 +00:00
Lauri Ojansivu 3ecc62c960 Prepare v11.81 release 2026-09-16 01:22:26 +03:00
Lauri Ojansivu 5e951845eb Update Upcoming WeKan changelog for recent fixes 2026-09-16 01:17:08 +03:00
Lauri Ojansivu 3865c3fdea Avoid incomplete escaping in Veps sync test 2026-09-16 01:10:05 +03:00
Lauri Ojansivu 8acf5d947d Preserve PR 6695 human translations across pull and push 2026-09-16 01:07:00 +03:00
Lauri Ojansivu c399aeeee6 Protect PR 6695 human translations from machine fills 2026-09-16 01:04:06 +03:00
Lauri Ojansivu 2078c21819 Merge pull request #6695 from l10n-tw/main
Replace zh-Hant and zh-TW translations with Transifex ones
2026-09-16 01:01:44 +03:00
Lauri Ojansivu 25ed0c3067 Keep local Docker build available during Quay outages 2026-09-16 00:58:35 +03:00
Lauri Ojansivu 0e962bba3f Record interrupted translation audit status 2026-09-16 00:54:14 +03:00
Lauri Ojansivu 99570e213f Serve Admin Panel assetlinks at well-known URL 2026-09-16 00:51:35 +03:00
Lauri Ojansivu 1cf93f7e52 Distinguish native Tamazight card and list URL labels 2026-09-15 15:54:57 +03:00
Lauri Ojansivu bfac55c30c Record dated board and card translation audit progress 2026-09-15 15:52:25 +03:00
Lauri Ojansivu 18a11d1210 Replace Arabic and French Tamazight board and card labels 2026-09-15 15:51:48 +03:00
Lauri Ojansivu 77d574a627 Record dated Tamazight version-check audit progress 2026-09-15 15:49:32 +03:00
Lauri Ojansivu ff5a286b53 Replace mixed Tamazight version-check messages 2026-09-15 15:48:22 +03:00
Lauri Ojansivu cf14bd7db9 Record dated Tamazight version terminology progress 2026-09-15 15:46:44 +03:00
Lauri Ojansivu 413b4ef405 Use native Tamazight version noun in Admin Panel 2026-09-15 15:46:05 +03:00
Lauri Ojansivu 7da3334c47 Record dated Tamazight shared-label repair progress 2026-09-15 15:42:18 +03:00
Lauri Ojansivu 63da3d3c97 Reuse native Tamazight labels in shared UI controls 2026-09-15 15:41:26 +03:00
Lauri Ojansivu 1cf705484e Record Tamazight popup audit progress 2026-09-15 15:28:59 +03:00
Lauri Ojansivu e11a2654c7 Align Arabic Tamazight popup labels with native controls 2026-09-15 15:27:09 +03:00
Lauri Ojansivu 1405047ba7 Record Tamazight noun audit progress and source evidence 2026-09-15 15:24:39 +03:00
Lauri Ojansivu 2b22dd0bef Replace Arabic Tamazight common nouns with native labels 2026-09-15 15:23:19 +03:00
Lauri Ojansivu 3447011b4d Record successful sequential EVERYTHING verification 2026-09-15 15:17:51 +03:00
Lauri Ojansivu 7a37811883 Record browser matrix and Firefox avatar fixture repair 2026-09-15 13:57:45 +03:00
Lauri Ojansivu 66c5b85c30 Use a valid PNG in legacy avatar browser coverage 2026-09-15 13:57:26 +03:00
Lauri Ojansivu 7b5dd6549d Document sequential board title viewer coverage 2026-09-15 12:59:21 +03:00
Lauri Ojansivu 929326ba22 Exercise board title viewers through visible controls
Activate the card link instead of its date badge and open the Board View popup before choosing timeline, chart and Gantt views. The complete 24-case Chromium title suite passes sequentially.
2026-09-15 12:58:07 +03:00
Lauri Ojansivu 56f04dcab7 Guard external link autolink sync against repeated renders
Update the regression check to require one value-guarded ReactiveVar setter shared by the viewer and startup autorun. The focused external-link suite now passes all 24 tests.
2026-09-15 12:20:06 +03:00
Lauri Ojansivu 128e482d7a Document browser and markdown repairs in Upcoming release 2026-09-15 12:12:08 +03:00
Lauri Ojansivu fb66fa95cf Repair browser test flows and Meteor markdown package exports
Expose the renderer through a Meteor main module, synchronize security settings without repeated reactive invalidation, and route canonical avatar downloads around the automatic Meteor-Files handler. Update browser tests for the visible Board View and list menus, correct fixture IDs and authentication headers, and wait for the settings publication where direct Mongo updates are used.
2026-09-15 12:10:40 +03:00
Lauri Ojansivu 0ebfe1d775 Record security reporting and node regression repairs 2026-09-15 10:36:01 +03:00
Lauri Ojansivu c4c6e34ec4 Repair node regressions and align security problem names 2026-09-15 10:35:31 +03:00
Lauri Ojansivu 9b49eb1c43 Document Docker outage handling and repair language test harness 2026-09-15 10:16:14 +03:00
Lauri Ojansivu e6685eb0b4 Keep Docker publication available during Quay read-only outages 2026-09-15 10:12:39 +03:00
Lauri Ojansivu 0134b39520 Record Tamazight popup title audit progress 2026-09-15 10:12:19 +03:00
Lauri Ojansivu 79e5a93b42 Repair Tamazight common popup titles 2026-09-15 10:09:27 +03:00
Lauri Ojansivu 0b7606194e Record Tamazight Unset context audit progress 2026-09-15 10:04:49 +03:00
Lauri Ojansivu f0ceb8d72b Separate translated removal actions from Unset status 2026-09-15 10:04:08 +03:00
Lauri Ojansivu 0b2cc01e6f Record Tamazight board popup audit progress 2026-09-15 09:58:53 +03:00
Lauri Ojansivu 87c0495710 Repair Tamazight board and color popup titles 2026-09-15 09:58:15 +03:00
Lauri Ojansivu 0539ac8a23 Merge remote-tracking branch 'origin/main' 2026-09-15 09:55:38 +03:00
Lauri Ojansivu 6de53fb0c1 Record Tamazight board background audit progress 2026-09-15 09:55:12 +03:00
Lauri Ojansivu 79d58098f4 Repair Tamazight board background controls 2026-09-15 09:54:38 +03:00
Lauri Ojansivu 8922a87f2a Record Tamazight activity audit progress 2026-09-15 09:52:17 +03:00
Lauri Ojansivu ada7e35333 Repair Tamazight activity messages using UI meanings 2026-09-15 09:51:39 +03:00
Lauri Ojansivu 1990e60396 Record Tamazight admin status audit progress 2026-09-15 09:49:06 +03:00
Lauri Ojansivu f10daf2ae9 Repair Tamazight admin activity status labels 2026-09-15 09:48:29 +03:00
Lauri Ojansivu 987c5c22b5 Record Tamazight filter and sort audit progress 2026-09-15 09:47:07 +03:00
Lauri Ojansivu 65c6674196 Repair Tamazight filter and sort control labels 2026-09-15 09:46:20 +03:00
Lauri Ojansivu a58c572961 Record Tamazight creator-label audit progress 2026-09-15 09:44:15 +03:00
Lauri Ojansivu bea96680ee Repair Tamazight creator labels in filters and minicards 2026-09-15 09:43:31 +03:00
Lauri Ojansivu f30c32e897 Record Tamazight search predicate audit progress 2026-09-15 09:41:33 +03:00
Lauri Ojansivu 32c2d95756 Repair Tamazight search predicates and protect syntax keywords 2026-09-15 09:38:45 +03:00
Lauri Ojansivu fc2a8da2c8 Record Tamazight search syntax audit progress 2026-09-15 09:32:43 +03:00
Lauri Ojansivu c365e2cf6a Repair Tamazight search operator names and aliases 2026-09-15 09:31:43 +03:00
Lauri Ojansivu 278ca10a0f Record Tamazight board label repairs and Arabic-script queue 2026-09-15 09:27:31 +03:00
Lauri Ojansivu d1c4a7290a Replace Arabic and French Tamazight board labels 2026-09-15 09:26:37 +03:00
Lauri Ojansivu f851f409d7 Record Tamazight sky and metal-color audit progress 2026-09-15 09:23:34 +03:00
Lauri Ojansivu e114ffde89 Replace Tamazight sky and metal-color seeds 2026-09-15 09:22:50 +03:00
Lauri Ojansivu 5fcd43df39 Record Tamazight color repairs and remaining plural review 2026-09-15 09:20:42 +03:00
Lauri Ojansivu 06678678d0 Replace Arabic and French Tamazight color labels 2026-09-15 09:19:58 +03:00
Lauri Ojansivu 3ed7dd3761 Record Tamazight email-address repair and plural review 2026-09-15 09:14:58 +03:00
Lauri Ojansivu ce50929661 Replace French Tamazight email-address placeholder 2026-09-15 09:14:13 +03:00
Lauri Ojansivu a43727f0bc Record Tigre slate-blue review and reconcile overlap counts 2026-09-15 09:10:49 +03:00
Lauri Ojansivu 6f14c9a8c6 Repair Tigre slate-blue component 2026-09-15 09:09:19 +03:00
Lauri Ojansivu 59a7261e33 Record Tigre dark-green audit progress 2026-09-15 09:06:57 +03:00
Lauri Ojansivu de0cc0ce45 Repair copied Tigre dark-green translation 2026-09-15 09:05:50 +03:00
Lauri Ojansivu 10fff07af3 Record dated Tigre transport corpus audit 2026-09-15 09:00:19 +03:00
Jeff Huang 460263f21b Replace zh-Hant and zh-TW translations with Transifex ones 2026-09-15 14:00:08 +08:00
Lauri Ojansivu ae76ba79d0 Use corpus Tigre noun for DDP transport 2026-09-15 08:58:45 +03:00
Lauri Ojansivu 23b77ed14a Reconcile translation audit summary after width repairs 2026-09-15 08:54:31 +03:00
Lauri Ojansivu f4097bfda1 Record dated final width-rule translation audit 2026-09-15 08:53:36 +03:00
Lauri Ojansivu 31a238d91b Correct Cherokee and Wolaytta list-width bounds 2026-09-15 08:52:54 +03:00
Lauri Ojansivu 4ac8c5d129 Record dated Xitsonga translation audit 2026-09-15 08:49:12 +03:00
Lauri Ojansivu 21319c523d Replace corrupt Tsonga list-width seeds 2026-09-15 08:48:42 +03:00
Lauri Ojansivu c95017d560 Record dated Aymara Quechua translation audit 2026-09-15 08:47:12 +03:00
Lauri Ojansivu 83c8d0c2bb Repair Aymara and Quechua list-width strings 2026-09-15 08:46:36 +03:00
Lauri Ojansivu 04df435baf Record dated Hawaiian Klingon Inuktitut audit 2026-09-15 08:43:38 +03:00
Lauri Ojansivu c18780d5bd Correct three more native list-width bounds 2026-09-15 08:43:08 +03:00
Lauri Ojansivu 4e67398440 Record dated three-locale width repair audit 2026-09-15 08:41:11 +03:00
Lauri Ojansivu bb2d2dfebe Correct three native list-width minimum messages 2026-09-15 08:40:38 +03:00
Lauri Ojansivu 0bd8f32316 Record dated Serbian and regional translation audit progress 2026-09-15 08:35:55 +03:00
Lauri Ojansivu beddec7cb9 Repair Serbian list-width controls and four locale rules 2026-09-15 08:35:09 +03:00
Lauri Ojansivu 77cf4af47e Record dated wrong-language translation audit progress 2026-09-15 08:31:00 +03:00
Lauri Ojansivu 3eaeac9e7e Repair ten wrong-language and obsolete list-width values 2026-09-15 08:29:49 +03:00
Lauri Ojansivu e855bb7ede Record dated native-minimum translation audit progress 2026-09-15 08:25:37 +03:00
Lauri Ojansivu 92ea2a0f0c Repair ten native list-width minimum translations 2026-09-15 08:25:01 +03:00
Lauri Ojansivu 91555657e2 Record dated ten-locale translation audit progress 2026-09-15 08:21:53 +03:00
Lauri Ojansivu 9c1388a496 Repair ten native and mixed-seed list-width values 2026-09-15 08:21:00 +03:00
Lauri Ojansivu 376ecf3043 Record dated nine-locale translation audit progress 2026-09-15 08:18:23 +03:00
Lauri Ojansivu 3bf6a4c821 Repair nine African and Pacific list-width translations 2026-09-15 08:17:46 +03:00
Lauri Ojansivu bf18a5b737 Record dated list-width audit progress for twelve locales 2026-09-15 08:15:04 +03:00
Lauri Ojansivu 74e6deabe8 Repair twelve African, Kurdish and Yiddish list-width translations 2026-09-15 08:14:26 +03:00
Lauri Ojansivu e20321100b Record Asian-script list-width audit progress on 2026-09-15 2026-09-15 08:08:37 +03:00
Lauri Ojansivu 8cc659ac92 Repair 24 Asian list-width locale values and progress gates 2026-09-15 08:07:38 +03:00
Lauri Ojansivu 217dd1e3df Record Romance and Celtic audit progress on 2026-09-15 2026-09-15 08:02:49 +03:00
Lauri Ojansivu 0fe4ecb480 Repair twenty Romance and Celtic list-width meanings 2026-09-15 08:02:03 +03:00
Lauri Ojansivu d0388a2c0e Record Eurasian list-width audit progress on 2026-09-15 2026-09-15 07:57:31 +03:00
Lauri Ojansivu 6d94f4edb4 Repair 28 locale list-width rules and update regression gates 2026-09-15 07:56:49 +03:00
Lauri Ojansivu 17d3a34054 Record Chinese variant audit progress on 2026-09-15 2026-09-15 07:52:18 +03:00
Lauri Ojansivu 0b0349d34d Repair Chinese variant list-width rules in eleven locales 2026-09-15 07:51:42 +03:00
Lauri Ojansivu 7bf55675f6 Record native lower-bound audit progress on 2026-09-15 2026-09-15 07:48:52 +03:00
Lauri Ojansivu 212a46c078 Repair 33 native list-width rules and align Galician archive check 2026-09-15 07:48:17 +03:00
Lauri Ojansivu 0fdeaf7994 Record multilingual list-width audit progress on 2026-09-15 2026-09-15 07:44:58 +03:00
Lauri Ojansivu d6e6c558d6 Repair 22 multilingual list-width validation translations 2026-09-15 07:44:00 +03:00
Lauri Ojansivu 3a6463541c Record German and Romance list-width translation repairs 2026-09-15 07:38:28 +03:00
Lauri Ojansivu e6895b3dee Repair Romance and German list-width validation translations 2026-09-15 07:37:31 +03:00
Lauri Ojansivu 49a0654bd1 Document Persian-digit list-width repairs 2026-09-15 07:35:35 +03:00
Lauri Ojansivu 196bc49df9 Repair Persian-digit list-width messages 2026-09-15 07:34:12 +03:00
Lauri Ojansivu c249bacaa2 Record list-width source mismatch and locale repair queue 2026-09-15 07:32:39 +03:00
Lauri Ojansivu 68d679c6c7 Align list-width validation and source translations with 200px minimum 2026-09-15 07:30:40 +03:00
Lauri Ojansivu 8bf19dada2 Document Tigre Trello and S3 plural repair 2026-09-15 07:23:24 +03:00
Lauri Ojansivu 6909fb85e7 Repair Tigre Trello key terminology and S3 file plural 2026-09-15 07:22:09 +03:00
Lauri Ojansivu 99e0121b0e Document Tigre PDF preview translation review 2026-09-15 07:17:34 +03:00
Lauri Ojansivu 78f2c9bf78 Repair Tigre PDF preview warning copied from Tigrinya 2026-09-15 07:16:11 +03:00
Lauri Ojansivu e24a4fe717 Document Tigre authentication error review 2026-09-15 07:14:24 +03:00
Lauri Ojansivu 36c2148ecb Repair Tigre authentication error copied from Tigrinya 2026-09-15 07:12:31 +03:00
Lauri Ojansivu 7030814546 Record Tigre S3 translation audit progress 2026-09-15 07:10:05 +03:00
Lauri Ojansivu 970d9e664b Fill Tigre S3 settings English placeholders 2026-09-15 07:08:17 +03:00
Lauri Ojansivu 3d8b41be8c Document Tamazight due-reminder activity repair 2026-09-15 07:04:46 +03:00
Lauri Ojansivu 780a75a2bb Repair French Tamazight due-reminder activity 2026-09-15 07:04:00 +03:00
Lauri Ojansivu d140ac347f Document corpus-attested Tigre plural follow-up 2026-09-15 06:59:29 +03:00
Lauri Ojansivu 5d53aa9904 Correct provisional Tigre plurals using corpus evidence 2026-09-15 06:58:48 +03:00
Lauri Ojansivu 95a3a4baff Document Tigre Star and Break repairs 2026-09-15 06:54:02 +03:00
Lauri Ojansivu 009d7f8329 Repair Tigre star and break terms and refresh reviewed tests 2026-09-15 06:53:22 +03:00
Lauri Ojansivu f4dd67eb3b Document Tigre Endpoint meaning repair 2026-09-15 06:48:18 +03:00
Lauri Ojansivu 6b78c88cb9 Repair Tigre Endpoint terminology 2026-09-15 06:47:45 +03:00
Lauri Ojansivu c450740d78 Document Tigre exact control reviews 2026-09-15 06:46:53 +03:00
Lauri Ojansivu 0eac733cdb Repair corpus-supported Tigre exact controls 2026-09-15 06:45:57 +03:00
Lauri Ojansivu 0f9f13d1d9 Document missed Tigre organizations repair 2026-09-15 06:44:49 +03:00
Lauri Ojansivu 0ef3ca6dd6 Repair missed Tigre organizations control 2026-09-15 06:44:21 +03:00
Lauri Ojansivu 238e331278 Document full Tigre noun family repairs 2026-09-15 06:43:26 +03:00
Lauri Ojansivu 08e7092c36 Repair established Tigre noun families throughout 2026-09-15 06:42:10 +03:00
Lauri Ojansivu d954922c90 Document full Tigre board and card cleanup 2026-09-15 06:38:45 +03:00
Lauri Ojansivu 83546ed248 Repair Tigre board and card terms throughout 2026-09-15 06:37:59 +03:00
Lauri Ojansivu b30134be78 Document Tigre short control repairs 2026-09-15 06:34:26 +03:00
Lauri Ojansivu 40a20207bd Repair Tigre short interface controls 2026-09-15 06:33:45 +03:00
Lauri Ojansivu 4e09c23b21 Document additional Tigre interface repairs 2026-09-15 06:32:18 +03:00
Lauri Ojansivu 088b01587f Repair additional Tigre interface terms 2026-09-15 06:31:27 +03:00
Lauri Ojansivu 828800a301 Document second Tigre ledger reuse pass 2026-09-15 06:29:02 +03:00
Lauri Ojansivu 45a568aea7 Reuse reviewed Tigre terms in remaining controls 2026-09-15 06:28:06 +03:00
Lauri Ojansivu 2fa5a88449 Document Tigre edit and template repairs 2026-09-15 06:25:39 +03:00
Lauri Ojansivu e50072bc32 Repair Tigre edit and template terms 2026-09-15 06:24:01 +03:00
Lauri Ojansivu 876d879596 Document embedded Tigre terminology repairs 2026-09-15 06:22:22 +03:00
Lauri Ojansivu eb1add1f21 Repair embedded Tigre account storage and description terms 2026-09-15 06:21:31 +03:00
Lauri Ojansivu 79b144ae76 Document Tigre account storage and template repairs 2026-09-15 06:19:12 +03:00
Lauri Ojansivu 8296c12a57 Repair Tigre account storage and template terms 2026-09-15 06:17:14 +03:00
Lauri Ojansivu 9173c62599 Document remaining Tigre card repairs 2026-09-15 06:14:14 +03:00
Lauri Ojansivu c73362aae6 Repair remaining Tigre card terms 2026-09-15 06:13:23 +03:00
Lauri Ojansivu b0fd098cea Document Tigre outcome and account repairs 2026-09-15 06:11:46 +03:00
Lauri Ojansivu c91e56a504 Repair Tigre outcome and account terms 2026-09-15 06:10:55 +03:00
Lauri Ojansivu 1ae4305237 Document Tigre date and time repairs 2026-09-15 06:09:49 +03:00
Lauri Ojansivu 0896117b5a Repair Tigre date and time terms 2026-09-15 06:08:58 +03:00
Lauri Ojansivu 4a8323c96f Document Tigre noun repairs 2026-09-15 06:07:42 +03:00
Lauri Ojansivu 242c1f27ba Repair Tigre label organization and team terms 2026-09-15 06:06:53 +03:00
Lauri Ojansivu b658832f59 Document repeated Tigre component repairs 2026-09-15 06:05:39 +03:00
Lauri Ojansivu 8b6c21ff90 Repair repeated Tigre interface components 2026-09-15 06:04:49 +03:00
Lauri Ojansivu 7e49057262 Document Tigre board repairs 2026-09-15 06:02:56 +03:00
Lauri Ojansivu 1196bebaab Repair Tigre board terms 2026-09-15 06:02:01 +03:00
Lauri Ojansivu 79708827c7 Document Tigre file-clause repairs 2026-09-15 06:00:37 +03:00
Lauri Ojansivu 71a15613c7 Repair Tigre file terms in longer clauses 2026-09-15 05:59:48 +03:00
Lauri Ojansivu f81c9ab58f Document Tigre card and file repairs 2026-09-15 05:58:49 +03:00
Lauri Ojansivu 8560f3d7c2 Repair Tigre card and file terms 2026-09-15 05:58:06 +03:00
Lauri Ojansivu b0bef07079 Document shared Tigre plural review 2026-09-15 05:55:59 +03:00
Lauri Ojansivu a69ed23251 Retain attested Tigre account and error plurals 2026-09-15 05:55:23 +03:00
Lauri Ojansivu a56cccfeb3 Document multi-component Tigre repairs 2026-09-15 05:54:08 +03:00
Lauri Ojansivu f64f9c98d3 Repair multi-component Tigre labels 2026-09-15 05:52:49 +03:00
Lauri Ojansivu 4a25311bf7 Document compact Tigre repairs 2026-09-15 05:47:59 +03:00
Lauri Ojansivu fc61397455 Repair compact Tigre terminology 2026-09-15 05:47:17 +03:00
Lauri Ojansivu edb736acf0 Document Tigre name repairs 2026-09-15 05:43:52 +03:00
Lauri Ojansivu 002f8bc7bb Repair Tigre name controls 2026-09-15 05:43:11 +03:00
Lauri Ojansivu fcc461b156 Document Tigre import repairs 2026-09-15 05:41:32 +03:00
Lauri Ojansivu b8bbd8fa39 Repair Tigre import controls 2026-09-15 05:40:52 +03:00
Lauri Ojansivu 69c1145a87 Document reused Tigre terms 2026-09-15 05:39:29 +03:00
Lauri Ojansivu 22615501d1 Reuse reviewed Tigre interface terms 2026-09-15 05:38:51 +03:00
Lauri Ojansivu 46cccccd3a Document Tigre selection repairs 2026-09-15 05:37:13 +03:00
Lauri Ojansivu a6dd320fd2 Repair Tigre selection controls 2026-09-15 05:36:33 +03:00
Lauri Ojansivu d73a200439 Document Tigre date repairs 2026-09-15 05:35:04 +03:00
Lauri Ojansivu a03aa027cb Repair Tigre date controls 2026-09-15 05:33:22 +03:00
Lauri Ojansivu d44beb0228 Document Tigre member compound repairs
Record reused corpus evidence, residual overlap, limitations and regression coverage for seven corrected member controls.\n\nThanks to xet7 !
2026-09-15 05:28:28 +03:00
Lauri Ojansivu 284fcff144 Repair Tigre member compounds
Use the explicit Tigre Member and Members pair across seven board, filter, selection and rule controls. Reuse reviewed Tigre control terms and add former-seed coverage.\n\nThanks to xet7 !
2026-09-15 05:27:54 +03:00
Lauri Ojansivu d39930eb77 Document ledger-grounded Tigre repairs
Record the reused evidence, residual overlap, limitations and regression coverage for 17 corrected Tigre compounds.\n\nThanks to xet7 !
2026-09-15 05:25:10 +03:00
Lauri Ojansivu 0651f745bc Repair Tigre interface compounds
Replace 17 compact Tigrinya-seeded controls using only independently reviewed Tigre ledger terms. Add exact and former-seed regression coverage.\n\nThanks to xet7 !
2026-09-15 05:24:37 +03:00
Lauri Ojansivu 387e10980e Document Tigre speed and storage repairs
Record corpus and native-interface evidence, residual overlap and regression coverage for four corrected controls.\n\nThanks to xet7 !
2026-09-15 05:21:32 +03:00
Lauri Ojansivu b8ebfc67e3 Repair Tigre speed and storage terms
Replace Tigrinya-specific Speed and Storage spellings with forms attested in the Tigre corpus and native Tigre Wikidata interface. Add exact and former-seed coverage.\n\nThanks to xet7 !
2026-09-15 05:20:58 +03:00
Lauri Ojansivu 194f4556aa Document Tigre interface action repairs
Record corpus evidence, residual overlap, limitations and regression coverage for the Font, Invite and permissions controls.\n\nThanks to xet7 !
2026-09-15 05:20:07 +03:00
Lauri Ojansivu e4ae5173fc Repair Tigre interface actions
Use corpus-grounded Tigre terms across seven Font, Invite People and Change permissions controls. Add exact and former-seed regression coverage.\n\nThanks to xet7 !
2026-09-15 05:19:35 +03:00
Lauri Ojansivu 4a26ff5412 Document Tigre visual repairs
Record corpus evidence, residual overlap, limitations and regression coverage for the repaired Rename and board-background controls.\n\nThanks to xet7 !
2026-09-15 05:17:49 +03:00
Lauri Ojansivu f1b12cc668 Repair Tigre visual controls
Use corpus-attested Tigre name, change, image and background terms across eight Rename and board-background controls. Add exact and former-seed regression coverage.\n\nThanks to xet7 !
2026-09-15 05:17:15 +03:00
Lauri Ojansivu c0f430b0b3 Document Tigre status repairs
Record the evidence, residual overlap count, limitations and regression coverage for eight repaired Tigre status controls.\n\nThanks to xet7 !
2026-09-15 05:15:49 +03:00
Lauri Ojansivu b4586ba970 Repair Tigre status controls
Use corpus-attested Tigre terms across eight Overall progress, CPU usage, Card loading and Remaining time controls. Preserve CPU and add exact former-seed coverage.\n\nThanks to xet7 !
2026-09-15 05:15:16 +03:00
Lauri Ojansivu 33300f303e Add Tigre severity repair to changelog
Record the three corrected Severity controls in the upcoming WeKan release.\n\nThanks to xet7 !
2026-09-15 05:13:52 +03:00
Lauri Ojansivu c59789dfb5 Document Tigre severity repairs
Record the contradicted weight sense, replacement evidence, low-confidence compound limitation and residual overlap count.\n\nThanks to xet7 !
2026-09-15 05:13:39 +03:00
Lauri Ojansivu e27abadf67 Repair Tigre severity controls
Replace the misleading physical-weight seed in three Severity controls with a corpus-grounded Tigre severity-level compound. Record its compound grammar as low confidence and add negative coverage for the former seed.\n\nThanks to xet7 !
2026-09-15 05:13:06 +03:00
Lauri Ojansivu 3d2c249f21 Document Tigre export repairs
Record the corpus evidence, residual overlap count, limitations and regression coverage for the repaired Tigre Export controls.\n\nThanks to xet7 !
2026-09-15 05:12:12 +03:00
Lauri Ojansivu 6d8f830a70 Repair Tigre export controls
Use the Tigre export action attested in a software-context corpus entry across the generic, list and board export controls. Add exact and former-seed regression coverage.\n\nThanks to xet7 !
2026-09-15 05:11:36 +03:00
Lauri Ojansivu ee6607dc9d Document Tigre administrative term repairs
Record the evidence, residual overlap counts, test coverage and source commit for the reviewed Tigre administrative and location controls.\n\nThanks to xet7 !
2026-09-15 05:10:30 +03:00
Lauri Ojansivu 9f5f2d1dba Repair Tigre administrative controls
Use corpus-attested Tigre terminology for administrator, organization administrators, confirmation, type, location and adding a location. Add focused coverage for all corrected controls and their former Tigrinya seeds.\n\nThanks to xet7 !
2026-09-15 05:09:45 +03:00
Lauri Ojansivu fbfb37e272 Document Tigre member terminology correction 2026-09-15 05:06:21 +03:00
Lauri Ojansivu 2bb4b88cb5 Correct Tigre member terminology 2026-09-15 05:05:42 +03:00
Lauri Ojansivu 212232a5c0 Document Tigre interface compound repairs 2026-09-15 05:04:06 +03:00
Lauri Ojansivu 6f25928145 Repair Tigre interface compounds 2026-09-15 05:03:31 +03:00
Lauri Ojansivu eb797932fe Document Tigre operational term repairs 2026-09-15 05:02:14 +03:00
Lauri Ojansivu fa6cbd1666 Retain shared Tigre seconds term 2026-09-15 05:01:34 +03:00
Lauri Ojansivu a268d6167a Repair Tigre operational terms 2026-09-15 05:01:19 +03:00
Lauri Ojansivu 4936e9fc47 Document basic Tigre term repairs 2026-09-15 05:00:07 +03:00
Lauri Ojansivu 62ff72c607 Repair basic Tigre interface terms 2026-09-15 04:59:34 +03:00
Lauri Ojansivu 8c324ff5e9 Document Tigre sense repairs 2026-09-15 04:58:14 +03:00
Lauri Ojansivu 67632637c2 Repair conflicting Tigre interface senses 2026-09-15 04:57:44 +03:00
Lauri Ojansivu c839ae2bf3 Document shared Tigre term review 2026-09-15 04:56:51 +03:00
Lauri Ojansivu b2caad45b7 Retain corpus-backed shared Tigre terms 2026-09-15 04:56:15 +03:00
Lauri Ojansivu 154c3cc31f Document repeated Tigre noun repairs 2026-09-15 04:54:11 +03:00
Lauri Ojansivu f9b716fd61 Repair repeated Tigre interface nouns 2026-09-15 04:53:42 +03:00
Lauri Ojansivu 9b266cc353 Document Tigre Azure path repairs 2026-09-15 04:52:45 +03:00
Lauri Ojansivu 77e08a8f61 Repair Tigre Azure menu paths 2026-09-15 04:52:14 +03:00
Lauri Ojansivu a568dc4208 Document repeated Tigre interface repairs 2026-09-15 04:50:49 +03:00
Lauri Ojansivu 2530126f27 Repair repeated Tigre interface families 2026-09-15 04:50:10 +03:00
Lauri Ojansivu b7b7cd2de8 Document Tigre address and name repairs 2026-09-15 04:48:26 +03:00
Lauri Ojansivu 62956f2819 Repair Tigre address and name labels 2026-09-15 04:47:52 +03:00
Lauri Ojansivu 2fcf2788e5 Document lexical Tigre repairs 2026-09-15 04:47:01 +03:00
Lauri Ojansivu f27cb82ca6 Repair lexical Tigre interface terms 2026-09-15 04:45:56 +03:00
Lauri Ojansivu 7db8afc76c Document completed Veps seed review 2026-09-15 04:42:55 +03:00
Lauri Ojansivu a4ec295ffa Complete Finnish-seeded Veps translation review 2026-09-15 04:42:14 +03:00
Lauri Ojansivu 33d1a89761 Document Veps account and rule repairs 2026-09-15 04:39:52 +03:00
Lauri Ojansivu 4caef872b0 Repair Veps account and rule interface families 2026-09-15 04:39:14 +03:00
Lauri Ojansivu be8c47c26a Document Veps board interface repairs 2026-09-15 04:36:32 +03:00
Lauri Ojansivu a222bad2d7 Repair Veps board interface families 2026-09-15 04:35:53 +03:00
Lauri Ojansivu 8858912ed4 Document lexical Veps repairs 2026-09-15 04:33:33 +03:00
Lauri Ojansivu c67a91a416 Repair lexical Veps interface terms 2026-09-15 04:32:34 +03:00
Lauri Ojansivu 8d02a5d758 Document native-catalogue Veps repairs 2026-09-15 04:31:18 +03:00
Lauri Ojansivu e17bb2b5e3 Repair native-catalogue Veps interface terms 2026-09-15 04:30:06 +03:00
Lauri Ojansivu 3e492107cb Document shared Tigre vocabulary review 2026-09-15 04:27:38 +03:00
Lauri Ojansivu 5b2c5942ef Record corpus-attested shared Tigre terms 2026-09-15 04:27:05 +03:00
Lauri Ojansivu 8db30cf182 Document corpus-backed Tigre interface repairs 2026-09-15 04:25:58 +03:00
Lauri Ojansivu 0e94189604 Repair corpus-backed Tigre interface terms 2026-09-15 04:24:56 +03:00
Lauri Ojansivu 391e7fc811 Document Tigre date vocabulary repairs 2026-09-15 04:22:31 +03:00
Lauri Ojansivu 9ab9215c6b Repair Tigre basic date vocabulary 2026-09-15 04:19:37 +03:00
Lauri Ojansivu e911879b6b Document completion of tracked translation audit 2026-09-15 04:17:44 +03:00
Lauri Ojansivu 2ecd746378 Complete restored Basque rule translation review 2026-09-15 04:16:25 +03:00
Lauri Ojansivu f18a925c26 Translate Aromanian magenta color label 2026-09-15 04:14:38 +03:00
Lauri Ojansivu ee9228ab6d Complete tracked Tamazight migration translations 2026-09-15 04:13:44 +03:00
Lauri Ojansivu a11978016d Repair Tamazight global search guidance 2026-09-15 04:11:42 +03:00
Lauri Ojansivu 36c8afc8ea Repair Tamazight board and import guidance 2026-09-15 04:10:38 +03:00
Lauri Ojansivu 667eb393dd Document Tamazight diagnostic translation repairs 2026-09-15 04:08:50 +03:00
Lauri Ojansivu 3d79d5ca5b Repair Tamazight diagnostics and repository actions 2026-09-15 04:07:33 +03:00
Lauri Ojansivu c9d838170b Document Tamazight accessibility translation repairs 2026-09-15 04:06:17 +03:00
Lauri Ojansivu 7d87955de3 Repair Tamazight accessibility and UI labels 2026-09-15 04:05:00 +03:00
Lauri Ojansivu e0bb7f2ca1 Document Tamazight calendar repairs 2026-09-15 04:01:51 +03:00
Lauri Ojansivu 10aa310033 Translate Tamazight tabular Hijri labels 2026-09-15 04:01:24 +03:00
Lauri Ojansivu 3caf5f6a73 Document completed Inuktitut audit queue 2026-09-15 03:59:31 +03:00
Lauri Ojansivu b5acfdaec3 Complete Inuktitut calendar labels 2026-09-15 03:58:53 +03:00
Lauri Ojansivu 97dc41d076 Document Inuktitut orthography review 2026-09-15 03:57:22 +03:00
Lauri Ojansivu a0eeee9d63 Retain valid Roman Inuktitut translations 2026-09-15 03:56:49 +03:00
Lauri Ojansivu 81f18f4807 Document Tigre translation audit progress 2026-09-15 03:55:55 +03:00
Lauri Ojansivu 88caf17515 Translate Tigre calendar system labels 2026-09-15 03:54:16 +03:00
Lauri Ojansivu 85d994c2fb Document completed Wolaytta tracked audit
Record the final calendar repairs, source commit, updated counts, verification, and remaining low-confidence compounds.

Thanks to xet7 !
2026-09-15 03:49:22 +03:00
Lauri Ojansivu e97f8fb206 Complete Wolaytta calendar labels
Replace ten incomplete English seeds with the attested calendar-noun family while preserving named systems, calculated epochs, and moon-sighting distinctions.

Thanks to xet7 !
2026-09-15 03:48:41 +03:00
Lauri Ojansivu 54f008fc5d Document completed Nahuatl tracked audit
Record the calendar-system repair, source rationale, updated counts, verification, and remaining low-confidence scope.

Thanks to xet7 !
2026-09-15 03:47:22 +03:00
Lauri Ojansivu 56ce505d0c Translate Nahuatl calendar system labels
Replace seventeen Azerbaijani, English, and incomplete labels with a coordinated modern date-reckoning family while preserving every named calendar and variant distinction.

Thanks to xet7 !
2026-09-15 03:46:07 +03:00
Lauri Ojansivu 78fda5345a Document completed Ewe tracked audit
Record the calendar terminology evidence, source commit, updated counts, and remaining low-confidence construction.

Thanks to xet7 !
2026-09-15 03:43:57 +03:00
Lauri Ojansivu 52836b241d Complete Ewe tabular Hijri label
Replace the incomplete English calendar name with an Ewe calculation-and-epoch draft while preserving the distinct astronomical variant.

Thanks to xet7 !
2026-09-15 03:42:46 +03:00
Lauri Ojansivu 38cde52187 Document completed Veps tracked audit
Record the final tracked Veps repair, parser evidence, updated counts, and the remaining low-confidence review scope.

Thanks to xet7 !
2026-09-15 03:40:32 +03:00
Lauri Ojansivu 7bc3b73b05 Translate Veps advanced filter help
Replace the final tracked Finnish paragraph with a Veps draft while preserving every executable operator, quote, escape, grouping, and regular-expression example.

Thanks to xet7 !
2026-09-15 03:39:52 +03:00
Lauri Ojansivu a229d53471 Document Veps keyboard translation repair
Record the dated source commit, verified behavior, updated audit counts, and remaining low-confidence terminology.

Thanks to xet7 !
2026-09-15 03:38:17 +03:00
Lauri Ojansivu 4d74f7080f Fix Veps keyboard command translations
Replace four Finnish keyboard-shortcut values with a coordinated Veps keyboard-command draft while preserving state transitions, route behavior, and the literal question-mark binding.

Thanks to xet7 !
2026-09-15 03:36:49 +03:00
Lauri Ojansivu 3830365ecd Document Veps scrollbar translation repair
Record the affected UI scope, native component evidence, focused checks, updated queue, and low-confidence compound limit.\n\nThanks to xet7 !
2026-09-15 03:33:39 +03:00
Lauri Ojansivu b82d5761ed Fix Veps vertical scrollbar translation
Replace the Finnish toggle label with a Veps vertical-scrollbar draft and verify its visible control and affected containers.\n\nThanks to xet7 !
2026-09-15 03:32:56 +03:00
Lauri Ojansivu e8d971489b Document Veps field sum translation repair
Record the numeric-only source context, dictionary evidence, focused checks, updated queue, and remaining low-confidence grammar.\n\nThanks to xet7 !
2026-09-15 03:31:49 +03:00
Lauri Ojansivu 1b35cd8b4f Fix Veps field sum control translation
Replace the Finnish checkbox label with Veps arithmetic-addition and list-top terminology already verified for the numeric total tooltip.\n\nThanks to xet7 !
2026-09-15 03:31:03 +03:00
Lauri Ojansivu 68c82d941c Document Veps accessibility translation repairs
Record the five repaired UI roles, source bindings, descriptive terminology basis, focused checks, and remaining low-confidence review.\n\nThanks to xet7 !
2026-09-15 03:29:21 +03:00
Lauri Ojansivu 09b62ffa40 Fix Veps accessibility translations
Replace the Finnish accessibility family with a consistent Veps access-for-everyone draft across navigation, settings, fields, and the public empty state.\n\nThanks to xet7 !
2026-09-15 03:28:29 +03:00
Lauri Ojansivu a31eebdc39 Document Veps card loading translation repairs
Record the corrected card-loading semantics, native terminology basis, focused verification, and remaining low-confidence review in the audit and Upcoming changelog.\n\nThanks to xet7 !
2026-09-15 03:26:07 +03:00
Lauri Ojansivu 8480bee00b Fix Veps card loading translations
Replace wrong-language labels, stale manual loading guidance, and a Russian multi-selection hybrid with a coherent Veps card-loading group. Preserve the automatic threshold behavior and exact operator configuration literals.\n\nThanks to xet7 !
2026-09-15 03:25:01 +03:00
Lauri Ojansivu c4f049e320 Record Veps voting translation repairs
Update the audit, native evidence review, and Upcoming changelog with the verified voting-side repair and its remaining confidence limit.\n\nThanks to xet7 !
2026-09-15 03:19:14 +03:00
Lauri Ojansivu 8a66f4c746 Repair Veps voting-side translations
Replace four Finnish participant and action labels with Veps forms based on the native voting, support, defense, and opposition entries. Add semantic regression checks.\n\nThanks to xet7 !
2026-09-15 03:18:50 +03:00
Lauri Ojansivu 8061759d2c Record Veps Problems heading repairs
Update the short audit, detailed evidence, Veps review, and Upcoming changelog with the verified native heading evidence.\n\nThanks to xet7 !
2026-09-15 03:16:11 +03:00
Lauri Ojansivu 052937ff0a Repair Veps Problems page headings
Replace Tshivenda and Finnish headings with an attested Veps summary term and the locale's existing status label. Add exact regression checks.\n\nThanks to xet7 !
2026-09-15 03:15:49 +03:00
Lauri Ojansivu 3e0c124d4a Record Veps card-number translation repair
Update the short audit, detailed evidence, Veps review, and Upcoming changelog with the verified local repair and expanded wrong-language candidate scan.\n\nThanks to xet7 !
2026-09-15 03:15:07 +03:00
Lauri Ojansivu 21e43e8b56 Repair Veps card-number search translations
Replace Zulu card-number labels with Veps wording, preserve the search syntax placeholders, and add regression coverage for the complete search group.\n\nThanks to xet7 !
2026-09-15 03:14:23 +03:00
Lauri Ojansivu 832e44c7e9 Record Veps numeric addition repair and primary evidence 2026-09-15 03:11:15 +03:00
Lauri Ojansivu 7c17acd340 Replace wrong-language Veps numeric total description
Use the rendered native phrase for addition of numbers and preserve custom number fields marked for list top. Full compound and case grammar remains under review.
2026-09-15 03:10:54 +03:00
Lauri Ojansivu 3d47446dfe Record Veps date range repair and native interval evidence 2026-09-15 03:09:58 +03:00
Lauri Ojansivu 046830b355 Replace wrong-language Veps list date range description
Use rendered native time-interval noun; preserve custom date fields marked for list top. Full case and compound grammar remain low confidence.
2026-09-15 03:09:39 +03:00
Lauri Ojansivu 7dcdc054a4 Record Veps severity draft and technical review limits 2026-09-15 03:08:30 +03:00
Lauri Ojansivu 91d0cbf5ed Replace wrong-language Veps event severity label
Use rendered native noun forms in a direct severity-level draft. Technical harshness analogy and full compound remain low confidence and under review.
2026-09-15 03:08:09 +03:00
Lauri Ojansivu 9e5758a508 Verify native Veps abstract-level noun for severity review 2026-09-15 03:07:35 +03:00
Lauri Ojansivu dad7f2ae35 Verify rendered Veps hardness noun and limit severity inference 2026-09-15 03:06:44 +03:00
Lauri Ojansivu 99aeb90cdb Record Veps severity lexical evidence and excluded meanings 2026-09-15 03:06:02 +03:00
Lauri Ojansivu 10f1a4964b Record Veps backup scope repairs and terminology limits 2026-09-15 03:05:19 +03:00
Lauri Ojansivu cfed82e70c Replace wrong-language Veps backup scope text
Preserve organization boards and attachments, shared accounts/settings exclusions and restore ownership restriction. Whole-system paraphrase and full native grammar remain low confidence.
2026-09-15 03:04:59 +03:00
Lauri Ojansivu eb056cf59f Record Veps acknowledgment repairs and open grammar review 2026-09-15 03:04:15 +03:00
Lauri Ojansivu bc8988c9d7 Replace wrong-language Veps problem acknowledgment text
Preserve checked reviewed areas and resetting new-problem counts. Full plural, case and acknowledgment grammar remains low confidence.
2026-09-15 03:03:57 +03:00
Lauri Ojansivu ed2565e9b7 Record Veps CPU repairs and remaining core terminology review 2026-09-15 03:03:14 +03:00
Lauri Ojansivu 9e06803310 Replace wrong-language Veps CPU usage and load labels
Preserve current versus generic usage and average system load. Reuse local Veps terminology; full computing compounds and core vocabulary remain under review.
2026-09-15 03:02:55 +03:00
Lauri Ojansivu f46fb0272e Record Veps loading and status repairs with review limits 2026-09-15 03:02:14 +03:00
Lauri Ojansivu e8826e71a0 Replace wrong-language Veps loading and migration status
Preserve possible login delay, login-required message, loading indicator and CPU condition until background work finishes. Full computing terminology and grammar remain low confidence.
2026-09-15 03:01:55 +03:00
Lauri Ojansivu 4cf2381c66 Record Veps forecast repairs and remaining grammar review 2026-09-15 03:01:03 +03:00
Lauri Ojansivu 201f8d0a41 Replace wrong-language Veps chart forecast messages
Preserve zero remaining, missing recent completions, weekly pace and conditional completion date. Full projection, recency and count grammar remains low confidence.
2026-09-15 03:00:44 +03:00
Lauri Ojansivu b7a903473d Record Veps repair message corrections and grammar limits 2026-09-15 02:59:49 +03:00
Lauri Ojansivu f5d72bae8a Replace wrong-language Veps repair and restore messages
Preserve exact counters, missing-board explanation and automatic-repair limitation. Full count, passive and case grammar remains low confidence.
2026-09-15 02:59:29 +03:00
Lauri Ojansivu 6fd9eb88d3 Record Veps voting repairs and expanded review scope 2026-09-15 02:58:45 +03:00
Lauri Ojansivu 4b2009229c Fix Veps voting and event detail labels
Replace Tshivenda vote-sorting and detail text and Finnish voting heading with Veps vocabulary. Sorting case and full context terminology remain under review.
2026-09-15 02:58:23 +03:00
Lauri Ojansivu 5a62a6b3b1 Record native Veps software-key evidence and title repair 2026-09-15 02:57:43 +03:00
Lauri Ojansivu e722a6c17c Improve Veps synchronization title and software-key wording
Use native MediaWiki software sort-key evidence for the project-key analogy, preserving ID/path alternatives. Replace overlooked Tshivenda popup title. Full compound grammar remains low confidence.
2026-09-15 02:57:22 +03:00
Lauri Ojansivu 1aacb92a3b Record Veps two-factor instruction repairs and uncertainty 2026-09-15 02:56:38 +03:00
Lauri Ojansivu f9d9c454dc Replace wrong-language Veps two-factor setup and login text
Preserve QR and manual setup alternatives, six-digit codes, every-login requirement and invalid-code retry. Full computing compounds and native grammar remain low confidence.
2026-09-15 02:56:16 +03:00
Lauri Ojansivu c476aa4a10 Record Veps two-factor control repairs and open instructions 2026-09-15 02:55:29 +03:00
Lauri Ojansivu 7deff5697c Replace wrong-language Veps two-factor controls
Preserve enable and disable actions, popup title and account-specific enabled status. Full two-factor terminology and grammar remain low confidence; setup instructions still require repair.
2026-09-15 02:55:08 +03:00
Lauri Ojansivu 8e8150478f Record Veps identifier repair and terminology limits 2026-09-15 02:54:25 +03:00
Lauri Ojansivu 29f93a8886 Fix Veps multi-tracker project identifier label
Replace Tshivenda with a project-code paraphrase and retain ID and owner/repo alternatives. Native project and code vocabulary is attested; full multi-tracker terminology remains low confidence.
2026-09-15 02:54:03 +03:00
Lauri Ojansivu d84b5bb9be Record Veps sync instruction draft and remaining review 2026-09-15 02:52:37 +03:00
Lauri Ojansivu 25e6eefba0 Replace wrong-language Veps synchronization instructions
Preserve external tracker, background checks every 15 minutes, and the immediate manual action. Full draft grammar and computing loans remain low confidence.
2026-09-15 02:52:16 +03:00
Lauri Ojansivu c37bd8f962 Record Veps inactive sync repair and review limits 2026-09-15 02:51:34 +03:00
Lauri Ojansivu b30fa05a93 Fix Veps inactive synchronization source label
Replace the Tshivenda empty-source option with a Veps inactive-state paraphrase. Keep enabled, failed and never-synced states distinct. Derived noun and complete grammar remain low confidence.
2026-09-15 02:51:14 +03:00
Lauri Ojansivu 11b3061c98 Record Veps never-synced repair and primary native evidence 2026-09-15 02:48:10 +03:00
Lauri Ojansivu 81c17c1960 Fix Veps never-synced state with native adverb 2026-09-15 02:47:47 +03:00
Lauri Ojansivu 7e59bb8b63 Record Veps credential repairs and remaining language review 2026-09-15 02:47:08 +03:00
Lauri Ojansivu c117936edd Fix Veps synchronization credential labels and status messages
Preserve API token/password meaning and set versus not-yet-set state. Full status grammar and credential paraphrase remain low confidence.
2026-09-15 02:46:45 +03:00
Lauri Ojansivu d04457bb37 Record dated Veps optional-field repairs and review limits 2026-09-15 02:46:09 +03:00
Lauri Ojansivu 5d0583ef54 Fix Veps optional-field and synchronization username wording
Preserve the not-required qualification using a direct native-wording draft. Full contextual grammar remains low confidence.
2026-09-15 02:45:44 +03:00
Lauri Ojansivu 7cbef0b3c7 Record Veps source label repair and optional-field review 2026-09-15 02:45:10 +03:00
Lauri Ojansivu 0ca3422a46 Fix wrong-language Veps synchronization source label 2026-09-15 02:44:48 +03:00
Lauri Ojansivu be501c32fa Record Veps synchronization state repairs and remaining grammar 2026-09-15 02:44:14 +03:00
Lauri Ojansivu c379de7d7e Fix six Veps synchronization state and result translations
Preserve pending, success, error and stop meanings and the exact error placeholder. Derived synchronization noun and case grammar remain low confidence.
2026-09-15 02:43:51 +03:00
Lauri Ojansivu e7cecc9c7f Record five Veps synchronization repairs and remaining block 2026-09-15 02:43:12 +03:00
Lauri Ojansivu 93a48d724d Fix five wrong-language Veps synchronization labels
Reuse existing Veps wording and preserve literal project and credential examples. Full assembled synchronization grammar remains under review.
2026-09-15 02:42:49 +03:00
Lauri Ojansivu 7c540c5f5b Record dated Veps rule-state repairs and grammar limits 2026-09-15 02:41:59 +03:00
Lauri Ojansivu 3658f17556 Fix Tshivenda rule-state text in Veps translations
Keep both enable and disable actions and preserve the existing enabled label. Assembled Veps rule-object grammar remains low confidence.
2026-09-15 02:41:37 +03:00
Lauri Ojansivu 4f879334c9 Record Veps rules repair and additional mixed-language findings 2026-09-15 02:40:54 +03:00
Lauri Ojansivu 29bf7aa221 Fix unflagged Finnish rules heading in Veps translation 2026-09-15 02:40:32 +03:00
Lauri Ojansivu 84af30f9b5 Record dated Veps URL scheme repair and remaining review 2026-09-15 02:39:51 +03:00
Lauri Ojansivu f10fe2fa5b Fix Veps automatic URL scheme setting translation
Preserve automatic clickability and one scheme per line. Assembled grammar and singular scheme form remain low confidence.
2026-09-15 02:39:29 +03:00
Lauri Ojansivu be42c5771f Verify Veps button dictionary senses from rendered source 2026-09-15 02:38:40 +03:00
Lauri Ojansivu b60ea7bc12 Correct Veps dictionary search evidence for legacy PDF text 2026-09-15 02:38:09 +03:00
Lauri Ojansivu d0f721b481 Extend Veps shortcut review to untranslated popup title 2026-09-15 02:37:10 +03:00
Lauri Ojansivu b9dae272e7 Record dated resident-memory repair and remaining native review 2026-09-15 02:36:19 +03:00
Lauri Ojansivu ef5a6c5bad Fix Tamazight resident-memory diagnostic label
Use native memory-usage and size wording with the precise RSS acronym. Full computing grammar and native expansion of resident set remain under review.
2026-09-15 02:35:57 +03:00
Lauri Ojansivu d30a2f5737 Correct Tamazight memory review and preserve attested alternatives 2026-09-15 02:35:26 +03:00
Lauri Ojansivu efa6b0eb11 Record dated used-memory translation repair and terminology limits 2026-09-15 02:34:47 +03:00
Lauri Ojansivu 06be232017 Fix Tamazight actual used-memory diagnostic wording
Preserve the V8 used-memory scope. Computing memory adaptation, passive grammar and actual-use paraphrase remain low confidence.
2026-09-15 02:34:24 +03:00
Lauri Ojansivu 9570c3af3f Record dated Tamazight reminder repair and review limits 2026-09-15 02:33:33 +03:00
Lauri Ojansivu a6d77ba0e0 Fix Tamazight first due-reminder activity translation
Preserve named activity placeholders and first-reminder wording. Full deadline compound and possessive grammar remain low confidence.
2026-09-15 02:33:12 +03:00
Lauri Ojansivu 36f33b5304 Record CSV schema translation repair and remaining phrase review 2026-09-15 02:29:30 +03:00
Lauri Ojansivu 52b73cacc5 Repair Tamazight CSV and TSV schema warning 2026-09-15 02:29:06 +03:00
Lauri Ojansivu 3797e952ed Document JSON schema translation repair and remaining import terminology 2026-09-15 02:28:25 +03:00
Lauri Ojansivu ac95ce3aad Repair Tamazight JSON schema error wording 2026-09-15 02:28:02 +03:00
Lauri Ojansivu c3bc95dc3f Record accessibility translation scope and disabled page wording conflict 2026-09-15 02:27:04 +03:00
Lauri Ojansivu 68d7f2d899 Document logo wording repairs and remaining contextual review 2026-09-15 02:26:09 +03:00
Lauri Ojansivu 8d9facf9a8 Repair Tamazight logo height hide and navigation labels 2026-09-15 02:25:47 +03:00
Lauri Ojansivu 0c34382f87 Document logo URL translation repairs and remaining spatial review 2026-09-15 02:24:58 +03:00
Lauri Ojansivu ec1f2cb219 Repair Tamazight top left logo image and link URL labels 2026-09-15 02:24:36 +03:00
Lauri Ojansivu b31065a9d0 Document physical heap translation repair and remaining batch validation 2026-09-15 02:23:33 +03:00
Lauri Ojansivu 0b259c2d3d Repair Tamazight physical heap size diagnostic label 2026-09-15 02:23:10 +03:00
Lauri Ojansivu ddc8e9fcab Document V8 context translation repairs and borrowed qualifier limits 2026-09-15 02:22:35 +03:00
Lauri Ojansivu 0f4fc5d227 Repair Tamazight V8 context count diagnostic wording 2026-09-15 02:22:09 +03:00
Lauri Ojansivu 1951b0417a Document garbage flag translation repair and literal identifier rationale 2026-09-15 02:21:15 +03:00
Lauri Ojansivu 8516ab9a72 Repair Tamazight heap garbage diagnostic flag label 2026-09-15 02:20:52 +03:00
Lauri Ojansivu f194046ec2 Document heap capability translation repairs and review limits 2026-09-15 02:20:17 +03:00
Lauri Ojansivu 0f545fd8a7 Repair Tamazight available and executable heap diagnostic labels 2026-09-15 02:19:55 +03:00
Lauri Ojansivu 2f8b930391 Document malloc diagnostic translation repairs and paraphrase limits 2026-09-15 02:19:12 +03:00
Lauri Ojansivu 6481e8bbd6 Repair Tamazight malloc memory and peak diagnostic labels 2026-09-15 02:18:48 +03:00
Lauri Ojansivu dfbe6056fc Record heap limit translation repair and allocation wording review 2026-09-15 02:18:01 +03:00
Lauri Ojansivu 88add30060 Repair Tamazight heap size limit diagnostic 2026-09-15 02:17:39 +03:00
Lauri Ojansivu 9ab9db9611 Document heap diagnostic translation repairs and remaining metric distinctions 2026-09-15 02:16:51 +03:00
Lauri Ojansivu 0567c1aacc Repair Tamazight total and used heap size diagnostics 2026-09-15 02:16:24 +03:00
Lauri Ojansivu b8bffce9c3 Record account protection explanation repair and contextual limits 2026-09-15 02:15:20 +03:00
Lauri Ojansivu 5211bf3152 Repair Tamazight account protection explanatory text 2026-09-15 02:14:56 +03:00
Lauri Ojansivu 64b716d623 Document account protection translation repairs and grammar limits 2026-09-15 02:14:07 +03:00
Lauri Ojansivu 7c58d6c3df Repair Tamazight brute force settings heading and update confirmation 2026-09-15 02:13:44 +03:00
Lauri Ojansivu 4dad59ba12 Record failed attempts translation draft and remaining contextual review 2026-09-15 02:12:56 +03:00
Lauri Ojansivu bf9a929e13 Repair Tamazight failed attempts label with trial and failure terminology 2026-09-15 02:12:34 +03:00
Lauri Ojansivu b1ba19e837 Record native software evidence for Tamazight attempt wording 2026-09-15 02:11:58 +03:00
Lauri Ojansivu 3cc93cb240 Document locked users translation repair and remaining attempt terminology 2026-09-15 02:10:55 +03:00
Lauri Ojansivu e908ec0a2f Repair Tamazight locked users navigation label 2026-09-15 02:10:36 +03:00
Lauri Ojansivu 41dbd148c2 Document due search translation repair and source cutoff discrepancy 2026-09-15 02:09:46 +03:00
Lauri Ojansivu 64e524f27a Repair Tamazight due date search instruction 2026-09-15 02:09:25 +03:00
Lauri Ojansivu b347233c1b Document Tamazight search date hint repairs and contextual review limits 2026-09-15 02:08:34 +03:00
Lauri Ojansivu 66f2a8feed Repair Tamazight created and modified search date hints 2026-09-15 02:08:12 +03:00
Lauri Ojansivu 87323967b3 Record Tamazight drag handle terminology repair and review limits 2026-09-15 02:07:08 +03:00
Lauri Ojansivu b0ad9320f2 Repair Tamazight desktop drag handle label 2026-09-15 02:06:47 +03:00
Lauri Ojansivu 0f73724fa8 Document SMTP port translation repairs and retained terminology limits 2026-09-15 02:05:50 +03:00
Lauri Ojansivu e1c3720715 Repair Tamazight outgoing SMTP port description 2026-09-15 02:05:27 +03:00
Lauri Ojansivu 8f49df189a Record Tamazight SMTP translation repairs and remaining terminology review 2026-09-15 02:04:36 +03:00
Lauri Ojansivu 11fc595a00 Repair Tamazight SMTP labels and test email subject 2026-09-15 02:04:17 +03:00
Lauri Ojansivu cd895cfef7 Document Tamazight owner heading repair and review limits 2026-09-15 02:03:30 +03:00
Lauri Ojansivu 3da9c1f4ee Repair Tamazight card owner export heading 2026-09-15 02:03:11 +03:00
Lauri Ojansivu 8505afdf9e Document list warning repair and pending Transifex language support 2026-09-15 02:02:22 +03:00
Lauri Ojansivu 0f393c5c8f Repair Tamazight list deletion warning 2026-09-15 01:59:17 +03:00
Lauri Ojansivu f7b7da9c3f Document member removal translation repair and preserved placeholders 2026-09-15 01:58:39 +03:00
Lauri Ojansivu 3fd9413b3f Repair Tamazight member removal confirmation 2026-09-15 01:58:20 +03:00
Lauri Ojansivu a026b27dc5 Document Saudi calendar translation repair and sighting grammar limits 2026-09-15 01:57:35 +03:00
Lauri Ojansivu 103457ada4 Repair Tamazight Saudi sighting calendar label 2026-09-15 01:57:15 +03:00
Lauri Ojansivu 65b0fe0ab4 Record Tamazight tabular calendar qualifier evidence limits 2026-09-15 01:56:37 +03:00
Lauri Ojansivu 2c200ab5b9 Document Dzongkha epoch label repairs and native review limits 2026-09-15 01:55:53 +03:00
Lauri Ojansivu 1a174e357a Repair Dzongkha tabular Hijri calendar epoch labels 2026-09-15 01:55:32 +03:00
Lauri Ojansivu 87835255ae Verify Dzongkha epoch and start entries from rendered source 2026-09-15 01:54:51 +03:00
Lauri Ojansivu 1b10527d1b Verify Dzongkha tabular spelling from rendered dictionary page 2026-09-15 01:54:08 +03:00
Lauri Ojansivu fd0b18431f Verify Dzongkha tabular calendar epoch distinction against current CLDR 2026-09-15 01:52:50 +03:00
Lauri Ojansivu 39daf05c88 Document clipboard gesture translation repair and review limits 2026-09-15 01:52:06 +03:00
Lauri Ojansivu 047c860ea3 Repair Tamazight clipboard image gesture instruction 2026-09-15 01:51:47 +03:00
Lauri Ojansivu 29121d44c8 Record clipboard image gesture meaning for translation repair 2026-09-15 01:51:20 +03:00
Lauri Ojansivu aafcae48fb Document private page translation repair and remaining reviews 2026-09-15 01:50:39 +03:00
Lauri Ojansivu 27dbc30c22 Repair Tamazight private page login guidance 2026-09-15 01:50:21 +03:00
Lauri Ojansivu f2c6e77eb2 Document list dependency translation repair and contextual review limits 2026-09-15 01:49:37 +03:00
Lauri Ojansivu 18e1979477 Repair Tamazight linked card list deletion warning 2026-09-15 01:49:18 +03:00
Lauri Ojansivu 8cbcb52451 Document linked card warning repair and native grammar review 2026-09-15 01:48:45 +03:00
Lauri Ojansivu efdf49b29f Repair Tamazight linked card deletion warning 2026-09-15 01:48:26 +03:00
Lauri Ojansivu 9bd8e04d9f Record linked card deletion dependency direction for translation repair 2026-09-15 01:47:46 +03:00
Lauri Ojansivu 32b46051fa Document permanent attachment translation repair and grammar limits 2026-09-15 01:47:13 +03:00
Lauri Ojansivu ecbe3856d2 Repair Tamazight permanent attachment warning 2026-09-15 01:46:53 +03:00
Lauri Ojansivu 6eda842fea Record search limit grammar and parser distinction for translation review 2026-09-15 01:46:15 +03:00
Lauri Ojansivu 04a4f92b45 Document rule instruction repair and computing matching evidence 2026-09-15 01:45:34 +03:00
Lauri Ojansivu d90707d5dc Repair Tamazight blank rule field instruction 2026-09-15 01:45:13 +03:00
Lauri Ojansivu 0a1ce60ed7 Document mobile mode translation repairs and native review limits 2026-09-15 01:44:32 +03:00
Lauri Ojansivu 5f41f83ca3 Repair Tamazight mobile and desktop mode labels 2026-09-15 01:44:14 +03:00
Lauri Ojansivu 9774b20c12 Exclude misleading matching homographs from Tamazight rule repair 2026-09-15 01:43:40 +03:00
Lauri Ojansivu 2a3c8ad7b2 Record blank rule field wildcard meaning for translation repair 2026-09-15 01:42:56 +03:00
Lauri Ojansivu 0a333ce51a Document card export people translation repair and review limits 2026-09-15 01:42:11 +03:00
Lauri Ojansivu 897c89740d Repair Tamazight card export people label 2026-09-15 01:41:52 +03:00
Lauri Ojansivu 98bbc03fcf Record computing disk space translation evidence and limits 2026-09-15 01:41:15 +03:00
Lauri Ojansivu 343971eb86 Document automatic watching translation repair and audit progress 2026-09-15 01:40:39 +03:00
Lauri Ojansivu d0bf777156 Repair Tamazight automatic board watching instruction 2026-09-15 01:40:20 +03:00
Lauri Ojansivu 9a56d1720b Record imported member empty state translation constraints 2026-09-15 01:39:44 +03:00
Lauri Ojansivu b39e4e0ee2 Document due card translation repairs and pending native review 2026-09-15 01:39:11 +03:00
Lauri Ojansivu 3ce7ed2a04 Repair Tamazight due card empty states 2026-09-15 01:38:51 +03:00
Lauri Ojansivu 1a64d5a1fc Record Tamazight possessive placement constraint for notice acceptance 2026-09-15 01:38:08 +03:00
Lauri Ojansivu 61be887618 Verify generic Tamazight acceptance verb from dictionary source 2026-09-15 01:37:33 +03:00
Lauri Ojansivu f37ba5f581 Record missing ownership in legal notice acceptance translation 2026-09-15 01:36:40 +03:00
Lauri Ojansivu 89185d39ca Document legal notice translation repairs and review limits 2026-09-15 01:36:04 +03:00
Lauri Ojansivu 9baa8f69df Repair Tamazight legal notice labels 2026-09-15 01:35:47 +03:00
Lauri Ojansivu 74d2228fbd Document web manifest translation repairs and remaining review 2026-09-15 01:34:59 +03:00
Lauri Ojansivu 7c1c4623d3 Repair Tamazight custom web manifest labels 2026-09-15 01:34:42 +03:00
Lauri Ojansivu 0d6f145961 Document custom head-tag translation repair and audit progress 2026-09-15 01:34:13 +03:00
Lauri Ojansivu 8362a8a2eb Repair Tamazight custom head tag activation label 2026-09-15 01:33:55 +03:00
Lauri Ojansivu 2722961397 Record Tamazight folder evidence and ZIP import constraints 2026-09-15 01:33:15 +03:00
Lauri Ojansivu 8dee03d542 Record attachment permanence terminology evidence and review limits 2026-09-15 01:32:39 +03:00
Lauri Ojansivu 53a6891d59 Record Tamazight import translation repair and remaining review 2026-09-15 01:31:58 +03:00
Lauri Ojansivu cc2b110ba4 Repair Tamazight CSV and TSV import guidance
Preserve paste action and comma or tab separators; Tab loan and full grammar remain low confidence.

Thanks to xet7 !
2026-09-15 01:29:03 +03:00
Lauri Ojansivu 4065b7c91f Record Tamazight checklist terminology revisions
Update dated audit and Upcoming notes with evidence for the noun and remaining contextual limits.

Thanks to xet7 !
2026-09-15 01:28:24 +03:00
Lauri Ojansivu 5434775ba1 Repair Tamazight checklist verification terminology
Revise 49 prior checklist compounds with attested verification noun, preserving prose and placeholders. Full software compound remains low confidence.

Thanks to xet7 !
2026-09-15 01:27:46 +03:00
Lauri Ojansivu b7837bf05b Record Tamazight mapping repair and check spelling evidence
Update dated audit and Upcoming notes; retain prior checklist compounds for renewed terminology review.

Thanks to xet7 !
2026-09-15 01:26:48 +03:00
Lauri Ojansivu 4a28218359 Repair Tamazight import member mapping label
Use attested check verb distinct from light; mapping compound remains low confidence.

Thanks to xet7 !
2026-09-15 01:26:21 +03:00
Lauri Ojansivu f259be34a4 Record translation constraints for search date bounds
Verify inclusive created/modified lower bounds and keep uncertain quantitative Tamazight wording open.

Thanks to xet7 !
2026-09-15 01:25:25 +03:00
Lauri Ojansivu 47883aa91d Record Tamazight filename warning repair progress
Update dated audit and Upcoming notes with contextual uncertainty and lexical evidence.

Thanks to xet7 !
2026-09-15 01:23:53 +03:00
Lauri Ojansivu 327a35b82f Repair Tamazight invalid filename warning
Preserve conditional rejection and upload or rename cancellation; avoid physical invalidity vocabulary. Complete grammar remains low confidence.

Thanks to xet7 !
2026-09-15 01:23:28 +03:00
Lauri Ojansivu 5704470be8 Record newer Aromanian dictionary search results
Verify Bara search with a positive color entry; retain unresolved magenta without misclassifying missing results as language approval.

Thanks to xet7 !
2026-09-15 01:22:39 +03:00
Lauri Ojansivu 2b60bc6ce0 Record second Aromanian color dictionary review
Inspect Cunia dictionary entries and identify the newer Bara dictionary for unresolved magenta attestation. Preserve existing translation and counts.

Thanks to xet7 !
2026-09-15 01:21:41 +03:00
Lauri Ojansivu 8bc4426f53 Record Tamazight search guidance translation repair
Update dated audit and Upcoming notes with lexical evidence and contextual uncertainty.

Thanks to xet7 !
2026-09-15 01:18:15 +03:00
Lauri Ojansivu 35f74fd042 Repair Tamazight case-insensitive search guidance
Preserve letter-case distinction with an A/a example. Complete computing terminology and grammar remain low confidence.

Thanks to xet7 !
2026-09-15 01:17:41 +03:00
Lauri Ojansivu efe7146186 Record Tamazight watch translation repair progress
Update dated audit and concise Upcoming summary with source evidence and grammar review limits.

Thanks to xet7 !
2026-09-15 01:16:38 +03:00
Lauri Ojansivu 45d2b2b2e7 Repair Tamazight watching label and disabled warning
Preserve administrator action and board/card scope using IRCAM monitoring terminology. Complete grammar remains low confidence.

Thanks to xet7 !
2026-09-15 01:16:03 +03:00
Lauri Ojansivu 5104dbaf43 Record source review of Tamazight JSON schema warning
Identify the invalid watch-type caller and keep unverified format terminology open without changing translation counts.

Thanks to xet7 !
2026-09-15 01:15:18 +03:00
Lauri Ojansivu aed519ac5f Record Tamazight import guidance translation repair
Update dated audit and Upcoming notes with lexical evidence and contextual review limits.

Thanks to xet7 !
2026-09-15 01:14:29 +03:00
Lauri Ojansivu 40f06e1d8d Repair Tamazight import error guidance
Preserve possible successful import despite errors and the All Boards location. Probability paraphrase and complete grammar remain low confidence.

Thanks to xet7 !
2026-09-15 01:14:04 +03:00
Lauri Ojansivu 6ceeace0e7 Record Tamazight last administrator translation repair
Update dated audit, verified counts and Upcoming notes with contextual review limits.

Thanks to xet7 !
2026-09-15 01:13:17 +03:00
Lauri Ojansivu 14af25915c Repair Tamazight last administrator warning
Preserve the requirement for at least one administrator, replacing the wrong permission explanation. Complete grammar remains low confidence.

Thanks to xet7 !
2026-09-15 01:12:51 +03:00
Lauri Ojansivu 7ba4642883 Record Tamazight PDF warning translation repair
Update dated audit and Upcoming notes; retain accessibility and contextual grammar reviews.

Thanks to xet7 !
2026-09-15 01:12:07 +03:00
Lauri Ojansivu 9680e2393e Repair Tamazight PDF preview warning
Retain device capability failure and download alternative. Complete contextual grammar remains low confidence.

Thanks to xet7 !
2026-09-15 01:11:41 +03:00
Lauri Ojansivu 2e50c81154 Record Tamazight shortcut translation repair progress
Update dated audit and Upcoming notes with lexical evidence and contextual uncertainty.

Thanks to xet7 !
2026-09-15 01:10:48 +03:00
Lauri Ojansivu 260258983d Repair Tamazight keyboard shortcut status labels
Preserve current enabled/disabled state and opposite click action. Derived passive and complete grammar remain low confidence.

Thanks to xet7 !
2026-09-15 01:10:23 +03:00
Lauri Ojansivu 94549d762a Record Tamazight label warning repair evidence
Update dated audit progress and Upcoming notes, retaining native grammar review limits.

Thanks to xet7 !
2026-09-15 01:09:24 +03:00
Lauri Ojansivu 4f07f43d81 Repair Tamazight label deletion warning
Retain no undo, removal from all cards and destruction of label history. Complete grammatical composition remains low confidence.

Thanks to xet7 !
2026-09-15 01:09:00 +03:00
Lauri Ojansivu d7f32047fc Record Tamazight deletion warning repair progress
Update dated audit and Upcoming notes; distinguish legacy hard-delete wording from active attachment soft deletion.

Thanks to xet7 !
2026-09-15 01:08:06 +03:00
Lauri Ojansivu 53fba52404 Repair Tamazight Planning Poker deletion warning
Preserve irreversibility and loss of all associated actions. Complete contextual grammar remains low confidence.

Thanks to xet7 !
2026-09-15 01:07:42 +03:00
Lauri Ojansivu 884f040d64 Record Tamazight board action translation repairs
Update dated evidence, audit counts and Upcoming notes with contextual review limits.

Thanks to xet7 !
2026-09-15 01:06:46 +03:00
Lauri Ojansivu 7c6688b63e Repair Tamazight card movement and creation rule labels
Preserve destination and new-card semantics using existing terminology and IRCAM lexical evidence. Complete contextual grammar remains low confidence.

Thanks to xet7 !
2026-09-15 01:06:13 +03:00
Lauri Ojansivu 0245682e3e Record Tamazight checkbox-state repair evidence
Update dated audit, uncertainty limits and Upcoming release notes.

Thanks to xet7 !
2026-09-15 01:05:09 +03:00
Lauri Ojansivu 4e8f1b7dde Repair Tamazight checklist trigger state labels
Replace French checked and unchecked labels with state phrases, keeping them distinct from imperative actions. Checkbox terminology and complete grammar remain low confidence.

Thanks to xet7 !
2026-09-15 01:04:29 +03:00
Lauri Ojansivu 723ffbd524 Reconcile dated Tamazight assignee repair audit
Record source commit, verified counts and remaining contextual grammar uncertainty.

Thanks to xet7 !
2026-09-15 01:03:39 +03:00
761 changed files with 51942 additions and 5497 deletions

No files matched your search

-1
View File
@@ -38,7 +38,6 @@ ENV \
AVATARS_UPLOAD_EXTERNAL_PROGRAM="" \
AVATARS_UPLOAD_MIME_TYPES="" \
AVATARS_UPLOAD_MAX_SIZE=0 \
RICHER_CARD_COMMENT_EDITOR=false \
CARD_OPENED_WEBHOOK_ENABLED=false \
MAX_IMAGE_PIXEL="" \
IMAGE_COMPRESS_RATIO="" \
-1
View File
@@ -29,7 +29,6 @@ services:
- MONGO_URL=mongodb://wekandb-dev:27017/wekan
- ROOT_URL=http://localhost:3000
- WITH_API=true
- RICHER_CARD_COMMENT_EDITOR=true
- BROWSER_POLICY_ENABLED=true
- WRITABLE_PATH=/data
depends_on:
+1 -1
View File
@@ -82,7 +82,7 @@ jobs:
# https://github.com/docker/build-push-action
- name: Build and push Docker image
if: steps.rel.outputs.build == 'yes'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
@@ -150,7 +150,6 @@ jobs:
PORT=3000 \
WRITABLE_PATH="$PWD/wekan-writable" \
WITH_API=true \
RICHER_CARD_COMMENT_EDITOR=false \
node output/bundle/main.js > wekan-run.log 2>&1 &
# The production server serves the client JS statically, so once :3000
# responds the bundle is being served. Wait up to 10 minutes.
@@ -269,7 +268,6 @@ jobs:
PORT=3000 \
WRITABLE_PATH="$PWD/wekan-writable" \
WITH_API=true \
RICHER_CARD_COMMENT_EDITOR=false \
node output/bundle/main.js > wekan-run.log 2>&1 &
for i in $(seq 1 60); do
if curl -sf http://localhost:3000 > /dev/null; then
+132 -130
View File
@@ -3900,6 +3900,11 @@ jobs:
# continuation comments out the rest of the joined line, leaving
# `docker buildx build` with no platforms and no tags. This block is a
# shell script like any other, and it was written the wrong way first.
# Build once for Docker Hub and GHCR, then copy the published manifest
# to Quay in the next step. Quay can switch to read-only mode after
# this expensive build starts; a single --push across all three
# registries then reports the whole job failed even when the first
# two received a complete image.
# ONE IMAGE NAME, on three registries. wekan-ondra and wekan-gantt-gpl
# are SNAP names - they exist because a snap name cannot be changed once
# people have installed it - and there is nothing for them to be as
@@ -3921,8 +3926,6 @@ jobs:
--build-arg VERSION=${VERSION} \
-t wekanteam/wekan:v${VERSION} \
-t wekanteam/wekan:latest \
-t quay.io/wekan/wekan:v${VERSION} \
-t quay.io/wekan/wekan:latest \
-t ghcr.io/wekan/wekan:v${VERSION} \
-t ghcr.io/wekan/wekan:latest \
--push \
@@ -3960,8 +3963,7 @@ jobs:
# the image deliberately dropped.
want="${WANT}"
rc=0
for image in "wekanteam/wekan:v${VERSION}" "quay.io/wekan/wekan:v${VERSION}" \
"ghcr.io/wekan/wekan:v${VERSION}"; do
for image in "wekanteam/wekan:v${VERSION}" "ghcr.io/wekan/wekan:v${VERSION}"; do
# The format MUST carry the CPU variant. buildx builds linux/arm/v7,
# but the registry records that as architecture "arm" with variant
# "v7" in a SEPARATE field - so an OS/Architecture-only format prints
@@ -3990,6 +3992,45 @@ jobs:
done
exit $rc
- name: Copy published manifest to Quay
run: |
set -uo pipefail
# imagetools copies the existing multi-platform manifest without
# rebuilding every architecture. A Quay-wide read-only outage is
# temporary and affects every user; keep the completed publications
# on Docker Hub and GHCR. Any other refusal remains a real failure.
if output="$(docker buildx imagetools create \
-t "quay.io/wekan/wekan:v${VERSION}" \
-t "quay.io/wekan/wekan:latest" \
"ghcr.io/wekan/wekan:v${VERSION}" 2>&1)"; then
printf '%s\n' "$output"
echo "QUAY_PUBLISHED=true" >> "$GITHUB_ENV"
else
printf '%s\n' "$output"
if printf '%s' "$output" | grep -qi 'System is currently read-only.*write operations are currently suspended'; then
echo "::warning::Quay is in read-only mode; Docker Hub and GHCR were published and verified. Re-run this Docker job when Quay resumes writes to copy the same release manifest."
echo "QUAY_PUBLISHED=false" >> "$GITHUB_ENV"
else
echo "::error::Could not copy the published manifest to Quay; this is not the registry's read-only outage."
exit 1
fi
fi
- name: Verify Quay manifest platforms
if: env.QUAY_PUBLISHED == 'true'
run: |
set -euo pipefail
image="quay.io/wekan/wekan:v${VERSION}"
got="$(docker buildx imagetools inspect "$image" --format '{{range .Manifest.Manifests}}{{.Platform.OS}}/{{.Platform.Architecture}}{{if .Platform.Variant}}/{{.Platform.Variant}}{{end}} {{end}}')"
got="$(printf '%s' "$got" | sed 's#/v8 # #g')"
for plat in $WANT; do
case " $got " in
*" $plat "*) ;;
*) echo "::error::$image has no $plat in its manifest (it has:$got)."; exit 1 ;;
esac
done
echo "OK: $image carries:$got"
# PUSHED IS NOT THE SAME AS PULLABLE BY ANYONE.
#
# The check above asks the registries with the release's own credentials, so
@@ -4055,10 +4096,12 @@ jobs:
}
# The variant names are commented out with the -t lines they went with;
# nothing pushes them, so nothing checks them.
for image in "wekanteam/wekan:v${VERSION}" "quay.io/wekan/wekan:v${VERSION}" \
"ghcr.io/wekan/wekan:v${VERSION}"; do
for image in "wekanteam/wekan:v${VERSION}" "ghcr.io/wekan/wekan:v${VERSION}"; do
anon_check "$image"
done
if [ "${QUAY_PUBLISHED:-false}" = true ]; then
anon_check "quay.io/wekan/wekan:v${VERSION}"
fi
exit $rc
# Always, as the last step: the run summary then answers "what worked and what
@@ -4307,15 +4350,15 @@ jobs:
- id: publish
name: Push ${{ matrix.arch }} to the Snap Store (stable + candidate + beta + edge)
uses: snapcore/action-publish@v1
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_AUTH }}
with:
snap: ${{ steps.build.outputs.snap }}
release: stable,candidate,beta,edge
run: |
set -euo pipefail
SNAP_UPLOAD_LABEL="${{ steps.build.outputs.snap }}" \
bash releases/snap-upload-retry.sh "${{ steps.build.outputs.snap }}" stable,candidate,beta,edge
# The upload is a `uses:` action, so its failure would otherwise be a bare
# store error. Name the secret that was refused.
# The upload step above retries only transient store-side processing/server
# failures. Name the secret when the remaining failure is credentials/ACL.
- name: Say which secret the Snap Store refused
if: failure() && steps.publish.outcome == 'failure'
run: |
@@ -4344,18 +4387,7 @@ jobs:
# one still upload cleanly.
up=("$asset")
[ -f "${asset}.sha256sum" ] && up+=("${asset}.sha256sum")
if ! gh release upload --repo "${GITHUB_REPOSITORY:-wekan/wekan}" "v${VERSION}" "${up[@]}" --clobber; then
echo "::error::Could not attach $asset to release v${VERSION}: the release must exist first (the release job), and GITHUB_TOKEN must be allowed to write releases here."
exit 1
fi
# Confirm from the OTHER side. An upload that reports success but leaves no
# asset behind is the failure nobody notices until a download 404s.
gh release view --repo "${GITHUB_REPOSITORY:-wekan/wekan}" "v${VERSION}" --json assets --jq '.assets[].name' > release-assets.txt
if ! grep -qxF "$(basename "$asset")" release-assets.txt; then
echo "::error::$asset is not listed in release v${VERSION} although the upload reported success. Re-run this job."
exit 1
fi
echo "OK: $(basename "$asset") is attached to release v${VERSION} ($(($(wc -c < "$asset")/1024/1024)) MiB)."
bash releases/github-release-upload.sh "${GITHUB_REPOSITORY:-wekan/wekan}" "v${VERSION}" "${up[@]}"
# Always, as the last step: the run summary then answers "what worked and what
# did not" for the whole release at a glance, instead of eighteen job logs. A
@@ -4412,7 +4444,7 @@ jobs:
# never cancels the others - each publishes the moment it finishes.
continue-on-error: true
# GitHub's hosted-job ceiling is six hours. The remote command below stops
# waiting after five hours on riscv64 and leaves the queued Launchpad build
# waiting after five hours across all attempts and leaves the Launchpad build
# alive, giving log collection and the summary a full hour to finish cleanly
# instead of having GitHub cancel the job at 360 minutes.
timeout-minutes: 360
@@ -4484,36 +4516,9 @@ jobs:
- name: Flatten history so the Launchpad push stays small
if: steps.bundle.outputs.available == 'true'
# snapcraft remote-build pushes the project's git repository to
# git.launchpad.net and builds it there. It rejects a SHALLOW clone
# (that is why the checkout above is fetch-depth: 0), but it does NOT
# need the history - and WeKan's full history is large enough that the
# push times out or is refused mid-upload:
# Git operation failed with: Could not push 'HEAD' to
# git.launchpad.net/.../+git/snapcraft-wekan-<hash>
# after ~4-5 minutes, three attempts running, which is how v10.55
# riscv64 and v10.64 ppc64el failed. Re-init the repo as ONE commit of
# the tagged tree: a COMPLETE repository (git rev-parse
# --is-shallow-repository is false, so remote-build accepts it), just
# without the history it pushes for nothing. The push is then the source
# tree (tens of MB) instead of the whole history (hundreds). The snap
# version comes from snapcraft.yaml (verified below), not git-describe,
# so dropping the history changes nothing about what is built.
run: |
set -euo pipefail
# remote-build derives its Launchpad recipe name from this commit.
# Preserve the tagged commit's timestamp so a re-run creates the SAME
# root commit and reconnects to the queued build instead of submitting
# another riscv64 build behind it.
snapshot_date="$(git show -s --format=%cI HEAD)"
rm -rf .git
git init -q
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -A
GIT_AUTHOR_DATE="$snapshot_date" GIT_COMMITTER_DATE="$snapshot_date" \
git commit -q -m "WeKan ${VERSION} snapshot for Launchpad remote-build"
echo "Re-initialised as one commit; shallow: $(git rev-parse --is-shallow-repository)"
# Snapcraft hashes the project files, not only its commit. Use a complete
# one-commit source tree with Git metadata/logs outside the hashed tree.
run: bash releases/prepare-launchpad-source.sh "${{ matrix.arch }}"
- name: Install snapcraft
if: steps.bundle.outputs.available == 'true'
@@ -4588,6 +4593,34 @@ jobs:
}
echo "built=false" >> "$GITHUB_OUTPUT"
echo "pending=false" >> "$GITHUB_OUTPUT"
# A shared deadline, not five hours PER retry. Every architecture can
# queue (v11.85 ppc64el/s390x), and TLS recovery must not reset the budget.
deadline=$((SECONDS + 18000))
export TMPDIR="$PWD/.tools/tmp"
remote_wrapper="$PWD/releases/snapcraft-remote-compat.sh"
remote_source="$PWD/.tools/tmp/snap-launchpad-source"
remote_log="$PWD/.tools/tmp/remote-build-attempt.log"
run_remote_build() {
local remaining=$((deadline - SECONDS))
[ "$remaining" -gt 0 ] || return 124
# Isolate the local waiter, and kill it if it ignores TERM. Never
# cancel the remote build; a later run can recover the same recipe.
(
cd "$remote_source" || { echo "::error::Prepared Launchpad source is missing: $remote_source" >&2; exit 1; }
timeout --kill-after=30s "${remaining}s" bash "$remote_wrapper" remote-build --launchpad-accept-public-upload \
--build-for ${{ matrix.arch }} "$@"
) 2>&1 | tee "$remote_log"
local remote_rc=${PIPESTATUS[0]}
# Downloaded artifacts and build logs also change the project hash.
# Move them out before the next recovery attempt, keeping the logs
# where the existing diagnostics/artifact collection expect them.
local download
for download in "$remote_source"/*.snap "$remote_source"/snapcraft-wekan-*.txt; do
mv "$download" .
done
return "$remote_rc"
}
attempts=3
for i in $(seq 1 "$attempts"); do
# A partial file from a previous attempt must not be mistaken for
@@ -4595,22 +4628,13 @@ jobs:
# already has, so the truncated one would be uploaded again.
rm -f wekan_${VERSION}_${{ matrix.arch }}*.snap *_${{ matrix.arch }}.snap
echo "::group::Remote build ${{ matrix.arch }} attempt $i/$attempts"
if [ "${{ matrix.arch }}" = riscv64 ]; then
# Launchpad's riscv64 queue exceeded GitHub's six-hour job cap in
# v11.06. Stop only the LOCAL waiter after five hours; Launchpad
# keeps the named build, and the deterministic snapshot above lets
# a job re-run reconnect and download it when ready.
# Do not use --foreground here. GNU timeout then owns a separate
# process group and returns 124 to this shell when it expires.
# With --foreground, terminating snapcraft surfaced to Actions as
# "The operation was canceled", so the rc=124 pending branch
# below never ran and the otherwise healthy matrix leg was red.
timeout 300m snapcraft remote-build \
--launchpad-accept-public-upload \
--build-for ${{ matrix.arch }} && rc=0 || rc=$?
else
snapcraft remote-build --launchpad-accept-public-upload \
--build-for ${{ matrix.arch }} && rc=0 || rc=$?
# Always try recovery first, including a re-run of this matrix job.
# start_builds replaces the recipe; it does NOT resume polling after
# an SSL EOF. Only an explicitly absent recipe/repository permits a
# new submission. Authentication/network failures are not absence.
run_remote_build --recover && rc=0 || rc=$?
if [ "$rc" -ne 0 ] && grep -qE 'Could not find (snap recipe|repository at path)' "$remote_log"; then
run_remote_build && rc=0 || rc=$?
fi
echo "::endgroup::"
# A real glob, so nullglob can empty it, plus any name Launchpad chose -
@@ -4630,13 +4654,13 @@ jobs:
echo "built=true" >> "$GITHUB_OUTPUT"
exit 0
fi
if [ "${{ matrix.arch }}" = riscv64 ] && [ "$rc" -eq 124 ]; then
if [ "$rc" -eq 124 ] || [ "$SECONDS" -ge "$deadline" ]; then
echo "pending=true" >> "$GITHUB_OUTPUT"
echo "::warning::Launchpad riscv64 is still queued after five hours. Ending the local wait cleanly; re-run this matrix job later to reconnect to the same build and publish its snap."
echo "::warning::Launchpad ${{ matrix.arch }} has not produced a snap within the five-hour wait budget. Ending the local wait cleanly; re-run this matrix job later to reconnect to the same build and publish its snap."
exit 0
fi
if [ "$rc" -eq 0 ] && [ "${#snaps[@]}" -gt 0 ]; then
echo "::warning::Attempt $i produced ${snaps[0]}, but it is not a usable snap (see above). Deleting it and building again rather than handing it to the Snap Store."
echo "::warning::Attempt $i produced ${snaps[0]}, but it is not a usable snap (see above). Refusing to publish it; recovery must retrieve a valid artifact."
fi
if [ "$rc" -eq 0 ]; then
echo "Attempt $i: snapcraft exited 0 but produced NO .snap for ${{ matrix.arch }} - the Launchpad build did not finish (a 'Stopped' build downloads logs and no artifact). Everything in the directory:"
@@ -4675,12 +4699,12 @@ jobs:
echo "::endgroup::"
done
if [ "$i" -lt "$attempts" ]; then
# Start the next attempt from nothing. snapcraft keeps a local
# clone of the Launchpad repository under ~/.cache, and a retry
# that reuses a half-pushed one repeats the same failure - which is
# what three identical attempts sixteen minutes apart look like.
echo "Clearing snapcraft's remote-build cache before retrying."
rm -rf "$HOME/.cache/snapcraft/remote-build"
# Clear a half-pushed clone only when submission never reached
# monitoring. A failed poll must keep the running build intact.
if ! grep -qE 'Monitoring build|Recovering build' "$remote_log"; then
echo "Clearing snapcraft's remote-build cache before retrying submission."
rm -rf "$HOME/.cache/snapcraft/remote-build"
fi
echo "Retrying in 60s..."
sleep 60
fi
@@ -4762,7 +4786,7 @@ jobs:
# good builds were reported as FAILED by the step that saves their logs.
# The timestamp is worth keeping, so the colons become dashes rather
# than the name being thrown away.
for f in snapcraft-wekan-*.txt "$HOME"/.local/state/snapcraft/log/*.log; do
for f in snapcraft-wekan-*.txt .tools/tmp/remote-build-attempt.log "$HOME"/.local/state/snapcraft/log/*.log; do
[ -f "$f" ] || continue
base="$(basename "$f")"
cp -f "$f" "snap-logs/${base//:/-}" || true
@@ -4817,31 +4841,26 @@ jobs:
# riscv64, ppc64el and s390x in the same v10.78 run, each after a
# Launchpad build that had succeeded, so three good snaps were lost
# to it at once. Re-uploading the identical file is what fixes it.
up_attempts=3
uploaded=0
for u in $(seq 1 "$up_attempts"); do
echo "Uploading $f to the Snap Store (stable,candidate,beta,edge), attempt $u/$up_attempts..."
if snapcraft upload --release=stable,candidate,beta,edge "$f" 2>&1 | tee /tmp/snap-upload.log; then
uploaded=1
break
fi
# Only the store's own processing errors are worth retrying. A
# rejected file, or credentials the store will not accept, will be
# rejected exactly the same way three times - and retrying those
# only buries the message that says what to fix.
if grep -qiE 'Error checking upload uniqueness|error while processing|502 Bad Gateway|503 Service|504 Gateway|Timeout|Connection (reset|aborted)' /tmp/snap-upload.log; then
if [ "$u" -lt "$up_attempts" ]; then
echo "::warning::The Snap Store failed while PROCESSING $f (not a problem with the snap). Retrying in $((u * 30))s."
sleep $((u * 30))
continue
fi
echo "::error::The Snap Store failed while processing $f on every one of $up_attempts attempts. This is the store's own upload-uniqueness/processing check failing on a snap it accepted - the file itself built and downloaded fine, so re-running this job later usually publishes it unchanged. Nothing in this repository needs to change." >&2
# Only the store's own processing errors are worth retrying. A rejected
# file, or credentials the store will not accept, will be rejected the
# same way every time - and retrying those only buries the message that
# says what to fix.
set +e
SNAP_UPLOAD_LABEL="$f" bash releases/snap-upload-retry.sh "$f" stable,candidate,beta,edge
rc=$?
set -e
case "$rc" in
0)
;;
10)
echo "::error::The Snap Store failed while processing $f on every one of 3 attempts. This is the store's own upload-uniqueness/processing check failing on a snap it accepted - the file itself built and downloaded fine, so re-running this job later usually publishes it unchanged. Nothing in this repository needs to change." >&2
exit 1
fi
echo "::error::The Snap Store refused the upload of $f. The snapcraft line above says which: 'is not a valid file' means the build produced nothing usable; 'Credentials could not be parsed' means SNAP_AUTH is not a readable exported login; 401/403 means it lacks the ACL for this snap name. The last two are fixed by re-exporting for all three names: snapcraft export-login --snaps wekan,wekan-ondra,wekan-gantt-gpl --acls package_access,package_push,package_release,package_update snap-auth.txt" >&2
exit 1
done
[ "$uploaded" -eq 1 ] || exit 1
;;
*)
echo "::error::The Snap Store refused the upload of $f. The snapcraft line above says which: 'is not a valid file' means the build produced nothing usable; 'Credentials could not be parsed' means SNAP_AUTH is not a readable exported login; 401/403 means it lacks the ACL for this snap name. The last two are fixed by re-exporting for all three names: snapcraft export-login --snaps wekan,wekan-ondra,wekan-gantt-gpl --acls package_access,package_push,package_release,package_update snap-auth.txt" >&2
exit 1
;;
esac
done
- name: Attach the ${{ matrix.arch }} snap to the GitHub Release
@@ -4866,24 +4885,7 @@ jobs:
# attaching, which is the half people download from.
snaps=( $(printf '%s\n' wekan_${VERSION}_${{ matrix.arch }}*.snap *_${{ matrix.arch }}.snap | sort -u) )
if [ "${#snaps[@]}" -gt 0 ] && [ -s "${snaps[0]}" ]; then
if ! gh release upload --repo "${GITHUB_REPOSITORY:-wekan/wekan}" "v${VERSION}" "${snaps[@]}" --clobber; then
echo "::error::Could not attach ${snaps[*]} to release v${VERSION}. The snap BUILT and was published to the Snap Store by the step above, so this is the attaching alone - the half people download from."
exit 1
fi
# Confirm from the OTHER side, the way the native snap job does: an
# upload that reports success but leaves no asset behind is the
# failure nobody notices until a download 404s. v10.91 lost three
# architectures from the Release while every job that lost one had
# already said the snap was built and published.
gh release view --repo "${GITHUB_REPOSITORY:-wekan/wekan}" "v${VERSION}" \
--json assets --jq '.assets[].name' > release-assets.txt
for snap in "${snaps[@]}"; do
if ! grep -qxF "$(basename "$snap")" release-assets.txt; then
echo "::error::$(basename "$snap") is not listed in release v${VERSION} although the upload reported success. Re-run this job."
exit 1
fi
done
echo "OK: ${snaps[*]} attached to release v${VERSION}."
bash releases/github-release-upload.sh "${GITHUB_REPOSITORY:-wekan/wekan}" "v${VERSION}" "${snaps[@]}"
else
echo "No ${{ matrix.arch }} snap to attach - the build produced none."
fi
@@ -4892,14 +4894,14 @@ jobs:
# did not" for the whole release at a glance, instead of eighteen job logs. A
# step that failed has already printed an ::error:: saying why; this names the
# job it was in.
- name: Say what a queued riscv64 build leaves behind
- name: Say what an unfinished ${{ matrix.arch }} build leaves behind
if: steps.launchpad.outputs.pending == 'true'
run: |
echo "::warning::snap-launchpad riscv64 is still queued on Launchpad; this Actions job stopped waiting cleanly before GitHub's cap."
echo "::warning::snap-launchpad ${{ matrix.arch }} is unfinished on Launchpad; this Actions job stopped waiting cleanly before GitHub's cap."
echo "Re-run this matrix job after Launchpad finishes. Its deterministic snapshot reconnects to the same build, downloads the snap, and publishes it."
# An unexpected job-level cancellation is distinct from the deliberate,
# clean riscv64 waiter timeout above.
# clean waiter timeout above.
- name: Say what a cancelled ${{ matrix.arch }} job leaves behind
if: cancelled()
run: |
@@ -5068,14 +5070,14 @@ jobs:
- name: Publish ${{ matrix.snapname }} to the Snap Store (all channels)
if: steps.guard.outputs.snap == 'true'
uses: snapcore/action-publish@v1
env:
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_AUTH }}
with:
snap: ${{ steps.build.outputs.snap }}
run: |
set -euo pipefail
# base: core24, grade: stable - so all four channels accept it, exactly
# as they do for the default wekan snap.
release: stable,candidate,beta,edge
SNAP_UPLOAD_LABEL="${{ steps.build.outputs.snap }}" \
bash releases/snap-upload-retry.sh "${{ steps.build.outputs.snap }}" stable,candidate,beta,edge
# Always, as the last step: the run summary then answers "what worked and what
# did not" for the whole release at a glance, instead of eighteen job logs. A
+3242 -12
View File
File diff suppressed because it is too large. Load diff
+3 -2
View File
@@ -20,7 +20,7 @@ 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=11.80
ARG VERSION=11.89
ARG DEBIAN_FRONTEND=noninteractive
ENV BUILD_DEPS="apt-utils gnupg wget bzip2 g++ curl libarchive-tools build-essential git ca-certificates python3 unzip"
@@ -50,7 +50,6 @@ ENV \
AVATARS_UPLOAD_EXTERNAL_PROGRAM="" \
AVATARS_UPLOAD_MIME_TYPES="" \
AVATARS_UPLOAD_MAX_SIZE=72000 \
RICHER_CARD_COMMENT_EDITOR=false \
CARD_OPENED_WEBHOOK_ENABLED=false \
MAX_IMAGE_PIXEL="" \
IMAGE_COMPRESS_RATIO="" \
@@ -261,6 +260,8 @@ useradd --user-group --system --create-home --home-dir /home/wekan wekan
apt-get update --assume-yes
apt-get upgrade --assume-yes
apt-get install --assume-yes --no-install-recommends ${BUILD_DEPS}
# Runtime MIME detection: keep this outside BUILD_DEPS so cleanup retains it.
apt-get install --assume-yes --no-install-recommends file
# Multi-arch mapping: Docker TARGETARCH -> WeKan's own platform name, which is
# both the bundle .zip's name and what resolve-node-source.sh is asked about.
+1 -1
View File
@@ -1,5 +1,5 @@
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
appVersion: "v11.80.0"
appVersion: "v11.89.0"
files:
userUploads:
- README.md
+43 -16
View File
@@ -89,22 +89,24 @@ echo.
echo ==================== WeKan ( Windows ) ====================
echo 1^) Setup ^(install dependencies, build^)
echo 2^) Dev server ^(meteor run variants^)
echo 3^) Tests ^(mocha, playwright, e2e, ...^)
echo 4^) Docker ^(start / follow logs / stop^)
echo 5^) Releases ^(release, snap, bundles, translations, ...^)
echo 6^) CLI commands ^(run any of them without the menu^)
echo 7^) Tools ^(save deps, forge tools, mirror^)
echo 3^) Dev server nobuild ^(source loader + bundled MongoDB^)
echo 4^) Tests ^(mocha, playwright, e2e, ...^)
echo 5^) Docker ^(start / follow logs / stop^)
echo 6^) Releases ^(release, snap, bundles, translations, ...^)
echo 7^) CLI commands ^(run any of them without the menu^)
echo 8^) Tools ^(save deps, forge tools, mirror^)
echo 0^) Quit
echo ==========================================================
set "choice="
set /p "choice=Choose a category: "
if "%choice%"=="1" goto menu_setup
if "%choice%"=="2" goto menu_dev
if "%choice%"=="3" goto menu_tests
if "%choice%"=="4" goto menu_docker
if "%choice%"=="5" goto menu_releases
if "%choice%"=="6" goto menu_cli
if "%choice%"=="7" goto menu_tools
if "%choice%"=="3" goto menu_dev_nobuild
if "%choice%"=="4" goto menu_tests
if "%choice%"=="5" goto menu_docker
if "%choice%"=="6" goto menu_releases
if "%choice%"=="7" goto menu_cli
if "%choice%"=="8" goto menu_tools
if "%choice%"=="0" goto end
echo invalid option
goto menu
@@ -130,8 +132,17 @@ goto menu_setup
REM ===========================================================================
:menu_dev
set "DEV_NOBUILD="
set "DEV_MENU_TITLE=Dev server"
goto menu_dev_options
:menu_dev_nobuild
set "DEV_NOBUILD=1"
set "DEV_MENU_TITLE=Dev server nobuild"
:menu_dev_options
echo.
echo -- Dev server -- ^(0 = Back^)
echo == %DEV_MENU_TITLE% ==
echo 1^) localhost:3000
echo 2^) localhost:3000 + trace warnings
echo 3^) localhost:3000 + bundle visualizer
@@ -151,7 +162,7 @@ if "%choice%"=="6" goto dev_customip
if "%choice%"=="7" goto dev_customurl
if "%choice%"=="8" goto dev_killall
if "%choice%"=="0" goto menu
goto menu_dev
goto menu_dev_options
REM ===========================================================================
:menu_tests
@@ -177,6 +188,7 @@ echo 13^) Count tests by category
echo 14^) All databases ^(sequential^): build newest FerretDB v1, run every query type
echo against every database with an image for this CPU, compare the answers
echo 15^) Run all FerretDB tests - SEQUENTIAL ^(unit, vet, integration^)
echo 16^) EVERYTHING source one by one ^(Dev server nobuild, all browsers^)
set "choice="
set /p "choice=Choose: "
if "%choice%"=="1" goto test_everything_two
@@ -194,6 +206,7 @@ if "%choice%"=="12" goto check_floating
if "%choice%"=="13" goto count_tests
if "%choice%"=="14" goto test_all_databases
if "%choice%"=="15" goto test_ferretdb
if "%choice%"=="16" goto test_everything_source
if "%choice%"=="0" goto menu
goto menu_tests
@@ -810,6 +823,8 @@ goto end
REM ===========================================================================
:install
echo Installing WeKan dependencies for Windows.
rem Full MIME detection uses the libmagic WASM engine/database installed by npm.
rem No external file.exe installation is needed.
echo This uses Chocolatey to install git, curl, Node.js LTS and MongoDB tools,
echo then installs Meteor via npm.
where choco >nul 2>&1
@@ -980,6 +995,8 @@ call :detect_ip
echo Your IP address is !IPADDRESS!
call :set_dev_env
set "MONGO_URL=mongodb://127.0.0.1:27019/wekan"
set "WEKAN_SOURCE_MONGO_PORT=27019"
set "WEKAN_SOURCE_MONGO_DATABASE=wekan"
set "ROOT_URL=http://!IPADDRESS!:3000"
call :runlog --port 3000
goto end
@@ -1488,10 +1505,9 @@ exit /b 0
REM Common dev-server environment (caller sets ROOT_URL afterwards).
set "DEFAULT_METEOR_REACTIVITY_ORDER=changeStreams,oplog,polling"
set "DDP_TRANSPORT=sockjs"
set "DEBUG=true"
if not defined DEBUG set "DEBUG=false"
set "WRITABLE_PATH=.."
set "WITH_API=true"
set "RICHER_CARD_COMMENT_EDITOR=false"
exit /b 0
:logdir
@@ -1544,9 +1560,14 @@ call :logdir dev-server
if errorlevel 1 exit /b 1
set "WEKAN_SERVER_LOG=%LOG_DIRECTORY%\dev.txt"
echo Log: %WEKAN_SERVER_LOG%
if "%DEV_NOBUILD%"=="1" goto runlog_nobuild
call meteor run %* 2>&1 | powershell -NoProfile -Command "$input | Tee-Object -FilePath $env:WEKAN_SERVER_LOG"
exit /b 0
:runlog_nobuild
node "%REPO%\scripts\dev-source\start.cjs" %* 2>&1 | powershell -NoProfile -Command "$input | Tee-Object -FilePath $env:WEKAN_SERVER_LOG"
exit /b 0
:kill_meteor_on_port
REM %1 = app port. Frees BOTH the app port and the rspack dev-server port (8080),
REM because "meteor run" starts an rspack dev server on 8080 (rspack.config.js:
@@ -1827,11 +1848,10 @@ set "ROOT_URL=http://localhost:3000"
set "PORT=3000"
set "WRITABLE_PATH=%WRITABLE_ABS%"
set "WITH_API=true"
set "RICHER_CARD_COMMENT_EDITOR=false"
set "DEFAULT_METEOR_REACTIVITY_ORDER=changeStreams,oplog,polling"
echo ==^> Starting the WeKan test server on http://localhost:3000 from .build\bundle ^(precompiled - no rebuild^).
start "WekanTestServer" /MIN /D "%REPO%" cmd /c "(echo ===== WeKan test server [bundle node :3000 db :3001/meteor] started: %DATE% %TIME% =====) 1>%RUN_LOGDIR%\wekan-test-server.log 2>&1 & "%NODE_BIN%" "%REPO%\.build\bundle\main.js" 1>>%RUN_LOGDIR%\wekan-test-server.log 2>&1"
set "MONGO_URL=" & set "ROOT_URL=" & set "PORT=" & set "WRITABLE_PATH=" & set "WITH_API=" & set "RICHER_CARD_COMMENT_EDITOR=" & set "DEFAULT_METEOR_REACTIVITY_ORDER="
set "MONGO_URL=" & set "ROOT_URL=" & set "PORT=" & set "WRITABLE_PATH=" & set "WITH_API=" & set "DEFAULT_METEOR_REACTIVITY_ORDER="
REM 6) Wait for :3000 to answer (bundle boots in seconds; curl-timeout poll).
call :wait_server_ready
@@ -1952,6 +1972,13 @@ bash -c "cd .tools/FerretDB && ./build.sh test-all"
goto end
REM ===========================================================================
:test_everything_source
set "WEKAN_TEST_SERVER_MODE=source"
set "WEKAN_TEST_BAIL=1"
set "WEKAN_PLAYWRIGHT_PROBE=0"
set "WEKAN_EVERYTHING_MODE=sequential"
goto test_everything
:test_everything_two
set "WEKAN_EVERYTHING_MODE=two-worker"
goto test_everything
+95 -32
View File
@@ -656,6 +656,10 @@ function run_playwright_docker(){
-e WEKAN_PLAYWRIGHT_PROJECT="$browser" \
-e WEKAN_PLAYWRIGHT_WORKERS="${WEKAN_PLAYWRIGHT_WORKERS:-1}" \
-e WEKAN_PLAYWRIGHT_PROBE=0 \
-e WEKAN_TEST_BAIL="${WEKAN_TEST_BAIL:-0}" \
-e WEKAN_TEST_SERIAL="${WEKAN_TEST_SERIAL:-0}" \
-e WEKAN_TEST_SERVER_MODE="${WEKAN_TEST_SERVER_MODE:-bundle}" \
-e WEKAN_TEST_SMTP_PORT="${WEKAN_TEST_SMTP_PORT:-}" \
-e PLAYWRIGHT_HTML_OPEN=never \
-e WEKAN_FILES_PATH=/wekan-files \
-e PLAYWRIGHT_JSON_OUTPUT_NAME="${PLAYWRIGHT_JSON_OUTPUT_NAME:-}" \
@@ -1016,6 +1020,8 @@ function run_all_tests(){
*) echo "ERROR: unknown EVERYTHING mode: $REQUESTED_MODE" >&2; return 2 ;;
esac
export WEKAN_PLAYWRIGHT_WORKERS="$PLAYWRIGHT_WORKERS"
export WEKAN_TEST_SERIAL=0
[ "$REQUESTED_MODE" = sequential ] && export WEKAN_TEST_SERIAL=1
local modeword
[ "$REQUESTED_MODE" = parallel ] && modeword="at once (concurrently)" || modeword="one stage at a time (${PLAYWRIGHT_WORKERS} Playwright worker(s))"
# The large heap at the top of this file is for compiling WeKan. Do not hand
@@ -1055,22 +1061,29 @@ function run_all_tests(){
# fails on code that is no longer in the working tree - the one thing a test
# run must never do. This used to build only when .build/bundle was missing,
# which is exactly the case where the bundle is present but old.
echo "==> Deleting .build and building WeKan before running the tests (always, so the tests run against the current source)."
build_wekan
if [ ! -d .build/bundle ]; then
echo "ERROR: .build/bundle is missing after building. Aborting the test run."
return 1
if [ "${WEKAN_TEST_SERVER_MODE:-bundle}" != source ]; then
echo "==> Deleting .build and building WeKan before running the tests (always, so the tests run against the current source)."
build_wekan
if [ ! -d .build/bundle ]; then
echo "ERROR: .build/bundle is missing after building. Aborting the test run."
return 1
fi
fi
if [ "$RUN_MODE" = parallel ]; then
echo "Running ALL tests against ONE WeKan server on http://localhost:3000 - all jobs run IN PARALLEL (concurrently). Needs plenty of RAM (fine on 32 GB)."
else
echo "Running ALL tests against ONE WeKan server on http://localhost:3000 - all jobs run SEQUENTIALLY (one at a time)."
fi
if [ "${WEKAN_TEST_SERVER_MODE:-bundle}" = source ]; then
echo " :3000 - current source loaded by Node; Meteor's bundled development MongoDB on :3001."
echo " Mocha runs first in the source process, then a fresh source process serves E2E and browsers."
else
echo "Two WeKan servers are involved (they do NOT run tests in parallel unless you chose parallel):"
echo " :3000 - the PRECOMPILED .build/bundle run as a plain Node server (Meteor's mongod on :3001)"
echo " - serves Node E2E + Playwright browser tests. Built fresh above, so the tests run against the current source."
echo " :3100 - Mocha via 'meteor test' (its own .meteor/local-test build; the in-process server-side tests"
echo " CANNOT run from a production bundle, so this one build is unavoidable)."
fi
echo " Import regression is a plain Node script (no server, no MongoDB)."
SUMMARY=()
record() { SUMMARY+=("$1|$2|${3:-}"); }
@@ -1239,6 +1252,7 @@ function run_all_tests(){
[ "$jrc" = "0" ] && jst="PASS" || jst="FAIL"
jok=$(count_pass "$k" "$jlog"); jbad=$(count_fail "$k" "$jlog")
printf ' [%-4s] %-22s tests:%-4s fail:%s\n' "$jst" "$(label_of "$k")" "$jok" "$jbad"
if [ "${WEKAN_TEST_BAIL:-0}" = 1 ] && [ "$jrc" -ne 0 ]; then return "$jrc"; fi
fi
}
@@ -1278,6 +1292,33 @@ function run_all_tests(){
echo " Port 3000 is now free."
fi
if [ "${WEKAN_TEST_SERVER_MODE:-bundle}" = source ]; then
export WEKAN_PLAYWRIGHT_PROBE=0
export WEKAN_TEST_SMTP_PORT="${WEKAN_TEST_SMTP_PORT:-2525}"
export MAIL_URL="smtp://127.0.0.1:$WEKAN_TEST_SMTP_PORT"
export EMAIL_NOTIFICATION_TIMEOUT=100
local WRITABLE_ABS="$WEKAN_DIR/.tools/test-writable"
mkdir -p "$WRITABLE_ABS/files"
export WEKAN_FILES_PATH_HOST="$WRITABLE_ABS/files"
export WEKAN_FILES_PATH="$WRITABLE_ABS/files"
export WEKAN_MONGO_URL="mongodb://127.0.0.1:3001/meteor"
local NODE_BIN="$(command -v node)"
local TAIL_PID=""
echo "==> Running Meteor server tests in the source-loaded Node process on :3000."
WEKAN_SOURCE_WATCH=0 ROOT_URL=http://localhost:3000 WRITABLE_PATH="$WRITABLE_ABS" WITH_API=true \
NODE_OPTIONS="$TEST_NODE_OPTIONS" "$NODE_BIN" scripts/dev-source/start.cjs --port 3000 --server-tests \
2>&1 | tee "$RUN_LOGDIR/wekan-alltests-mocha.log"
local source_mocha_rc=${PIPESTATUS[0]}
if [ "$source_mocha_rc" -ne 0 ]; then return "$source_mocha_rc"; fi
echo 0 > "$STATDIR/mocha"
echo "==> Starting source-loaded WeKan :3000 and Meteor's development MongoDB :3001."
WEKAN_SOURCE_WATCH=0 ROOT_URL=http://localhost:3000 WRITABLE_PATH="$WRITABLE_ABS" WITH_API=true \
NODE_OPTIONS="$TEST_NODE_OPTIONS" "$NODE_BIN" scripts/dev-source/start.cjs --port 3000 \
> "$RUN_LOGDIR/wekan-test-server.log" 2>&1 &
TEST_SERVER_PID=$!
tail -n 0 -f "$RUN_LOGDIR/wekan-test-server.log" &
TAIL_PID=$!
else
# Start the :3000 test server from the PRECOMPILED .build/bundle (NOT `meteor run`)
# so Node E2E + Playwright reuse the WeKan you already built with
# `meteor build .build --directory` — no recompile. The bundle is a plain Node
@@ -1427,11 +1468,13 @@ function run_all_tests(){
# different one, so every test that needs seeded data fails.
{ echo "===== WeKan test server [bundle node :3000 db :$TEST_DB_PORT/meteor] - started $(date '+%Y-%m-%d %H:%M:%S %Z') ====="; echo; \
MONGO_URL="$TEST_MONGO_URL" ROOT_URL="http://localhost:3000" PORT=3000 \
WRITABLE_PATH="$WRITABLE_ABS" WITH_API=true RICHER_CARD_COMMENT_EDITOR=false \
WRITABLE_PATH="$WRITABLE_ABS" WITH_API=true \
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" \
NODE_OPTIONS="$TEST_NODE_OPTIONS" exec "$NODE_BIN" "$BUNDLE_DIR/main.js"; } >> "$RUN_LOGDIR/wekan-test-server.log" 2>&1 &
TEST_SERVER_PID=$!
fi
SERVER_READY=0
server_wait_start=$(date +%s)
server_wait_max=300
@@ -1446,16 +1489,16 @@ function run_all_tests(){
wait "$TAIL_PID" 2>/dev/null || true
echo " -------------------------------------------------------------------"
if [ "$SERVER_READY" -eq 1 ]; then
echo "==> WeKan test server ready on http://localhost:3000 (precompiled bundle, no rebuild)."
echo "==> WeKan test server ready on http://localhost:3000 (${WEKAN_TEST_SERVER_MODE:-bundle} mode)."
fi
# Mocha and the import regression do not need the :3000 server; launch them
# now (after the server build is past, so they no longer compete with it),
# then the E2E and browser jobs below.
echo "==> Launching Mocha (separate .meteor/local-test build, port 3100), the node unit suites and the import regression, $modeword."
launch_job mocha
launch_job unit
launch_job import
echo "==> Running registered server, node and import suites, $modeword."
if [ "${WEKAN_TEST_SERVER_MODE:-bundle}" != source ]; then launch_job mocha || return $?; fi
launch_job unit || return $?
launch_job import || return $?
if [ "$SERVER_READY" -ne 1 ]; then
echo "FAIL: server did not become ready on http://localhost:3000 (see $RUN_LOGDIR/wekan-test-server.log)"
@@ -1468,10 +1511,10 @@ function run_all_tests(){
else
record PASS "Server startup"
# Server is up: add the server-facing jobs to the running set.
launch_job e2e
launch_job chromium
launch_job firefox
launch_job webkit
launch_job e2e || return $?
launch_job chromium || return $?
launch_job firefox || return $?
launch_job webkit || return $?
ALLKEYS="mocha unit import e2e chromium firefox webkit"
fi
@@ -1634,6 +1677,12 @@ function run_everything(){
local FERRET_GO_MEMORY_MB=$(( _mem_total_mb / 8 ))
[ "$FERRET_GO_MEMORY_MB" -gt 4096 ] && FERRET_GO_MEMORY_MB=4096
local FERRET_GOFLAGS="${WEKAN_FERRETDB_GOFLAGS:--p=$FERRET_GO_JOBS}"
if [ "$EVERYTHING_MODE" = sequential ]; then
FERRET_GOFLAGS="$FERRET_GOFLAGS -p=1 -parallel=1"
fi
if [ "${WEKAN_TEST_BAIL:-0}" = 1 ]; then
FERRET_GOFLAGS="$FERRET_GOFLAGS -failfast"
fi
local FERRET_GOMEMLIMIT="${WEKAN_FERRETDB_GOMEMLIMIT:-${FERRET_GO_MEMORY_MB}MiB}"
RUN_TS="$(date '+%Y-%m-%d_%H-%M-%S')"
RUN_LOGDIR="$(log_directory "test-everything-$EVERYTHING_MODE")" || return $?
@@ -1644,7 +1693,7 @@ function run_everything(){
echo "=============================================================================="
echo "EVERYTHING mode: $EVERYTHING_MODE. Logs: $RUN_LOGDIR/"
echo " 1/4 Floating-promises guard (seconds: the rule, and unawaited auth checks)"
echo " 2/4 WeKan's own tests (builds the bundle, starts a server; mocha, the"
echo " 2/4 WeKan's own tests (${WEKAN_TEST_SERVER_MODE:-bundle} server; mocha, the"
echo " node suites, import, node E2E, three browsers)"
echo " 3/4 Database conformance (builds FerretDB, every database this CPU runs)"
echo " 4/4 FerretDB's own tests (unit, vet, integration)"
@@ -1662,10 +1711,12 @@ function run_everything(){
# an hour of browser tests.
floating_promises_checks 2>&1 | tee "$RUN_LOGDIR/wekan-floating-promises.log"
guard_rc=${PIPESTATUS[0]}
if [ "${WEKAN_TEST_BAIL:-0}" = 1 ] && [ "$guard_rc" -ne 0 ]; then return "$guard_rc"; fi
echo
echo "### 2/4 WeKan tests ###########################################################"
run_all_tests "$EVERYTHING_MODE" || wekan_rc=$?
if [ "${WEKAN_TEST_BAIL:-0}" = 1 ] && [ "$wekan_rc" -ne 0 ]; then return "$wekan_rc"; fi
echo
echo "### 3/4 Database conformance ##################################################"
@@ -1675,6 +1726,7 @@ function run_everything(){
else
echo "ERROR: releases/db-conformance.sh is missing."; conf_rc=1
fi
if [ "${WEKAN_TEST_BAIL:-0}" = 1 ] && [ "$conf_rc" -ne 0 ]; then return "$conf_rc"; fi
echo
echo "### 4/4 FerretDB tests ########################################################"
@@ -1684,6 +1736,7 @@ function run_everything(){
ferret_dir="$(ensure_tool_repo FerretDB)" || ferret_dir=""
if [ -n "$ferret_dir" ] && [ -x "$ferret_dir/build.sh" ]; then
( cd "$ferret_dir" && WEKAN_LOGDIR="$RUN_LOGDIR" \
FERRETDB_TEST_BAIL="${WEKAN_TEST_BAIL:-0}" \
GOFLAGS="$FERRET_GOFLAGS" GOMEMLIMIT="$FERRET_GOMEMLIMIT" \
./build.sh test-all ) || ferret_rc=$?
else
@@ -2710,7 +2763,7 @@ esac
opt=""
while [ -z "$opt" ]; do
echo; echo "==================== WeKan ===================="
select cat in "Setup" "Dev server" "Tests" "Docker" "Releases" "CLI commands" "Tools" "Quit"; do
select cat in "Setup" "Dev server" "Dev server nobuild" "Tests" "Docker" "Releases" "CLI commands" "Tools" "Quit"; do
case $cat in
"Setup")
choose "Setup" \
@@ -2719,8 +2772,8 @@ while [ -z "$opt" ]; do
"Build WeKan development bundle|Build WeKan development bundle" \
"git pull|git pull: fetch, fast-forward or merge onto origin, repoint the CHANGELOG commit links the rebase moved, and preserve unresolved conflict state" \
"git push|git push: check the CHANGELOG commit links resolve before publishing them, push this branch to origin, and pull-then-retry once if origin moved meanwhile" ;;
"Dev server")
choose "Dev server" \
"Dev server"|"Dev server nobuild")
choose "$cat" \
"localhost:3000|Run Meteor for dev on http://localhost:3000" \
"localhost:3000 + trace warnings|Run Meteor for dev on http://localhost:3000 with trace warnings, and warnings using old Meteor API that will not exist in Meteor 3.0" \
"localhost:3000 + bundle visualizer|Run Meteor for dev on http://localhost:3000 with bundle visualizer" \
@@ -2734,6 +2787,7 @@ while [ -z "$opt" ]; do
"EVERYTHING two-worker|Run EVERYTHING with stages one by one and two Playwright workers per browser; database and FerretDB stages stay sequential; logs in log/<datetime>/" \
"EVERYTHING one by one|Run EVERYTHING one stage and one Playwright worker at a time for minimum RAM usage; database and FerretDB stages stay sequential; logs in log/<datetime>/" \
"EVERYTHING at once|Run EVERYTHING with the WeKan test jobs concurrently; database backends and FerretDB stages stay sequential; logs in log/<datetime>/" \
"EVERYTHING source one by one|Run EVERYTHING against Dev server nobuild on localhost:3000, one test at a time, all browsers" \
"Mocha (server-side)|Test Mocha unit + security + API-logic tests (server-side only, no browser)" \
"Import regression|Test import regression (tests/wekanCreator.import.test.js, fast, no server)" \
"Node E2E regressions|Test Node E2E regressions (tests/e2e/list-regressions.js, needs running server)" \
@@ -2762,33 +2816,42 @@ while [ -z "$opt" ]; do
done
done
# Both menus share their URL/port prompts and environment.
DEV_COMMAND=(meteor run)
if [ "$cat" = "Dev server nobuild" ]; then
DEV_COMMAND=(node "$WEKAN_DIR/scripts/dev-source/start.cjs")
fi
for _once in 1; do
case "$opt" in
"Run EVERYTHING against Dev server nobuild on localhost:3000, one test at a time, all browsers")
WEKAN_TEST_SERVER_MODE=source WEKAN_TEST_BAIL=1 WEKAN_PLAYWRIGHT_PROBE=0 run_everything sequential
;;
"Install WeKan dependencies")
if [[ "$OSTYPE" == "linux-gnu" ]]; then
echo "Linux";
case "$(_et_linux_family)" in
alpine)
sudo apk add --no-cache bash build-base git curl wget p7zip zip unzip npm
sudo apk add --no-cache bash build-base git curl wget p7zip zip unzip file npm
;;
arch)
sudo pacman -Sy --needed --noconfirm base-devel git curl wget p7zip zip unzip npm
sudo pacman -Sy --needed --noconfirm base-devel git curl wget p7zip zip unzip file npm
;;
fedora)
sudo dnf group install -y development-tools
sudo dnf install -y gcc gcc-c++ make git curl wget 7zip zip unzip npm
sudo dnf install -y gcc gcc-c++ make git curl wget 7zip zip unzip file npm
;;
rhel)
pm=dnf; command -v dnf >/dev/null 2>&1 || pm=yum
sudo "$pm" groupinstall -y "Development Tools"
sudo "$pm" install -y gcc gcc-c++ make git curl wget p7zip zip unzip npm
sudo "$pm" install -y gcc gcc-c++ make git curl wget p7zip zip unzip file npm
;;
debian)
sudo apt-get update
sudo apt-get install -y build-essential gcc g++ make git curl wget p7zip-full zip unzip unp npm
sudo apt-get install -y build-essential gcc g++ make git curl wget p7zip-full zip unzip unp file npm
;;
*) echo "Unsupported Linux distribution; install a C/C++ toolchain, git, curl, wget, 7zip, zip, unzip and npm." >&2; exit 1 ;;
*) echo "Unsupported Linux distribution; install a C/C++ toolchain, git, curl, wget, 7zip, zip, unzip, file and npm." >&2; exit 1 ;;
esac
#sudo chown -R $(id -u):$(id -g) $HOME/.npm
sudo npm -g install n
@@ -2920,7 +2983,7 @@ for _once in 1; do
#---------------------------------------------------------------------
# Logging of terminal output to console and to .tools/log/wekan-log.log at end of this line: 2>&1 | tee "$(one_log dev-server)"
#WARN_WHEN_USING_OLD_API=true NODE_OPTIONS="--trace-warnings"
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=sockjs DEBUG=true WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://localhost:3000 meteor run --port 3000 2>&1 | tee "$(one_log dev-server)"
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=sockjs DEBUG="${DEBUG:-false}" WRITABLE_PATH=.. WITH_API=true ROOT_URL=http://localhost:3000 "${DEV_COMMAND[@]}" --port 3000 2>&1 | tee "$(one_log dev-server)"
#---------------------------------------------------------------------
break
;;
@@ -2932,7 +2995,7 @@ for _once in 1; do
#Not in use, could increase RAM usage: NODE_OPTIONS="--max_old_space_size=4096"
#---------------------------------------------------------------------
# Logging of terminal output to console and to .tools/log/wekan-log.log at end of this line: 2>&1 | tee "$(one_log dev-server)"
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=sockjs DEBUG=true WARN_WHEN_USING_OLD_API=true NODE_OPTIONS="--trace-warnings --max-old-space-size=$_heap_mb" WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://localhost:3000 meteor run --port 3000 2>&1 | tee "$(one_log dev-server)"
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=sockjs DEBUG="${DEBUG:-false}" WARN_WHEN_USING_OLD_API=true NODE_OPTIONS="--trace-warnings --max-old-space-size=$_heap_mb" WRITABLE_PATH=.. WITH_API=true ROOT_URL=http://localhost:3000 "${DEV_COMMAND[@]}" --port 3000 2>&1 | tee "$(one_log dev-server)"
#---------------------------------------------------------------------
break
;;
@@ -2944,7 +3007,7 @@ for _once in 1; do
#---------------------------------------------------------------------
#Logging of terminal output to console and to .tools/log/wekan-log.log at end of this line: 2>&1 | tee "$(one_log dev-server)"
#WARN_WHEN_USING_OLD_API=true NODE_OPTIONS="--trace-warnings"
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=sockjs DEBUG=true WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://localhost:3000 meteor run --port 3000 --extra-packages bundle-visualizer --production 2>&1 | tee "$(one_log dev-server)"
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=sockjs DEBUG="${DEBUG:-false}" WRITABLE_PATH=.. WITH_API=true ROOT_URL=http://localhost:3000 "${DEV_COMMAND[@]}" --port 3000 --extra-packages bundle-visualizer --production 2>&1 | tee "$(one_log dev-server)"
#---------------------------------------------------------------------
break
;;
@@ -2963,7 +3026,7 @@ for _once in 1; do
#---------------------------------------------------------------------
#Logging of terminal output to console and to .tools/log/wekan-log.log at end of this line: 2>&1 | tee "$(one_log dev-server)"
#WARN_WHEN_USING_OLD_API=true NODE_OPTIONS="--trace-warnings"
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=sockjs DEBUG=true WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://$IPADDRESS:3000 meteor run --port 3000 2>&1 | tee "$(one_log dev-server)"
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=sockjs DEBUG="${DEBUG:-false}" WRITABLE_PATH=.. WITH_API=true ROOT_URL=http://$IPADDRESS:3000 "${DEV_COMMAND[@]}" --port 3000 2>&1 | tee "$(one_log dev-server)"
#---------------------------------------------------------------------
break
;;
@@ -2982,7 +3045,7 @@ for _once in 1; do
#---------------------------------------------------------------------
#Logging of terminal output to console and to .tools/log/wekan-log.log at end of this line: 2>&1 | tee "$(one_log dev-server)"
#WARN_WHEN_USING_OLD_API=true NODE_OPTIONS="--trace-warnings"
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=sockjs DEBUG=true MONGO_URL=mongodb://127.0.0.1:27019/wekan WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://$IPADDRESS:3000 meteor run --port 3000 2>&1 | tee "$(one_log dev-server)"
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=sockjs DEBUG="${DEBUG:-false}" WEKAN_SOURCE_MONGO_PORT=27019 WEKAN_SOURCE_MONGO_DATABASE=wekan MONGO_URL=mongodb://127.0.0.1:27019/wekan WRITABLE_PATH=.. WITH_API=true ROOT_URL=http://$IPADDRESS:3000 "${DEV_COMMAND[@]}" --port 3000 2>&1 | tee "$(one_log dev-server)"
#---------------------------------------------------------------------
break
;;
@@ -2996,7 +3059,7 @@ for _once in 1; do
# ROOT_URL differ. Logging of terminal output to console and to
# .tools/log/wekan-log.log at the end of the line: 2>&1 | tee "$(one_log dev-server)"
#---------------------------------------------------------------------
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=sockjs DEBUG=true WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL="$DEV_ROOT_URL" meteor run --port "$DEV_PORT" 2>&1 | tee "$(one_log dev-server)"
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=sockjs DEBUG="${DEBUG:-false}" WRITABLE_PATH=.. WITH_API=true ROOT_URL="$DEV_ROOT_URL" "${DEV_COMMAND[@]}" --port "$DEV_PORT" 2>&1 | tee "$(one_log dev-server)"
#---------------------------------------------------------------------
break
;;
@@ -3015,7 +3078,7 @@ for _once in 1; do
#---------------------------------------------------------------------
#Logging of terminal output to console and to .tools/log/wekan-log.log at end of this line: 2>&1 | tee "$(one_log dev-server)"
#WARN_WHEN_USING_OLD_API=true NODE_OPTIONS="--trace-warnings"
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=sockjs DEBUG=true WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://$IPADDRESS:$PORT meteor run --port $PORT 2>&1 | tee "$(one_log dev-server)"
DEFAULT_METEOR_REACTIVITY_ORDER="changeStreams,oplog,polling" DDP_TRANSPORT=sockjs DEBUG="${DEBUG:-false}" WRITABLE_PATH=.. WITH_API=true ROOT_URL=http://$IPADDRESS:$PORT "${DEV_COMMAND[@]}" --port $PORT 2>&1 | tee "$(one_log dev-server)"
#---------------------------------------------------------------------
break
;;
+1 -1
View File
@@ -90,7 +90,7 @@ template(name="commentReactions")
unless currentUser.isReadOnly
unless currentUser.isReadAssignedOnly
a.open-comment-reaction-popup(title="{{_ 'addReactionPopup-title'}}")
i.fa.fa-smile-o(title="{{_ 'reaction' }}")
i.fa.fa-smile-o(title="{{_ 'addReactionPopup-title' }}")
i.fa.fa-plus(title="{{_ 'add' }}")
template(name="addReactionPopup")
-1
View File
@@ -67,7 +67,6 @@ Template.commentForm.onDestroyed(function () {
// button, a route change); make sure the last keystrokes reach the draft.
flushCommentDraftSave();
commentFormIsOpen.set(false);
$('.note-popover').hide();
});
Template.commentForm.helpers({
+1 -1
View File
@@ -476,7 +476,7 @@ Template.boardBody.onRendered(function () {
el.setAttribute('role', 'button');
// Short, descriptive label for favorite/star toggle
if (el.classList.contains('js-favorite-toggle')) {
el.setAttribute('aria-label', TAPi18n.__('favorite-toggle-label'));
el.setAttribute('aria-label', `${TAPi18n.__('star-item')} / ${TAPi18n.__('unstar-item')}`);
} else {
el.setAttribute('aria-label', 'Toggle');
}
+2 -2
View File
@@ -774,7 +774,7 @@ Template.cardsSortPopup.events({
createdAt: 1,
};
setCardsSortBy(sortBy);
sortCardsBy.set(TAPi18n.__('date-created-newest-first'));
sortCardsBy.set(TAPi18n.__('created-at-newest-first'));
Popup.back();
},
'click .js-sort-created-desc'() {
@@ -782,7 +782,7 @@ Template.cardsSortPopup.events({
createdAt: -1,
};
setCardsSortBy(sortBy);
sortCardsBy.set(TAPi18n.__('date-created-oldest-first'));
sortCardsBy.set(TAPi18n.__('created-at-oldest-first'));
Popup.back();
},
// #3050: float highest-voted cards to the top of each list. `votes` is not
@@ -7,6 +7,7 @@ import { Meteor } from 'meteor/meteor';
import { Session } from 'meteor/session';
import { Accounts } from 'meteor/accounts-base';
import { TAPi18n } from '/imports/i18n';
import { cardDateFormat } from '/client/lib/exportLocale';
// ONE url builder for every chart export, every view. There used to be five
// copies of this (gantt.js, frappeGantt.js, dhtmlxGantt.js, timeView.js,
@@ -18,12 +19,11 @@ export function chartExportUrl(chartKey, format) {
const boardId = Session.get('currentBoard');
if (!boardId || !chartKey) return '';
const path = format === 'PDF' ? 'exportPDF' : 'exportExcel';
const user = Meteor.user();
const params = new URLSearchParams({
authToken: Accounts._storedLoginToken() || '',
lang: TAPi18n.getLanguage ? TAPi18n.getLanguage() : 'en',
tz: Intl.DateTimeFormat().resolvedOptions().timeZone || '',
dateFormat: (user && user.profile && user.profile.dateFormat) || 'YYYY-MM-DD',
dateFormat: cardDateFormat(),
});
return `/api/boards/${boardId}/charts/${chartKey}/${path}?${params.toString()}`;
}
+1 -1
View File
@@ -3,7 +3,7 @@ template(name="miniboard")
class="minicard-{{colorClass}}")
.minicard-title
.handle
span.drag-handle(title="{{_ 'dragBoard'}}")
span.drag-handle(title="{{_ 'drag-board'}}")
i.fa.fa-arrows
+viewer
= title
+2 -2
View File
@@ -25,9 +25,9 @@
}
.timeline-view .timeline-marker.is-selected {
background: #29a3a3;
background: #187575;
color: #fff;
border-color: #29a3a3;
border-color: #187575;
}
.timeline-view .timeline-view-asof {
+1 -1
View File
@@ -44,7 +44,7 @@ template(name="timelineView")
| {{this.memberNames}}
if this.dueAt
p.timeline-card-due
| {{_ 'card-due-date'}}: {{this.formattedDueAt}}
| {{_ 'due-date'}}: {{this.formattedDueAt}}
if selectedTimestamp
button.timeline-card-restore.js-restore-card-timeline.primary(type="button")
| {{_ 'board-view-timeline-restore'}}
+18
View File
@@ -44,9 +44,13 @@
.attachment-details-container {
display: block;
flex-grow: 1;
min-width: 0;
overflow-wrap: anywhere;
}
.attachment-details {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: space-between;
margin-inline-end: 25px; /* Make sure the icons are not to far to the right */
}
@@ -384,3 +388,17 @@
margin-top: 10px;
}
}
/* Native viewer controls retain the previous icon-only presentation. */
#viewer-close, #prev-attachment, #next-attachment {
background: transparent;
border: 0;
box-shadow: none;
padding: 0;
}
button.attachment-thumbnail-container {
padding: 0;
border: 0;
text-align: start;
font: inherit;
}
+6 -6
View File
@@ -33,14 +33,14 @@ template(name="attachmentDeletePopup")
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
template(name="attachmentViewer")
#viewer-overlay.hidden
#viewer-overlay.hidden(role="dialog" aria-modal="true" aria-labelledby="attachment-name")
#viewer-top-bar
span#attachment-name
a#viewer-close
button#viewer-close(type="button" aria-label="{{_ 'close'}}")
i.fa.fa-times-thin
#viewer-container
i.fa.fa-caret-left#prev-attachment
button.fa.fa-caret-left#prev-attachment(type="button" aria-label="{{_ 'previous'}}")
#viewer-content
img#image-viewer.hidden
video#video-viewer.hidden(controls="true")
@@ -49,7 +49,7 @@ template(name="attachmentViewer")
span.pdf-preview-error {{_ 'preview-pdf-not-supported' }}
object#txt-viewer.hidden(type="text/plain")
#office-viewer.hidden
i.fa.fa-caret-right#next-attachment
button.fa.fa-caret-right#next-attachment(type="button" aria-label="{{_ 'next'}}")
template(name="attachmentGallery")
@@ -62,7 +62,7 @@ template(name="attachmentGallery")
each attachments
.attachment-item(class="{{#if isAttachmentMigrating _id}}migrating{{/if}}")
.attachment-thumbnail-container.open-preview(data-attachment-id="{{_id}}" data-card-id="{{ meta.cardId }}")
button.attachment-thumbnail-container.open-preview(type="button" aria-label="{{_ 'preview'}}: {{cleanFilename name}}" data-attachment-id="{{_id}}" data-card-id="{{ meta.cardId }}")
if link
if(isImage)
img.attachment-thumbnail(src="{{link}}" title="{{cleanFilename name}}" alt="{{cleanFilename name}}")
@@ -109,7 +109,7 @@ template(name="attachmentGallery")
.attachment-migration-overlay
.migration-spinner
i.fa.fa-cog.fa-spin
.migration-text {{_ 'migrating-attachment'}}
.migration-text {{_ 'attachment-migration'}}
template(name="attachmentActionsPopup")
ul.pop-over-list
+63 -31
View File
@@ -1,3 +1,5 @@
import { TAPi18n } from '/imports/i18n';
import { trapTabKey } from '/client/lib/accessibility';
import { formatDateForDisplay } from '/client/lib/dateDisplay';
import { ReactiveCache } from '/imports/reactiveCache';
import '/client/components/cards/attachments.jade';
@@ -21,6 +23,7 @@ import prettyMilliseconds from 'pretty-ms';
// We store current card ID and the ID of currently opened attachment in a
// global var. This is used so that we know what's the next attachment to open
// when the user clicks on the prev/next button in the attachment viewer.
let viewerOpener = null;
let cardId = null;
let openAttachmentId = null;
let slideshowAttachmentIds = null;
@@ -29,14 +32,13 @@ let slideshowAttachmentIds = null;
let touchStartCoords = null;
let touchEndCoords = null;
// Stores link to the attachment for which attachment actions popup was opened
let attachmentActionsLink = null;
let officePreview = null;
let officePreviewAbortController = null;
let officePreviewGeneration = 0;
Template.attachmentGallery.events({
'click .open-preview'(event) {
event.preventDefault();
openAttachmentId = $(event.currentTarget).attr("data-attachment-id");
cardId = $(event.currentTarget).attr("data-card-id");
@@ -51,9 +53,6 @@ Template.attachmentGallery.events({
event.stopPropagation();
},
'click .js-open-attachment-menu': Popup.open('attachmentActions'),
'mouseover .js-open-attachment-menu'(event) { // For some reason I cannot combine handlers for "click .js-open-attachment-menu" and "mouseover .js-open-attachment-menu" events so this is a quick workaround.
attachmentActionsLink = event.currentTarget.getAttribute("data-attachment-link");
},
'click .js-rename': Popup.open('attachmentRename'),
// History.md §12.1: Delete is a SOFT delete, done by the server. The method
// marks the attachment deleted, keeps the file, unsets the cover if this was
@@ -197,7 +196,10 @@ function openAttachmentViewer(attachmentId) {
// Show the cleaned name: URL-decoded, homoglyphs folded, invisible characters
// and exploit markup removed (plain text; .text() escapes, so never HTML).
$('#attachment-name').text(cleanFileName(attachment.name));
const opening = !viewerOpener;
if (opening) viewerOpener = document.activeElement;
$('#viewer-overlay').removeClass('hidden');
if (opening) document.getElementById('viewer-close')?.focus();
}
// Admin Panel / Files uses the same viewer as an opened card, but its next and
@@ -213,7 +215,7 @@ export function openAttachmentSlideshow(attachmentId, attachmentIds = []) {
openAttachmentViewer(attachmentId);
}
function closeAttachmentViewer() {
function closeAttachmentViewer(restoreFocus = true) {
officePreviewGeneration++;
if (officePreviewAbortController) {
officePreviewAbortController.abort();
@@ -224,8 +226,8 @@ function closeAttachmentViewer() {
officePreview = null;
}
const officeViewer = document.getElementById('office-viewer');
officeViewer.replaceChildren();
officeViewer.classList.add('hidden');
officeViewer?.replaceChildren();
officeViewer?.classList.add('hidden');
$("#viewer-overlay").addClass("hidden");
@@ -239,19 +241,23 @@ function closeAttachmentViewer() {
$("#txt-viewer").attr("data", "");
$("#txt-viewer").addClass("hidden");
$("#video-viewer").get(0).pause(); // Stop playback
$("#video-viewer").get(0).currentTime = 0;
$("#video-viewer").get(0)?.pause(); // Stop playback
if ($("#video-viewer").get(0)) $("#video-viewer").get(0).currentTime = 0;
$("#video-viewer").empty();
$("#video-viewer").addClass("hidden");
$("#audio-viewer").get(0).pause(); // Stop playback
$("#audio-viewer").get(0).currentTime = 0;
$("#audio-viewer").get(0)?.pause(); // Stop playback
if ($("#audio-viewer").get(0)) $("#audio-viewer").get(0).currentTime = 0;
$("#audio-viewer").empty();
$("#audio-viewer").addClass("hidden");
if (restoreFocus) {
if (viewerOpener?.isConnected) viewerOpener.focus();
viewerOpener = null;
}
}
function openNextAttachment() {
closeAttachmentViewer();
closeAttachmentViewer(false);
let i = 0;
// Find an attachment that can be opened
@@ -268,7 +274,7 @@ function openNextAttachment() {
}
function openPrevAttachment() {
closeAttachmentViewer();
closeAttachmentViewer(false);
let i = 0;
// Find an attachment that can be opened
@@ -306,7 +312,20 @@ function processTouch(){
}
Template.attachmentViewer.onDestroyed(function () {
closeAttachmentViewer();
});
Template.attachmentViewer.events({
'keydown #viewer-overlay'(event) {
if (event.key === 'Escape') {
event.preventDefault();
event.stopPropagation();
closeAttachmentViewer();
} else {
trapTabKey(event);
}
},
'touchstart #viewer-container'(event) {
touchStartCoords = {
x: event.changedTouches[0].screenX,
@@ -340,13 +359,16 @@ Template.attachmentViewer.events({
closeAttachmentViewer();
},
'click #viewer-close'() {
'click #viewer-close'(event) {
event.preventDefault();
closeAttachmentViewer();
},
'click #next-attachment'() {
'click #next-attachment'(event) {
event.preventDefault();
openNextAttachment();
},
'click #prev-attachment'() {
'click #prev-attachment'(event) {
event.preventDefault();
openPrevAttachment();
},
});
@@ -641,9 +663,8 @@ Template.attachmentActionsPopup.helpers({
return ret;
},
isBackgroundImage() {
//const currentBoard = Utils.getCurrentBoard();
//return currentBoard.backgroundImageURL === $(".attachment-thumbnail-img").attr("src");
return false;
const url = getAttachmentUrl(this);
return !!url && Utils.getCurrentBoard()?.backgroundImageURL === url;
},
});
@@ -656,20 +677,31 @@ Template.attachmentActionsPopup.events({
ReactiveCache.getCard(this.meta.cardId).unsetCover();
Popup.back();
},
'click .js-add-background-image'(event) {
const currentBoard = Utils.getCurrentBoard();
currentBoard.setBackgroundImageURL(attachmentActionsLink);
Utils.setBackgroundImage(attachmentActionsLink);
Popup.back();
async 'click .js-add-background-image'(event) {
event.preventDefault();
const currentBoard = Utils.getCurrentBoard();
const url = getAttachmentUrl(this);
if (!currentBoard || !url) return;
try {
if (await currentBoard.setBackgroundImageURL(url) === false) return;
Utils.setBackgroundImage(url);
Popup.back();
} catch (error) {
window.alert(TAPi18n.__('server-error'));
}
},
'click .js-remove-background-image'(event) {
const currentBoard = Utils.getCurrentBoard();
currentBoard.setBackgroundImageURL("");
Utils.setBackgroundImage("");
Popup.back();
Utils.reload();
async 'click .js-remove-background-image'(event) {
event.preventDefault();
const currentBoard = Utils.getCurrentBoard();
if (!currentBoard) return;
try {
if (await currentBoard.setBackgroundImageURL('') === false) return;
Utils.setBackgroundImage('');
Popup.back();
Utils.reload();
} catch (error) {
window.alert(TAPi18n.__('server-error'));
}
},
});
@@ -4,7 +4,6 @@ const descriptionFormIsOpen = new ReactiveVar(false);
Template.descriptionForm.onDestroyed(function () {
descriptionFormIsOpen.set(false);
$('.note-popover').hide();
});
Template.descriptionForm.helpers({
+1 -1
View File
@@ -1217,7 +1217,7 @@ body.mobile-mode .card-details .card-details-header .close-card-details-mobile-w
gone with the grouping; a field that is its own section is a group of one. */
.card-details .card-details-section-rule {
width: 100%;
margin: 14px 0 6px 0;
margin: calc(14px * min(1, var(--wekan-ui-font-scale, 1))) 0 calc(6px * min(1, var(--wekan-ui-font-scale, 1))) 0;
}
.card-details h3.card-details-item-title.js-toggle-card-section {
cursor: pointer;
+266 -257
View File
@@ -3,8 +3,6 @@ template(name="cardDetailsPopup")
template(name="cardDetails")
+attachmentViewer
//- `card-details-with-handle` when drag handles are on, the same class the
minicard carries for the same reason: with a handle, the title is all edit
target; without one, half of it is what the window is dragged by.
@@ -28,7 +26,7 @@ template(name="cardDetails")
+editCardTitleForm
else
unless isMiniScreen
a.card-collapse-toggle.js-card-collapse-toggle(title="{{_ 'collapse-card'}}")
a.card-collapse-toggle.js-card-collapse-toggle(title="{{_ 'collapse'}}")
if cardCollapsed
i.fa.fa-caret-right
else
@@ -96,7 +94,7 @@ template(name="cardDetails")
| &nbsp; &gt; &nbsp;
a.js-parent-card(href=linkForCard) {{title}}
// else
{{_ 'top-level-card'}}
{{_ 'card'}}
if isLinkedCard
a.linked-card-location.js-go-to-linked-card
+viewer
@@ -173,8 +171,9 @@ template(name="cardDetails")
.card-checklist-attachmentGalleries
.card-checklist-attachmentGallery.card-checklists
if currentBoard.allowsChecklists
+cardSectionHeader(section="checklists" icon="fa-check" label="checklists")
if isSectionOpen "checklists"
unless $eq currentBoard.allowsChecklistTitle false
+cardSectionHeader(section="checklists" icon="fa-check" label="checklists")
if checklistSectionOpen
//- 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.
@@ -324,194 +323,200 @@ template(name="cardFieldSectionLabels")
+cardButtons(_id=_id boardId=boardId)
template(name="cardFieldSectionDates")
.card-details-group.card-details-group-date-format
hr.card-details-section-rule
.card-details-group-body
if currentBoard.hasAnyAllowsDate
.card-details-item.card-details-item-date-format
+cardSectionHeader(section="date-format" icon="fa-calendar" label="date-format" noRule=true)
if isSectionOpen "date-format"
.card-details-item-content
select.js-date-format-selector
option(value="YYYY-MM-DD" selected="{{#if isDateFormat 'YYYY-MM-DD'}}selected{{/if}}") {{_ 'date-format-yyyy-mm-dd'}}
option(value="DD-MM-YYYY" selected="{{#if isDateFormat 'DD-MM-YYYY'}}selected{{/if}}") {{_ 'date-format-dd-mm-yyyy'}}
option(value="MM-DD-YYYY" selected="{{#if isDateFormat 'MM-DD-YYYY'}}selected{{/if}}") {{_ 'date-format-mm-dd-yyyy'}}
if currentBoard.hasAnyAllowsDate
.card-details-group.card-details-group-date-format
hr.card-details-section-rule
.card-details-group-body
if currentBoard.hasAnyAllowsDate
.card-details-item.card-details-item-date-format
+cardSectionHeader(section="date-format" icon="fa-calendar" label=dateSectionLabel noRule=true)
if isSectionOpen "date-format"
unless isDateFormatForced
.card-details-item-content
select.js-date-format-selector
option(value="YYYY-MM-DD" selected="{{#if isDateFormat 'YYYY-MM-DD'}}selected{{/if}}") {{_ 'date-format-yyyy-mm-dd'}} · {{_ 'time'}}
option(value="DD-MM-YYYY" selected="{{#if isDateFormat 'DD-MM-YYYY'}}selected{{/if}}") {{_ 'date-format-dd-mm-yyyy'}} · {{_ 'time'}}
option(value="MM-DD-YYYY" selected="{{#if isDateFormat 'MM-DD-YYYY'}}selected{{/if}}") {{_ 'date-format-mm-dd-yyyy'}} · {{_ 'time'}}
option(value="YYYY-MM-DD-date-only" selected="{{#if isDateFormat 'YYYY-MM-DD-date-only'}}selected{{/if}}") {{_ 'date'}}: {{_ 'date-format-yyyy-mm-dd'}}
option(value="DD-MM-YYYY-date-only" selected="{{#if isDateFormat 'DD-MM-YYYY-date-only'}}selected{{/if}}") {{_ 'date'}}: {{_ 'date-format-dd-mm-yyyy'}}
option(value="MM-DD-YYYY-date-only" selected="{{#if isDateFormat 'MM-DD-YYYY-date-only'}}selected{{/if}}") {{_ 'date'}}: {{_ 'date-format-mm-dd-yyyy'}}
if isSectionOpen "date-format"
each field in orderedDatesFields
if $eq field "receivedDate"
if currentBoard.allowsReceivedDate
.card-details-item.card-details-item-received
h3.card-details-item-title
i.fa.fa-sign-out
| {{_ 'card-received'}}
if getReceived
+cardReceivedDate(card=this canModifyCard=canModifyCard)
else
if canModifyCard
unless currentUser.isWorker
a.card-label.add-label.js-received-date
i.fa.fa-plus
if isSectionOpen "date-format"
each field in orderedDatesFields
if $eq field "receivedDate"
if currentBoard.allowsReceivedDate
.card-details-item.card-details-item-received
h3.card-details-item-title
i.fa.fa-sign-out
| {{_ 'card-received'}}
if getReceived
+cardReceivedDate(card=this canModifyCard=canModifyCard)
else
if canModifyCard
unless currentUser.isWorker
a.card-label.add-label.js-received-date
i.fa.fa-plus
if $eq field "startDate"
if currentBoard.allowsStartDate
.card-details-item.card-details-item-start
h3.card-details-item-title
i.fa.fa-hourglass-start
| {{_ 'card-start'}}
if getStart
+cardStartDate(card=this canModifyCard=canModifyCard)
else
if canModifyCard
unless currentUser.isWorker
a.card-label.add-label.js-start-date
i.fa.fa-plus
if $eq field "startDate"
if currentBoard.allowsStartDate
.card-details-item.card-details-item-start
h3.card-details-item-title
i.fa.fa-hourglass-start
| {{_ 'card-start'}}
if getStart
+cardStartDate(card=this canModifyCard=canModifyCard)
else
if canModifyCard
unless currentUser.isWorker
a.card-label.add-label.js-start-date
i.fa.fa-plus
if $eq field "dueDate"
if currentBoard.allowsDueDate
.card-details-item.card-details-item-due
h3.card-details-item-title
i.fa.fa-clock-o
| {{_ 'card-due'}}
if getDue
+cardDueDate(card=this canModifyCard=canModifyCard)
else
if canModifyCard
unless currentUser.isWorker
a.card-label.add-label.js-due-date
i.fa.fa-plus
if dueDateChangeCount
.card-details-due-date-changes(title="{{_ 'due-date-changes'}}")
| {{_ 'due-date-changed-times' dueDateChangeCount}}
if $eq field "dueDate"
if currentBoard.allowsDueDate
.card-details-item.card-details-item-due
h3.card-details-item-title
i.fa.fa-clock-o
| {{_ 'card-due'}}
if getDue
+cardDueDate(card=this canModifyCard=canModifyCard)
else
if canModifyCard
unless currentUser.isWorker
a.card-label.add-label.js-due-date
i.fa.fa-plus
if dueDateChangeCount
.card-details-due-date-changes(title="{{_ 'due-date-changes'}}")
| {{_ 'due-date-changed-times' dueDateChangeCount}}
if $eq field "endDate"
if currentBoard.allowsEndDate
.card-details-item.card-details-item-end
h3.card-details-item-title
i.fa.fa-hourglass-end
| {{_ 'card-end'}}
if getEnd
+cardEndDate(card=this canModifyCard=canModifyCard)
else
if canModifyCard
unless currentUser.isWorker
a.card-label.add-label.js-end-date
i.fa.fa-plus
if $eq field "endDate"
if currentBoard.allowsEndDate
.card-details-item.card-details-item-end
h3.card-details-item-title
i.fa.fa-hourglass-end
| {{_ 'card-end'}}
if getEnd
+cardEndDate(card=this canModifyCard=canModifyCard)
else
if canModifyCard
unless currentUser.isWorker
a.card-label.add-label.js-end-date
i.fa.fa-plus
template(name="cardFieldSectionMembers")
.card-details-group.card-details-group-members
hr.card-details-section-rule
.card-details-group-body
if currentBoard.allowsMembers
.card-details-item.card-details-item-members
+cardSectionHeader(section="members" icon="fa-users" label="members" noRule=true)
if isSectionOpen "members"
each userId in getMembers
+userAvatar(userId=userId cardId=_id)
| {{! XXX Hack to hide syntaxic coloration /// }}
if canModifyCard
unless currentUser.isWorker
a.member.add-member.card-details-item-add-button.js-add-members(title="{{_ 'card-members-title'}}")
i.fa.fa-plus
if isSectionOpen "members"
each field in orderedMembersFields
if $eq field "assignee"
if currentBoard.allowsAssignee
.card-details-item.card-details-item-assignees
h3.card-details-item-title
i.fa.fa-user
| {{_ 'assignee'}}
each userId in getAssignees
+userAvatar(userId=userId cardId=_id assignee=true)
| {{! XXX Hack to hide syntaxic coloration /// }}
if canModifyCard
a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}")
if currentBoard.hasAnyAllowsUser
.card-details-group.card-details-group-members
hr.card-details-section-rule
.card-details-group-body
if currentBoard.allowsMembers
.card-details-item.card-details-item-members
+cardSectionHeader(section="members" icon="fa-users" label="members" noRule=true)
if isSectionOpen "members"
each userId in getMembers
+userAvatar(userId=userId cardId=_id)
| {{! XXX Hack to hide syntaxic coloration /// }}
if canModifyCard
unless currentUser.isWorker
a.member.add-member.card-details-item-add-button.js-add-members(title="{{_ 'card-members-title'}}")
i.fa.fa-plus
if currentUser.isWorker
unless assigneeSelected
if isSectionOpen "members"
each field in orderedMembersFields
if $eq field "assignee"
if currentBoard.allowsAssignee
.card-details-item.card-details-item-assignees
h3.card-details-item-title
i.fa.fa-user
| {{_ 'assignee'}}
each userId in getAssignees
+userAvatar(userId=userId cardId=_id assignee=true)
| {{! XXX Hack to hide syntaxic coloration /// }}
if canModifyCard
a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}")
i.fa.fa-plus
if currentUser.isWorker
unless assigneeSelected
a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}")
i.fa.fa-plus
if $eq field "creator"
if currentBoard.allowsCreator
.card-details-item.card-details-item-creator
h3.card-details-item-title
i.fa.fa-user
| {{_ 'creator'}}
if $eq field "creator"
if currentBoard.allowsCreator
.card-details-item.card-details-item-creator
h3.card-details-item-title
i.fa.fa-user
| {{_ 'creator'}}
+userAvatar(userId=userId noRemove=true)
| {{! XXX Hack to hide syntaxic coloration /// }}
+userAvatar(userId=userId noRemove=true)
| {{! XXX Hack to hide syntaxic coloration /// }}
//.card-details-items
//if assigneeSelected
//.card-details-items
if $eq field "requestedBy"
if currentBoard.allowsRequestedBy
.card-details-item.card-details-item-name
h3.card-details-item-title
i.fa.fa-shopping-cart
| {{_ 'requested-by'}}
if canModifyCard
unless currentUser.isWorker
+inlinedForm(classNames="js-card-details-requester")
+editCardRequesterForm
else
each userId in getRequesters
+userAvatar(userId=userId cardId=_id noRemove=true)
if getRequestedBy
+viewer
= getRequestedBy
.card-details-person-controls
a.member.add-member.card-details-item-add-button.js-select-requester(title="{{_ 'requested-by'}}")
i.fa.fa-plus
a.js-open-inlined-form(
title="{{#if getRequestedBy}}{{_ 'edit'}}{{else}}{{_ 'add'}}{{/if}}"
aria-label="{{#if getRequestedBy}}{{_ 'edit'}}{{else}}{{_ 'add'}}{{/if}}")
if getRequestedBy
i.fa.fa-pencil-square-o(aria-hidden="true")
else
i.fa.fa-plus(aria-hidden="true")
else
each userId in getRequesters
+userAvatar(userId=userId noRemove=true)
if getRequestedBy
+viewer
= getRequestedBy
//.card-details-items
//if assigneeSelected
//.card-details-items
if $eq field "requestedBy"
if currentBoard.allowsRequestedBy
.card-details-item.card-details-item-name
h3.card-details-item-title
i.fa.fa-shopping-cart
| {{_ 'requested-by'}}
if canModifyCard
unless currentUser.isWorker
+inlinedForm(classNames="js-card-details-requester")
+editCardRequesterForm
else
each userId in getRequesters
+userAvatar(userId=userId cardId=_id noRemove=true)
if getRequestedBy
+viewer
= getRequestedBy
.card-details-person-controls
a.member.add-member.card-details-item-add-button.js-select-requester(title="{{_ 'requested-by'}}")
i.fa.fa-plus
a.js-open-inlined-form(
title="{{#if getRequestedBy}}{{_ 'edit'}}{{else}}{{_ 'add'}}{{/if}}"
aria-label="{{#if getRequestedBy}}{{_ 'edit'}}{{else}}{{_ 'add'}}{{/if}}")
if getRequestedBy
i.fa.fa-pencil-square-o(aria-hidden="true")
else
i.fa.fa-plus(aria-hidden="true")
else
each userId in getRequesters
+userAvatar(userId=userId noRemove=true)
if getRequestedBy
+viewer
= getRequestedBy
if $eq field "assignedBy"
if currentBoard.allowsAssignedBy
.card-details-item.card-details-item-name
h3.card-details-item-title
i.fa.fa-user-plus
| {{_ 'assigned-by'}}
if canModifyCard
unless currentUser.isWorker
+inlinedForm(classNames="js-card-details-assigner")
+editCardAssignerForm
else
each userId in getAssigners
+userAvatar(userId=userId cardId=_id noRemove=true)
if getAssignedBy
+viewer
= getAssignedBy
.card-details-person-controls
a.member.add-member.card-details-item-add-button.js-select-assigner(title="{{_ 'assigned-by'}}")
i.fa.fa-plus
a.js-open-inlined-form(
title="{{#if getAssignedBy}}{{_ 'edit'}}{{else}}{{_ 'add'}}{{/if}}"
aria-label="{{#if getAssignedBy}}{{_ 'edit'}}{{else}}{{_ 'add'}}{{/if}}")
if getAssignedBy
i.fa.fa-pencil-square-o(aria-hidden="true")
else
i.fa.fa-plus(aria-hidden="true")
else
each userId in getAssigners
+userAvatar(userId=userId noRemove=true)
if getAssignedBy
+viewer
= getAssignedBy
if $eq field "assignedBy"
if currentBoard.allowsAssignedBy
.card-details-item.card-details-item-name
h3.card-details-item-title
i.fa.fa-user-plus
| {{_ 'assigned-by'}}
if canModifyCard
unless currentUser.isWorker
+inlinedForm(classNames="js-card-details-assigner")
+editCardAssignerForm
else
each userId in getAssigners
+userAvatar(userId=userId cardId=_id noRemove=true)
if getAssignedBy
+viewer
= getAssignedBy
.card-details-person-controls
a.member.add-member.card-details-item-add-button.js-select-assigner(title="{{_ 'assigned-by'}}")
i.fa.fa-plus
a.js-open-inlined-form(
title="{{#if getAssignedBy}}{{_ 'edit'}}{{else}}{{_ 'add'}}{{/if}}"
aria-label="{{#if getAssignedBy}}{{_ 'edit'}}{{else}}{{_ 'add'}}{{/if}}")
if getAssignedBy
i.fa.fa-pencil-square-o(aria-hidden="true")
else
i.fa.fa-plus(aria-hidden="true")
else
each userId in getAssigners
+userAvatar(userId=userId noRemove=true)
if getAssignedBy
+viewer
= getAssignedBy
template(name="cardFieldSectionDependencies")
//- #6688: Board Settings / Card can hide the Dependencies section.
@@ -548,79 +553,80 @@ template(name="cardFieldSectionDependencies")
i.fa.fa-plus
| {{_ 'add-dependency'}}
template(name="cardFieldSectionSort")
.card-details-group.card-details-group-sort
hr.card-details-section-rule
.card-details-group-body
if currentBoard.allowsCardSortingByNumber
.card-details-item.card-details-sort-order
+cardSectionHeader(section="sort" icon="fa-sort-numeric-asc" label="sort" noRule=true)
if isSectionOpen "sort"
if canModifyCard
+inlinedForm(classNames="js-card-details-sort")
+editCardSortOrderForm
else
a.js-open-inlined-form
+viewer
= sort
if isSectionOpen "sort"
each field in orderedSortFields
if $eq field "showLists"
if currentBoard.allowsShowLists
.card-details-item.card-details-show-lists
h3.card-details-item-title
i.fa.fa-list
| {{_ 'list'}}
if canModifyCard
details.card-details-list-picker
summary
each currentSwimlaneListsSorted
if isCurrentListId _id
+viewer
= title
div.card-details-list-options(role="listbox" aria-label="{{_ 'list'}}")
each currentSwimlaneListsSorted
div.js-select-card-details-list-option(role="option" tabindex="0" data-list-id="{{_id}}" aria-selected="{{#if isCurrentListId _id}}true{{else}}false{{/if}}")
+viewer
= title
if hasVisibleSortFields
.card-details-group.card-details-group-sort
hr.card-details-section-rule
.card-details-group-body
if currentBoard.allowsCardSortingByNumber
.card-details-item.card-details-sort-order
+cardSectionHeader(section="sort" icon="fa-sort-numeric-asc" label="sort" noRule=true)
if isSectionOpen "sort"
if canModifyCard
+inlinedForm(classNames="js-card-details-sort")
+editCardSortOrderForm
else
each currentSwimlaneListsSorted
if isCurrentListId _id
+viewer
= title
a.js-open-inlined-form
+viewer
= sort
//.card-details-items
if $eq field "spentTime"
if currentBoard.allowsSpentTime
if getSpentTime
.card-details-item.card-details-item-spent
if getIsOvertime
h3.card-details-item-title
| {{_ 'overtime-hours'}}
if isSectionOpen "sort"
each field in orderedSortFields
if $eq field "showLists"
if currentBoard.allowsShowLists
.card-details-item.card-details-show-lists
h3.card-details-item-title
i.fa.fa-list
| {{_ 'list'}}
if canModifyCard
details.card-details-list-picker
summary
each currentSwimlaneListsSorted
if isCurrentListId _id
+viewer
= title
div.card-details-list-options(role="listbox" aria-label="{{_ 'list'}}")
each currentSwimlaneListsSorted
div.js-select-card-details-list-option(role="option" tabindex="0" data-list-id="{{_id}}" aria-selected="{{#if isCurrentListId _id}}true{{else}}false{{/if}}")
+viewer
= title
else
h3.card-details-item-title
| {{_ 'spent-time-hours'}}
+cardSpentTime
each currentSwimlaneListsSorted
if isCurrentListId _id
+viewer
= title
//- #6688: the two timers each have a Board Settings / Card toggle,
like Spent time above; before they rendered on every card.
if $eq field "flowtime"
if currentBoard.allowsFlowtime
.card-details-item.card-details-item-flow
h3.card-details-item-title
i.fa.fa-bolt
| {{_ 'flowtime'}}
+cardFlowtime
//.card-details-items
if $eq field "spentTime"
if currentBoard.allowsSpentTime
if getSpentTime
.card-details-item.card-details-item-spent
if getIsOvertime
h3.card-details-item-title
| {{_ 'overtime-hours'}}
else
h3.card-details-item-title
| {{_ 'spent-time-hours'}}
+cardSpentTime
if $eq field "pomodoro"
if currentBoard.allowsPomodoro
.card-details-item.card-details-item-pomodoro
h3.card-details-item-title
i.fa.fa-clock-o
| {{_ 'pomodoro'}}
+cardPomodoro
//- #6688: the two timers each have a Board Settings / Card toggle,
like Spent time above; before they rendered on every card.
if $eq field "flowtime"
if currentBoard.allowsFlowtime
.card-details-item.card-details-item-flow
h3.card-details-item-title
i.fa.fa-bolt
| {{_ 'flowtime'}}
+cardFlowtime
//.card-details-items
if $eq field "pomodoro"
if currentBoard.allowsPomodoro
.card-details-item.card-details-item-pomodoro
h3.card-details-item-title
i.fa.fa-clock-o
| {{_ 'pomodoro'}}
+cardPomodoro
//.card-details-items
template(name="cardFieldSectionCustomFields")
//- The heading is here for EVERY card the reader may write to, not only
@@ -1248,23 +1254,23 @@ template(name="exportCardPopup")
which is the one holding the dialog. client/components/cards/cardDetails.js
template(name="cardDestinationPicker")
unless currentUser.isWorker
label {{_ 'boards'}}:
select.js-select-boards(autofocus)
label(for="{{pickerFieldId 'boards'}}") {{_ 'boards'}}:
select.js-select-boards(autofocus id="{{pickerFieldId 'boards'}}")
each boards
option(value="{{_id}}" selected="{{#if isSelectedBoardId _id}}selected{{/if}}") {{title}}
label {{_ 'swimlanes'}}:
select.js-select-swimlanes
label(for="{{pickerFieldId 'swimlanes'}}") {{_ 'swimlanes'}}:
select.js-select-swimlanes(id="{{pickerFieldId 'swimlanes'}}")
each swimlanes
option(value="{{_id}}" selected="{{#if isSelectedSwimlaneId _id}}selected{{/if}}") {{isTitleDefault title}}
label {{_ 'lists'}}:
select.js-select-lists
label(for="{{pickerFieldId 'lists'}}") {{_ 'lists'}}:
select.js-select-lists(id="{{pickerFieldId 'lists'}}")
each lists
option(value="{{_id}}" selected="{{#if isSelectedListId _id}}selected{{/if}}") {{title}}
label {{_ 'cards'}}:
select.js-select-cards
label(for="{{pickerFieldId 'cards'}}") {{_ 'cards'}}:
select.js-select-cards(id="{{pickerFieldId 'cards'}}")
each cards
option(value="{{_id}}" selected="{{#if isDialogOptionCardId _id}}selected{{/if}}") {{title}}
@@ -1275,8 +1281,11 @@ template(name="cardDestinationPicker")
input(type="radio" name="position" value="below" id="position-below" style="display: inline")
label(for="position-below") {{_ 'below-selected-card'}}
if destinationError
.warning.js-destination-error(role="alert") {{destinationError}}
.edit-controls.clearfix
button.primary.confirm.js-done {{_ 'done'}}
button.primary.confirm.js-done(type="button" disabled=destinationDisabled) {{_ 'done'}}
template(name="moveCardPopup")
label(for='move-card-title') {{_ 'title'}}:
@@ -1466,7 +1475,7 @@ template(name="setCardColorPopup")
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'}}
button.js-remove-color.negate.wide.right {{_ 'remove-btn'}}
template(name="cardDeletePopup")
p {{_ "card-delete-pop"}}
+63 -49
View File
@@ -1,3 +1,6 @@
import { relativeCardSort } from '/client/lib/relativeCardPosition';
import { Random } from 'meteor/random';
import { dateDisplayPreferences, isDateFormatForced } from '/client/lib/dateDisplay';
import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import { ReactiveDict } from 'meteor/reactive-dict';
@@ -770,6 +773,17 @@ Template.registerHelper('isCurrentListId', function isCurrentListId(listId) {
Template.registerHelper('orderedVoteAndPokerFields', sectionHelper('voteAndPoker'));
}
Template.cardFieldSectionSort.helpers({
hasVisibleSortFields() {
const board = this.board();
return !!(board && (
board.allowsCardSortingByNumber || board.allowsShowLists ||
board.allowsFlowtime || board.allowsPomodoro ||
(board.allowsSpentTime && this.getSpentTime())
));
},
});
Template.cardDetails.helpers({
// #4448: the order the reorderable card-detail sections (Labels, Dates,
// Members, Dependencies, Sort, Custom Fields, Vote/Poker, Description)
@@ -835,6 +849,10 @@ Template.cardDetails.helpers({
return user && user.isVerticalScrollbars();
},
checklistSectionOpen() {
return ReactiveCache.getBoard(this.boardId)?.allowsChecklistTitle === false || isCardSectionOpen('checklists');
},
isLoaded() {
return Template.instance().isLoaded;
},
@@ -1095,6 +1113,7 @@ Template.cardDetails.events({
}
},
'change .js-date-format-selector'(event) {
if (isDateFormatForced()) return;
const dateFormat = event.target.value;
if (Meteor.userId()) {
Meteor.call('changeDateFormat', dateFormat);
@@ -1524,13 +1543,11 @@ Template.cardDetails.events({
// function: isDateFormat" the instant that section rendered and broke
// opening the card popup entirely. Registered globally, like isSectionOpen
// just below, so every template can see it.
Template.registerHelper('isDateFormatForced', isDateFormatForced);
Template.registerHelper('dateSectionLabel', () => isDateFormatForced() ? 'date' : 'date-format');
Template.registerHelper('isDateFormat', function isDateFormat(format) {
const currentUser = ReactiveCache.getCurrentUser();
if (!currentUser) {
const stored = window.localStorage.getItem('dateFormat') || 'YYYY-MM-DD';
return format === stored;
}
return currentUser.getDateFormat() === format;
return dateDisplayPreferences().dateFormat === format;
});
Template.cardDetails.helpers({
@@ -2118,6 +2135,7 @@ Template.editCardAssignerForm.events({
// `each boards` the data context is a board, so a helper reaching into the
// context for it would find nothing there.
Template.cardDestinationPicker.onCreated(function () {
this.pickerId = Random.id();
this.autorun(() => {
const data = Template.currentData();
this.dialog = data && data.dialog;
@@ -2125,6 +2143,12 @@ Template.cardDestinationPicker.onCreated(function () {
});
Template.cardDestinationPicker.helpers({
pickerFieldId(field) { return `${Template.instance().pickerId}-${field}`; },
destinationError() { return Template.instance().dialog.error.get(); },
destinationDisabled() {
const dialog = Template.instance().dialog;
return dialog.loading.get() || dialog.saving.get() || !dialog.selectedSwimlaneId.get() || !dialog.selectedListId.get();
},
boards() {
return Template.instance().dialog.boards();
},
@@ -2186,7 +2210,9 @@ function registerCardDialogTemplate(templateName) {
Template[templateName].events({
async 'click .js-done'(event, tpl) {
event.preventDefault();
const dialog = tpl.dialog;
if (dialog.loading.get() || dialog.saving.get()) return;
// Read the target board/swimlane/list from the dialog's live reactive
// selection rather than the DOM <select> elements. A reactive re-render
// (e.g. the boards() helper transiently returning [] while a 'board'
@@ -2203,11 +2229,19 @@ function registerCardDialogTemplate(templateName) {
? cardSelect.options[cardSelect.selectedIndex].value
: null;
if (!boardId || !swimlaneId || !listId) return;
if (cardId && !dialog.cards().some(card => card._id === cardId)) return;
dialog.error.set('');
dialog.saving.set(true);
const options = { boardId, swimlaneId, listId, cardId };
try {
await dialog.setDone(cardId, options);
} catch (e) {
console.error('Error in card dialog operation:', e);
dialog.error.set(e.reason || e.message || TAPi18n.__('server-error'));
return;
} finally {
dialog.saving.set(false);
}
Popup.back(2);
},
@@ -2217,6 +2251,7 @@ function registerCardDialogTemplate(templateName) {
'change .js-select-swimlanes'(event, tpl) {
tpl.dialog.selectedSwimlaneId.set($(event.currentTarget).val());
tpl.dialog.setFirstListId();
tpl.dialog.selectedCardId?.set('');
},
'change .js-select-lists'(event, tpl) {
tpl.dialog.selectedListId.set($(event.currentTarget).val());
@@ -2259,12 +2294,7 @@ Template.moveCardPopup.onCreated(function () {
if (cardId) {
const targetCard = ReactiveCache.getCard(cardId);
if (targetCard) {
const targetSort = targetCard.sort || 0;
if (position === 'above') {
sortIndex = targetSort - 0.5;
} else {
sortIndex = targetSort + 0.5;
}
sortIndex = await relativeCardSort(targetCard, position, card._id);
}
} else {
const maxSort = await card.getMaxSort(options.listId, options.swimlaneId);
@@ -2313,12 +2343,7 @@ Template.copyCardPopup.onCreated(function () {
if (cardId) {
const targetCard = ReactiveCache.getCard(cardId);
if (targetCard) {
const targetSort = targetCard.sort || 0;
if (position === 'above') {
sortIndex = targetSort - 0.5;
} else {
sortIndex = targetSort + 0.5;
}
sortIndex = await relativeCardSort(targetCard, position, newCard._id);
}
} else {
const maxSort = await newCard.getMaxSort(options.listId, options.swimlaneId);
@@ -2367,8 +2392,7 @@ Template.linkCardToBoardPopup.onCreated(function () {
if (cardId) {
const targetCard = ReactiveCache.getCard(cardId);
if (targetCard) {
const targetSort = targetCard.sort || 0;
sortIndex = position === 'above' ? targetSort - 0.5 : targetSort + 0.5;
sortIndex = await relativeCardSort(targetCard, position, newCard._id);
}
} else {
const maxSort = await newCard.getMaxSort(options.listId, options.swimlaneId);
@@ -2414,12 +2438,7 @@ Template.convertChecklistItemToCardPopup.onCreated(function () {
if (cardId) {
const targetCard = ReactiveCache.getCard(cardId);
if (targetCard) {
const targetSort = targetCard.sort || 0;
if (position === 'above') {
sortIndex = targetSort - 0.5;
} else {
sortIndex = targetSort + 0.5;
}
sortIndex = await relativeCardSort(targetCard, position, newCard._id);
}
} else {
const maxSort = await newCard.getMaxSort(options.listId, options.swimlaneId);
@@ -2462,12 +2481,7 @@ Template.copyManyCardsPopup.onCreated(function () {
if (cardId) {
const targetCard = ReactiveCache.getCard(cardId);
if (targetCard) {
const targetSort = targetCard.sort || 0;
if (position === 'above') {
sortIndex = targetSort - 0.5;
} else {
sortIndex = targetSort + 0.5;
}
sortIndex = await relativeCardSort(targetCard, position, newCard._id);
}
} else {
const maxSort = await newCard.getMaxSort(options.listId, options.swimlaneId);
@@ -2475,6 +2489,7 @@ Template.copyManyCardsPopup.onCreated(function () {
}
await newCard.move(options.boardId, options.swimlaneId, options.listId, sortIndex);
if (position === 'below') cardId = newCardId;
}
// In case the filter is active we need to add the newly inserted card in
@@ -2961,23 +2976,22 @@ EscapeActions.register(
async () => {
// if card description diverges from database due to editing
// ask user whether changes should be applied
if (ReactiveCache.getCurrentUser()) {
if (ReactiveCache.getCurrentUser().profile.rescueCardDescription == true) {
const currentCard = getCurrentCardFromContext();
const cardDetailsElement = getCardDetailsElement(currentCard?._id);
const currentDescription = cardDetailsElement?.querySelector(
'.editor.js-new-description-input',
);
if (currentDescription?.value && currentCard && !(currentDescription.value === currentCard.getDescription())) {
if (confirm(TAPi18n.__('rescue-card-description-dialogue'))) {
await currentCard.setDescription(currentDescription.value);
// Save it!
console.log(currentDescription.value);
console.log("current description", currentCard.getDescription());
} else {
// Do nothing!
console.log('Description changes were not saved to the database.');
}
const currentUser = ReactiveCache.getCurrentUser();
if (currentUser?.profile?.rescueCardDescription === true) {
const currentCard = getCurrentCardFromContext();
const cardDetailsElement = getCardDetailsElement(currentCard?._id);
const currentDescription = cardDetailsElement?.querySelector(
'.editor.js-new-description-input',
);
if (currentDescription?.value && currentCard && !(currentDescription.value === currentCard.getDescription())) {
if (confirm(TAPi18n.__('rescue-card-description-dialogue'))) {
await currentCard.setDescription(currentDescription.value);
// Save it!
console.log(currentDescription.value);
console.log("current description", currentCard.getDescription());
} else {
// Do nothing!
console.log('Description changes were not saved to the database.');
}
}
}
+6 -12
View File
@@ -9,6 +9,7 @@ function getCardId() {
Template.editCardSpentTimePopup.onCreated(function () {
this.error = new ReactiveVar('');
this.card = Cards.findOne(getCardId());
this.overtime = new ReactiveVar(this.card?.getIsOvertime() || false);
});
Template.editCardSpentTimePopup.helpers({
@@ -19,8 +20,7 @@ Template.editCardSpentTimePopup.helpers({
return Cards.findOne(getCardId());
},
getIsOvertime() {
const card = Cards.findOne(getCardId());
return card?.getIsOvertime ? card.getIsOvertime() : false;
return Template.instance().overtime.get();
},
});
@@ -32,10 +32,7 @@ Template.editCardSpentTimePopup.events({
if (!card) return;
const spentTime = parseFloat(evt.target.time.value);
let isOvertime = false;
if ($('#overtime').attr('class').indexOf('is-checked') >= 0) {
isOvertime = true;
}
const isOvertime = tpl.overtime.get();
if (spentTime >= 0) {
card.setSpentTime(spentTime);
card.setIsOvertime(isOvertime);
@@ -53,12 +50,9 @@ Template.editCardSpentTimePopup.events({
card.setIsOvertime(false);
Popup.back();
},
'click a.js-toggle-overtime'(evt) {
const card = Cards.findOne(getCardId());
if (!card) return;
card.setIsOvertime(!card.getIsOvertime());
$('#overtime .materialCheckBox').toggleClass('is-checked');
$('#overtime').toggleClass('is-checked');
'click a.js-toggle-overtime'(evt, tpl) {
evt.preventDefault();
tpl.overtime.set(!tpl.overtime.get());
},
});
+17 -3
View File
@@ -43,12 +43,13 @@ textarea.js-edit-checklist-item {
.checklist-progress-bar-container .checklist-progress-bar .checklist-progress {
color: #fff;
background-color: #666;
padding: 0.01em 16px;
padding: 0;
box-sizing: border-box;
border-radius: 16px;
height: 100%;
}
.checklist-title {
padding: 10px;
padding: calc(10px * min(1, var(--wekan-ui-font-scale, 1)));
}
.checklist-title .checkbox {
float: inline-start;
@@ -134,6 +135,8 @@ textarea.js-edit-checklist-item {
}
.checklist-item .item-title {
flex: 1;
min-width: 0;
overflow-wrap: anywhere;
}
.checklist-item .item-title.is-checked {
color: #8c8c8c;
@@ -143,7 +146,7 @@ textarea.js-edit-checklist-item {
.checklist-item .item-title .viewer p {
margin-bottom: 2px;
display: block;
word-wrap: break-word;
overflow-wrap: anywhere;
max-width: 420px;
}
.checklist-item span.fa.checklistitem-handle {
@@ -234,3 +237,14 @@ textarea.js-edit-checklist-item {
.checklist-title .checklist-collapse:focus-visible {
color: #2980b9;
}
.checklist-title .checklist-due-date-add {
float: inline-end;
width: auto;
min-width: 28px;
padding: 4px 6px;
margin: 0 4px;
background: transparent;
color: inherit;
border: 0;
}
+22 -20
View File
@@ -15,10 +15,10 @@ template(name="checklists")
.material-toggle-switch(title="{{_ 'hide-finished-checklist'}}")
//span.toggle-switch-title
if card.hideFinishedChecklistIfItemsAreHidden
input.toggle-switch(type="checkbox" id="toggleHideFinishedChecklist" checked="checked")
input.toggle-switch.js-toggle-hide-finished-checklist(type="checkbox" aria-label="{{_ 'hide-finished-checklist'}}" id="{{visibilitySwitchId}}" checked="checked")
else
input.toggle-switch(type="checkbox" id="toggleHideFinishedChecklist")
label.toggle-label(for="toggleHideFinishedChecklist")
input.toggle-switch.js-toggle-hide-finished-checklist(type="checkbox" aria-label="{{_ 'hide-finished-checklist'}}" id="{{visibilitySwitchId}}")
label.toggle-label(for="{{visibilitySwitchId}}")
.card-checklist-items
each checklist in checklists
@@ -53,13 +53,17 @@ template(name="checklistDetail")
title="{{#if checklistCollapsed}}{{_ 'expand-checklist'}}{{else}}{{_ 'collapse-checklist'}}{{/if}}"
role="button" tabindex="0"
aria-expanded="{{#if checklistCollapsed}}false{{else}}true{{/if}}")
unless $eq currentBoard.allowsChecklistDueDate false
unless checklist.getDue
if canModifyCard
+checklistDueDateAdd(checklist=checklist)
if canModifyCard
a.fa.fa-navicon.checklist-details-menu.js-open-checklist-details-menu(title="{{_ 'checklistActionsPopup-title'}}")
if canModifyCard
h4.title.js-open-inlined-form.is-editable
if isTouchScreenOrShowDesktopDragHandles
span.fa.checklist-handle(class="fa-arrows" title="{{_ 'dragChecklist'}}")
span.fa.checklist-handle(class="fa-arrows" title="{{_ 'moveChecklist'}}")
+viewer
= checklist.title
else
@@ -67,10 +71,9 @@ template(name="checklistDetail")
+viewer
= checklist.title
if checklist.getDue
+checklistDueDate(checklist=checklist canModifyCard=canModifyCard)
else if canModifyCard
+checklistDueDateAdd(checklist=checklist)
unless $eq currentBoard.allowsChecklistDueDate false
if checklist.getDue
+checklistDueDate(checklist=checklist canModifyCard=canModifyCard)
//- The progress bar stays visible when folded: it is the summary of what was
folded away, so hiding it too would leave a title that says nothing.
@@ -99,12 +102,12 @@ template(name="addChecklistItemForm")
a.fa.fa-times-thin.js-close-inlined-form(title="{{_ 'close-add-checklist-item'}}")
if showNewlineBecomesNewChecklistItem
.material-toggle-switch(title="{{_ 'newlineBecomesNewChecklistItem'}}")
input.toggle-switch(type="checkbox" id="toggleNewlineBecomesNewChecklistItem")
input.toggle-switch(type="checkbox" aria-label="{{_ 'newlineBecomesNewChecklistItem'}}" id="toggleNewlineBecomesNewChecklistItem")
label.toggle-label(for="toggleNewlineBecomesNewChecklistItem")
| {{_ 'newLineNewItem'}}
if $eq position 'top'
.material-toggle-switch(title="{{_ 'newlineBecomesNewChecklistItemOriginOrder'}}")
input.toggle-switch(type="checkbox" id="toggleNewlineBecomesNewChecklistItemOriginOrder")
input.toggle-switch(type="checkbox" aria-label="{{_ 'newlineBecomesNewChecklistItemOriginOrder'}}" id="toggleNewlineBecomesNewChecklistItemOriginOrder")
label.toggle-label(for="toggleNewlineBecomesNewChecklistItemOriginOrder")
| {{_ 'originOrder'}}
@@ -165,7 +168,7 @@ template(name="checklistItems")
template(name='checklistItemDetail')
.js-checklist-item.checklist-item(class="{{#if item.isFinished }}is-checked{{#if checklist.isItemHidden item.isFinished}} invisible{{/if}}{{/if}}{{#if checklist.hideAllChecklistItems}} is-checked invisible{{/if}}"
role="checkbox" aria-checked="{{#if item.isFinished }}true{{else}}false{{/if}}" tabindex="0")
role="checkbox" aria-checked="{{#if item.isFinished }}true{{else}}false{{/if}}" aria-readonly="{{#if canCheckChecklistItem}}false{{else}}true{{/if}}" tabindex="{{#if canCheckChecklistItem}}0{{else}}-1{{/if}}")
//- #3307: checking off an item is a lower bar than editing/deleting one - a
Worker may do the former but not the latter, so the checkbox itself is
gated on canCheckChecklistItem while everything else here still needs
@@ -177,7 +180,7 @@ template(name='checklistItemDetail')
.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
if canModifyCard
if isTouchScreenOrShowDesktopDragHandles
span.fa.checklistitem-handle(class="fa-arrows" title="{{_ 'dragChecklistItem'}}")
span.fa.checklistitem-handle(class="fa-arrows" title="{{_ 'move-scope'}}")
.item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}")
+viewer
= item.title
@@ -239,18 +242,18 @@ template(name="checklistActionsPopup")
| {{_ "hideCheckedChecklistItems"}} ...
.material-toggle-switch(title="{{_ 'hide-checked-items'}}")
if checklist.hideCheckedState
input.toggle-switch(type="checkbox" id="toggleHideCheckedChecklistItems_{{checklist._id}}" checked="checked")
input.toggle-switch(type="checkbox" aria-label="{{_ 'hide-checked-items'}}" id="toggleHideCheckedChecklistItems_{{checklist._id}}" checked="checked")
else
input.toggle-switch(type="checkbox" id="toggleHideCheckedChecklistItems_{{checklist._id}}")
input.toggle-switch(type="checkbox" aria-label="{{_ 'hide-checked-items'}}" id="toggleHideCheckedChecklistItems_{{checklist._id}}")
label.toggle-label(for="toggleHideCheckedChecklistItems_{{checklist._id}}")
a.js-hide-all-checklist-items
i.fa.fa-ban
| {{_ "hideAllChecklistItems"}} ...
.material-toggle-switch(title="{{_ 'hideAllChecklistItems'}}")
if checklist.hideAllChecklistItems
input.toggle-switch(type="checkbox" id="toggleHideAllChecklistItems_{{checklist._id}}" checked="checked")
input.toggle-switch(type="checkbox" aria-label="{{_ 'hideAllChecklistItems'}}" id="toggleHideAllChecklistItems_{{checklist._id}}" checked="checked")
else
input.toggle-switch(type="checkbox" id="toggleHideAllChecklistItems_{{checklist._id}}")
input.toggle-switch(type="checkbox" aria-label="{{_ 'hideAllChecklistItems'}}" id="toggleHideAllChecklistItems_{{checklist._id}}")
label.toggle-label(for="toggleHideAllChecklistItems_{{checklist._id}}")
a.js-show-checklist-at-minicard
i.fa.fa-th-list
@@ -261,9 +264,9 @@ template(name="checklistActionsPopup")
because the board default (allowsChecklistsOnMinicard, on out of the
box) put it there - reported by email.
if shownAtMinicard
input.toggle-switch(type="checkbox" id="toggleShowChecklistAtMinicard_{{checklist._id}}" checked="checked")
input.toggle-switch(type="checkbox" aria-label="{{_ 'show-on-minicard'}}" id="toggleShowChecklistAtMinicard_{{checklist._id}}" checked="checked")
else
input.toggle-switch(type="checkbox" id="toggleShowChecklistAtMinicard_{{checklist._id}}")
input.toggle-switch(type="checkbox" aria-label="{{_ 'show-on-minicard'}}" id="toggleShowChecklistAtMinicard_{{checklist._id}}")
label.toggle-label(for="toggleShowChecklistAtMinicard_{{checklist._id}}")
//- #3818 / #4729: pick this checklist's automatic-reset interval (or 'none' to
@@ -340,9 +343,8 @@ template(name="checklistDueDate")
+dateBadgeBody(canModifyCard=canModifyCard baseClass="card-date due-date checklist-due-date" showTitle=showTitle classes=classes showISODate=showISODate showDate=showDate showWeekOfYear=showWeekOfYear showWeek=showWeek)
template(name="checklistDueDateAdd")
button.js-checklist-due-date(type="button" title="{{_ 'card-due'}}" role="button" tabindex="0")
button.js-checklist-due-date.checklist-due-date-add(type="button" title="{{_ 'card-due'}}" aria-label="{{_ 'card-due'}}")
i.fa.fa-clock-o
| &nbsp;{{_ 'card-due'}}
template(name="editChecklistDueDatePopup")
+editDateForm(showDate=showDate showTime=showTime error=error datePicker=datePicker)
+34 -8
View File
@@ -1,3 +1,4 @@
import { Random } from 'meteor/random';
import { formatDateForDisplay } from '/client/lib/dateDisplay';
import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
@@ -205,7 +206,12 @@ Template.checklistDetail.events({
},
});
Template.checklists.onCreated(function () {
this.visibilitySwitchId = `toggleHideFinishedChecklist_${Random.id()}`;
});
Template.checklists.helpers({
visibilitySwitchId() { return Template.instance().visibilitySwitchId; },
checklists() {
const card = ReactiveCache.getCard(this.cardId);
if (!card || typeof card.checklists !== 'function') {
@@ -221,9 +227,11 @@ Template.checklists.events({
// #4017: apply/append a template card's checklists onto this already
// existing card, alongside whatever checklists it already has.
'click .js-copy-checklist-from-template': Popup.open('copyChecklistFromTemplate'),
'submit .js-add-checklist'(event, tpl) {
async 'submit .js-add-checklist'(event, tpl) {
event.preventDefault();
const textarea = tpl.find('textarea.js-add-checklist-item');
const form = event.currentTarget;
if (form.dataset.submitting === 'true') return;
const textarea = form.querySelector('textarea.js-add-checklist-item');
const title = textarea.value.trim();
let cardId = Template.currentData().cardId;
const card = ReactiveCache.getCard(cardId);
@@ -242,11 +250,22 @@ Template.checklists.events({
}
if (title) {
Checklists.insert({
cardId,
title,
sort: sortIndex,
});
form.dataset.submitting = 'true';
try {
// Keep the client insert path used by this form; wait for the server
// acknowledgement before accepting another submission.
await new Promise((resolve, reject) => {
Checklists.insert({ cardId, title, sort: sortIndex }, (error, id) => {
if (error) reject(error);
else resolve(id);
});
});
} catch (error) {
delete form.dataset.submitting;
alert(error.reason || error.message || TAPi18n.__('server-error'));
return;
}
textarea.value = '';
tpl.$('.js-close-inlined-form').click();
setTimeout(() => {
tpl.$('.add-checklist-item')
@@ -389,7 +408,7 @@ Template.checklists.events({
'click .js-open-inlined-form'(event, tpl) {
tpl.$('.js-close-inlined-form').click();
},
'click #toggleHideFinishedChecklist'(event) {
'click .js-toggle-hide-finished-checklist'(event) {
event.preventDefault();
Template.currentData().card.toggleHideFinishedChecklist();
},
@@ -596,6 +615,13 @@ Template.checklistItemDetail.helpers({
});
Template.checklistItemDetail.events({
'keydown .js-checklist-item'(event) {
if (event.target !== event.currentTarget || event.repeat || event.originalEvent?.repeat || ![' ', 'Enter'].includes(event.key)) return;
if (event.currentTarget.getAttribute('aria-readonly') === 'true') return;
event.preventDefault();
event.stopPropagation();
event.currentTarget.querySelector('.check-box-container')?.click();
},
// #2422: open the checklist item's linked subtask card. Reuses the same
// navigation guard subtasks.js uses for its own "View it" button, so a
// subtask on another (not-yet-loaded) board resolves the same way.
+1 -1
View File
@@ -36,7 +36,7 @@ template(name="cardLabelsPopup")
li.js-card-label-item
a.card-label-edit-button.fa.fa-pencil.js-edit-label
if isTouchScreenOrShowDesktopDragHandles
span.fa.label-handle(class="fa-arrows" title="{{_ 'dragLabel'}}")
span.fa.label-handle(class="fa-arrows" title="{{_ 'move-scope'}}")
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
+10 -5
View File
@@ -309,9 +309,17 @@
/* #4990: the title text is the edit target. It is a block so that the whole
line, not only the words, is that target: a click on the empty space after a
short title is still a click on the title. */
.minicard .minicard-title-row {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
}
.minicard .minicard-title .minicard-title-text {
display: block;
position: relative;
flex: 1;
min-width: 0;
}
/* Direct title editing is disabled; the former editor styles are retained as
@@ -1070,11 +1078,8 @@
display: inline-flex;
align-items: center;
vertical-align: middle;
/* Nudge down so the checkmark sits at the vertical middle of the title text
(the checked materialCheckBox is shifted up 4px by its own positioning). */
position: relative;
top: 4px;
margin-inline-end: 6px;
flex-shrink: 0;
margin-inline-end: 0;
cursor: pointer;
}
+45 -37
View File
@@ -38,40 +38,44 @@ template(name="minicard")
fold on purpose - they must always be reachable, collapsed or not - and
the title right after the caret is likewise unconditional: collapsed, the
minicard shows only the caret and the title.
a.fa.minicard-collapse.js-collapse-minicard(
class="{{#if minicardCollapsed}}fa-caret-right{{else}}fa-caret-down{{/if}}"
title="{{#if minicardCollapsed}}{{_ 'uncollapse'}}{{else}}{{_ 'collapse'}}{{/if}}"
aria-label="{{#if minicardCollapsed}}{{_ 'uncollapse'}}{{else}}{{_ 'collapse'}}{{/if}}"
role="button" tabindex="0"
aria-expanded="{{#if minicardCollapsed}}false{{else}}true{{/if}}")
if showCollapseControl
a.fa.minicard-collapse.js-collapse-minicard(
class="{{#if minicardCollapsed}}fa-caret-right{{else}}fa-caret-down{{/if}}"
title="{{#if minicardCollapsed}}{{_ 'uncollapse'}}{{else}}{{_ 'collapse'}}{{/if}}"
aria-label="{{#if minicardCollapsed}}{{_ 'uncollapse'}}{{else}}{{_ 'collapse'}}{{/if}}"
role="button" tabindex="0"
aria-expanded="{{#if minicardCollapsed}}false{{else}}true{{/if}}")
if showLabelsAboveTitle
+minicardLabels(show=showLabels labels=labels hideText=hiddenMinicardLabelText)
.minicard-title
if showDueComplete
if canModifyCard
a.minicard-complete-toggle.js-toggle-card-complete(title="{{#if getDueComplete}}{{_ 'card-mark-incomplete'}}{{else}}{{_ 'card-mark-complete'}}{{/if}}")
.materialCheckBox(class="{{#if getDueComplete}}is-checked{{/if}}")
if $eq 'prefix-with-full-path' currentBoard.presentParentTask
.parent-prefix
| {{ parentString ' > ' }}
if $eq 'prefix-with-parent' currentBoard.presentParentTask
.parent-prefix
| {{ parentCardName }}
if isLinkedBoard
a.js-linked-link
span.linked-icon
i.fa.fa-folder
else if isLinkedCard
a.js-linked-link
span.linked-icon
i.fa.fa-id-card
if getArchived
span.linked-icon.linked-archived
i.fa.fa-archive
span.minicard-title-text
if showCardNumber
span.card-number
| ##{getCardNumber} &nbsp;
+viewer
= getTitle
.minicard-title-row
if showDueComplete
if canModifyCard
a.minicard-complete-toggle.js-toggle-card-complete(title="{{#if getDueComplete}}{{_ 'card-mark-incomplete'}}{{else}}{{_ 'card-mark-complete'}}{{/if}}")
.materialCheckBox(class="{{#if getDueComplete}}is-checked{{/if}}")
if isLinkedBoard
a.js-linked-link
span.linked-icon
i.fa.fa-folder
else if isLinkedCard
a.js-linked-link
span.linked-icon
i.fa.fa-id-card
if getArchived
span.linked-icon.linked-archived
i.fa.fa-archive
span.minicard-title-text
if showCardNumber
span.card-number
| ##{getCardNumber} &nbsp;
+viewer
= getTitle
//- Everything under the title renders in the board's minicard order
(Board Settings / Card, "Show on Minicard"; board.minicardFieldOrder,
models/lib/cardFieldOrder.js): the sections top to bottom, and inside
@@ -143,16 +147,8 @@ template(name="minicard")
.minicard-cover(style="background-image: url('{{cover.link 'original'}}?dummyReloadAfterSessionEstablished={{sess}}');")
if $eq section "labels"
if showLabels
if labels
.minicard-labels(class="{{#if hiddenMinicardLabelText}}minicard-labels-no-text{{/if}}")
each labels
unless hiddenMinicardLabelText
span.js-card-label.card-label(class="{{labelColorClass color}}" style="{{labelColorStyle color}}" title=name)
+viewer
= name
if hiddenMinicardLabelText
.minicard-label(class="{{labelColorClass color}}" style="{{labelColorStyle color}}" title="{{name}}")
unless labelsAboveTitle
+minicardLabels(show=showLabels labels=labels hideText=hiddenMinicardLabelText)
if $eq section "customFields"
if showCustomFieldsOnMinicard
@@ -402,3 +398,15 @@ template(name="minicardChecklist")
else
a.add-checklist-item.js-open-inlined-form(title="{{_ 'add-checklist-item'}}")
i.fa.fa-plus
template(name="minicardLabels")
if show
if labels
.minicard-labels(class="{{#if hideText}}minicard-labels-no-text{{/if}}")
each label in labels
unless hideText
span.js-card-label.card-label(class="{{labelColorClass label.color}}" style="{{labelColorStyle label.color}}" title=label.name)
+viewer
= label.name
if hideText
.minicard-label(class="{{labelColorClass label.color}}" style="{{labelColorStyle label.color}}" title="{{label.name}}")
+18 -14
View File
@@ -1,3 +1,4 @@
import { relativeCardSort } from '/client/lib/relativeCardPosition';
import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import { CustomFieldStringTemplate } from '/client/lib/customFields';
@@ -40,8 +41,14 @@ function getMinicardFlag(board, onMinicardField, legacyField, defaultValue) {
Template.minicard.helpers({
// #1591: the whole-minicard fold, same shape as a list's collapsed() helper.
showCollapseControl() { return ReactiveCache.getBoard(this.boardId)?.allowsMinicardCollapse !== false; },
showLabelsAboveTitle() {
const board = ReactiveCache.getBoard(this.boardId);
return board?.labelsAboveTitleOnMinicard === true && (board.allowsMinicardCollapse === false || !Utils.getCardCollapseState(this));
},
labelsAboveTitle() { return ReactiveCache.getBoard(this.boardId)?.labelsAboveTitleOnMinicard === true; },
minicardCollapsed() {
return Utils.getCardCollapseState(this);
return ReactiveCache.getBoard(this.boardId)?.allowsMinicardCollapse !== false && Utils.getCardCollapseState(this);
},
// The minicard's field order from Board Settings / Card's "Show on
// Minicard" column (board.minicardFieldOrder): the sections top to bottom,
@@ -390,24 +397,21 @@ Template.minicard.helpers({
// #459: accessible reordering — keyboard/screen-reader users can move a card up
// or down within its list via sr-only buttons (no drag-and-drop required). The
// move swaps the card's sort value with its neighbour in the same list+swimlane.
function moveCardBy(card, delta) {
// move inserts beside its neighbor in the same list+swimlane, repairing ties.
async function moveCardBy(card, delta) {
const siblings = ReactiveCache.getCards(
{ listId: card.listId, swimlaneId: card.swimlaneId, archived: false },
{ sort: { sort: 1 } },
{ boardId: card.boardId, listId: card.listId, swimlaneId: card.swimlaneId, archived: false, deletedAt: null },
{ sort: { sort: 1, _id: 1 } },
);
const idx = siblings.findIndex(c => c._id === card._id);
const target = siblings[idx + delta];
if (idx < 0 || !target) return;
// Capture both sort values before either update; the docs are reactive and
// card.sort would otherwise change after the first move.
const cardSort = card.sort;
const targetSort = target.sort;
// Persist through the card model's move() mutation — the canonical client
// path (e.g. editCardSortOrderPopup). A raw Cards.update of `sort` is the
// wrong path here and would be reverted.
card.move(card.boardId, card.swimlaneId, card.listId, targetSort);
target.move(target.boardId, target.swimlaneId, target.listId, cardSort);
try {
const sort = await relativeCardSort(target, delta < 0 ? 'above' : 'below', card._id);
await card.move(card.boardId, card.swimlaneId, card.listId, sort);
} catch (error) {
window.alert(error.reason || error.message || TAPi18n.__('server-error'));
}
}
Template.minicard.events({
+1 -1
View File
@@ -93,7 +93,7 @@ template(name="subtasksItems")
else
a.add-subtask-item.js-open-inlined-form
i.fa.fa-plus
| {{_ 'add-subtask-item'}}...
| {{_ 'add-subtask'}}...
template(name='subtaskItemDetail')
.js-subtasks-item.subtasks-item
+15 -2
View File
@@ -807,8 +807,21 @@ button.loud-text-button:hover {
.toggle-switch:checked:disabled ~ .toggle-label:after {
background-color: #bcbdbc;
}
.toggle-switch {
display: none;
.material-toggle-switch input.toggle-switch[type="checkbox"] {
display: block;
visibility: visible;
position: absolute;
inset-inline-start: auto;
width: 1px;
height: 1px;
margin: 0;
padding: 0;
clip-path: inset(50%);
overflow: hidden;
}
.material-toggle-switch .toggle-switch:focus-visible ~ .toggle-label {
outline: 2px solid currentColor;
outline-offset: 4px;
}
.toggle-switch-title {
margin: 0 0.5em;
+15
View File
@@ -3,6 +3,21 @@
min-height: 80px;
}
.gantt-view .gantt-container {
font-size: calc(12px * var(--wekan-ui-font-scale, 1));
line-height: calc(14.5px * var(--wekan-ui-font-scale, 1));
}
.gantt-view .gantt-container .lower-text {
font-size: calc(12px * var(--wekan-ui-font-scale, 1));
}
.gantt-view .gantt-container .upper-text,
.gantt-view .gantt-container .side-header * {
font-size: calc(14px * var(--wekan-ui-font-scale, 1));
}
.gantt-view .gantt .bar-label {
font-size: calc(13px * var(--wekan-ui-font-scale, 1));
}
.frappe-gantt-empty {
color: #888;
}
+8
View File
@@ -1,3 +1,5 @@
import { Meteor } from 'meteor/meteor';
import { fontScaleValue } from '/models/lib/uiFonts';
import { titleViewerHtml, titleViewerSvgText } from '/client/lib/titleViewer';
import { formatDateForDisplay, dateDisplayPreferences } from '/client/lib/dateDisplay';
import { Template } from 'meteor/templating';
@@ -204,7 +206,13 @@ Template.frappeGanttView.onRendered(function() {
// observer below re-translates them whenever they reappear.
const viewModes = translatedViewModes(GanttLib);
translateFrappeChrome(container, templateInstance);
const fontScale = Number(fontScaleValue(Meteor.user()?.profile?.uiFontSize) || 1);
new GanttLib(container, tasks, {
// Scale geometry with SVG text, so the 150% preset still fits its bars
// and date headers rather than merely increasing the glyph sizes.
bar_height: 30 * fontScale,
upper_header_height: 45 * fontScale,
lower_header_height: 30 * fontScale,
view_modes: viewModes,
view_mode: viewModes.find(mode => mode._key === 'WEEK').name,
// Month names in the header come from Intl.DateTimeFormat(language);
+1 -1
View File
@@ -211,7 +211,7 @@ template(name="importMapMembersAddPopup")
p
| {{_ 'import-user-select'}}
.js-map-member
input.js-search-member-input(type="text" placeholder="{{_ 'search-users'}}")
input.js-search-member-input(type="text" placeholder="{{_ 'search'}}")
ul.pop-over-list
each searchResults
li.item.js-member-item
+19 -3
View File
@@ -281,6 +281,13 @@ Template.list.onCreated(function () {
// callback, we basically solve all issues related to reactive updates. A
// comment below provides further details.
Template.list.onRendered(function () {
this.initializeListResize();
});
// #6705: collapse removes listBody but keeps the parent list alive. Bind card
// widgets and their reactive work to the body so expansion initializes the new
// .js-minicards element, including lists that were collapsed on page load.
Template.listBody.onRendered(function () {
const boardBodyEl =
this.firstNode?.parentElement?.closest?.('.board-body') ||
document.querySelector('.board-body');
@@ -288,11 +295,10 @@ Template.list.onRendered(function () {
boardBodyEl && Blaze.getView(boardBodyEl, 'Template.boardBody');
const boardComponent = boardView?.templateInstance?.();
// Initialize list resize functionality immediately
this.initializeListResize();
const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)';
const $cards = this.$('.js-minicards');
this.cardDragTargets = $cards;
this.cardDragDestroyed = false;
// Destroy any existing sortable before re-initializing. Without this, a
// re-render of the list (e.g. when a Card Details panel opens) binds a SECOND
@@ -668,6 +674,7 @@ Template.list.onRendered(function () {
// .droppable() on initialized elements is an idempotent option refresh.
ReactiveCache.getCards({ boardId: currentBoardId });
Tracker.afterFlush(() => {
if (this.cardDragDestroyed) return;
$cards.find(itemsSelector).droppable({
hoverClass: 'draggable-hover-card',
accept: '.js-member,.js-label',
@@ -688,6 +695,15 @@ Template.list.onRendered(function () {
});
});
Template.listBody.onDestroyed(function () {
this.cardDragDestroyed = true;
const $cards = this.cardDragTargets;
if ($cards && ($cards.data('uiSortable') || $cards.data('sortable'))) {
$cards.sortable('destroy');
}
this.cardDragTargets = null;
});
// A board-wide list (no swimlaneId of its own) renders once per swimlane in
// Swimlanes view - the SAME list document, one row per swimlane - so its
// collapse state must be resolved per-swimlane, or collapsing it in one
+5 -5
View File
@@ -131,7 +131,7 @@ template(name="listHeader")
unless currentUser.isReadOnly
unless currentUser.isReadAssignedOnly
if isTouchScreenOrShowDesktopDragHandles
a.list-header-handle-desktop.handle.nodragscroll.js-list-handle(title="{{_ 'drag-list'}}")
a.list-header-handle-desktop.handle.nodragscroll.js-list-handle(title="{{_ 'moveListPopup-title'}}")
i.fa.fa-arrows
unless collapsed
div.list-header-menu
@@ -142,7 +142,7 @@ template(name="listHeader")
//
a.fa.js-list-star.list-header-plus-top(class="fa-star{{#unless starred}}-o{{/unless}}")
if isTouchScreenOrShowDesktopDragHandles
a.list-header-handle-desktop.handle.nodragscroll.js-list-handle(title="{{_ 'drag-list'}}")
a.list-header-handle-desktop.handle.nodragscroll.js-list-handle(title="{{_ 'moveListPopup-title'}}")
i.fa.fa-arrows
if canSeeAddCard
a.js-add-card.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}")
@@ -384,7 +384,7 @@ template(name="setListWidthPopup")
unless isAutoWidth
label {{_ 'set-list-width-value'}}
p
input.list-width-value(type="number" value="{{ listWidthValue }}" min="270")
input.list-width-value(type="number" value="{{ listWidthValue }}" min="{{ listWidthMinimum }}" step="1")
input.list-width-apply(type="submit" value="{{_ 'apply'}}")
input.list-width-error
if canChangeAutoWidth
@@ -398,7 +398,7 @@ template(name="setListWidthPopup")
template(name="listWidthErrorPopup")
.list-width-invalid
p {{_ 'list-width-error-message'}} '&gt;=270'
p {{_ 'list-width-error-message'}}
button.full.js-back-view(type="submit") {{_ 'cancel'}}
template(name="setListColorPopup")
@@ -413,7 +413,7 @@ template(name="setListColorPopup")
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'}}
button.js-remove-color.negate.wide.right {{_ 'remove-btn'}}
//- List sync settings (docs/Features/ImportExport/Sync.md). UI wiring only -
calls the EXISTING setListSyncSource/hasListSyncCredential/syncListNow
+24 -17
View File
@@ -1,3 +1,4 @@
import { relativePosition } from '/models/lib/relativePosition';
import { formatDateForDisplay } from '/client/lib/dateDisplay';
import { ReactiveCache } from '/imports/reactiveCache';
import Lists from '../../../models/lists';
@@ -23,6 +24,7 @@ import {
import {
DEFAULT_LIST_WIDTH,
MIN_LIST_WIDTH,
parseListWidthInput,
normalizeListWidth,
} from '/models/lib/listWidth';
// List sync (docs/Features/ImportExport/Sync.md): UI wiring only, calls the
@@ -341,24 +343,26 @@ Template.listHeader.onCreated(function () {
});
// #459: accessible reordering — move a list left/right via sr-only buttons by
// swapping its sort value with the adjacent list (no drag-and-drop required).
function moveListBy(list, delta) {
const siblings = ReactiveCache.getLists(
{ boardId: list.boardId, archived: false },
{ sort: { sort: 1 } },
);
// inserting beside an adjacent visible list (no drag-and-drop required).
async function moveListBy(list, delta) {
const swimlaneId = resolveContainerSwimlaneId(list);
const selector = { boardId: list.boardId, archived: false, deletedAt: null };
if (Utils.boardView() === 'board-view-swimlanes' && swimlaneId) {
selector.swimlaneId = { $in: [swimlaneId, null, ''] };
}
const siblings = ReactiveCache.getLists(selector, { sort: { sort: 1 } });
const idx = siblings.findIndex(l => l._id === list._id);
const target = siblings[idx + delta];
if (idx < 0 || !target) return;
// Capture both sort values before either update; the docs are reactive and
// list.sort would otherwise change after the first update.
const listSort = list.sort;
const targetSort = target.sort;
// Lists are a server-restricted collection; persist the swap through the
// updateListSort method — the same path the drag-and-drop reorder uses. A raw
// client Lists.update of `sort` is reverted by the server.
Meteor.call('updateListSort', list._id, list.boardId, { sort: targetSort });
Meteor.call('updateListSort', target._id, target.boardId, { sort: listSort });
try {
const plan = relativePosition(siblings, target._id, delta < 0 ? 'above' : 'below', list._id);
for (const update of plan.updates) {
await Meteor.callAsync('updateListSort', update.id, list.boardId, { sort: update.sort });
}
await Meteor.callAsync('updateListSort', list._id, list.boardId, { sort: plan.sort });
} catch (error) {
window.alert(error.reason || error.message || TAPi18n.__('server-error'));
}
}
Template.listHeader.events({
@@ -1094,6 +1098,9 @@ function setAnonFixedListWidth(boardId, width) {
}
Template.setListWidthPopup.helpers({
listWidthMinimum() {
return MIN_LIST_WIDTH;
},
listWidthValue() {
const list = Template.currentData();
// #5729 In fixed width mode the input edits the single per-board value.
@@ -1188,9 +1195,9 @@ Template.setListWidthPopup.events({
'click .list-width-apply'(event, tpl) {
const list = Template.currentData();
const boardId = list.boardId;
const width = parseInt(tpl.$('.list-width-value').val(), 10);
const width = parseListWidthInput(tpl.$('.list-width-value').val());
if (!width || width < MIN_LIST_WIDTH) {
if (width === null) {
tpl.$('.list-width-error').click();
return;
}
+1 -1
View File
@@ -3,7 +3,7 @@ template(name="minilist")
class="minicard-{{colorClass}}")
.minicard-title
.handle
span.drag-handle(title="{{_ 'dragList'}}")
span.drag-handle(title="{{_ 'moveListPopup-title'}}")
i.fa.fa-arrows
+viewer
= title
+6 -6
View File
@@ -1,18 +1,18 @@
template(name="bookmarks")
.panel
h2 {{_ 'bookmarks'}}
h2 {{_ 'starred-boards'}}
if currentUser
if hasStarredBoards
ul
each starredBoards
li
a(href="{{pathFor 'board' id=_id slug=slug}}")= title
a.js-toggle-star(title="{{_ 'star-board-short-unstar'}}")
a.js-toggle-star(title="{{_ 'unstar-item'}}")
i.fa.fa-star
else
p {{_ 'no-starred-boards'}}
p {{_ 'no-starred-items'}}
else
p {{_ 'please-sign-in'}}
p {{_ 'log-in'}}
// Desktop popup
template(name="bookmarksPopup")
@@ -23,7 +23,7 @@ template(name="bookmarksPopup")
a(href="{{pathFor 'board' id=_id slug=slug}}")
i.fa.fa-star
| #{title}
a.js-toggle-star.right(title="{{_ 'star-board-short-unstar'}}")
a.js-toggle-star.right(title="{{_ 'unstar-item'}}")
i.fa.fa-star
else
li {{_ 'no-starred-boards'}}
li {{_ 'no-starred-items'}}
+19 -235
View File
@@ -1,12 +1,25 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { findWhere } from '/imports/lib/collectionHelpers';
import { TAPi18n } from '/imports/i18n';
import Attachments from '/models/attachments';
import { Markdown } from 'meteor/wekan-markdown';
import { Utils } from '/client/lib/utils';
import { memberMatchesTerm } from '/models/lib/memberAutocomplete';
import autosize from 'autosize';
var converter = require('@wekanteam/html-to-markdown');
function syncMarkdownExternalLinkPattern(setting) {
const pattern = {
prefix: (setting && setting.externalLinkPatternPrefix) || '',
urlTemplate: (setting && setting.externalLinkPatternUrl) || '',
};
const current = Markdown.externalLinkPattern.get();
// ReactiveVar compares objects by identity. Setting a new object on every
// viewer render would invalidate that viewer again indefinitely.
if (current?.prefix !== pattern.prefix || current?.urlTemplate !== pattern.urlTemplate) {
Markdown.externalLinkPattern.set(pattern);
}
}
const specialHandles = [
{userId: 'board_members', username: 'board_members'},
{userId: 'card_members', username: 'card_members'},
@@ -66,232 +79,9 @@ Template.editor.onRendered(function () {
},
];
const enableTextarea = function() {
const $textarea = tpl.$(textareaSelector);
autosize($textarea);
$textarea.escapeableTextComplete(mentions);
};
if (Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR === true || Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR === 'true') {
const isSmall = Utils.isMiniScreen();
const toolbar = isSmall
? [
['view', ['fullscreen']],
['table', ['table']],
['font', ['bold', 'underline']],
//['fontsize', ['fontsize']],
['color', ['color']],
]
: [
['style', ['style']],
['font', ['bold', 'underline', 'clear']],
['fontsize', ['fontsize']],
['fontname', ['fontname']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
//['insert', ['link', 'picture', 'video']], // iframe tag will be sanitized TODO if iframe[class=note-video-clip] can be added into safe list, insert video can be enabled
['insert', ['link']], //, 'picture']], // modal popup has issue somehow :(
['view', ['fullscreen', 'codeview', 'help']],
];
const cleanPastedHTML = function(input) {
const badTags = [
'style',
'script',
'applet',
'embed',
'noframes',
'noscript',
'meta',
'link',
'button',
'form',
].join('|');
const badPatterns = new RegExp(
`(?:${[
`<(${badTags})\\s*[^>][\\s\\S]*?<\\/\\1>`,
`<(${badTags})[^>]*?\\/>`,
].join('|')})`,
'gi',
);
let output = input;
// remove bad Tags
output = output.replace(badPatterns, '');
// remove attributes ' style="..."'
const badAttributes = new RegExp(
`(?:${[
'on\\S+=([\'\"]?).*?\\1',
'href=([\'\"]?)javascript:.*?\\2',
'style=([\'\"]?).*?\\3',
'target=\\S+',
].join('|')})`,
'gi',
);
output = output.replace(badAttributes, '');
output = output.replace(/(<a )/gi, '$1target=_ '); // always to new target
return output;
};
const editor = '.editor';
const selectors = [
`.js-new-description-form ${editor}`,
`.js-new-comment-form ${editor}`,
`.js-edit-comment ${editor}`,
].join(','); // only new comment and edit comment
const inputs = $(selectors);
if (inputs.length === 0) {
// only enable richereditor to new comment or edit comment no others
enableTextarea();
} else {
const placeholder = inputs.attr('placeholder') || '';
const mSummernotes = [];
const getSummernote = function(input) {
const idx = inputs.index(input);
if (idx > -1) {
return mSummernotes[idx];
}
return undefined;
};
inputs.each(function(idx, input) {
mSummernotes[idx] = $(input).summernote({
placeholder,
callbacks: {
onInit(object) {
const originalInput = this;
$(originalInput).on('submitted', function() {
// when comment is submitted, the original textarea will be set to '', so shall we
if (!this.value) {
const sn = getSummernote(this);
sn && sn.summernote('code', '');
}
});
const jEditor = object && object.editable;
const toolbar = object && object.toolbar;
if (jEditor !== undefined) {
jEditor.escapeableTextComplete(mentions);
}
if (toolbar !== undefined) {
const fBtn = toolbar.find('.btn-fullscreen');
fBtn.on('click', function() {
const $this = $(this),
isActive = $this.hasClass('active');
$('.minicards,#header-quick-access').toggle(!isActive); // mini card is still showing when editor is in fullscreen mode, we hide here manually
});
}
},
onImageUpload(files) {
const $summernote = getSummernote(this);
if (files && files.length > 0) {
const image = files[0];
const currentCard = Utils.getCurrentCard();
const MAX_IMAGE_PIXEL = Utils.MAX_IMAGE_PIXEL;
const COMPRESS_RATIO = Utils.IMAGE_COMPRESS_RATIO;
const processUpload = async function(file) {
const uploader = await Attachments.insertAsync(
{
file,
meta: Utils.getCommonAttachmentMetaFrom(currentCard),
chunkSize: 'dynamic',
},
false,
);
uploader.on('uploaded', (error, fileRef) => {
if (!error) {
if (fileRef.isImage) {
const img = document.createElement('img');
img.src = fileRef.link();
img.setAttribute('width', '100%');
$summernote.summernote('insertNode', img);
}
}
});
uploader.start();
};
if (MAX_IMAGE_PIXEL) {
const reader = new FileReader();
reader.onload = function(e) {
const dataurl = e && e.target && e.target.result;
if (dataurl !== undefined) {
// need to shrink image
Utils.shrinkImage({
dataurl,
maxSize: MAX_IMAGE_PIXEL,
ratio: COMPRESS_RATIO,
toBlob: true,
callback(blob) {
if (blob !== false) {
blob.name = image.name;
processUpload(blob);
}
},
});
}
};
reader.readAsDataURL(image);
} else {
processUpload(image);
}
}
},
onPaste(e) {
var clipboardData = e.clipboardData;
var pastedData = clipboardData.getData('Text');
//if pasted data is an image, exit
if (!pastedData.length) {
e.preventDefault();
return;
}
// clear up unwanted tag info when user pasted in text
const thisNote = this;
const updatePastedText = function(object) {
const someNote = getSummernote(object);
// Fix Pasting text into a card is adding a line before and after
// (and multiplies by pasting more) by changing paste "p" to "br".
// Fixes https://github.com/wekan/wekan/2890 .
// == Fix Start ==
someNote.execCommand('defaultParagraphSeparator', false, 'br');
// == Fix End ==
const original = someNote.summernote('code');
const cleaned = cleanPastedHTML(original); //this is where to call whatever clean function you want. I have mine in a different file, called CleanPastedHTML.
someNote.summernote('code', ''); //clear original
someNote.summernote('pasteHTML', cleaned); //this sets the displayed content editor to the cleaned pasted code.
};
setTimeout(function() {
//this kinda sucks, but if you don't do a setTimeout,
//the function is called before the text is really pasted.
updatePastedText(thisNote);
}, 10);
},
},
dialogsInBody: true,
spellCheck: true,
disableGrammar: false,
disableDragAndDrop: false,
toolbar,
popover: {
image: [
['imagesize', ['imageSize100', 'imageSize50', 'imageSize25']],
['float', ['floatLeft', 'floatRight', 'floatNone']],
['remove', ['removeMedia']],
],
link: [['link', ['linkDialogShow', 'unlink']]],
table: [
['add', ['addRowDown', 'addRowUp', 'addColLeft', 'addColRight']],
['delete', ['deleteRow', 'deleteCol', 'deleteTable']],
],
air: [
['color', ['color']],
['font', ['bold', 'underline', 'clear']],
],
},
height: 200,
});
});
}
} else {
enableTextarea();
}
enableTextarea();
const $textarea = tpl.$(textareaSelector);
autosize($textarea);
$textarea.escapeableTextComplete(mentions);
});
Template.editor.events({
@@ -373,10 +163,7 @@ Blaze.Template.registerHelper(
// wekan/wekan#3069: same same-render-pass reasoning as alwaysShowCodeAsText
// above, for the external issue-tracker autolink pattern.
if (typeof Markdown !== 'undefined' && Markdown.externalLinkPattern) {
Markdown.externalLinkPattern.set({
prefix: (setting && setting.externalLinkPatternPrefix) || '',
urlTemplate: (setting && setting.externalLinkPatternUrl) || '',
});
syncMarkdownExternalLinkPattern(setting);
}
let content = Blaze.toHTML(view.templateContentBlock);
// Admin Panel / Features: when "render links as plain text" is enabled, every
@@ -534,10 +321,7 @@ Meteor.startup(() => {
Markdown.alwaysShowCodeAsText.set(!!(setting && setting.alwaysShowCodeAsText));
}
if (typeof Markdown !== 'undefined' && Markdown.externalLinkPattern) {
Markdown.externalLinkPattern.set({
prefix: (setting && setting.externalLinkPatternPrefix) || '',
urlTemplate: (setting && setting.externalLinkPatternUrl) || '',
});
syncMarkdownExternalLinkPattern(setting);
}
});
-4
View File
@@ -1541,10 +1541,6 @@ body.mobile-mode #header-quick-access .allBoards {
margin: 7px;
padding: 0;
}
#headerIsSettingDatabaseCallDone {
display: none;
}
/* ── Small screens: home ICON only, never the "All Boards" label ─────────────
This block is last in the file on purpose.
+14 -13
View File
@@ -52,19 +52,20 @@ template(name="header")
= headerTitleText
// Logo - visible; on mobile constrained by CSS
unless currentSetting.hideLogo
if currentSetting.customTopLeftCornerLogoImageUrl
if currentSetting.customTopLeftCornerLogoLinkUrl
a.header-logo(href="{{currentSetting.customTopLeftCornerLogoLinkUrl}}" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
img(src="{{currentSetting.customTopLeftCornerLogoImageUrl}}" alt="{{currentSetting.productName}}" height="{{#if currentSetting.customTopLeftCornerLogoHeight}}#{currentSetting.customTopLeftCornerLogoHeight}{{else}}27{{/if}}" width="auto" margin="0" padding="0")
unless currentSetting.customTopLeftCornerLogoLinkUrl
img.header-logo(src="{{currentSetting.customTopLeftCornerLogoImageUrl}}" height="{{#if currentSetting.customTopLeftCornerLogoHeight}}#{currentSetting.customTopLeftCornerLogoHeight}{{else}}27{{/if}}" width="auto" margin="0" padding="0" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
unless currentSetting.customTopLeftCornerLogoImageUrl
//- `header-logo` on all three branches - a custom logo that links, one
that does not, and the stock one - so the bar can space the logo the
same way whichever is drawn. docs/Features/Page/Header.md
div#headerIsSettingDatabaseCallDone.header-logo
img(src="{{pathFor '/logo-header.png'}}" alt="{{headerLogoAlt}}" title="{{headerLogoAlt}}")
if logoSettingsReady
unless currentSetting.hideLogo
if currentSetting.customTopLeftCornerLogoImageUrl
if currentSetting.customTopLeftCornerLogoLinkUrl
a.header-logo(href="{{currentSetting.customTopLeftCornerLogoLinkUrl}}" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
img(src="{{currentSetting.customTopLeftCornerLogoImageUrl}}" alt="{{currentSetting.productName}}" height="{{#if currentSetting.customTopLeftCornerLogoHeight}}#{currentSetting.customTopLeftCornerLogoHeight}{{else}}27{{/if}}" width="auto" margin="0" padding="0")
unless currentSetting.customTopLeftCornerLogoLinkUrl
img.header-logo(src="{{currentSetting.customTopLeftCornerLogoImageUrl}}" height="{{#if currentSetting.customTopLeftCornerLogoHeight}}#{currentSetting.customTopLeftCornerLogoHeight}{{else}}27{{/if}}" width="auto" margin="0" padding="0" alt="{{currentSetting.productName}}" title="{{currentSetting.productName}}")
unless currentSetting.customTopLeftCornerLogoImageUrl
//- `header-logo` on all three branches - a custom logo that links, one
that does not, and the stock one - so the bar can space the logo the
same way whichever is drawn. docs/Features/Page/Header.md
div.header-logo
img(src="{{pathFor '/logo-header.png'}}" alt="{{headerLogoAlt}}" title="{{headerLogoAlt}}")
// Mobile/desktop mode toggle (swapped with zoom)
// #6680 follow-up: .js-header-collapsible-icon marks every icon the new
+14 -21
View File
@@ -1,5 +1,6 @@
import { Meteor } from 'meteor/meteor';
import { Session } from 'meteor/session';
import { Tracker } from 'meteor/tracker';
import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
@@ -66,6 +67,7 @@ Template.header.onCreated(function () {
const templateInstance = this;
templateInstance.currentSetting = new ReactiveVar();
templateInstance.isLoading = new ReactiveVar(false);
templateInstance.subscribe('setting');
// Publish the page's path so the browser tab can carry it too. Here rather
// than in `Utils`, because this is the one place that already works it out
@@ -74,30 +76,21 @@ Template.header.onCreated(function () {
headerPathVar.set(headerFullPath());
});
Meteor.subscribe('setting', {
onReady() {
templateInstance.currentSetting.set(ReactiveCache.getCurrentSetting());
let currSetting = templateInstance.currentSetting.curValue;
if (
currSetting &&
currSetting !== undefined &&
currSetting.customLoginLogoImageUrl !== undefined &&
document.getElementById('headerIsSettingDatabaseCallDone') != null
)
document.getElementById(
'headerIsSettingDatabaseCallDone',
).style.display = 'none';
else if (
document.getElementById('headerIsSettingDatabaseCallDone') != null
)
document.getElementById(
'headerIsSettingDatabaseCallDone',
).style.display = 'block';
return this.stop();
},
});
Template.header.onRendered(function () {
this.autorun(() => {
Meteor.userId();
FlowRouter.watchPathChange();
Tracker.afterFlush(() => {
if (!this.view.isDestroyed) Utils.watchHeaderHeight();
});
});
});
Template.header.helpers({
logoSettingsReady() {
return Template.instance().subscriptionsReady();
},
// The page's title, beside the house icon. A board's own title wherever there
// is a board; otherwise the page's, by route name. Two helpers rather than
// one string, because a translated title has to go through {{_ }} and a board
+18 -5
View File
@@ -19,10 +19,6 @@ table, tbody, td, tfoot, th, thread, tr, tt, ul, var {
font-size: 100%;
vertical-align: baseline;
}
.note-popover .popover-content .note-color-palette div .note-color-btn,
.panel-heading.note-toolbar .note-color-palette div .note-color-btn {
background: none;
}
a:focus {
outline: unset;
outline-offset: unset;
@@ -577,7 +573,24 @@ kbd {
cursor: grabbing;
}
.textcomplete-dropdown {
z-index: 2000 !important;
/* The menu is attached to body, above both card windows (2001) and
card-detail popups (100000). #6704 */
z-index: 100001 !important;
background: #fff;
color: #333;
list-style: none;
margin: 0;
padding: 4px 0;
border: 1px solid #ccc;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
}
.textcomplete-dropdown .textcomplete-item > span {
display: block;
padding: 4px 12px 4px 8px;
cursor: pointer;
}
.textcomplete-dropdown .textcomplete-item.active > span {
color: #fff;
}
.fa.fa-times-thin:before {
content: '\00d7';
+17 -15
View File
@@ -25,23 +25,24 @@ template(name="main")
template(name="userFormsLayout")
+recoveryMaintenance
section.auth-layout
if currentSetting.hideLogo
h1.at-form-landing-logo
br
br
unless currentSetting.hideLogo
h1.at-form-landing-logo
if currentSetting.customLoginLogoImageUrl
if currentSetting.customLoginLogoLinkUrl
a(href="{{currentSetting.customLoginLogoLinkUrl}}")
if logoSettingsReady
if currentSetting.hideLogo
h1.at-form-landing-logo
br
br
unless currentSetting.hideLogo
h1.at-form-landing-logo
if currentSetting.customLoginLogoImageUrl
if currentSetting.customLoginLogoLinkUrl
a(href="{{currentSetting.customLoginLogoLinkUrl}}")
img(src="{{currentSetting.customLoginLogoImageUrl}}" alt="{{currentSetting.productName}}" width="300" height="auto")
br
unless currentSetting.customLoginLogoLinkUrl
img(src="{{currentSetting.customLoginLogoImageUrl}}" alt="{{currentSetting.productName}}" width="300" height="auto")
br
else
img(src="{{pathFor '/wekan-logo.svg'}}" alt="" width="300" height="auto")
br
unless currentSetting.customLoginLogoLinkUrl
img(src="{{currentSetting.customLoginLogoImageUrl}}" alt="{{currentSetting.productName}}" width="300" height="auto")
br
else
img(src="{{pathFor '/wekan-logo.svg'}}" alt="" width="300" height="auto")
br
if currentSetting.textBelowCustomLoginLogo
hr
section.textBelowCustomLoginLogo
@@ -129,6 +130,7 @@ template(name="userFormsLayout")
.auth-bottom-space
template(name="defaultLayout")
+attachmentViewer
+recoveryMaintenance
//- Shared migration / board-repair progress dashboard. Mounted app-wide so it
//- shows for both a board-open repair AND an Admin Panel database migration.
+8 -6
View File
@@ -29,6 +29,11 @@ Template.userFormsLayout.onCreated(function () {
const templateInstance = this;
templateInstance.currentSetting = new ReactiveVar();
templateInstance.isLoading = new ReactiveVar(false);
templateInstance.subscribe('setting', {
onReady() {
templateInstance.currentSetting.set(ReactiveCache.getCurrentSetting());
},
});
// The Meteor accounts-* OAuth providers the server reports enabled (keys
// only) and whether e-mailed one-time-code login is on; both are filled by
// the getAuthenticationsEnabled call in onRendered.
@@ -64,12 +69,6 @@ Template.userFormsLayout.onCreated(function () {
}
});
Meteor.subscribe('setting', {
onReady() {
templateInstance.currentSetting.set(ReactiveCache.getCurrentSetting());
return this.stop();
},
});
}
});
@@ -272,6 +271,9 @@ Template.userFormsLayout.onDestroyed(() => {
});
Template.userFormsLayout.helpers({
logoSettingsReady() {
return Template.instance().subscriptionsReady();
},
// One entry per enabled Meteor accounts-* provider: key, icon class and the
// translated provider name for {{_ 'sign-in-with' label}}. No credential
// ever reaches this list - the server sends keys only.
+1 -1
View File
@@ -22,7 +22,7 @@ template(name="myAttachments")
= list.title
each card in list.myAttachmentCards
.my-attachments-card-wrapper
a.my-attachments-card-title.js-my-attachment-card(href=card.originRelativeUrl)
a.my-attachments-card-title.js-my-attachment-card(href=card.originRelativeUrl data-card-id=card._id data-board-id=card.boardId)
i.fa.fa-external-link
= card.title
.attachment-gallery.my-attachments-gallery
+5 -4
View File
@@ -205,11 +205,12 @@ Template.myAttachments.events({
// would navigate away from this page and lose the reader's place in a
// list that spans many boards.
'click .js-my-attachment-card'(evt) {
if (evt.button !== 0 || evt.ctrlKey || evt.metaKey || evt.shiftKey || evt.altKey) return;
// `each card in ...` binds a lexical variable, not a Blaze data context.
// Read the identifiers from the link even when its child was clicked (#6702).
const { cardId, boardId } = evt.currentTarget.dataset;
if (!cardId || !boardId) return;
evt.preventDefault();
const card = Blaze.getData(evt.currentTarget);
if (!card || !card._id) return;
const cardId = card._id;
const boardId = card.boardId;
Meteor.subscribe('popupCardData', cardId, {
onReady() {
Session.set('popupCardId', cardId);
+8 -8
View File
@@ -41,7 +41,7 @@ template(name="myCards")
= list.title
each card in list.myCards
.my-cards-card-wrapper
a.minicard-wrapper.js-minicard(href=card.originRelativeUrl)
a.minicard-wrapper.js-minicard(href=card.originRelativeUrl data-card-id=card._id data-board-id=card.boardId)
+minicard(card)
if $eq myCardsView 'table'
.wrapper
@@ -49,14 +49,14 @@ template(name="myCards")
caption.sr-only {{_ 'my-cards'}}
thead
tr
th(scope="col") {{_ "Card"}}
th(scope="col") {{_ "List"}}
th(scope="col") {{_ "Board"}}
th(scope="col") {{_ "Swimlane"}}
th(scope="col") {{_ "card"}}
th(scope="col") {{_ "list"}}
th(scope="col") {{_ "board"}}
th(scope="col") {{_ "swimlane"}}
unless isMiniScreen
th(scope="col") {{_ "Members"}}
th(scope="col") {{_ "Labels"}}
th(scope="col") {{_ "Due Date"}}
th(scope="col") {{_ "members"}}
th(scope="col") {{_ "labels"}}
th(scope="col") {{_ "due-date"}}
tbody
each board in myCardsList
each swimlane in board.mySwimlanes
+5 -4
View File
@@ -184,11 +184,12 @@ Template.myCards.events({
// tableView.js (Board Table view) already use: subscribe the popup-only
// publication, then open the shared `cardDetails` popup template.
'click .js-minicard'(evt) {
if (evt.button !== 0 || evt.ctrlKey || evt.metaKey || evt.shiftKey || evt.altKey) return;
// `each card in ...` binds a lexical variable, not a Blaze data context.
// Read the identifiers from the link even when its child was clicked (#6702).
const { cardId, boardId } = evt.currentTarget.dataset;
if (!cardId || !boardId) return;
evt.preventDefault();
const card = Blaze.getData(evt.currentTarget);
if (!card || !card._id) return;
const cardId = card._id;
const boardId = card.boardId;
Meteor.subscribe('popupCardData', cardId, {
onReady() {
Session.set('popupCardId', cardId);
+7 -6
View File
@@ -39,7 +39,7 @@
first row of a popup, and it must match the space beside it: `.content` has 18px of
side padding, so 6 + 12 = 18 makes the top gap and the left gutter the same. */
.pop-over .header {
height: 41px;
height: calc(41px * var(--wekan-ui-font-scale, 1));
position: relative;
margin-bottom: 6px;
background: #f7f7f7;
@@ -141,7 +141,7 @@
.pop-over[data-popup='memberMenuPopup'] > .content-wrapper {
/* The header is out of the flow here, so its height is padding instead: 41px of
header + its 1px bottom border + the same 6px gap every other popup has. */
padding-top: calc(41px + 1px + 6px);
padding-top: calc(41px * var(--wekan-ui-font-scale, 1) + 1px + 6px);
box-sizing: border-box;
}
@@ -883,7 +883,8 @@ html[dir='rtl'] .pop-over:has(.edit-date) > .js-date-popup-resize {
width: 50px;
}
.pop-over.miniprofile .miniprofile-header .info {
margin: 0 0 0 64px;
margin: 0;
margin-inline-start: 64px;
word-wrap: break-word;
}
.pop-over.miniprofile .miniprofile-header .info h3 a {
@@ -926,7 +927,7 @@ html[dir='rtl'] .pop-over:has(.edit-date) > .js-date-popup-resize {
force. The fallback is the stock WeKan blue, so an unthemed instance is
unchanged. */
background: var(--theme-accent, #2980b9);
height: 48px;
height: calc(48px * var(--wekan-ui-font-scale, 1));
padding: 0px 0px;
border: 0px;
margin: 0px 0px;
@@ -955,10 +956,10 @@ html[dir='rtl'] .pop-over:has(.edit-date) > .js-date-popup-resize {
}
.pop-over .content-wrapper {
width: 100%;
height: calc(100% - 48px);
height: calc(100% - 48px * var(--wekan-ui-font-scale, 1));
overflow-y: scroll;
overflow-x: hidden;
margin: 48px 0px 0px 0px;
margin: calc(48px * var(--wekan-ui-font-scale, 1)) 0 0;
}
.pop-over .content-container {
width: 100%;
@@ -14,7 +14,9 @@ Template.body.events({
Template.notifications.helpers({
unreadNotifications() {
const notifications = ReactiveCache.getCurrentUser().notifications();
// Activity details are subscribed only while the drawer is open. The bell
// must count the user's unread records before that subscription exists.
const notifications = ReactiveCache.getCurrentUser()?.profile?.notifications || [];
const unreadNotifications = notifications.filter(v => !v.read);
return unreadNotifications.length;
},
@@ -9,8 +9,8 @@ template(name="boardActions")
option(value="bottom") {{_'r-bottom-of'}}
div.trigger-text
| {{_'r-its-list'}}
div.trigger-button.js-add-gen-move-action.js-goto-rules
i.fa.fa-plus
button.trigger-button.js-add-gen-move-action.js-goto-rules(type="button" aria-label="{{_ 'r-add-action'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -38,8 +38,8 @@ template(name="boardActions")
| {{_'r-in-swimlane'}}
div.trigger-dropdown
input(id="swimlaneName",type=text,placeholder="{{_'r-name'}}")
div.trigger-button.js-add-spec-move-action.js-goto-rules
i.fa.fa-plus
button.trigger-button.js-add-spec-move-action.js-goto-rules(type="button" aria-label="{{_ 'r-add-action'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -49,8 +49,8 @@ template(name="boardActions")
option(value="unarchive") {{_'r-unarchive'}}
div.trigger-text
| {{_'r-card'}}
div.trigger-button.js-add-arch-action.js-goto-rules
i.fa.fa-plus
button.trigger-button.js-add-arch-action.js-goto-rules(type="button" aria-label="{{_ 'r-add-action'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -58,8 +58,8 @@ template(name="boardActions")
| {{_'r-add-swimlane'}}
div.trigger-dropdown
input(id="swimlane-name",type=text,placeholder="{{_'r-name'}}")
div.trigger-button.js-add-swimlane-action.js-goto-rules
i.fa.fa-plus
button.trigger-button.js-add-swimlane-action.js-goto-rules(type="button" aria-label="{{_ 'r-add-action'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -75,8 +75,8 @@ template(name="boardActions")
| {{_'r-in-swimlane'}}
div.trigger-dropdown
input(id="swimlane-name2",type=text,placeholder="{{_'r-name'}}")
div.trigger-button.js-create-card-action.js-goto-rules
i.fa.fa-plus
button.trigger-button.js-create-card-action.js-goto-rules(type="button" aria-label="{{_ 'r-add-action'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -100,8 +100,8 @@ template(name="boardActions")
| {{_'r-in-swimlane'}}
div.trigger-dropdown
input(id="swimlaneName-link",type=text,placeholder="{{_'r-name'}}")
div.trigger-button.js-link-card-action.js-goto-rules
i.fa.fa-plus
button.trigger-button.js-link-card-action.js-goto-rules(type="button" aria-label="{{_ 'r-add-action'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -117,8 +117,8 @@ template(name="boardActions")
option(value="name") {{_'r-sort-name'}}
option(value="created") {{_'createdAt'}}
option(value="modified") {{_'modifiedAt'}}
div.trigger-button.js-sort-list-action.js-goto-rules
i.fa.fa-plus
button.trigger-button.js-sort-list-action.js-goto-rules(type="button" aria-label="{{_ 'r-add-action'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -130,8 +130,8 @@ template(name="boardActions")
| {{_'r-in-list'}}
div.trigger-dropdown
input(id="moveall-to-list",type=text,placeholder="{{_'r-list-name'}}")
div.trigger-button.js-move-all-cards-action.js-goto-rules
i.fa.fa-plus
button.trigger-button.js-move-all-cards-action.js-goto-rules(type="button" aria-label="{{_ 'r-add-action'}}")
i.fa.fa-plus(aria-hidden="true")
@@ -38,7 +38,7 @@ Template.boardActions.helpers({
loadingBoardsLabel() {
try {
const txt = TAPi18n.__('loading-boards');
const txt = TAPi18n.__('loading');
if (txt && !txt.startsWith("key '")) return txt;
} catch (e) {
// ignore translation lookup errors
@@ -15,8 +15,8 @@ template(name="cardActions")
option(value="receivedAt") {{_'r-df-received-at'}}
div.trigger-text
| {{_'r-to-current-datetime'}}
div.trigger-button.js-set-date-action.js-goto-rules
i.fa.fa-plus
button.trigger-button.js-set-date-action.js-goto-rules(type="button" aria-label="{{_ 'r-add-action'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -29,8 +29,8 @@ template(name="cardActions")
option(value="dueAt") {{_'r-df-due-at'}}
option(value="endAt") {{_'r-df-end-at'}}
option(value="receivedAt") {{_'r-df-received-at'}}
div.trigger-button.js-remove-datevalue-action.js-goto-rules
i.fa.fa-plus
button.trigger-button.js-remove-datevalue-action.js-goto-rules(type="button" aria-label="{{_ 'r-add-action'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -45,8 +45,8 @@ template(name="cardActions")
each labels
option(value="#{_id}")
= name
div.trigger-button.js-add-label-action.js-goto-rules
i.fa.fa-plus
button.trigger-button.js-add-label-action.js-goto-rules(type="button" aria-label="{{_ 'r-add-action'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -58,29 +58,29 @@ template(name="cardActions")
| {{_'r-member'}}
div.trigger-dropdown
input(id="member-name",type=text,placeholder="{{_'r-name'}}")
div.trigger-button.js-add-member-action.js-goto-rules
i.fa.fa-plus
button.trigger-button.js-add-member-action.js-goto-rules(type="button" aria-label="{{_ 'r-add-action'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
div.trigger-text
| {{_'r-add-actinguser-member'}}
div.trigger-button.js-add-actinguser-member-action.js-goto-rules
i.fa.fa-plus
button.trigger-button.js-add-actinguser-member-action.js-goto-rules(type="button" aria-label="{{_ 'r-add-action'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
div.trigger-text
| {{_'r-remove-all'}}
div.trigger-button.js-add-removeall-action.js-goto-rules
i.fa.fa-plus
button.trigger-button.js-add-removeall-action.js-goto-rules(type="button" aria-label="{{_ 'r-add-action'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
div.trigger-text
| {{_'r-remove-all-labels'}}
div.trigger-button.js-add-removealllabels-action.js-goto-rules
i.fa.fa-plus
button.trigger-button.js-add-removealllabels-action.js-goto-rules(type="button" aria-label="{{_ 'r-add-action'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -90,8 +90,8 @@ template(name="cardActions")
id="color-action"
class="card-details-{{cardColorButton}}")
| {{_ cardColorButtonText }}
div.trigger-button.js-set-color-action.js-goto-rules
i.fa.fa-plus
button.trigger-button.js-set-color-action.js-goto-rules(type="button" aria-label="{{_ 'select-color'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -99,8 +99,8 @@ template(name="cardActions")
select(id="complete-action")
option(value="markCardComplete") {{_'r-mark-complete'}}
option(value="markCardIncomplete") {{_'r-mark-incomplete'}}
div.trigger-button.js-set-complete-action.js-goto-rules
i.fa.fa-plus
button.trigger-button.js-set-complete-action.js-goto-rules(type="button" aria-label="{{_ 'r-add-action'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -123,8 +123,8 @@ template(name="cardActions")
option(value="months") {{_'r-unit-months'}}
div.trigger-text
| {{_'r-later'}}
div.trigger-button.js-set-reldate-action.js-goto-rules
i.fa.fa-plus
button.trigger-button.js-set-reldate-action.js-goto-rules(type="button" aria-label="{{_ 'r-add-action'}}")
i.fa.fa-plus(aria-hidden="true")
template(name="setCardActionsColorPopup")
form.edit-label
@@ -9,8 +9,8 @@ template(name="checklistActions")
| {{_'r-checklist'}}
div.trigger-dropdown
input(id="checklist-name",type=text,placeholder="{{_'r-name'}}")
div.trigger-button.js-add-checklist-action.js-goto-rules
i.fa.fa-plus
button.trigger-button.js-add-checklist-action.js-goto-rules(type="button" aria-label="{{_ 'r-add-action'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -22,8 +22,8 @@ template(name="checklistActions")
| {{_'r-items-check'}}
div.trigger-dropdown
input(id="checklist-name2",type=text,placeholder="{{_'r-name'}}")
div.trigger-button.js-add-checkall-action.js-goto-rules
i.fa.fa-plus
button.trigger-button.js-add-checkall-action.js-goto-rules(type="button" aria-label="{{_ 'r-add-action'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
@@ -40,8 +40,8 @@ template(name="checklistActions")
| {{_'r-of-checklist'}}
div.trigger-dropdown
input(id="checklist-name3",type=text,placeholder="{{_'r-name'}}")
div.trigger-button.js-add-check-item-action.js-goto-rules
i.fa.fa-plus
button.trigger-button.js-add-check-item-action.js-goto-rules(type="button" aria-label="{{_ 'r-add-action'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -53,8 +53,8 @@ template(name="checklistActions")
| {{_'r-with-items'}}
div.trigger-dropdown
input(id="checklist-items",type=text,placeholder="{{_'r-items-list'}}")
div.trigger-button.js-add-checklist-items-action.js-goto-rules
i.fa.fa-plus
button.trigger-button.js-add-checklist-items-action.js-goto-rules(type="button" aria-label="{{_ 'r-add-action'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -76,8 +76,8 @@ Template.checklistActions.events({
const data = Template.currentData();
const ruleName = data.ruleName.get();
const trigger = data.triggerVar.get();
const checkItemName = tpl.find('#checkitem-name');
const checklistName = tpl.find('#checklist-name3');
const checkItemName = tpl.find('#checkitem-name').value;
const checklistName = tpl.find('#checklist-name3').value;
const actionSelected = tpl.find('#check-item-action').value;
const boardId = Session.get('currentBoard');
const desc = Utils.getTriggerActionDesc(event, tpl);
@@ -9,5 +9,5 @@ template(name="mailActions")
textarea(id="email-msg")
div.trigger-hint.trigger-hint-email-vars
| {{_'r-email-vars-hint'}}
div.trigger-button.trigger-button-email.js-mail-action.js-goto-rules
i.fa.fa-plus
button.trigger-button.trigger-button-email.js-mail-action.js-goto-rules(type="button" aria-label="{{_ 'r-add-action'}}")
i.fa.fa-plus(aria-hidden="true")
@@ -17,15 +17,15 @@ template(name="boardTriggers")
| {{_'r-in-swimlane'}}
div.trigger-dropdown
input(id="create-swimlane-name",type=text,placeholder="{{_'r-swimlane-name'}}")
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
button.trigger-button.trigger-button-person.js-show-user-field(type="button" aria-label="{{_ 'username'}}")
i.fa.fa-user(aria-hidden="true")
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-create-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-create-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item#trigger-three
div.trigger-content
@@ -35,15 +35,15 @@ template(name="boardTriggers")
i.fa.fa-search
div.trigger-text
| {{_'r-is-moved'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
button.trigger-button.trigger-button-person.js-show-user-field(type="button" aria-label="{{_ 'username'}}")
i.fa.fa-user(aria-hidden="true")
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-gen-moved-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-gen-moved-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item#trigger-four
div.trigger-content
@@ -65,15 +65,15 @@ template(name="boardTriggers")
| {{_'r-in-swimlane'}}
div.trigger-dropdown
input(id="create-swimlane-name-2",type=text,placeholder="{{_'r-swimlane-name'}}")
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
button.trigger-button.trigger-button-person.js-show-user-field(type="button" aria-label="{{_ 'username'}}")
i.fa.fa-user(aria-hidden="true")
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-moved-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-moved-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item#trigger-five
div.trigger-content
@@ -87,15 +87,15 @@ template(name="boardTriggers")
select(id="arch-action")
option(value="archived") {{_'r-archived'}}
option(value="unarchived") {{_'r-unarchived'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
button.trigger-button.trigger-button-person.js-show-user-field(type="button" aria-label="{{_ 'username'}}")
i.fa.fa-user(aria-hidden="true")
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-arch-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-arch-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -9,5 +9,5 @@ template(name="buttonTriggers")
select#button-type.js-button-type
option(value="card" selected="selected") {{_'r-card-button'}}
option(value="board") {{_'r-board-button'}}
div.trigger-button.js-add-button-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-button-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
@@ -9,15 +9,15 @@ template(name="cardTriggers")
option(value="removed") {{_'r-removed-from'}}
div.trigger-text
| {{_'r-a-card'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
button.trigger-button.trigger-button-person.js-show-user-field(type="button" aria-label="{{_ 'username'}}")
i.fa.fa-user(aria-hidden="true")
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-gen-label-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-gen-label-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -40,15 +40,15 @@ template(name="cardTriggers")
option(value="removed") {{_'r-removed-from'}}
div.trigger-text
| {{_'r-a-card'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
button.trigger-button.trigger-button-person.js-show-user-field(type="button" aria-label="{{_ 'username'}}")
i.fa.fa-user(aria-hidden="true")
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-spec-label-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-spec-label-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -60,15 +60,15 @@ template(name="cardTriggers")
option(value="removed") {{_'r-removed-from'}}
div.trigger-text
| {{_'r-a-card'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
button.trigger-button.trigger-button-person.js-show-user-field(type="button" aria-label="{{_ 'username'}}")
i.fa.fa-user(aria-hidden="true")
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-gen-member-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-gen-member-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
@@ -89,15 +89,15 @@ template(name="cardTriggers")
option(value="removed") {{_'r-removed-from'}}
div.trigger-text
| {{_'r-a-card'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
button.trigger-button.trigger-button-person.js-show-user-field(type="button" aria-label="{{_ 'username'}}")
i.fa.fa-user(aria-hidden="true")
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-spec-member-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-spec-member-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -109,15 +109,15 @@ template(name="cardTriggers")
option(value="removed") {{_'r-removed-from'}}
div.trigger-text
| {{_'r-a-card'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
button.trigger-button.trigger-button-person.js-show-user-field(type="button" aria-label="{{_ 'username'}}")
i.fa.fa-user(aria-hidden="true")
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-gen-assignee-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-gen-assignee-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
@@ -138,15 +138,15 @@ template(name="cardTriggers")
option(value="removed") {{_'r-removed-from'}}
div.trigger-text
| {{_'r-a-card'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
button.trigger-button.trigger-button-person.js-show-user-field(type="button" aria-label="{{_ 'username'}}")
i.fa.fa-user(aria-hidden="true")
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-spec-assignee-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-spec-assignee-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -160,15 +160,15 @@ template(name="cardTriggers")
option(value="removed") {{_'r-attachment-removed-from'}}
div.trigger-text
| {{_'r-a-card'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
button.trigger-button.trigger-button-person.js-show-user-field(type="button" aria-label="{{_ 'username'}}")
i.fa.fa-user(aria-hidden="true")
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-attachment-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-attachment-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item(title="{{_'advanced-filter-description'}}")
div.trigger-content
@@ -176,8 +176,8 @@ template(name="cardTriggers")
| {{_'r-when-a-card-matches-advanced-filter'}}
div.trigger-dropdown
input(class="advanced-filter-trigger-value",type=text,placeholder="{{_'advanced-filter-label'}}")
div.trigger-button.js-add-advanced-filter-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-advanced-filter-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item(title="{{_'text-contains-trigger-description'}}")
div.trigger-content
@@ -185,61 +185,61 @@ template(name="cardTriggers")
| {{_'r-when-a-card-title-or-description-contains'}}
div.trigger-dropdown
input(class="text-contains-trigger-value",type=text,placeholder="{{_'text-contains-trigger-label'}}")
div.trigger-button.js-add-text-contains-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-text-contains-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
div.trigger-text
| {{_'r-when-a-due-date-changed'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
button.trigger-button.trigger-button-person.js-show-user-field(type="button" aria-label="{{_ 'username'}}")
i.fa.fa-user(aria-hidden="true")
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-due-date-changed-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-due-date-changed-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
div.trigger-text
| {{_'r-when-a-start-date-changed'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
button.trigger-button.trigger-button-person.js-show-user-field(type="button" aria-label="{{_ 'username'}}")
i.fa.fa-user(aria-hidden="true")
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-start-date-changed-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-start-date-changed-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
div.trigger-text
| {{_'r-when-a-end-date-changed'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
button.trigger-button.trigger-button-person.js-show-user-field(type="button" aria-label="{{_ 'username'}}")
i.fa.fa-user(aria-hidden="true")
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-end-date-changed-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-end-date-changed-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
div.trigger-text
| {{_'r-when-a-received-date-changed'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
button.trigger-button.trigger-button-person.js-show-user-field(type="button" aria-label="{{_ 'username'}}")
i.fa.fa-user(aria-hidden="true")
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-received-date-changed-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-received-date-changed-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
@@ -9,15 +9,15 @@ template(name="checklistTriggers")
option(value="removed") {{_'r-removed-from'}}
div.trigger-text
| {{_'r-a-card'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
button.trigger-button.trigger-button-person.js-show-user-field(type="button" aria-label="{{_ 'username'}}")
i.fa.fa-user(aria-hidden="true")
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-gen-check-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-gen-check-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
@@ -38,15 +38,15 @@ template(name="checklistTriggers")
option(value="removed") {{_'r-removed-from'}}
div.trigger-text
| {{_'r-a-card'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
button.trigger-button.trigger-button-person.js-show-user-field(type="button" aria-label="{{_ 'username'}}")
i.fa.fa-user(aria-hidden="true")
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-spec-check-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-spec-check-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -56,15 +56,15 @@ template(name="checklistTriggers")
select(id="gen-comp-check-action")
option(value="completed") {{_'r-completed'}}
option(value="uncompleted") {{_'r-made-incomplete'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
button.trigger-button.trigger-button-person.js-show-user-field(type="button" aria-label="{{_ 'username'}}")
i.fa.fa-user(aria-hidden="true")
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-gen-comp-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-gen-comp-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -82,15 +82,15 @@ template(name="checklistTriggers")
select(id="spec-comp-check-action")
option(value="completed") {{_'r-completed'}}
option(value="uncompleted") {{_'r-made-incomplete'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
button.trigger-button.trigger-button-person.js-show-user-field(type="button" aria-label="{{_ 'username'}}")
i.fa.fa-user(aria-hidden="true")
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-spec-comp-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-spec-comp-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -100,15 +100,15 @@ template(name="checklistTriggers")
select(id="check-item-gen-action")
option(value="checked") {{_'r-checked'}}
option(value="unchecked") {{_'r-unchecked'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
button.trigger-button.trigger-button-person.js-show-user-field(type="button" aria-label="{{_ 'username'}}")
i.fa.fa-user(aria-hidden="true")
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-gen-check-item-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-gen-check-item-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item
div.trigger-content
@@ -126,12 +126,12 @@ template(name="checklistTriggers")
select(id="check-item-spec-action")
option(value="checked") {{_'r-checked'}}
option(value="unchecked") {{_'r-unchecked'}}
div.trigger-button.trigger-button-person.js-show-user-field
i.fa.fa-user
button.trigger-button.trigger-button-person.js-show-user-field(type="button" aria-label="{{_ 'username'}}")
i.fa.fa-user(aria-hidden="true")
div.user-details.hide-element
div.trigger-text
| {{_'r-by'}}
div.trigger-dropdown
input(class="user-name",type=text,placeholder="{{_'username'}}")
div.trigger-button.js-add-spec-check-item-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-spec-check-item-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
@@ -39,8 +39,8 @@ template(name="scheduledTriggers")
| {{_'r-of-cards-in-list'}}
div.trigger-dropdown
input#schedule-list-name(type=text placeholder="{{_'r-list-name'}}")
div.trigger-button.js-add-scheduled-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-scheduled-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item#trigger-due
div.trigger-content
@@ -59,8 +59,8 @@ template(name="scheduledTriggers")
| {{_'r-schedule-at-time'}}
div.trigger-dropdown
+calendarTimeInput(inputId="due-time" value="09:00")
div.trigger-button.js-add-due-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-due-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
div.trigger-item#trigger-aging
div.trigger-content
@@ -76,5 +76,5 @@ template(name="scheduledTriggers")
| {{_'r-schedule-at-time'}}
div.trigger-dropdown
+calendarTimeInput(inputId="aging-time" value="09:00")
div.trigger-button.js-add-aging-trigger.js-goto-action
i.fa.fa-plus
button.trigger-button.js-add-aging-trigger.js-goto-action(type="button" aria-label="{{_ 'r-add-trigger'}}")
i.fa.fa-plus(aria-hidden="true")
@@ -40,3 +40,7 @@
.admin-cpu-current.admin-cpu-high .admin-cpu-percent {
color: #b04632;
}
.file-status-audit { margin-bottom: 1rem; }
.file-status-audit-actions { display: flex; flex-wrap: wrap; gap: .5rem; }
.file-status-audit pre { white-space: pre-wrap; overflow-wrap: anywhere; }
+43 -1
View File
@@ -1,5 +1,4 @@
template(name="adminProblems")
+attachmentViewer
.setting-content.admin-reports-content
unless currentUser.isAdmin
| {{_ 'error-notAuthorized'}}
@@ -11,6 +10,8 @@ template(name="adminProblems")
//- Every Admin Panel pane opens with the same heading, and it is the
//- open menu entry's own label (docs/Features/Page/Left-Menu.md).
+paneTitle(paneTitleData)
if showFileStatusAudit
+fileStatusAudit
if loading.get
+spinner
else if isPane 'report-summary'
@@ -150,3 +151,44 @@ template(name="featuresNotifications")
.materialCheckBox(class="{{#if disableWatch}}is-checked{{/if}}")
span {{_ 'disable-watch'}}
p.description {{_ 'disable-watch-description'}}
//- Read-only storage diagnostics are shared by Files, Integrity and Recovery.
template(name="fileStatusAudit")
section.file-status-audit(aria-label="File status checks")
p Check file contents, database metadata and recovery evidence. No files or records are changed.
.file-status-audit-actions
button.js-file-audit-all(type="button" disabled=running) Check files and history
button.js-file-audit-types(type="button" disabled=running) Check file types and extensions
button.js-file-audit-inventory(type="button" disabled=running) Check missing and untracked files
if running
button.js-file-audit-cancel(type="button") Cancel check
if result
button.js-file-audit-download(type="button") Download status summary
if error
p(role="alert") {{error}}
if result
.file-status-audit-result(role="status" aria-live="polite")
p {{statusText}}
p {{countText}}
if result.limitations
details
summary Coverage and limitations
ul
each result.limitations
li {{this}}
if totals
ul
each totals
li {{kind}}: {{count}}
details
summary Storage directories
ul
each result.roots
li {{path}} — {{status}} {{resolved}}
if result.findings
details
summary Findings (bounded sample; full counts above)
each findings
pre {{this}}
if result.omittedFindings
p Additional findings not displayed: {{result.omittedFindings}}
@@ -343,6 +343,9 @@ const PROBLEMS_MENU = [
];
Template.adminProblems.helpers({
showFileStatusAudit() {
return ['report-files', 'report-integrity', 'report-recovery'].includes(Template.instance().activeReport.get());
},
menuItems() {
// The pane opens on Summary, before any menu click has set activeReport.
return leftMenuData(PROBLEMS_MENU,
@@ -1275,3 +1278,72 @@ Template.officeReport.events({
}
},
});
Template.fileStatusAudit.onCreated(function () {
this.result = new ReactiveVar(null);
this.error = new ReactiveVar('');
this.disposed = false;
this.polling = false;
this.refresh = () => {
if (this.disposed || this.polling) return;
this.polling = true;
Meteor.call('getFileStatusAudit', (error, result) => {
this.polling = false;
if (this.disposed) return;
if (error) this.error.set(error.reason || error.message);
else this.result.set(result);
});
};
this.refresh();
this.timer = Meteor.setInterval(this.refresh, 2000);
});
Template.fileStatusAudit.onDestroyed(function () {
this.disposed = true;
Meteor.clearInterval(this.timer);
});
Template.fileStatusAudit.helpers({
result() { return Template.instance().result.get(); },
running() { return Template.instance().result.get()?.state === 'running'; },
error() { return Template.instance().error.get(); },
totals() {
return Object.entries(Template.instance().result.get()?.totals || {}).map(([kind, count]) => ({ kind, count }));
},
findings() { return (Template.instance().result.get()?.findings || []).map(row => JSON.stringify(row, null, 2)); },
statusText() {
const result = Template.instance().result.get();
return result ? `${result.state} — ${result.phase} — ${new Date(result.startedAt).toLocaleString()}` : '';
},
countText() {
const c = Template.instance().result.get()?.counts;
return c ? `Records: ${c.records}; versions: ${c.versions}; disk files: ${c.diskFiles}; history rows: ${c.historyRows}; bytes inspected: ${c.bytesRead}` : '';
},
});
function startFileAudit(mode, instance) {
instance.error.set('');
Meteor.call('startFileStatusAudit', mode, error => {
if (instance.disposed) return;
if (error) instance.error.set(error.reason || error.message);
instance.refresh();
});
}
Template.fileStatusAudit.events({
'click .js-file-audit-download'(event, instance) {
event.preventDefault();
const report = instance.result.get();
if (!report) return;
const url = URL.createObjectURL(new Blob([JSON.stringify(report, null, 2)], { type: 'application/json' }));
const link = document.createElement('a');
link.href = url; link.download = 'wekan-file-status.json'; link.click();
Meteor.setTimeout(() => URL.revokeObjectURL(url), 1000);
},
'click .js-file-audit-all'(event, instance) { event.preventDefault(); startFileAudit('all', instance); },
'click .js-file-audit-types'(event, instance) { event.preventDefault(); startFileAudit('types', instance); },
'click .js-file-audit-inventory'(event, instance) { event.preventDefault(); startFileAudit('inventory', instance); },
'click .js-file-audit-cancel'(event, instance) {
event.preventDefault();
Meteor.call('cancelFileStatusAudit', error => {
if (!instance.disposed && error) instance.error.set(error.reason || error.message);
});
},
});
@@ -19,19 +19,19 @@ template(name="notificationSettingsPopup")
| {{_ labelKey}}
.notification-settings-popup-options
if isAdminScope
a.flex.js-notify-option(data-service="{{service}}" data-value="true" class="{{#if onSelected}}is-checked{{/if}}")
.materialCheckBox
a.flex.js-notify-option(data-service="{{service}}" data-value="true")
.materialCheckBox(class="{{#if onSelected}}is-checked{{/if}}")
span {{_ 'yes'}}
a.flex.js-notify-option(data-service="{{service}}" data-value="false" class="{{#if offSelected}}is-checked{{/if}}")
.materialCheckBox
a.flex.js-notify-option(data-service="{{service}}" data-value="false")
.materialCheckBox(class="{{#if offSelected}}is-checked{{/if}}")
span {{_ 'no'}}
else
a.flex.js-notify-option(data-service="{{service}}" data-value="" class="{{#if inheritSelected}}is-checked{{/if}}")
.materialCheckBox
a.flex.js-notify-option(data-service="{{service}}" data-value="")
.materialCheckBox(class="{{#if inheritSelected}}is-checked{{/if}}")
span {{_ 'default'}}
a.flex.js-notify-option(data-service="{{service}}" data-value="true" class="{{#if onSelected}}is-checked{{/if}}")
.materialCheckBox
a.flex.js-notify-option(data-service="{{service}}" data-value="true")
.materialCheckBox(class="{{#if onSelected}}is-checked{{/if}}")
span {{_ 'yes'}}
a.flex.js-notify-option(data-service="{{service}}" data-value="false" class="{{#if offSelected}}is-checked{{/if}}")
.materialCheckBox
a.flex.js-notify-option(data-service="{{service}}" data-value="false")
.materialCheckBox(class="{{#if offSelected}}is-checked{{/if}}")
span {{_ 'no'}}
@@ -35,10 +35,10 @@ function currentValue(scope, service) {
Template.notificationSettingsPopup.helpers({
isAdminScope() {
return Template.currentData().scope === 'admin';
return Template.instance().data.scope === 'admin';
},
notifyServiceRows() {
const scope = Template.currentData().scope;
const scope = Template.instance().data.scope;
return Object.keys(NOTIFICATION_SERVICES).map(service => {
const value = currentValue(scope, service);
return {
@@ -60,7 +60,7 @@ Template.notificationSettingsPopup.events({
const service = target.dataset.service;
const rawValue = target.dataset.value;
const value = rawValue === 'true' ? true : rawValue === 'false' ? false : null;
const scope = Template.currentData().scope;
const scope = instance.data.scope;
if (scope === 'admin') {
// Inherit is not offered at admin scope - it IS the base default - so
+1 -1
View File
@@ -687,7 +687,7 @@ template(name="settingsOrgPopup")
//
i.fa.fa-user
//
| {{_ 'impersonate-org'}}
| {{_ 'impersonate-user'}}
//
//
+3 -17
View File
@@ -1,3 +1,4 @@
import { accountOperationErrorKey } from '/client/lib/accountOperationError';
import { formatDateForDisplay } from '/client/lib/dateDisplay';
import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
@@ -2587,16 +2588,7 @@ Template.settingsUserPopup.events({
if (process.env.DEBUG === 'true') {
console.error('Error removing user:', error);
}
// Show error message to user
if (error.error === 'not-authorized') {
alert('You are not authorized to delete this user.');
} else if (error.error === 'user-not-found') {
alert('User not found.');
} else if (error.error === 'not-authorized' && error.reason === 'Cannot delete the last administrator') {
alert('Cannot delete the last administrator.');
} else {
alert('Error deleting user: ' + error.reason);
}
alert(TAPi18n.__(accountOperationErrorKey(error)));
} else {
if (process.env.DEBUG === 'true') {
console.log('User deleted successfully:', result);
@@ -2619,13 +2611,7 @@ Template.settingsUserPopup.events({
if (process.env.DEBUG === 'true') {
console.error('Error anonymizing user:', error);
}
if (error.error === 'not-authorized') {
alert('You are not authorized to anonymize this user.');
} else if (error.error === 'user-not-found') {
alert('User not found.');
} else {
alert('Error anonymizing user: ' + error.reason);
}
alert(TAPi18n.__(accountOperationErrorKey(error)));
} else {
if (process.env.DEBUG === 'true') {
console.log('User anonymized successfully:', result);
@@ -360,6 +360,17 @@ template(name='tableVisibilityModeSettings')
a.flex.js-toggle-all-boards-hide
.materialCheckBox#hide-board-member-list(class="{{#if currentSetting.hideBoardMemberList}}is-checked{{/if}}")
span {{_ 'board-member-list'}}
li.tableVisibilityMode-form
a.flex.js-toggle-all-boards-hide
.materialCheckBox#hide-date-format(class="{{#if currentSetting.hideDateFormat}}is-checked{{/if}}")
span {{_ 'date-format-for-everyone'}}:
select.wekan-form-control#global-date-format(aria-label="{{_ 'date-format-for-everyone'}}")
option(value="YYYY-MM-DD" selected="{{#if isGlobalDateFormat 'YYYY-MM-DD'}}selected{{/if}}") {{_ 'date-format-yyyy-mm-dd'}} · {{_ 'time'}}
option(value="DD-MM-YYYY" selected="{{#if isGlobalDateFormat 'DD-MM-YYYY'}}selected{{/if}}") {{_ 'date-format-dd-mm-yyyy'}} · {{_ 'time'}}
option(value="MM-DD-YYYY" selected="{{#if isGlobalDateFormat 'MM-DD-YYYY'}}selected{{/if}}") {{_ 'date-format-mm-dd-yyyy'}} · {{_ 'time'}}
option(value="YYYY-MM-DD-date-only" selected="{{#if isGlobalDateFormat 'YYYY-MM-DD-date-only'}}selected{{/if}}") {{_ 'date'}}: {{_ 'date-format-yyyy-mm-dd'}}
option(value="DD-MM-YYYY-date-only" selected="{{#if isGlobalDateFormat 'DD-MM-YYYY-date-only'}}selected{{/if}}") {{_ 'date'}}: {{_ 'date-format-dd-mm-yyyy'}}
option(value="MM-DD-YYYY-date-only" selected="{{#if isGlobalDateFormat 'MM-DD-YYYY-date-only'}}selected{{/if}}") {{_ 'date'}}: {{_ 'date-format-mm-dd-yyyy'}}
li.tableVisibilityMode-form
.title {{_ 'wait-spinner'}}
+selectSpinnerName(spinnerName=currentSetting.spinnerName)
@@ -487,6 +498,7 @@ template(name='tableVisibilityModeSettings')
.title {{_ 'custom-login-logo-image-url'}}
.form-group
input.wekan-form-control#custom-login-logo-image-url(type="text", placeholder="" value="{{currentSetting.customLoginLogoImageUrl}}")
input.js-site-logo-upload(type="file" data-kind="login" accept="image/png,image/jpeg,image/gif,image/webp" aria-label="{{_ 'upload'}} {{_ 'custom-login-logo-image-url'}}")
li.tableVisibilityMode-form
.title {{_ 'custom-login-logo-link-url'}}
.form-group
@@ -499,6 +511,9 @@ template(name='tableVisibilityModeSettings')
.title {{_ 'custom-top-left-corner-logo-image-url'}}
.form-group
input.wekan-form-control#custom-top-left-corner-logo-image-url(type="text", placeholder="" value="{{currentSetting.customTopLeftCornerLogoImageUrl}}")
input.js-site-logo-upload(type="file" data-kind="header" accept="image/png,image/jpeg,image/gif,image/webp" aria-label="{{_ 'upload'}} {{_ 'custom-top-left-corner-logo-image-url'}}")
li.tableVisibilityMode-form
span.js-site-logo-upload-status(role="status" aria-live="polite")
li.tableVisibilityMode-form
.title {{_ 'custom-top-left-corner-logo-link-url'}}
.form-group
+34
View File
@@ -901,6 +901,9 @@ Template.setting.events({
});
Template.tableVisibilityModeSettings.helpers({
isGlobalDateFormat(format) {
return (ReactiveCache.getCurrentSetting()?.globalDateFormat || 'YYYY-MM-DD') === format;
},
// Multitenancy option D: every group of this pane except "Change color" writes
// the INSTANCE settings, so only the site admin is shown them.
isSiteAdmin() {
@@ -947,6 +950,33 @@ function saveVisibilitySettings($set) {
}
Template.tableVisibilityModeSettings.events({
async 'change input.js-site-logo-upload'(event, tpl) {
const input = event.currentTarget;
const file = input.files?.[0];
if (!file) return;
const status = tpl.find('.js-site-logo-upload-status');
const kind = input.dataset.kind;
input.value = '';
if (file.size > 2 * 1024 * 1024 || !['image/png', 'image/jpeg', 'image/gif', 'image/webp'].includes(file.type)) {
if (status) status.textContent = 'Upload a PNG, JPEG, GIF or WebP image smaller than 2 MB';
return;
}
if (status) status.textContent = TAPi18n.__('upload') + '…';
try {
const base64 = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onerror = () => reject(reader.error);
reader.onload = () => resolve(String(reader.result).split(',')[1]);
reader.readAsDataURL(file);
});
const url = await Meteor.callAsync('uploadSiteLogo', kind, base64);
const field = tpl.find(kind === 'login' ? '#custom-login-logo-image-url' : '#custom-top-left-corner-logo-image-url');
if (field) field.value = url;
if (status) status.textContent = TAPi18n.__('save');
} catch (error) {
if (status) status.textContent = error.reason || error.message || 'Upload failed';
}
},
// ── All Boards ────────────────────────────────────────────────────────────
// Boards visibility, board activities, the two All Boards lists and the spinner.
//
@@ -982,11 +1012,15 @@ Template.tableVisibilityModeSettings.events({
['#hide-board-activities', 'hideBoardActivitiesOnAllBoards'],
['#hide-card-counter-list', 'hideCardCounterList'],
['#hide-board-member-list', 'hideBoardMemberList'],
['#hide-date-format', 'hideDateFormat'],
]) {
if ($(selector).length) {
$set[key] = $(selector).hasClass('is-checked');
}
}
if ($('#global-date-format').length) {
$set.globalDateFormat = visibilityText('#global-date-format');
}
if ($('#spinnerName').length) {
$set.spinnerName = visibilityText('#spinnerName');
}
@@ -57,7 +57,7 @@ template(name="newTranslationPopup")
label
| {{_ 'text'}}
span.error.hide.text-taken
| {{_ 'error-text-taken'}}
| {{_ 'editTranslationPopup-title'}}
input.js-translation-text(type="text" value="" required)
label
| {{_ 'translation-text'}}
@@ -1,3 +1,4 @@
import { TAPi18n } from '/imports/i18n';
import { ReactiveCache } from '/imports/reactiveCache';
import { buildHeader, pageInfo, TABLE_PAGE_ROWS_PER_PAGE } from '/models/lib/tablePage';
@@ -52,7 +53,7 @@ Template.translationSettings.onCreated(function () {
if (value === '') {
this.findTranslationsOptions.set({});
} else {
const regex = new RegExp(value, 'i');
const regex = new RegExp(value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
this.findTranslationsOptions.set({
$or: [
{ language: regex },
@@ -210,7 +211,9 @@ Template.newTranslationPopup.events({
if (error) {
const errorElement = error.error;
if (errorElement === 'text-already-taken') {
textMessageElement.show();
textMessageElement.text(`${TAPi18n.__('editTranslationPopup-title')}: ${text} (${language})`).show();
} else {
textMessageElement.text(TAPi18n.__('server-error')).show();
}
} else {
textMessageElement.hide();
@@ -218,7 +221,6 @@ Template.newTranslationPopup.events({
}
},
);
Popup.back();
},
});
+29 -3
View File
@@ -161,7 +161,7 @@
.card-field-order-columns {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-columns: repeat(2, minmax(0, 1fr));
column-gap: 18px;
align-items: start;
}
@@ -179,7 +179,7 @@
display: flex;
align-items: center;
gap: 6px;
padding: 4px 0;
padding: calc(4px * min(1, var(--wekan-ui-font-scale, 1))) 0;
border-bottom: 1px solid #eee;
}
@@ -190,7 +190,7 @@
.card-field-order-move.is-disabled {
opacity: 0.3;
pointer-events: none;
cursor: not-allowed;
}
.card-field-order-label {
@@ -765,4 +765,30 @@ body.sidebar-resizing-active {
.pop-over-list-subheading + .pop-over-list {
margin-top: 0;
}
/* #6699: this editor uses real, labelled checkboxes. Undo all three parts
of forms.css's material-checkbox hiding rule in this popup only. */
.wip-limit-groups input[type="checkbox"] {
display: inline-block;
visibility: visible;
position: static;
inset-inline-start: auto;
width: 16px;
height: 16px;
flex: 0 0 16px;
margin-inline-end: 6px;
appearance: auto;
cursor: pointer;
}
.wip-limit-groups label.flex {
align-items: center;
cursor: pointer;
}
/* Stack the two settings columns without changing single-side desktop headings. */
@media screen and (max-width: 800px) {
.card-field-order-columns { grid-template-columns: minmax(0, 1fr); }
}
.card-field-order-column { min-width: 0; }
.card-field-order-label { overflow-wrap: anywhere; }
+6 -6
View File
@@ -217,7 +217,7 @@ template(name="boardChangeBackgroundImagePopup")
Save it belongs beside.
.buttonsContainer.board-background-image-buttons
input.primary(type="submit" value="{{_ 'save'}}")
button.js-remove-background-image.negate.card-details-red(type="button") {{_ 'unset-color'}}
button.js-remove-background-image.negate.card-details-red(type="button") {{_ 'remove-background-image'}}
hr
//- ...and the same question answered from this machine instead of from a
URL, with what has been uploaded to this board under it - so an upload is
@@ -300,9 +300,9 @@ template(name="boardCardSettingsPopup")
a.flex.card-field-order-toggle(class=row.toggle class="{{#if row.checked}}is-checked{{/if}}" title="{{row.title}}")
i.fa(class="{{#if row.checked}}fa-check{{else}}fa-square-o{{/if}}")
if canModifyBoard
a.flex.card-field-order-move.js-card-field-order-up(href="#" role="button" class="{{#unless row.canMoveUp}}is-disabled{{/unless}}" title="{{_ 'card-field-order-move-up'}}" aria-label="{{_ 'card-field-order-move-up'}}")
a.flex.card-field-order-move.js-card-field-order-up(href="#" role="button" class="{{#unless row.canMoveUp}}is-disabled{{/unless}}" aria-disabled="{{#if row.canMoveUp}}false{{else}}true{{/if}}" title="{{#if row.canMoveUp}}{{_ 'card-field-order-move-up'}}{{else}}{{_ 'r-rule-disabled'}}{{/if}}" aria-label="{{_ 'card-field-order-move-up'}}")
i.fa.fa-arrow-up
a.flex.card-field-order-move.js-card-field-order-down(href="#" role="button" class="{{#unless row.canMoveDown}}is-disabled{{/unless}}" title="{{_ 'card-field-order-move-down'}}" aria-label="{{_ 'card-field-order-move-down'}}")
a.flex.card-field-order-move.js-card-field-order-down(href="#" role="button" class="{{#unless row.canMoveDown}}is-disabled{{/unless}}" aria-disabled="{{#if row.canMoveDown}}false{{else}}true{{/if}}" title="{{#if row.canMoveDown}}{{_ 'card-field-order-move-down'}}{{else}}{{_ 'r-rule-disabled'}}{{/if}}" aria-label="{{_ 'card-field-order-move-down'}}")
i.fa.fa-arrow-down
span.card-field-order-label
each icon in row.icons
@@ -326,9 +326,9 @@ template(name="boardCardSettingsPopup")
a.flex.card-field-order-toggle(class=row.toggle class="{{#if row.checked}}is-checked{{/if}}" title="{{row.title}}")
i.fa(class="{{#if row.checked}}fa-check{{else}}fa-square-o{{/if}}")
if canModifyBoard
a.flex.card-field-order-move.js-card-field-order-up(href="#" role="button" class="{{#unless row.canMoveUp}}is-disabled{{/unless}}" title="{{_ 'card-field-order-move-up'}}" aria-label="{{_ 'card-field-order-move-up'}}")
a.flex.card-field-order-move.js-card-field-order-up(href="#" role="button" class="{{#unless row.canMoveUp}}is-disabled{{/unless}}" aria-disabled="{{#if row.canMoveUp}}false{{else}}true{{/if}}" title="{{#if row.canMoveUp}}{{_ 'card-field-order-move-up'}}{{else}}{{_ 'r-rule-disabled'}}{{/if}}" aria-label="{{_ 'card-field-order-move-up'}}")
i.fa.fa-arrow-up
a.flex.card-field-order-move.js-card-field-order-down(href="#" role="button" class="{{#unless row.canMoveDown}}is-disabled{{/unless}}" title="{{_ 'card-field-order-move-down'}}" aria-label="{{_ 'card-field-order-move-down'}}")
a.flex.card-field-order-move.js-card-field-order-down(href="#" role="button" class="{{#unless row.canMoveDown}}is-disabled{{/unless}}" aria-disabled="{{#if row.canMoveDown}}false{{else}}true{{/if}}" title="{{#if row.canMoveDown}}{{_ 'card-field-order-move-down'}}{{else}}{{_ 'r-rule-disabled'}}{{/if}}" aria-label="{{_ 'card-field-order-move-down'}}")
i.fa.fa-arrow-down
span.card-field-order-label
each icon in row.icons
@@ -702,7 +702,7 @@ template(name="wipLimitGroupsPopup")
.wip-limit-group-name
input.js-wip-limit-group-name(type="text" value="{{name}}" placeholder="{{_ 'wip-limit-group-name-placeholder'}}")
.wip-limit-group-lists
each ../boardLists
each boardLists
label.flex
input.js-wip-limit-group-list-toggle(type="checkbox" data-list-id="{{_id}}" checked="{{isListInGroup _id ../listIds}}")
| {{title}}
+27 -2
View File
@@ -529,7 +529,7 @@ Template.boardMenuPopup.events({
'click .js-change-background-image': Popup.open('boardChangeBackgroundImage'),
'click .js-open-notification-settings': Popup.open('notificationSettings', {
titleKey: 'notifications',
scope: 'board',
dataContext: { scope: 'board' },
}),
'click .js-board-info-on-my-boards': Popup.open('boardInfoOnMyBoards'),
'click .js-change-language': Popup.open('changeLanguage'),
@@ -1836,7 +1836,7 @@ function buildCardSettingsRows(side, data) {
toggle: spec.toggle,
checked: typeof helper === 'function' ? Boolean(helper.call(data)) : false,
icons: row.icons,
title: row.label.map(k => TAPi18n.__(k)).join(' '),
title: row.label.map(k => TAPi18n.__(k)).join(row.labelSeparator || ' '),
personal: Boolean(spec.personal),
labelTextOverride: Boolean(spec.labelTextOverride),
canMoveUp: positioned && canMove(stored, layout, row.key, 'up'),
@@ -1852,6 +1852,10 @@ function buildCardSettingsRows(side, data) {
// allowsLabels}}` read, defaults and fallbacks included, without a second
// copy of that logic. Registered at the end of the object.
const boardCardSettingsHelpers = {
allowsMinicardCollapse() { return ReactiveCache.getBoard(Session.get('currentBoard'))?.allowsMinicardCollapse !== false; },
labelsAboveTitleOnMinicard() { return ReactiveCache.getBoard(Session.get('currentBoard'))?.labelsAboveTitleOnMinicard === true; },
allowsChecklistDueDate() { return ReactiveCache.getBoard(Session.get('currentBoard'))?.allowsChecklistDueDate !== false; },
allowsChecklistTitle() { return ReactiveCache.getBoard(Session.get('currentBoard'))?.allowsChecklistTitle !== false; },
// Board Settings / Card Settings shows both columns - "Show on Card" and
// "Show on Minicard" beside each other. The card's own menu and the
// minicard's menu open the SAME popup asking for one of them, and the other
@@ -2311,6 +2315,7 @@ Template.boardCardSettingsPopup.helpers(boardCardSettingsHelpers);
// board's setter, which normalises it. models/lib/cardFieldOrder.js
function moveCardSettingsRow(evt, direction) {
evt.preventDefault();
if (evt.currentTarget.classList.contains('is-disabled')) return;
const rowEl = evt.currentTarget.closest('.js-card-field-order-row');
if (!rowEl) return;
const { key, side } = rowEl.dataset;
@@ -2544,6 +2549,26 @@ Template.boardCardSettingsPopup.events({
const newValue = !tpl.currentBoard.allowsDescriptionText;
Boards.update(tpl.currentBoard._id, { $set: { allowsDescriptionText: newValue } });
},
'click .js-field-minicard-collapse'(evt, tpl) {
evt.preventDefault();
const value = tpl.currentBoard.allowsMinicardCollapse !== false;
Boards.update(tpl.currentBoard._id, { $set: { allowsMinicardCollapse: !value } });
},
'click .js-field-labels-above-title'(evt, tpl) {
evt.preventDefault();
const value = tpl.currentBoard.labelsAboveTitleOnMinicard === true;
Boards.update(tpl.currentBoard._id, { $set: { labelsAboveTitleOnMinicard: !value } });
},
'click .js-field-checklist-due-date'(evt, tpl) {
evt.preventDefault();
const value = tpl.currentBoard.allowsChecklistDueDate !== false;
Boards.update(tpl.currentBoard._id, { $set: { allowsChecklistDueDate: !value } });
},
'click .js-field-checklist-title'(evt, tpl) {
evt.preventDefault();
const value = tpl.currentBoard.allowsChecklistTitle !== false;
Boards.update(tpl.currentBoard._id, { $set: { allowsChecklistTitle: !value } });
},
'click .js-field-has-checklists'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsChecklists;
@@ -110,7 +110,7 @@ template(name="archivesSidebar")
+spinner
template(name="restoreArchivedCardToListPopup")
p {{_ 'no-list-found'}}, {{_ 'select-list'}}:
p {{_ 'error-list-doesNotExist'}}, {{_ 'list'}}:
select.js-select-restore-target-list(autofocus)
each availableLists
option(value="{{_id}}" selected="{{#if isSelectedList _id}}selected{{/if}}") {{add @index 1}}. {{title}}
@@ -119,7 +119,7 @@ template(name="restoreArchivedCardToListPopup")
button.primary.confirm.js-restore-card-to-list {{_ 'restore'}}
template(name="restoreArchivedListToSwimlanePopup")
p {{_ 'no-swimlane-found'}}, {{_ 'select-swimlane'}}:
p {{_ 'no-results'}}, {{_ 'wip-limit-group-select-swimlane'}}:
select.js-select-restore-target-swimlane(autofocus)
each availableSwimlanes
option(value="{{_id}}" selected="{{#if isSelectedSwimlane _id}}selected{{/if}}") {{add @index 1}}. {{isTitleDefault title}}
@@ -313,4 +313,4 @@ template(name="setSelectionColorPopup")
if(isSelected color)
i.fa.fa-check
button.primary.confirm.js-submit {{_ 'save'}}
button.js-remove-color.negate.wide.right {{_ 'unset-color'}}
button.js-remove-color.negate.wide.right {{_ 'remove-btn'}}
@@ -164,7 +164,7 @@ template(name="setSwimlaneColorPopup")
// Buttons aligned left too
.flush-left
button.primary.confirm.js-submit(style="margin-inline-start:0") {{_ 'save'}}
button.js-remove-color.negate.wide.right(style="margin-inline-start:8px") {{_ 'unset-color'}}
button.js-remove-color.negate.wide.right(style="margin-inline-start:8px") {{_ 'remove-btn'}}
template(name="setSwimlaneHeightPopup")
.flush-left.swimlane-height-popup
+1 -1
View File
@@ -3,7 +3,7 @@ template(name='passwordInput')
label(for='at-field-{{_id}}') {{displayName}}
.password-input-container
input.password-field(type="{{type}}" placeholder="{{displayName}}" autocomplete="{{autocomplete}}" required="{{required}}")
button.password-toggle-btn.primary(type="button" tabindex="-1" aria-label="{{_ 'password'}}: {{_ 'visibility'}}" title="{{_ 'password'}}: {{_ 'visibility'}}")
button.password-toggle-btn.primary(type="button" aria-label="{{_ 'password'}}: {{_ 'visibility'}}" title="{{_ 'password'}}: {{_ 'visibility'}}")
i.fa.fa-eye.eye-icon(aria-hidden="true")
i.fa.fa-eye-slash.eye-slash-icon(aria-hidden="true")
if errs
+2 -2
View File
@@ -329,7 +329,7 @@ Template.changeAvatarPopup.events({
event.preventDefault();
event.stopPropagation();
if (this && typeof this.link === 'function') {
const avatarUrl = this.link();
const avatarUrl = `/cdn/storage/avatars/${this._id}`;
changeAvatarSetAvatar(tpl, avatarUrl);
}
},
@@ -495,7 +495,7 @@ Template.adminChangeAvatarPopup.events({
event.preventDefault();
event.stopPropagation();
if (this && typeof this.link === 'function') {
const avatarUrl = this.link();
const avatarUrl = `/cdn/storage/avatars/${this._id}`;
adminChangeAvatarSetAvatar(tpl, avatarUrl);
}
},
+1 -1
View File
@@ -295,7 +295,7 @@ template(name="changeFontPopup")
label {{_ 'text-color'}}
.color-wheel-wrap
input.js-ui-text-color(type="color" value=textColorHex title="{{_ 'text-color'}}")
a.js-reset-text-color(class="{{#unless hasTextColor}}disabled{{/unless}}") {{_ 'unset-color'}}
a.js-reset-text-color(class="{{#unless hasTextColor}}disabled{{/unless}}") {{_ 'remove-btn'}}
template(name="changeSettingsPopup")
ul.pop-over-list
+4 -3
View File
@@ -1,3 +1,4 @@
import { accountOperationErrorKey } from '/client/lib/accountOperationError';
import { languageFlags, languageLabelParts } from '/imports/i18n/languageFlags';
const { availableCalendarSystems } = require('/imports/lib/calendarSystems');
import { ReactiveCache } from '/imports/reactiveCache';
@@ -93,7 +94,7 @@ Template.memberMenuPopup.events({
'click .js-change-color': Popup.open('changeColor'),
'click .js-open-notification-settings': Popup.open('notificationSettings', {
titleKey: 'notifications',
scope: 'member',
dataContext: { scope: 'member' },
}),
'click .js-change-font': Popup.open('changeFont'),
'click .js-change-avatar': Popup.open('changeAvatar'),
@@ -258,7 +259,7 @@ Template.editProfilePopup.events({
if (process.env.DEBUG === 'true') {
console.error('Error removing user:', error);
}
alert('Error deleting account: ' + error.reason);
alert(TAPi18n.__(accountOperationErrorKey(error)));
} else {
if (process.env.DEBUG === 'true') {
console.log('User deleted successfully:', result);
@@ -277,7 +278,7 @@ Template.editProfilePopup.events({
if (process.env.DEBUG === 'true') {
console.error('Error anonymizing user:', error);
}
alert('Error anonymizing account: ' + error.reason);
alert(TAPi18n.__(accountOperationErrorKey(error)));
} else {
if (process.env.DEBUG === 'true') {
console.log('User anonymized successfully:', result);
+8
View File
@@ -0,0 +1,8 @@
// Keep server diagnostics in the console; show existing localized UI messages.
// The last-admin case must precede the general authorization failure.
export function accountOperationErrorKey(error) {
if (error?.error === 'not-authorized' && error.reason === 'Cannot delete the last administrator') return 'last-admin-desc';
if (error?.error === 'not-authorized') return 'error-notAllowed';
if (error?.error === 'user-not-found') return 'error-user-doesNotExist';
return 'server-error';
}
+10 -4
View File
@@ -1,3 +1,4 @@
const { resolveDateFormat } = require('/models/lib/dateFormatPolicy');
import { ReactiveCache } from '/imports/reactiveCache';
import { formatDateByUserPreference } from '/imports/lib/dateUtils';
import { formatJalaliDate, gregorianToJalali } from '/imports/lib/jalaliDate';
@@ -5,21 +6,26 @@ import { TAPi18n } from '/imports/i18n';
const { CALENDAR_SYSTEMS, formatNativeCalendarDate, nativeCalendarParts } = require('/imports/lib/calendarSystems');
const { calendarMonthRange, shiftedDay } = require('/imports/lib/calendarMonth');
export function isDateFormatForced() {
return !!ReactiveCache.getCurrentSetting()?.hideDateFormat;
}
export function dateDisplayPreferences() {
const setting = ReactiveCache.getCurrentSetting();
const user = ReactiveCache.getCurrentUser();
if (user) {
return {
calendarSystem: user.getCalendarSystem ? user.getCalendarSystem() : 'gregorian',
dateFormat: user.getDateFormat ? user.getDateFormat() : 'YYYY-MM-DD',
dateFormat: resolveDateFormat(user.getDateFormat ? user.getDateFormat() : null, setting),
};
}
try {
return {
calendarSystem: window.localStorage.getItem('calendarSystem') || 'gregorian',
dateFormat: window.localStorage.getItem('dateFormat') || 'YYYY-MM-DD',
dateFormat: resolveDateFormat(window.localStorage.getItem('dateFormat'), setting),
};
} catch (error) {
return { calendarSystem: 'gregorian', dateFormat: 'YYYY-MM-DD' };
return { calendarSystem: 'gregorian', dateFormat: resolveDateFormat(null, setting) };
}
}
@@ -46,7 +52,7 @@ export function formatDateForDisplay(date, includeTime = true, gregorianFormatte
);
if (system) return text || '';
}
return gregorianFormatter
return gregorianFormatter && !isDateFormatForced() && !dateFormat.endsWith('-date-only')
? gregorianFormatter(value)
: formatDateByUserPreference(value, dateFormat, includeTime);
}
+53 -15
View File
@@ -3,6 +3,8 @@ import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import { Utils } from '/client/lib/utils';
import Boards from '/models/boards';
import Swimlanes from '/models/swimlanes';
import Lists from '/models/lists';
/**
* Helper class for popup dialogs that let users select a board, swimlane, and list.
@@ -31,6 +33,15 @@ export class BoardSwimlaneListDialog {
this.selectedBoardId = new ReactiveVar(this.currentBoardId);
this.selectedSwimlaneId = new ReactiveVar('');
this.selectedListId = new ReactiveVar('');
this.loading = new ReactiveVar(true);
this.error = new ReactiveVar('');
this.saving = new ReactiveVar(false);
this._request = 0;
this.tpl.view?.onViewDestroyed(() => {
this._disposed = true;
this._request++;
this._boardSubscription?.stop();
});
this.setOption(this.currentBoardId);
}
@@ -98,8 +109,7 @@ export class BoardSwimlaneListDialog {
/** sets the first swimlane id */
setFirstSwimlaneId() {
try {
const board = ReactiveCache.getBoard(this.selectedBoardId.get());
const swimlaneId = board.swimlanes()[0]._id;
const swimlaneId = this.swimlanes()[0]?._id || '';
this.selectedSwimlaneId.set(swimlaneId);
} catch (e) {}
}
@@ -118,12 +128,13 @@ export class BoardSwimlaneListDialog {
/** get lists filtered by board and swimlane */
getListsForBoardSwimlane(boardId, swimlaneId) {
if (!boardId) return [];
const board = ReactiveCache.getBoard(boardId);
const board = Boards.findOne(boardId);
if (!board) return [];
const selector = {
boardId,
archived: false,
deletedAt: null,
};
if (swimlaneId) {
@@ -132,7 +143,7 @@ export class BoardSwimlaneListDialog {
selector.swimlaneId = { $in: [swimlaneId, null, ''] };
}
return ReactiveCache.getLists(selector, { sort: { sort: 1 } });
return Lists.find(selector, { sort: { sort: 1 } }).fetch();
}
/** returns if the board id was the last confirmed one */
@@ -196,8 +207,15 @@ export class BoardSwimlaneListDialog {
/** returns all available swimlanes of the current board */
swimlanes() {
const board = ReactiveCache.getBoard(this.selectedBoardId.get());
return board.swimlanes();
const boardId = this.selectedBoardId.get();
if (!boardId) return [];
// onReady runs before DataCache's invalidated computations necessarily
// flush. Read the subscription's current documents so an old empty cache
// cannot leave the selected swimlane blank and Done disabled indefinitely.
return Swimlanes.find(
{ boardId, archived: false },
{ sort: { sort: 1 } },
).fetch();
}
/** returns all available lists of the current board */
@@ -233,16 +251,36 @@ export class BoardSwimlaneListDialog {
/** get the board data from the server */
getBoardData(boardId) {
const self = this;
Meteor.subscribe('board', boardId, false, {
onReady() {
const sameBoardId = self.selectedBoardId.get() == boardId;
self.selectedBoardId.set(boardId);
if (!sameBoardId) {
self.setFirstSwimlaneId();
self.setFirstListId();
const changed = this.selectedBoardId.get() !== boardId;
const request = ++this._request;
this._boardSubscription?.stop();
this.selectedBoardId.set(boardId);
this.loading.set(true);
this.error.set('');
if (changed) {
this.selectedSwimlaneId.set('');
this.selectedListId.set('');
this.selectedCardId?.set('');
}
if (!boardId) return;
this._boardSubscription = this.tpl.subscribe('board', boardId, false, {
onReady: () => {
if (this._disposed || request !== this._request) return;
const lanes = this.swimlanes();
if (!lanes.some(lane => lane._id === this.selectedSwimlaneId.get())) this.setFirstSwimlaneId();
if (!this.lists().some(list => list._id === this.selectedListId.get())) this.setFirstListId();
if (this.selectedCardId && !this.cards().some(card => card._id === this.selectedCardId.get())) {
this.selectedCardId.set('');
}
this.loading.set(false);
},
onStop: error => {
if (this._disposed || request !== this._request || !error) return;
this.selectedSwimlaneId.set('');
this.selectedListId.set('');
this.selectedCardId?.set('');
this.error.set(TAPi18n.__('server-error'));
this.loading.set(false);
},
});
}
+2 -22
View File
@@ -1,4 +1,3 @@
import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var';
import { ReactiveCache } from '/imports/reactiveCache';
import { BoardSwimlaneListDialog } from '/client/lib/dialogWithBoardSwimlaneList';
@@ -10,7 +9,7 @@ import { BoardSwimlaneListDialog } from '/client/lib/dialogWithBoardSwimlaneList
export class BoardSwimlaneListCardDialog extends BoardSwimlaneListDialog {
constructor(tpl, callbacks = {}) {
super(tpl, callbacks);
this.selectedCardId = new ReactiveVar('');
this.selectedCardId = new ReactiveVar(this.cardOption.cardId || '');
}
getDefaultOption() {
@@ -46,26 +45,7 @@ export class BoardSwimlaneListCardDialog extends BoardSwimlaneListDialog {
/** returns if the card id was the last confirmed one */
isDialogOptionCardId(cardId) {
return this.cardOption.cardId == cardId;
}
/** Override to also reset card id on board change */
getBoardData(boardId) {
const self = this;
Meteor.subscribe('board', boardId, false, {
onReady() {
const sameBoardId = self.selectedBoardId.get() == boardId;
self.selectedBoardId.set(boardId);
if (!sameBoardId) {
self.setFirstSwimlaneId();
self.setFirstListId();
if (self.selectedCardId) {
self.selectedCardId.set('');
}
}
},
});
return this.selectedCardId.get() === cardId;
}
}
+1 -1
View File
@@ -31,7 +31,7 @@ const MOVE_CANCEL_PX = 10; // finger moving this far before the hold = a scroll
// and jQuery-UI sortables (lists / swimlanes / cards) already have touch via
// touch-punch, so leave them to it.
const EXCLUDE =
'input, select, textarea, .note-editable, .ui-sortable, .ui-sortable-handle';
'input, select, textarea, .ui-sortable, .ui-sortable-handle';
function draggableAncestor(el) {
while (el && el !== document.body) {
+1 -1
View File
@@ -33,7 +33,7 @@ function isExcluded(target) {
// .js-list-handle) must be excluded too: those start a jQuery UI
// sortable reorder via touch-punch, and hijacking the touch to scroll
// here would block dragging the handle to reorder swimlanes/lists.
'input, select, textarea, .note-editable, .nodragscroll, .handle, .ui-sortable-handle',
'input, select, textarea, .nodragscroll, .handle, .ui-sortable-handle',
)
);
}
+5 -3
View File
@@ -1,3 +1,4 @@
const { resolveDateFormat } = require('/models/lib/dateFormatPolicy');
import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
@@ -44,12 +45,13 @@ export function browserTimezone() {
// back to, which the server cannot see at all. An export of a card should print
// its dates the way the card prints them.
export function cardDateFormat() {
const setting = ReactiveCache.getCurrentSetting();
try {
const currentUser = ReactiveCache.getCurrentUser();
if (currentUser) return currentUser.getDateFormat();
return window.localStorage.getItem('dateFormat') || 'YYYY-MM-DD';
if (currentUser) return resolveDateFormat(currentUser.getDateFormat(), setting);
return resolveDateFormat(window.localStorage.getItem('dateFormat'), setting);
} catch (error) {
return 'YYYY-MM-DD';
return resolveDateFormat(null, setting);
}
}
+2 -1
View File
@@ -1,3 +1,4 @@
import { resolveDateFormat } from '/models/lib/dateFormatPolicy';
import { Blaze } from 'meteor/blaze';
import { Tracker } from 'meteor/tracker';
import { ReactiveCache } from '/imports/reactiveCache';
@@ -410,7 +411,7 @@ class AdvancedFilter {
user && typeof user.getDateFormat === 'function'
? user.getDateFormat()
: 'YYYY-MM-DD';
dayFirst = /^D/.test(dateFormat);
dayFirst = /^D/.test(resolveDateFormat(dateFormat, ReactiveCache.getCurrentSetting()));
} catch (error) {
dayFirst = false;
}
+1 -1
View File
@@ -40,7 +40,7 @@ function isExcluded(target) {
target.closest &&
// Form fields keep their own behaviour (the zoom-level input lives in the
// quick-access bar), and `.nodragscroll` opts an element out explicitly.
target.closest('input, select, textarea, .note-editable, .nodragscroll')
target.closest('input, select, textarea, .nodragscroll')
);
}
+4
View File
@@ -83,6 +83,10 @@ Template.inlinedForm.events({
// in Member Settings submits on plain Enter (Shift+Enter for a newline).
const submitOnEnter = !!ReactiveCache.getCurrentUser()?.hasSubmitOnEnter?.();
if (isSubmitKey(evt, { submitOnEnter })) {
// #6700: the enclosing checklist has its own Enter handler. A handled
// submit key must not bubble to it and create a second document.
evt.preventDefault();
evt.stopPropagation();
tpl.find('button[type=submit]').click();
}
},
+11 -2
View File
@@ -35,8 +35,12 @@ hotkeys.filter = (event) => {
if (window.getSelection().type === "Range")
return false;
// Decide what the current element is
const currentElement = event.target || document.activeElement;
// Synthetic non-Latin keyboard events are dispatched on document, which has
// no element APIs. Keep filtering against the focused control in that case.
const currentElement = event.target instanceof Element
? event.target
: document.activeElement;
if (!currentElement) return false;
// If the current element is editable, we don't want to trigger an event
if (currentElement.isContentEditable)
@@ -46,6 +50,9 @@ hotkeys.filter = (event) => {
if (currentElement instanceof HTMLInputElement || currentElement instanceof HTMLSelectElement || currentElement instanceof HTMLTextAreaElement)
return false;
if (currentElement.closest('button, a[href], summary, [role="button"], [role="tab"], [role="checkbox"]'))
return false;
// We can trigger events!
return true;
};
@@ -165,6 +172,7 @@ hotkeys(shiftNums, (event, handler) => {
}
const currentBoardId = Session.get('currentBoard');
const board = ReactiveCache.getBoard(currentBoardId);
if (!board) return;
const labels = board.labels;
if (MultiSelection.isActive()) {
const cardIds = MultiSelection.getSelectedCardIds();
@@ -188,6 +196,7 @@ hotkeys(nums, (event, handler) => {
return;
}
const board = ReactiveCache.getBoard(currentBoardId);
if (!board) return;
const labels = board.labels;
if (MultiSelection.isActive() && ReactiveCache.getCurrentUser().isBoardMember()) {
const cardIds = MultiSelection.getSelectedCardIds();
+1 -1
View File
@@ -116,7 +116,7 @@ window.Popup = new (class {
title: self._getTitle(popupName, openOptions.titleKey),
depth: self._stack.length,
offset: self._getOffset(openerElement, popupName),
dataContext: (this && this.currentData && this.currentData()) || (options && options.dataContextIfCurrentDataIsUndefined) || this,
dataContext: openOptions.dataContext || (this && this.currentData && this.currentData()) || (options && options.dataContextIfCurrentDataIsUndefined) || this,
});
const $contentWrapper = $('.content-wrapper')
Loaded 100 of 761 files, more files were not shown because too many files have changed in this diff. Show more