Compare commits

...
910 Commits
Author SHA1 Message Date
Lauri Ojansivu 33ab8920ca v8.18 2025-12-28 20:32:44 +02:00
Lauri Ojansivu db4d47cc52 Updated ChangeLog. 2025-12-28 20:21:44 +02:00
Lauri Ojansivu e210c9973b Upgraded MongoDB to 7.0.28 to fix mongobleed at Snap Candidate.
Thanks to developers of MongoDB !
2025-12-28 19:59:36 +02:00
Lauri Ojansivu 98d05ce545 Updated translations 2025-12-25 08:47:55 +02:00
Lauri Ojansivu 9268d80974 Updated translations. 2025-12-25 08:46:43 +02:00
Lauri Ojansivu 1a221a6967 Updated translations. 2025-12-23 16:06:21 +02:00
Lauri Ojansivu a039bb1066 Per-User and Board-level data save fixes. Part 3.
Thanks to xet7 !
2025-12-23 09:03:41 +02:00
Lauri Ojansivu 90a7a61904 Updates 2025-12-23 08:03:04 +02:00
Lauri Ojansivu 58e970d685 Per-User and Board-level data save fixes. Part 2.
Thanks to xet7 !
2025-12-23 08:01:30 +02:00
Lauri Ojansivu edf0f3c05b Updated translations 2025-12-23 07:54:57 +02:00
Lauri Ojansivu 5bbf5e0dac Updated ChangeLog. 2025-12-23 07:52:46 +02:00
Lauri Ojansivu 414b8dbf41 Per-User and Board-level data save fixes. Per-User is collapse, width, height. Per-Board is Swimlanes, Lists, Cards etc.
Thanks to xet7 !

Fixes #5997
2025-12-23 07:49:37 +02:00
Lauri Ojansivu 2e0e1e56b5 Updated translations. 2025-12-23 06:59:50 +02:00
Lauri Ojansivu 546dcdf1de Updated ChangeLog. 2025-12-23 06:58:30 +02:00
Lauri Ojansivu 58f4884ad6 Collapse Swimlane, List, Opened Card. Opened Card window X and Y position can be moved freely from drag handle. Fix some dragging not possible. Fix iPhone Safari.
Thanks to xet7 !

Fixes #6040,
fixes #6027,
fixes #6021,
fixes #6002
2025-12-23 06:47:02 +02:00
Lauri Ojansivu 95d1625a9f Updated translations. 2025-12-23 01:45:40 +02:00
Lauri Ojansivu 05fbdd559f Updated ChangeLog. 2025-12-23 01:32:50 +02:00
Lauri Ojansivu 300b653ea3 Right top User Settings / Grey Icons. Also fixed Change Language popup.
Thanks to xet7 !
2025-12-23 01:31:02 +02:00
Lauri Ojansivu 1808ea7384 fix(jade): simplify rules headers and conditions; add helpers to expose ruleName safely 2025-12-22 23:35:30 +02:00
Lauri Ojansivu 4408eae158 feat: grey unicode icons without UI freezes 2025-12-22 23:26:30 +02:00
Lauri Ojansivu a68993d099 perf(unicode-icons): replace body-wide scans with added-nodes observer; prevent unresponsiveness while greying icons 2025-12-22 23:26:30 +02:00
Lauri Ojansivu ecfb0f0fdf Manually merged fixes from seve12.
Thanks to seve12 !

Related https://github.com/wekan/wekan/pull/5967
2025-12-22 23:18:01 +02:00
Lauri Ojansivu fc3bb962f7 Updated translations. 2025-12-22 23:01:14 +02:00
Lauri Ojansivu 23630a4c67 Updated translations. 2025-12-22 22:49:53 +02:00
Lauri Ojansivu b0a7fea724 Updated translations. 2025-12-22 22:41:05 +02:00
Lauri Ojansivu 47bbd5b4a8 Updated ChangeLog. 2025-12-22 22:25:41 +02:00
Lauri Ojansivu a7400dca45 More translations. Added support page to Admin Panel / Settings / Layout.
Thanks to xet7 !
2025-12-22 22:24:35 +02:00
Lauri Ojansivu 4227377449 Updated ChangeLog. 2025-12-22 21:46:14 +02:00
Lauri Ojansivu c1168d181b New Board Permissions: NormalAssignedOnly, CommentAssignedOnly, ReadOnly, ReadAssignedOnly.
Thanks to xet7 !

Fixes #1122,
fixes #6033,
fixes #3300
2025-12-22 21:45:09 +02:00
Lauri Ojansivu 21fb8e9164 Updated ChangeLog. 2025-12-22 21:08:53 +02:00
Lauri Ojansivu e569c2957e Number of cards per list and sum of custom number field in list head.
Thanks to xet7 !

Fixes #3796
2025-12-22 21:06:44 +02:00
Lauri Ojansivu 4292302c3c Updated ChangeLog. 2025-12-22 17:02:20 +02:00
Lauri Ojansivu f34e4c0e36 Gantt chart view to one board view menu Swimlanes/Lists/Calendar/Gantt.
Thanks to xet7 !

Fixes #2870
2025-12-22 16:51:10 +02:00
Lauri Ojansivu 1790918006 Updated translations. 2025-12-22 06:06:47 +02:00
Lauri Ojansivu 400eb81206 Updated Mac docs for Applite
Thanks to xet7 !
2025-12-18 01:58:53 +02:00
Lauri Ojansivu e07e461e48 Updates 2025-12-17 14:58:38 +02:00
Lauri Ojansivu 451e9f7870 Updates 2025-12-17 14:57:11 +02:00
Lauri Ojansivu 3c57840340 Updates 2025-12-17 14:53:41 +02:00
Lauri Ojansivu 2cbf64da33 Updates 2025-12-17 14:43:30 +02:00
Lauri Ojansivu 3ef0a3e685 Updates 2025-12-17 14:41:26 +02:00
Lauri Ojansivu 9ebdc82d46 Updates 2025-12-17 14:39:57 +02:00
Lauri Ojansivu fe4b36b85d Fix migration instructions from Snap to Docker
Corrected instructions for migrating from Snap to Docker, ensuring clarity on file paths and environment variables.
2025-12-17 14:37:37 +02:00
Lauri Ojansivu b7ff370561 Fix backup command for WeKan database
Corrected command in backup instructions to create directory before moving files.
2025-12-17 14:34:30 +02:00
Lauri Ojansivu 5686c92e05 Revise backup and upgrade instructions for Docker Wekan
Updated notes on Docker commands and migration steps.
2025-12-17 14:33:39 +02:00
Lauri Ojansivu 784c5c6b0c Update Backup.md 2025-12-17 14:27:43 +02:00
Lauri Ojansivu 30541260f0 Update Backup.md to reflect related topics 2025-12-17 14:25:15 +02:00
Lauri Ojansivu 59fc756a0b Revise WeKan Snap upgrade and backup instructions
Updated the WeKan Snap upgrade instructions, including backup steps and Caddy configuration.
2025-12-17 14:24:17 +02:00
Lauri Ojansivu 4e346c0ab7 Update Backup.md with migration instructions
Added instructions for handling board settings and migrations after starting Wekan.
2025-12-17 14:07:01 +02:00
Lauri Ojansivu 19fa12bb26 Update backup instructions for Docker and Snap
Updated docker-compose command to use 'docker compose' syntax and added note about Snap upgrade.
2025-12-17 14:00:35 +02:00
Lauri Ojansivu e669b1b9c7 Updated backup docs for docker upgrades 2025-12-17 13:57:17 +02:00
Lauri Ojansivu f24e4a4ce3 Added docs about Mac Homebrew. 2025-12-17 11:13:15 +02:00
Lauri Ojansivu 8b5430acb8 Added Mac docs about Homebrew GUI Applite.
Thanks to xet7 !
2025-12-17 11:10:57 +02:00
Lauri Ojansivu 614cb44b55 Updated translations. 2025-12-16 21:56:51 +02:00
Lauri Ojansivu 0ce8e8b74d Merge pull request #6043 from wekan/dependabot/github_actions/actions/cache-5
Bump actions/cache from 4 to 5
2025-12-16 05:47:09 +02:00
Lauri Ojansivu 4ea53af76e Merge pull request #6042 from wekan/dependabot/github_actions/actions/download-artifact-7
Bump actions/download-artifact from 6 to 7
2025-12-16 05:46:50 +02:00
Lauri Ojansivu 016f17d663 Merge pull request #6041 from wekan/dependabot/github_actions/actions/upload-artifact-6
Bump actions/upload-artifact from 5 to 6
2025-12-16 05:46:26 +02:00
dependabot[bot] 07f69950a7 Bump actions/cache from 4 to 5
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 20:13:04 +00:00
dependabot[bot] cec625607d Bump actions/download-artifact from 6 to 7
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 20:13:00 +00:00
dependabot[bot] a290c7b34b Bump actions/upload-artifact from 5 to 6
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 20:12:55 +00:00
Lauri Ojansivu 5b77ac1b44 Updated translations 2025-12-11 03:33:17 +02:00
Lauri Ojansivu 41c635afb5 Merge pull request #6029 from MialLewis/add_archive_card_to_api
Add archive card to api
2025-12-04 11:58:44 +02:00
Lauri Ojansivu adbf729cb2 Merge pull request #6032 from wekan/dependabot/github_actions/docker/metadata-action-5.10.0
Bump docker/metadata-action from 5.9.0 to 5.10.0
2025-12-04 11:58:02 +02:00
dependabot[bot] 88ea716d63 Bump docker/metadata-action from 5.9.0 to 5.10.0
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5.9.0 to 5.10.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/318604b99e75e41977312d83839a89be02ca4893...c299e40c65443455700f0fdfc63efafe5b349051)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 23:35:03 +00:00
Mial Lewis 003a07ebce change restore to unarchive 2025-11-27 22:00:43 +00:00
Mial Lewis d3c237bc66 fix more indenting 2025-11-27 08:29:36 +00:00
Mial Lewis bac0fa81fc correce indent 2025-11-27 08:27:38 +00:00
Mial Lewis a42915614a add restore to wekan.yml 2025-11-27 08:25:59 +00:00
Mial Lewis 5ff9bf331f add restore to api 2025-11-27 08:23:56 +00:00
Mial Lewis 36d7b0f8a7 correct return values 2025-11-27 00:52:28 +00:00
Mial Lewis 67c8a98f20 add route to wekan.yml 2025-11-27 00:05:53 +00:00
Mial Lewis a81a603031 update bool to boolean 2025-11-26 23:59:00 +00:00
Mial Lewis e30ce78053 add archive card to api 2025-11-26 23:57:49 +00:00
Lauri Ojansivu 3d70de94c6 Merge pull request #6028 from wekan/dependabot/github_actions/actions/checkout-6
Bump actions/checkout from 5 to 6
2025-11-26 18:04:09 +02:00
Lauri Ojansivu 1b6e8797ec Feature: Grey Icons. This makes WeKan very slow. Not recommended.
Thanks to xet7 !
2025-11-25 04:33:42 +02:00
dependabot[bot] 70975c2944 Bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 20:03:19 +00:00
Lauri Ojansivu 960e2126b4 Updated ChangeLog. 2025-11-21 03:02:41 +02:00
Lauri Ojansivu 3db1305e58 Updated build script for Linux arm64 bundle.
Thanks to xet7 !
2025-11-21 02:44:50 +02:00
Lauri Ojansivu f16780b5e3 Updated translations. 2025-11-19 09:34:57 +02:00
Lauri Ojansivu 37a3065f3c Updated translations. 2025-11-15 16:35:31 +02:00
Lauri Ojansivu 7ff1649d89 Updated security.md 2025-11-14 07:47:31 +02:00
Lauri Ojansivu a39ae31b45 Merge pull request #6012 from wekan/dependabot/github_actions/docker/metadata-action-5.9.0 2025-11-13 17:19:00 +02:00
dependabot[bot] 6302a48221 Bump docker/metadata-action from 5.8.0 to 5.9.0
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5.8.0 to 5.9.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/c1e51972afc2121e065aed6d45c65596fe445f3f...318604b99e75e41977312d83839a89be02ca4893)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-10 20:02:19 +00:00
Lauri Ojansivu c277bee9d2 Merge pull request #6009 from brlin-tw/patch-issue-6008
Fix Broken Strikethroughs in Markdown to HTML conversion.
2025-11-10 04:58:29 +02:00
Buo-ren Lin (OSSII) c5f5ce126d Fix Broken Strikethroughs in Markdown to HTML conversion.
Allow the s tag to be rendered.

Fixes #6008.

Signed-off-by: Buo-ren Lin (OSSII) <buoren.lin@ossii.com.tw>
2025-11-10 10:49:26 +08:00
Lauri Ojansivu 0004ae716b v8.17 2025-11-06 04:00:04 +02:00
Lauri Ojansivu 7f53dfac3c Updated ChangeLog. 2025-11-06 03:33:46 +02:00
Lauri Ojansivu 18003900c2 Fix Worker Permissions does not allow for cards to be moved. - v8.15.
Thanks to xet7 !

Fixes #5990
2025-11-06 03:31:14 +02:00
Lauri Ojansivu fe104791b5 Updated ChangeLog. 2025-11-06 03:08:51 +02:00
Lauri Ojansivu 6244657ca5 Fix Workspaces at All Boards to have correct count of remaining etc, while starred also at Starred/Favorites.
Thanks to xet7 !
2025-11-06 03:06:16 +02:00
Lauri Ojansivu 46866dac85 Updated ChangeLog. 2025-11-06 02:46:52 +02:00
Lauri Ojansivu c829c073cf Remove not working Bookmark menu option.
Thanks to xet7 !
2025-11-06 02:44:30 +02:00
Lauri Ojansivu 0772ca4036 Updated ChangeLog. 2025-11-06 02:36:10 +02:00
Lauri Ojansivu 581733d605 Fix Regression - Show calendar popup at set due date.
Thanks to xet7 !

Fixes #5978
2025-11-06 02:32:34 +02:00
Lauri Ojansivu b02af27ac3 Updated ChangeLog. 2025-11-06 01:06:19 +02:00
Lauri Ojansivu 20af0a2ef5 Try to fix Edit Custom Fields button not working. Removed duplicate option from Boards Settings.
Thanks to xet7 !

Fixes #5988
2025-11-06 01:04:20 +02:00
Lauri Ojansivu c58ab5b07d Updated ChangeLog. 2025-11-06 00:37:42 +02:00
Lauri Ojansivu e5e711c938 Fix Card emoji issues.
Thanks to xet7 !

Fixes #5995
2025-11-06 00:35:49 +02:00
Lauri Ojansivu 42594abe4e Updated ChangeLog. 2025-11-06 00:30:08 +02:00
Lauri Ojansivu 0afbdc95b4 Feature: Workspaces, at All Boards page.
Thanks to xet7 !
2025-11-06 00:26:35 +02:00
Lauri Ojansivu 16a74bb748 Updated ChangeLog. 2025-11-05 20:51:44 +02:00
Lauri Ojansivu 8711b476be Fix star board.
Thanks to xet7 !
2025-11-05 20:50:28 +02:00
Lauri Ojansivu df9fba4765 Updated translations. 2025-11-05 20:26:29 +02:00
Lauri Ojansivu 7d27139aa9 Updated ChangeLog. 2025-11-05 20:25:07 +02:00
Lauri Ojansivu e4638d5fbc Fixed sidebar migrations to be per-board, not global. Clarified translations.
Thanks to xet7 !
2025-11-05 20:22:56 +02:00
Lauri Ojansivu bc5854dd29 Updated ChangeLog. 2025-11-05 19:04:47 +02:00
Lauri Ojansivu ba49d4d140 Remove old translations and code not in use anymore.
Thanks to xet7 !
2025-11-05 19:03:21 +02:00
Lauri Ojansivu 71b7dcffb5 Updated ChangeLog. 2025-11-05 18:46:56 +02:00
Lauri Ojansivu 7713e613b4 Fix 8.16 Lists with no items are deleted every time when board is opened. Moved migrations to right sidebar.
Thanks to xet7 !

Fixes #5994
2025-11-05 18:44:48 +02:00
Lauri Ojansivu 91a0aa7387 Updated ChangeLog. 2025-11-05 17:08:52 +02:00
Lauri Ojansivu fbd6b920ef Updated ChangeLog. 2025-11-05 17:08:10 +02:00
Lauri Ojansivu 1b25d1d572 Moved migrations from opening board to right sidebar / Migrations.
Thanks to xet7 !
2025-11-05 17:06:26 +02:00
Lauri Ojansivu e93e72234c Updated ChangeLog. 2025-11-05 16:38:10 +02:00
Lauri Ojansivu 15d9b0ae3a Updated ChangeLog. 2025-11-05 16:38:03 +02:00
Lauri Ojansivu 550d87ac6c Fix 8.16: Switching Board View fails with 403 error.
Thanks to xet7 !
2025-11-05 16:35:29 +02:00
Lauri Ojansivu f8e576e890 Try to fix Snap.
Thanks to xet7 !
2025-11-02 22:23:16 +02:00
Lauri Ojansivu fb8ef4d978 Try to fix Snap.
Thanks to xet7 !
2025-11-02 21:36:17 +02:00
Lauri Ojansivu 5127e87898 Try to fix Snap.
Thanks to xet7 !
2025-11-02 21:33:06 +02:00
Lauri Ojansivu 3f2d4444e4 Try to fix Snap. Part 2.
Thanks to xet7 !
2025-11-02 16:14:45 +02:00
Lauri Ojansivu 9c7badb0eb Merge branch 'main' of github.com:wekan/wekan 2025-11-02 16:04:16 +02:00
Lauri Ojansivu 9d9f77a731 Try to fix Snap.
Thanks to xet7 !
2025-11-02 16:02:53 +02:00
Lauri Ojansivu c400ce74b1 v8.16 2025-11-02 12:09:27 +02:00
Lauri Ojansivu c2e20ee4a3 Updated ChangeLog. 2025-11-02 11:43:33 +02:00
Lauri Ojansivu ccd9034339 Fix SECURITY ISSUE 5: Attachment API uses bearer value as userId and DoS (Low).
Thanks to Siam Thanat Hack (STH) and xet7 !
2025-11-02 11:42:07 +02:00
Lauri Ojansivu 0a1a075f31 Fix SECURITY ISSUE 4: Members can forge others’ votes (Low). Bonus: Similar fixes to planning poker too done by xet7.
Thanks to Siam Thanat Hack (STH) and xet7 !
2025-11-02 11:12:41 +02:00
Lauri Ojansivu 4aaeec9515 Updated ChangeLog. 2025-11-02 10:17:33 +02:00
Lauri Ojansivu ea310d7508 Fix SECURITY ISSUE 3: Unauthenticated (or any) user can update board sort.
Thanks to Siam Thanat Hack (STH) !
2025-11-02 10:13:45 +02:00
Lauri Ojansivu 0a2e6a0c38 Updated ChangeLog. 2025-11-02 09:20:28 +02:00
Lauri Ojansivu f26d582018 Fix SECURITY ISSUE 2: Access to boards of any Orgs/Teams, and avatar permissions.
Thanks to Siam Thanat Hack (STH) !
2025-11-02 09:11:50 +02:00
Lauri Ojansivu e9a727301d Fix SECURITY ISSUE 1: File Attachments enables stored XSS (High).
Thanks to Siam Thanat Hack (STH) !
2025-11-02 08:36:29 +02:00
Lauri Ojansivu d64d2f9c42 Updated translations. 2025-11-02 07:30:24 +02:00
Lauri Ojansivu 5c0d122e84 Updated funding 2025-11-02 06:15:08 +02:00
Lauri Ojansivu 5079c853a7 Updated translations. 2025-10-29 02:58:00 +02:00
Lauri Ojansivu b039ba12a2 Merge pull request #5984 from wekan/dependabot/github_actions/actions/download-artifact-6
Bump actions/download-artifact from 5 to 6
2025-10-28 06:00:19 +02:00
Lauri Ojansivu 3323ac6ac1 Merge pull request #5983 from wekan/dependabot/github_actions/actions/upload-artifact-5
Bump actions/upload-artifact from 4 to 5
2025-10-28 05:59:59 +02:00
dependabot[bot] 3204311ac1 Bump actions/download-artifact from 5 to 6
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v5...v6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 23:12:09 +00:00
dependabot[bot] 0fc2ad97cd Bump actions/upload-artifact from 4 to 5
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-27 20:32:10 +00:00
Lauri Ojansivu 30620d0ca4 Some migrations and mobile fixes.
Thanks to xet7 !
2025-10-25 21:09:07 +03:00
Lauri Ojansivu bccc22c5fe Updated ChangeLog. 2025-10-25 19:25:04 +03:00
Lauri Ojansivu ecf2418347 Fix changing swimlane color to not reload webpage.
Thanks to xet7 !
2025-10-25 19:23:35 +03:00
Lauri Ojansivu 0c99cb3103 Updated ChangeLog. 2025-10-25 19:19:35 +03:00
Lauri Ojansivu 034dc08269 Disabled migrations that happen when opening board. Defaulting to per-swimlane lists and drag drop list to same or different swimlane.
Thanks to xet7 !
2025-10-25 19:17:09 +03:00
Lauri Ojansivu d1a51b42f6 Updated translations. 2025-10-24 18:43:21 +03:00
Lauri Ojansivu 92bfbb2d0c Updated ChangeLog. 2025-10-23 05:54:25 +03:00
Lauri Ojansivu 91b846e2cd List menu / More / Delete duplicate lists that do not have any cards.
Thanks to xet7 !
2025-10-23 05:50:43 +03:00
Lauri Ojansivu 7fe7fb4c15 v8.15 2025-10-23 04:41:34 +03:00
Lauri Ojansivu 0cebd8aa4d Fix drag lists did not work. Part 2.
Thanks to xet7 !
2025-10-23 04:35:33 +03:00
Lauri Ojansivu 8662c96d1c Fix drag lists did not work.
Thanks to xet7 !
2025-10-23 04:33:34 +03:00
Lauri Ojansivu 0cbc9402f3 v8.14 2025-10-23 04:09:14 +03:00
Lauri Ojansivu 940df02456 Updated translations. 2025-10-23 04:08:49 +03:00
Lauri Ojansivu b4b598f542 Fix board reloading page every second.
Thanks to xet7 !
2025-10-23 04:03:52 +03:00
Lauri Ojansivu ef19c35b5a v8.12 2025-10-23 03:29:23 +03:00
Lauri Ojansivu fc98120269 Updated translations. 2025-10-23 03:24:28 +03:00
Lauri Ojansivu b8a3d6deaf Updated ChangeLog. 2025-10-23 03:17:13 +03:00
Lauri Ojansivu 45537ede87 Fix UI issues of Right Sidebar / Subtasks Settings and Card Settings.
Thanks to xet7 !

Fixes #5971
2025-10-23 03:15:26 +03:00
Lauri Ojansivu 29a9c5bc7b Updated ChangeLog. 2025-10-23 01:02:15 +03:00
Lauri Ojansivu 7ca81285b1 Fix opened card Date Format to be used at dates popups.
Thanks to xet7 !

Related #5971
2025-10-23 01:00:11 +03:00
Lauri Ojansivu 49a865cdbf Updated ChangeLog. 2025-10-23 00:48:31 +03:00
Lauri Ojansivu a0c30c35ed Removed not needed | at left side of minicard badges.
Thanks to xet7 !
2025-10-23 00:47:18 +03:00
Lauri Ojansivu de20424885 Updated translations. 2025-10-23 00:38:34 +03:00
Lauri Ojansivu f7e09ae89c Updated ChangeLog. 2025-10-23 00:36:17 +03:00
Lauri Ojansivu c6d4600683 Fix unable to add members to board.
Fixes #5972
2025-10-23 00:34:19 +03:00
Lauri Ojansivu bd1837ee36 Updated ChangeLog. 2025-10-23 00:16:27 +03:00
Lauri Ojansivu 544b24ceb1 Fix Regression - unable to rearrange tasks within a checklist - v8.11.
Thanks to xet7 !

Fixes #5973
2025-10-23 00:14:30 +03:00
Lauri Ojansivu 0825374183 Updated translations. 2025-10-22 23:56:23 +03:00
Lauri Ojansivu b053fb8e61 Updated ChangeLog. 2025-10-22 23:33:38 +03:00
Lauri Ojansivu ae11e80bde Fix Regression - unable to view cards by due date v8.11.
Thanks to xet7 !

Fixes #5964
2025-10-22 23:31:36 +03:00
Lauri Ojansivu 8e296231ba Updated translations. 2025-10-22 22:59:35 +03:00
Lauri Ojansivu 49891eff36 Updated ChangeLog. 2025-10-21 15:34:07 +03:00
Lauri Ojansivu 58df525b49 Fix duplicated lists and do not show debug messages when env DEBUG is not true. Part 3.
Thanks to xet7 !

Fixes #5952
2025-10-21 15:31:34 +03:00
Lauri Ojansivu 1761f43afa Merge newest changes. 2025-10-21 15:22:55 +03:00
Lauri Ojansivu 37d7d938c5 Updated ChangeLog. 2025-10-21 15:21:25 +03:00
Lauri Ojansivu b7ca2310b2 Fix duplicated lists.
Thanks to xet7 !

Fixes #5952
2025-10-21 15:19:19 +03:00
Lauri Ojansivu c562b3969a v8.11 2025-10-21 15:17:53 +03:00
Lauri Ojansivu d1d553e8d7 Updated ChangeLog. 2025-10-21 15:15:15 +03:00
Lauri Ojansivu b6e7b258e0 Fix duplicated lists.
Thanks to xet7 !

Fixes #5952
2025-10-21 15:14:01 +03:00
Lauri Ojansivu c7bbe47221 Updated ChangeLog. 2025-10-21 15:10:07 +03:00
Lauri Ojansivu 347fa9e5cd Fix Regression - due date taking a while to load all cards v8.06.
Thanks to xet7 !

Fixes #5955
2025-10-21 15:08:50 +03:00
Lauri Ojansivu 07ce151508 Updated ChangeLog. 2025-10-21 15:04:01 +03:00
Lauri Ojansivu 665c9b5e52 Verify that due background colors are correct also at My Due Cards.
Thanks to xet7 !
2025-10-21 15:02:39 +03:00
Lauri Ojansivu 9399a0c545 Updated ChangeLog. 2025-10-21 14:59:48 +03:00
Lauri Ojansivu a540b12895 Fix My Due Cards to be sorted by due date, oldest first.
Thanks to xet7 !

Fixes #5956
2025-10-21 14:57:57 +03:00
Lauri Ojansivu e29d9dcd17 Updated ChangeLog. 2025-10-21 14:49:59 +03:00
Lauri Ojansivu 1aa0d84977 Fix due dates to use colors: red = overdue, amber = due soon, no shade = not due yet.
Thanks to xet7 !

Fixes #5963
2025-10-21 14:47:57 +03:00
Lauri Ojansivu 7f31d7c812 v8.10 2025-10-21 14:15:16 +03:00
Lauri Ojansivu 4987a95d8e Prevent opened board re-migrating and reloading every 5 seconds.
Thanks to xet7 !
2025-10-21 14:12:12 +03:00
Lauri Ojansivu ef7771febb v8.09 2025-10-21 13:54:37 +03:00
Lauri Ojansivu 12cba0e148 Updated ChangeLog. 2025-10-21 13:36:43 +03:00
Lauri Ojansivu c3a4052227 Fix upgrade to 8.08 duplicates lists.
Thanks to xet7 !

Fixes #5962,
fixes #5952
2025-10-21 13:34:39 +03:00
Lauri Ojansivu 82f048ccef Updated ChangeLog. 2025-10-21 13:26:41 +03:00
Lauri Ojansivu 7a585a3dfb Fix Admin Panel / People editing and layout.
Thanks to xet7 !

Fixes #5961
2025-10-21 13:22:58 +03:00
Lauri Ojansivu 8d3b53f51d v8.08 2025-10-21 11:01:40 +03:00
Lauri Ojansivu d73e006935 Updated ChangeLog. 2025-10-21 10:48:45 +03:00
Lauri Ojansivu 9536e60bd1 Fix opening board migration of Shared Lists to Per-Swimlane lists to use ReactiveCache correctly without errors.
Thanks to xet7 !

Fixes #5960
2025-10-21 10:46:37 +03:00
Lauri Ojansivu 678ca978a3 Merge branch 'main' of github.com:wekan/wekan 2025-10-20 19:13:10 +03:00
Lauri Ojansivu 39420877fd Updated ChangeLog. 2025-10-20 19:12:07 +03:00
Lauri Ojansivu 6ea03cfba3 Revert moving mongodb raw database files.
Thanks to xet7 !
2025-10-20 18:08:52 +03:00
Lauri Ojansivu 9214b56aea v8.07 2025-10-20 17:43:18 +03:00
Lauri Ojansivu 699b4c464f Updated translations. 2025-10-20 17:30:42 +03:00
Lauri Ojansivu 9fa54a3148 Updated ChangeLog. 2025-10-20 17:29:29 +03:00
Lauri Ojansivu f2019b1059 If Snap Candidate MongoDB raw database files were at SNAP_COMMON/wekan, migrate them back to SNAP_COMMON.
Thanks to xet7 !
2025-10-20 17:26:53 +03:00
Lauri Ojansivu 714bbd0fb0 Updated ChangeLog. 2025-10-20 17:10:30 +03:00
Lauri Ojansivu 80777b4663 When opening board, add missing lists.
Thanks to xet7 !

Fixes #5926
2025-10-20 17:06:42 +03:00
Lauri Ojansivu 9473c1fe41 Updated ChangeLog. 2025-10-20 16:50:35 +03:00
Lauri Ojansivu 98f141d62f Fix Snap Candidate WeKan 8.00-8.06 commit ae01ea5 database directory from /var/snap/wekan/common/wekan back to 8.07 /var/snap/wekan/common.
Thanks to xet7 !
2025-10-20 16:42:28 +03:00
Lauri Ojansivu 85dd213b14 Updated translations. 2025-10-20 06:21:02 +03:00
Lauri Ojansivu 3cf00911f7 v8.06 2025-10-20 03:29:24 +03:00
Lauri Ojansivu bddaad8346 Updated ChangeLog. 2025-10-20 03:13:32 +03:00
Lauri Ojansivu 5df4efd7ba Have all iPhone use mobile view by default, while still having possibility to use mobile/desktop switch button for desktop mode.
Thanks to xet7 !
2025-10-20 03:11:02 +03:00
Lauri Ojansivu 59df6aad05 Updated ChangeLog. 2025-10-20 03:00:21 +03:00
Lauri Ojansivu c4af4d03ac Some mobile view fixes.
Thanks to xet7 !
2025-10-20 02:58:30 +03:00
Lauri Ojansivu 62679819d9 Updated ChangeLog. 2025-10-20 02:32:08 +03:00
Lauri Ojansivu 46d46e313c Fix Bug Member settings drops to the second line and overlaps when many boards are starred as favourites.
Thanks to xet7 !

Fixes #5943
2025-10-20 02:30:03 +03:00
Lauri Ojansivu 27e9d3ce47 Updated ChangeLog. 2025-10-20 01:59:21 +03:00
Lauri Ojansivu b6b0c5fe6d Fix Bug: Scale of Minicard icons is linked to horizontal screensize.
Thanks to xet7 !

Fixes #5947
2025-10-20 01:56:54 +03:00
Lauri Ojansivu 87b934a955 Updated translations. 2025-10-20 01:52:37 +03:00
Lauri Ojansivu 8cc6e9b812 Updated translations. 2025-10-20 01:45:00 +03:00
Lauri Ojansivu b7da17ff31 Updated ChangeLog. 2025-10-20 01:39:29 +03:00
Lauri Ojansivu 2dd3916f7e Added Date Format setting to Opened Card.
Thanks to xet7 !

Fixes #2011,
fixes #1176
2025-10-20 01:36:44 +03:00
Lauri Ojansivu 516552cce6 Updated ChangeLog. 2025-10-20 01:23:43 +03:00
Lauri Ojansivu 2d44881619 Fix card popup to use HTML date, not anymore JQuery date.
Thanks to xet7 !
2025-10-20 01:21:54 +03:00
Lauri Ojansivu 0acbf30b03 Fix migrations.
Thanks to xet7 !
2025-10-20 01:20:28 +03:00
Lauri Ojansivu e61f6b1c89 Updated ChangeLog. 2025-10-20 01:03:34 +03:00
Lauri Ojansivu 973a49526f Fix Broken Hyperlinks in Markdown to HTML conversion.
Thanks to xet7 !

Fixes #5932
2025-10-20 01:01:55 +03:00
Lauri Ojansivu e1902d58c1 Updated ChangeLog. 2025-10-20 00:35:33 +03:00
Lauri Ojansivu 1e53125499 Fix opened card attachments button text to be at tooltip, not at opened card.
Thanks to xet7 !
2025-10-20 00:33:02 +03:00
Lauri Ojansivu 91fb7d9e70 Updated ChangeLog. 2025-10-20 00:29:56 +03:00
Lauri Ojansivu eb6b42c4c9 Fix syntax error at migrations.
Thanks to xet7 !
2025-10-20 00:28:19 +03:00
Lauri Ojansivu 679d210667 Updated ChangeLog. 2025-10-20 00:24:46 +03:00
Lauri Ojansivu 1e6252de7f When opening board, migrate from Shared Lists to Per-Swimlane Lists.
Thanks to xet7 !

Fixes #5952
2025-10-20 00:22:26 +03:00
Lauri Ojansivu 48b645ee1e Updated ChangeLog. 2025-10-19 23:48:15 +03:00
Lauri Ojansivu 951d2e4937 Legacy Lists button at one board view to restore missing lists/cards.
Thanks to xet7 !

Fixes #5952
2025-10-19 23:40:02 +03:00
Lauri Ojansivu 1658883b78 Updated ChangeLog. 2025-10-19 23:19:54 +03:00
Lauri Ojansivu 3514335247 At Public Board, drag resize list width and swimlane height. For logged in users, fix adding labels.
Thanks to xet7 !

Fixes #5922
2025-10-19 23:15:55 +03:00
Lauri Ojansivu 55bec31a3f Fix drag drop lists. Part 2.
Thanks to xet7 !

Fixes #5951
2025-10-19 22:22:21 +03:00
Lauri Ojansivu 0d36abee4e Updated ChangeLog. 2025-10-19 21:47:51 +03:00
Lauri Ojansivu caa6e615ff Removed extra pipe characters.
Thanks to xet7 !
2025-10-19 21:46:14 +03:00
Lauri Ojansivu cd3576b995 Updated ChangeLog. 2025-10-19 21:41:44 +03:00
Lauri Ojansivu 324f3f7794 Fix drag drop lists. Part 1.
Thanks to xet7 !

Related #5951
2025-10-19 21:38:55 +03:00
Lauri Ojansivu 3257110673 Updated ChangeLog. 2025-10-19 20:07:39 +03:00
Lauri Ojansivu 66b444e2b0 Fix unable to see My Due Cards.
Thanks to xet7 !

Fixes #5948
2025-10-19 20:05:36 +03:00
Lauri Ojansivu 23860b1ee8 Updated ChangeLog. 2025-10-19 18:58:36 +03:00
Lauri Ojansivu 101048339b Fix Due dates to be color coded and have icons. Part 2.
Thanks to xet7 !

Fixes #5950
2025-10-19 18:55:44 +03:00
Lauri Ojansivu dc78e3b7a0 Updated ChangeLog. 2025-10-19 18:45:32 +03:00
Lauri Ojansivu d965faa317 Fix Due dates to be color coded and have icons.
Thanks to xet7 !

Fixes #5950
2025-10-19 18:42:37 +03:00
Lauri Ojansivu 5d2bfab0f5 Updated ChangeLog. 2025-10-19 18:12:25 +03:00
Lauri Ojansivu 841a6eaf8c Merge branch 'helioguardabaxo-master' 2025-10-19 18:11:05 +03:00
Lauri Ojansivu db59bb4aa4 Merge branch 'master' of github.com:helioguardabaxo/wekan into helioguardabaxo-master 2025-10-19 18:01:30 +03:00
helioguardabaxo 61f7099106 Fix stared, archive and clone icons 2025-10-19 09:24:18 -03:00
Lauri Ojansivu ef828bdd38 Updated translations. 2025-10-19 14:19:51 +03:00
Lauri Ojansivu 1134b45b05 Updated ChangeLog. 2025-10-19 11:07:49 +03:00
Lauri Ojansivu b06daff4c7 Fix add and drag drop attachments to minicards and card.
Thanks to xet7 !

Fixes #5946,
fixes #5436,
fixes #2936,
fixes #1926,
fixes #300,
fixes #142
2025-10-19 10:57:36 +03:00
Lauri Ojansivu cea414b589 Updated translations. 2025-10-17 21:55:49 +03:00
Lauri Ojansivu b8942b728f v8.05 2025-10-17 17:53:00 +03:00
Lauri Ojansivu 8e6eabd9e8 Updated transations. 2025-10-17 17:38:54 +03:00
Lauri Ojansivu 290dd6c4d1 Updated ChangeLog. 2025-10-17 07:56:49 +03:00
Lauri Ojansivu 088bc16072 Font Awesome to Unicode icons. Part 4.
Thanks to xet7 !
2025-10-17 07:55:04 +03:00
Lauri Ojansivu daad2fbd71 Updated ChangeLog. 2025-10-17 07:10:07 +03:00
Lauri Ojansivu 3af94c2a90 Font Awesome to Unicode icons. Part 3.
Thanks to xet7 !
2025-10-17 07:08:01 +03:00
Lauri Ojansivu a3ca76d3c4 Updated ChangeLog. 2025-10-17 06:09:42 +03:00
Lauri Ojansivu 62ede48196 Removed not needed visible text from mobile desktop switch button.
Thanks to xet7 !
2025-10-17 06:07:24 +03:00
Lauri Ojansivu 390a86a7a7 Updated ChangeLog. 2025-10-17 06:02:46 +03:00
Lauri Ojansivu 09631d6b0c Resize height of swimlane by dragging. Font Awesome to Unicode icons.
Thanks to xet7 !
2025-10-17 05:58:53 +03:00
Lauri Ojansivu 2947238a02 Convert Font Awesome to Unicode Icons. Part 1. In Progress.
Thanks to xet7 !
2025-10-17 02:19:43 +03:00
Lauri Ojansivu a7af4b4809 Updated ChangeLog. 2025-10-17 00:27:30 +03:00
Lauri Ojansivu cb6afe67a7 Replaced moment.js with Javascript date.
Thanks to xet7 !
2025-10-17 00:26:11 +03:00
Lauri Ojansivu 8c5b43295d Updated ChangeLog. 2025-10-16 23:21:47 +03:00
Lauri Ojansivu 79b94824ef Changed wekan-boostrap-datepicker to HTML datepicker.
Thanks to xet7 !
2025-10-16 23:19:26 +03:00
Lauri Ojansivu 33e4b046e8 Updated ChangeLog. 2025-10-16 22:25:20 +03:00
Lauri Ojansivu 386aea7c78 Popup fixes. Part 2.
Thanks to xet7 !
2025-10-16 22:24:11 +03:00
Lauri Ojansivu 4a7bccd983 Updated ChangeLog. 2025-10-16 21:46:35 +03:00
Lauri Ojansivu 1f0cae9e76 Updated ChangeLog. 2025-10-16 21:42:41 +03:00
Lauri Ojansivu 87ae085e6d Fix Edit avatar UI issue.
Thanks to xet7 !

Fixes #5940
2025-10-16 21:40:49 +03:00
Lauri Ojansivu 640ac2330f Updated ChangeLog. 2025-10-16 20:24:46 +03:00
Lauri Ojansivu 2543df9425 Show original positions of swimlanes, lists and cards.
Thanks to xet7 !

Fixes #5939
2025-10-16 20:23:05 +03:00
Lauri Ojansivu 915ab47a72 v8.04 2025-10-16 17:59:24 +03:00
Lauri Ojansivu 09ff287da2 Updated translations. 2025-10-16 17:52:03 +03:00
Lauri Ojansivu 2896180f80 Updated ChangeLog. 2025-10-16 17:50:52 +03:00
Lauri Ojansivu 4283b5b0e3 Disable not working minio and s3 support temporarily.
Thanks to xet7 !
2025-10-16 17:49:39 +03:00
Lauri Ojansivu bbbd3abf06 Try to fix Broken Hyperlinks in Markdown to HTML conversion.
Thanks to xet7 !

Fixes #5932
2025-10-16 17:47:59 +03:00
Lauri Ojansivu dd88483ec7 Removed extra npm packages.
Thanks to xet7 !
2025-10-15 23:26:33 +03:00
Lauri Ojansivu 00ddec7575 Fix popups positioning. Part 2.
Thanks to xet7 !
2025-10-15 23:19:07 +03:00
Lauri Ojansivu ab0ebab240 Updated ChangeLog. 2025-10-15 07:57:18 +03:00
Lauri Ojansivu 79e83e33ec Use only MongoDB 7 at Snap.
Thanks to xet7 !
2025-10-15 07:56:11 +03:00
Lauri Ojansivu aa402d652d Updated ChangeLog. 2025-10-15 07:49:05 +03:00
Lauri Ojansivu 690481c138 Remove using fork with MongoDB at Snap.
Thanks to xet7 !
2025-10-15 07:47:57 +03:00
Lauri Ojansivu 881125aa98 Updated ChangeLog. 2025-10-15 07:46:54 +03:00
Lauri Ojansivu 77eea4d494 Fix popups positioning.
Thanks to xet7 !

Fixes #5924
2025-10-15 07:44:46 +03:00
Lauri Ojansivu b26e16abb8 Updated ChangeLog. 2025-10-15 07:39:18 +03:00
Lauri Ojansivu f08c7702ee Fix wide screen.
Thanks to xet7 !

Fixes #5915
2025-10-15 07:38:14 +03:00
Lauri Ojansivu a4399c7ef4 Updated translations. 2025-10-15 07:25:33 +03:00
Lauri Ojansivu d6e50ed9a0 Updated ChangeLog. 2025-10-15 07:21:15 +03:00
Lauri Ojansivu 6b848b318d Make sure that all cards are visible.
Thanks to xet7 !

Related #5915
2025-10-15 07:15:46 +03:00
Lauri Ojansivu 70ce70cf0e Try to fix Snap to not fork mongodb.
Thanks to xet7 !
2025-10-14 14:29:42 +03:00
Lauri Ojansivu 5a79bc5ee3 v8.03 2025-10-14 13:51:47 +03:00
Lauri Ojansivu 5792a86959 Fix Snap MongoDB to not fork at systemd, so it stays running.
Thanks to xet7 !
2025-10-14 13:45:51 +03:00
Lauri Ojansivu 37c5436087 v8.02 2025-10-14 11:58:14 +03:00
Lauri Ojansivu 6592102e8f v8.02 2025-10-14 11:56:11 +03:00
Lauri Ojansivu 06a5a8f70d Try to fix Docker secrets to be optional.
Thanks to xet7 !

Fixes #5920
2025-10-14 11:54:09 +03:00
Lauri Ojansivu ef54ebada6 Updated ChangeLog. 2025-10-14 11:25:36 +03:00
Lauri Ojansivu d4f13de1d9 Try to fix swimlane hamburger menu popup positioning. In progress.
Thanks to xet7 !
2025-10-14 11:24:22 +03:00
Lauri Ojansivu 4fcedde529 Updated ChangeLog. 2025-10-14 11:04:40 +03:00
Lauri Ojansivu a4518bbefc Fix drag drop reorder swimlanes.
Thanks to xet7 !
2025-10-14 11:03:36 +03:00
Lauri Ojansivu 95f771aa26 Updated ChangeLog. 2025-10-14 10:47:00 +03:00
Lauri Ojansivu da98942cce Updated mobile Bookmarks/Starred boards. Part 1. In Progress.
Thanks to xet7 !
2025-10-14 10:43:39 +03:00
Lauri Ojansivu f3efaf59e1 Updated translations. 2025-10-14 09:39:19 +03:00
Lauri Ojansivu d64032f2a3 Updated ChangeLog. 2025-10-14 09:37:49 +03:00
Lauri Ojansivu abad8cc4d5 Change list width by dragging between lists.
Thanks to xet7 !
2025-10-14 09:36:11 +03:00
Lauri Ojansivu 0d9536e2f9 Updated ChangeLog. 2025-10-14 08:26:46 +03:00
Lauri Ojansivu 67b078b805 Accessibility improvements.
Thanks to xet7 !
2025-10-14 08:25:39 +03:00
Lauri Ojansivu 6f02eeae53 Updated ChangeLog. 2025-10-14 07:24:46 +03:00
Lauri Ojansivu 5bc03b23ea Updated dependencies.
Thanks to developers of dependencies !
2025-10-14 07:23:13 +03:00
Lauri Ojansivu 3d4acd8c8f Updated translations. 2025-10-14 07:21:41 +03:00
Lauri Ojansivu 448bec8181 Updated ChangeLog. 2025-10-14 01:54:28 +03:00
Lauri Ojansivu 0a34ee1b64 Removed not needed console log message.
Thanks to xet7 !
2025-10-14 01:52:58 +03:00
Lauri Ojansivu 34e8e4d4c3 Updated translations. 2025-10-14 01:38:38 +03:00
Lauri Ojansivu 32627c03f4 Updated ChangeLog. 2025-10-14 01:32:43 +03:00
Lauri Ojansivu 63c314ca18 Fixed migrations.
Thanks to xet7 !
2025-10-14 01:30:59 +03:00
Lauri Ojansivu e8453783da Updated translations. 2025-10-13 23:48:30 +03:00
Lauri Ojansivu 96522ec3a3 Updated translations. 2025-10-13 23:38:17 +03:00
Lauri Ojansivu 17dedab391 Updated translations. 2025-10-13 23:37:02 +03:00
Lauri Ojansivu 283d2ee09c Updated translations. 2025-10-13 23:33:27 +03:00
Lauri Ojansivu 289ff0127e Updated translations. 2025-10-13 23:23:32 +03:00
Lauri Ojansivu 931d7217b1 Updated translations. 2025-10-13 23:04:24 +03:00
Lauri Ojansivu 3149a3927e Updated translations. 2025-10-13 22:25:41 +03:00
Lauri Ojansivu c57bced7b1 Updated translations. 2025-10-13 22:21:23 +03:00
Lauri Ojansivu 8d794a59dc Updated ChangeLog. 2025-10-13 22:19:56 +03:00
Lauri Ojansivu 7bb1e24bda Fixed Admin Panel Settings menus Attachments and Cron.
Thanks to xet7 !
2025-10-13 22:17:32 +03:00
Lauri Ojansivu e0013b9b63 Fix Admin Panel Settings menu to show options correctly. Part 1.
Thanks to xet7 !
2025-10-13 20:51:29 +03:00
Lauri Ojansivu 7d81aab900 Updated ChangeLog. 2025-10-13 20:35:49 +03:00
Lauri Ojansivu cc99da5357 Fixed Error in migrate-lists-to-per-swimlane migration.
Thanks to xet7 !

Fixes #5918
2025-10-13 20:34:23 +03:00
Lauri Ojansivu 9bd21e1d1b Updated ChangeLog. 2025-10-12 05:38:08 +03:00
Lauri Ojansivu 2148aeea42 Change Admin Panel "Attachment Settings" and "Cron Settings" options to be tabs, not submenu. Part 3.
Thanks to xet7 !
2025-10-12 05:36:51 +03:00
Lauri Ojansivu 6a7a5505f9 Updated ChangeLog. 2025-10-12 05:26:46 +03:00
Lauri Ojansivu 5a6faafa30 Change Admin Panel "Attachment Settings" and "Cron Settings" options to be tabs, not submenu. Part 2.
Thanks to xet7 !
2025-10-12 05:25:44 +03:00
Lauri Ojansivu e2f3dad779 Updated ChangeLog. 2025-10-12 04:52:45 +03:00
Lauri Ojansivu ae2aa1f5cd Change Admin Panel "Attachment Settings" and "Cron Settings" options to be tabs, not submenu.
Thanks to xet7 !
2025-10-12 04:50:17 +03:00
Lauri Ojansivu 0e3a17d922 Updated ChangeLog. 2025-10-12 04:40:27 +03:00
Lauri Ojansivu 033919a270 Fix Admin Panel menus "Attachment Settings" and "Cron Settings" and make them translateable.
Thanks to xet7 !
2025-10-12 04:39:04 +03:00
Lauri Ojansivu f5d40a0a12 Updated ChangeLog. 2025-10-12 04:22:35 +03:00
Lauri Ojansivu 0fd781e80a Fix opening sidebar.
Thanks to xet7 !
2025-10-12 04:21:38 +03:00
Lauri Ojansivu a8f6170fdf Updated ChangeLog. 2025-10-12 03:49:52 +03:00
Lauri Ojansivu bd8c565415 Fixes to make board showing correctly.
Thanks to xet7 !
2025-10-12 03:48:21 +03:00
Lauri Ojansivu ffb02fe0ec Updated translations. 2025-10-11 20:39:08 +03:00
Lauri Ojansivu 114520302c Updated ChangeLog. 2025-10-11 20:35:58 +03:00
Lauri Ojansivu 317138ab72 If there is no cron jobs running, run migrations for boards that have not been opened yet.
Thanks to xet7 !
2025-10-11 20:33:31 +03:00
Lauri Ojansivu a990109f43 Updated ChangeLog. 2025-10-11 19:43:20 +03:00
Lauri Ojansivu da68b01502 Added Cron Manager to Admin Panel for long running jobs, like running migrations when opening board, copying or moving boards swimlanes lists cards etc.
Thanks to xet7 !
2025-10-11 19:41:09 +03:00
Lauri Ojansivu e90bc744d9 Updated ChangeLog. 2025-10-11 19:26:07 +03:00
Lauri Ojansivu 2b5c56484a Run database migrations when opening board. Not when updating WeKan.
Thanks to xet7 !
2025-10-11 19:23:47 +03:00
Lauri Ojansivu ea30612013 v8.01 2025-10-11 12:32:38 +03:00
Lauri Ojansivu 4445612bb8 Updated translations. 2025-10-11 12:30:09 +03:00
Lauri Ojansivu da56393078 Updated ChangeLog. 2025-10-11 12:15:25 +03:00
Lauri Ojansivu a86ff1e8d0 Fixed attachments and minicard related bugs that prevented WeKan starting.
Thanks to xet7 !
2025-10-11 12:13:11 +03:00
Lauri Ojansivu 7fce5650b8 Updated ChangeLog. 2025-10-11 11:55:41 +03:00
Lauri Ojansivu b949357e66 Snap MongoDB Server 3-8 support. Part 1.
Thanks to xet7 !
2025-10-11 11:54:14 +03:00
Lauri Ojansivu 8e71b68dbf Updated ChangeLog. 2025-10-11 11:39:39 +03:00
Lauri Ojansivu b77eed221f Fix count of Orgs Teams People at Admin Panel.
Thanks to xet7 !
2025-10-11 11:38:28 +03:00
Lauri Ojansivu 1fa91474d0 Updated ChangeLog. 2025-10-11 11:34:45 +03:00
Lauri Ojansivu fc32a89292 Fixed per-card and per-board settings of showing checkist at minicard.
Thanks to xet7 !
2025-10-11 11:31:57 +03:00
Lauri Ojansivu ea24010298 Updated ChangeLog. 2025-10-11 11:08:27 +03:00
Lauri Ojansivu ae1f80a52c Added attachments API and admin panel attachment management for file storage backends settings. Fixed drag drop upload attachments from file manager to minicard or opened card.
Thanks to xet7 !
2025-10-11 11:05:46 +03:00
Lauri Ojansivu 2f95431c9b Updated ChangeLog. 2025-10-11 10:50:05 +03:00
Lauri Ojansivu d59683eff1 Fixed attachments migrations at Admin Panel to not use too much CPU while migrating attachments.
Thanks to xet7 !
2025-10-11 10:48:12 +03:00
Lauri Ojansivu de77776cd0 Updated ChangeLog. 2025-10-11 10:33:52 +03:00
Lauri Ojansivu 74ccfea570 Add support for MongoDB 3-8, detecting which one is in use.
Thanks to xet7 !
2025-10-11 10:32:20 +03:00
Lauri Ojansivu 1f5a549589 Updated ChangeLog. 2025-10-11 10:17:41 +03:00
Lauri Ojansivu 3ccdc2e307 Made possible to start WeKan immediately without running any database migrations.
Thanks to xet7 !
2025-10-11 10:15:08 +03:00
Lauri Ojansivu 688b725807 v8.00 2025-10-11 07:50:28 +03:00
Lauri Ojansivu ae01ea576c Snap: Migrate MongoDB from 3 to 7 only when "snap set wekan migrate-mongodb='true'". Not automatically.
Thanks to xet7 !
2025-10-11 07:42:51 +03:00
Lauri Ojansivu cd0cd64849 v7.99 2025-10-11 06:13:27 +03:00
Lauri Ojansivu aab671398c Improve automatic Snap upgrades.
Thanks to xet7 !
2025-10-11 05:52:41 +03:00
Lauri Ojansivu d64aeb25d5 Updated ChangeLog. 2025-10-11 04:57:24 +03:00
Lauri Ojansivu aa4fa83127 Updated ChangeLog. 2025-10-11 04:50:23 +03:00
Lauri Ojansivu 81c3dc1d95 Security Fix JVN#15385465: CWE-79 XSS, that affected WeKan 7.94.
Thanks to Sho Sugiyama and xet7 !
2025-10-11 04:47:17 +03:00
Lauri Ojansivu 746eecf3d8 Updated ChangeLog. 2025-10-11 04:25:26 +03:00
Lauri Ojansivu cd948fb576 Added missing metadata fields to snapcraft.yaml .
Thanks to xet7 !
2025-10-11 04:20:28 +03:00
Lauri Ojansivu 4ec4e19e63 Try to fix Snap automatic upgrade.
Thanks to xet7 !
2025-10-11 04:02:02 +03:00
Lauri Ojansivu d88d197de9 Updated release scripts of snapcraft pack command syntax.
Thanks to xet7 !
2025-10-11 03:11:32 +03:00
Lauri Ojansivu 2a24918a9c Snap: Removed double mongo3 that's already at migratemongo.
Thanks to xet7 !
2025-10-11 03:09:33 +03:00
Lauri Ojansivu d2a85b41e4 v7.98 2025-10-11 02:07:44 +03:00
Lauri Ojansivu 9bcd991a61 Updated ChangeLog. 2025-10-11 02:02:10 +03:00
Lauri Ojansivu 1a7bd65e59 Fixed showing translations always, regardsless of is ROOT_URL set correctly or not.
Thanks to xet7 !
2025-10-11 01:57:08 +03:00
Lauri Ojansivu f8ee929cf7 Updated ChangeLog. 2025-10-11 01:46:01 +03:00
Lauri Ojansivu 734165f3c7 Fix sizes of drag handles at desktop mode.
Thanks to xet7 !
2025-10-11 01:44:38 +03:00
Lauri Ojansivu 8f9ef1bde8 Updated ChangeLog. 2025-10-11 01:24:31 +03:00
Lauri Ojansivu 2119c6ab0c Fix DOMPurify paths. Part 4.
Thanks to xet7 !
2025-10-11 01:23:18 +03:00
Lauri Ojansivu a85479f73c Updated ChangeLog. 2025-10-11 01:09:58 +03:00
Lauri Ojansivu 21ba0a9606 Fix DOMPurify paths. Part 3.
Thanks to xet7 !
2025-10-11 01:08:39 +03:00
Lauri Ojansivu 82ba0f9593 Updated ChangeLog. 2025-10-11 01:03:22 +03:00
Lauri Ojansivu 7769124401 Fix DOMPurify paths. Part 2.
Thanks to xet7 !
2025-10-11 00:58:00 +03:00
Lauri Ojansivu d9c978e7fc Merge branch 'main' of github.com:wekan/wekan 2025-10-11 00:50:14 +03:00
Lauri Ojansivu 90899f0928 Fix DOMPurify paths.
Thanks to xet7 !
2025-10-11 00:49:43 +03:00
Lauri Ojansivu f0421da517 Updated translations. 2025-10-11 00:41:06 +03:00
Lauri Ojansivu 573d4bf2cb Updated ChangeLog. 2025-10-11 00:31:46 +03:00
Lauri Ojansivu f1e1fd3593 Add Snap automatic upgrades. Part 2.
Thanks to xet7 !
2025-10-11 00:29:46 +03:00
Lauri Ojansivu c387c5bc34 Updated ChangeLog. 2025-10-11 00:27:04 +03:00
Lauri Ojansivu 0549bc0b0c Add Snap automatic upgrades.
Thanks to xet7 !
2025-10-11 00:25:16 +03:00
Lauri Ojansivu c2a3e11324 Updated ChangeLog. 2025-10-10 23:50:49 +03:00
Lauri Ojansivu 107e2ac900 Add support for Docker/Compose Secrets for passwords to Docker/Snap/Bundle platforms.
Thanks to Roemer and xet7 !

Fixes #5724
2025-10-10 23:46:48 +03:00
Lauri Ojansivu 3b60bdea14 Updated ChangeLog. 2025-10-10 23:22:59 +03:00
Lauri Ojansivu f6591d7820 Security Fix usd-2022-0041: CWE-284 Improper Access Control.
Thanks to Christian Pöschl of usd AG and xet7 !
2025-10-10 23:19:58 +03:00
Lauri Ojansivu 6bbd622066 Updated ChangeLog. 2025-10-10 23:16:26 +03:00
Lauri Ojansivu ee79cab7b2 Security Fix JVN#86586539: Stored XSS.
Thanks to Ryoya Koyama of Mitsui Bussan Secure Directions, Inc and xet7.
2025-10-10 23:14:06 +03:00
Lauri Ojansivu a0b94065c5 Updated ChangeLog. 2025-10-10 23:09:28 +03:00
Lauri Ojansivu e1fa607f87 Security Fix JVN#74210258: Stored XSS.
Thanks to Ryoya Koyama of Mitsui Bussan Secure Directions, Inc and xet7 !
2025-10-10 23:06:06 +03:00
Lauri Ojansivu 2e91a359f5 Updated ChangeLog. 2025-10-10 23:02:37 +03:00
Lauri Ojansivu 9720e703fd Security Fix JVN#14269684: Broken access control.
Thanks to Ryoya Koyama of Mitsui Bussan Secure Directions, Inc and xet7 !
2025-10-10 22:59:20 +03:00
Lauri Ojansivu f88898d5b8 Updated ChangeLog. 2025-10-10 22:22:43 +03:00
Lauri Ojansivu 30c1597b65 Security Fix FG-VD-22-078: Prevent SVG Billion Laughs Attack.
Thanks to Nguyen Thanh Nguyen of Fortinet's FortiGuard Labs and xet7 !
2025-10-10 22:16:47 +03:00
Lauri Ojansivu 5bc5171220 Updated ChangeLog. 2025-10-10 22:12:25 +03:00
Lauri Ojansivu d0f118e7af Security Fix: Computational Resource Abuse in Export endpoints.
Thanks to Anynymous Security Researcher and xet7 !
2025-10-10 22:09:27 +03:00
Lauri Ojansivu c481443667 Updated ChangeLog. 2025-10-10 22:04:30 +03:00
Lauri Ojansivu b87cff1289 Security Fix: IDOR CWE-639 that affected WeKan 7.80-7.93.
Thanks to apitech.fr and xet7 !
2025-10-10 21:59:04 +03:00
Lauri Ojansivu 0c080830bc Updated ChangeLog. 2025-10-10 21:48:27 +03:00
Lauri Ojansivu cdd7d69c66 Drag any files from file manager to minicard or opened card. Part 2.
Thanks to xet7 !

Fixes #2936
2025-10-10 21:46:07 +03:00
Lauri Ojansivu c1cbcdcc72 Updated ChangeLog. 2025-10-10 21:21:12 +03:00
Lauri Ojansivu 1c84b19f24 Show console.log 'Legacy attachments route loaded' only when environment variable DEBUG=true.
Thanks to xet7 !
2025-10-10 21:19:00 +03:00
Lauri Ojansivu 2c73dae019 Merge branch 'main' of github.com:wekan/wekan 2025-10-10 21:16:29 +03:00
Lauri Ojansivu d285850a04 Updated ChangeLog. 2025-10-10 21:15:55 +03:00
Lauri Ojansivu 719ef87efc Make possible for lists to have different names at different swimlanes. Make possible to drag list from one swimlane to another swimlane.
Thanks to xet7 !
2025-10-10 21:14:44 +03:00
Lauri Ojansivu 039b9abf5e Updated translations 2025-10-10 20:37:22 +03:00
Lauri Ojansivu 39daf56811 Updated ChangeLog. 2025-10-10 19:12:09 +03:00
Lauri Ojansivu a8de2f224f Use attachments from old CollectionFS database structure, when not yet migrated to Meteor-Files/ostrio-files, without needing to migrate database structure.
Thanks to xet7 !
2025-10-10 19:07:04 +03:00
Lauri Ojansivu dda013844c Updated ChangeLog. 2025-10-10 18:55:13 +03:00
Lauri Ojansivu 3e9481c5bd Drag any files from file manager to minicard or opened card.
Thanks to xet7 !

Fixes #2936
2025-10-10 18:52:30 +03:00
Lauri Ojansivu 85ac03a892 Updated ChangeLog. 2025-10-09 05:54:04 +03:00
Lauri Ojansivu 752699d1c2 Mobile one board per row. Board zoom size percent. Board toggle mobile/desktop mode. In Progress.
Thanks to xet7 !

Related #5902
2025-10-09 05:48:41 +03:00
Lauri Ojansivu 339ca581ab Merge branch 'main' of github.com:wekan/wekan 2025-10-09 02:07:00 +03:00
Lauri Ojansivu 20e9cf1144 Text and icons from fixed sizes to scaleable sizes. Thanks to xet7. 2025-10-09 02:06:26 +03:00
Lauri Ojansivu 9737884c4b Text and icon from fixed sized to scaleable sizes. Thanks to xet7. 2025-10-09 02:06:16 +03:00
Lauri Ojansivu 2f5670e830 v7.97 2025-10-08 23:57:08 +03:00
Lauri Ojansivu 108b18cebf Updated ChangeLog. 2025-10-08 23:48:26 +03:00
Lauri Ojansivu 814ac22a0d Updated ChangeLog. 2025-10-08 23:43:55 +03:00
Lauri Ojansivu 3814a218c2 Removed white box that appeared when clicking something.
Thanks to xet7 !

Fixes #5899
2025-10-08 23:38:44 +03:00
Lauri Ojansivu 3fda90612d Some mobile fixes.
Thanks to xet7 !

Fixes #5899
2025-10-08 23:32:13 +03:00
Lauri Ojansivu 05762aa50c Updated ChangeLog. 2025-10-08 09:37:51 +03:00
Lauri Ojansivu 9a31371de0 Fixed translation of "Change Language" at login page.
Thanks to xet7 !
2025-10-08 09:36:39 +03:00
Lauri Ojansivu b9b7143185 Updated ChangeLog. 2025-10-08 09:21:26 +03:00
Lauri Ojansivu 972721bdb5 Merge branch 'seve12-main' 2025-10-08 09:17:53 +03:00
Lauri Ojansivu 3a48982916 v7.96 2025-10-08 08:44:09 +03:00
Lauri Ojansivu 6b3a6f9f7d Updated ChangeLog. 2025-10-08 08:44:09 +03:00
Lauri Ojansivu 48fddecb62 Updated to MongoDB 7.0.25 at Snap Candidate.
Thanks to MongoDB developers !
2025-10-08 08:44:09 +03:00
Lauri Ojansivu 7f0d1cc5b3 Updated ChangeLog. 2025-10-08 08:44:09 +03:00
Lauri Ojansivu 70e82be0b4 Hide extra keyboard shortcuts toggle. Thanks to xet7. 2025-10-08 08:44:09 +03:00
Lauri Ojansivu 5fc0809844 Updated ChangeLog. 2025-10-08 08:44:09 +03:00
Lauri Ojansivu c79990bcd9 Added missing screenshots from wekan.github.io history to wekan/docs/Features/ . Thanks to xet7 2025-10-08 08:44:09 +03:00
dependabot[bot] cd9e7412ea Bump docker/login-action from 3.5.0 to 3.6.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.5.0 to 3.6.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/184bdaa0721073962dff0199f1fb9940f07167d1...5e57cd118135c172c3672efd75eb46360885c0ef)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-08 08:44:09 +03:00
Lauri Ojansivu 3e472606b5 Fixed links at docs. Thanks to xet7 2025-10-08 08:44:08 +03:00
Lauri Ojansivu 2966bc3204 Updated translations 2025-10-08 08:44:08 +03:00
Lauri Ojansivu 5dc6093eec Updated email address 2025-10-08 08:44:08 +03:00
Lauri Ojansivu 86fc60f5ba Updated ChangeLog. 2025-10-08 08:44:08 +03:00
Lauri Ojansivu 8189a3f9ab Fixed find.sh script errors about unmatched single quotes and binary files that are now ignored.
Thanks to xet7 !
2025-10-08 08:44:08 +03:00
Lauri Ojansivu 76a65e2533 Updated ChangeLog. 2025-10-08 08:44:08 +03:00
Jean-Sébastien Guay ad2914934e Fix links in docker-compose.yml to point to doc in repo 2025-10-08 08:44:08 +03:00
Lauri Ojansivu 2769ba2874 Updated ChangeLog. 2025-10-08 08:44:08 +03:00
Lauri Ojansivu 294f1e178e Updated ChangeLog. 2025-10-08 08:44:08 +03:00
Lauri Ojansivu c994d34bb8 Updated to MongoDB 7.0.24 at Snap Candidate.
Thanks to MongoDB developers !
2025-10-08 08:44:08 +03:00
Lauri Ojansivu 42b5dd0751 Updated ChangeLog. 2025-10-08 08:44:08 +03:00
Lauri Ojansivu f884d6d471 Added missing mobile drag drop image to docs from wekan.github.io git history.
Thanks to xet7 !
2025-10-08 08:44:08 +03:00
Lauri Ojansivu abe915a8d6 Updated ChangeLog. 2025-10-08 08:44:08 +03:00
Lauri Ojansivu a2e8de2fd8 Updated release script for new command of uploading snap package to Snap Store.
Thanks to xet7 !
2025-10-08 08:44:08 +03:00
Lauri Ojansivu e34ca6a8a5 v7.95 2025-10-08 08:44:08 +03:00
Lauri Ojansivu 596c9de603 Updated translations 2025-10-08 08:44:08 +03:00
Lauri Ojansivu 4170361a1c Updated ChangeLog. 2025-10-08 08:44:08 +03:00
Lauri Ojansivu 978a36ea77 Updated dependencies.
Thanks to developers of dependencies !
2025-10-08 08:44:08 +03:00
Lauri Ojansivu 8745ef7de8 Reorganized docs 2025-10-08 08:44:08 +03:00
Lauri Ojansivu 2ce929b7b3 Updated ChangeLog. 2025-10-08 08:44:08 +03:00
Lauri Ojansivu 2dcdfa2862 Added PGP public key for sending encrypted email.
Thanks to xet7 !
2025-10-08 08:44:08 +03:00
Lauri Ojansivu b18daac9c9 Updated ChangeLog. 2025-10-08 08:44:08 +03:00
sowwos 606041a00d docs(windows): clarify WRITABLE_PATH/attachments + LAN bind instructions (nits) 2025-10-08 08:44:08 +03:00
Lauri Ojansivu 4ed5952a81 Updates 2025-10-08 08:44:08 +03:00
Lauri Ojansivu 37bfce2eb7 Added missing image to WeKan Multiverse page.
Thanks to xet7 !
2025-10-08 08:44:08 +03:00
Lauri Ojansivu 59541699ce Updated ChangeLog. 2025-10-08 08:44:08 +03:00
Lauri Ojansivu af73c16e37 More info about ToroDB, FerretDB, Turso, TigerBeetle, PostgreSQL, SQLite etc.
Thanks to xet7 !
2025-10-08 08:44:07 +03:00
Lauri Ojansivu 673b09b84b Updated ChangeLog. 2025-10-08 08:44:07 +03:00
Lauri Ojansivu 09f8edfead Fixed image link at docs.
Thanks to xet7 !
2025-10-08 08:44:07 +03:00
Lauri Ojansivu e1ebdeca4f Updated ChangeLog. 2025-10-08 08:44:07 +03:00
Lauri Ojansivu 31769aa4af Updated upgrade link.
Thanks to xet7 !
2025-10-08 08:44:07 +03:00
seve12 b32ad86ce7 fixed issue 5887 2025-10-07 22:23:19 +03:00
Lauri Ojansivu 7350473a65 v7.96 2025-10-07 01:14:42 +03:00
Lauri Ojansivu 1c87e337d6 Updated ChangeLog. 2025-10-07 01:06:21 +03:00
Lauri Ojansivu dec038e7ed Updated to MongoDB 7.0.25 at Snap Candidate.
Thanks to MongoDB developers !
2025-10-07 01:05:01 +03:00
Lauri Ojansivu ebe6f7813e Updated ChangeLog. 2025-10-07 00:45:05 +03:00
Lauri Ojansivu 706dbfacac Merge branch 'seve12-main' 2025-10-07 00:36:50 +03:00
Lauri Ojansivu 62ad588aa9 Hide extra keyboard shortcuts toggle. Thanks to xet7. 2025-10-07 00:35:17 +03:00
Lauri Ojansivu 3c776e2ab6 Merge branch 'main' of github.com:seve12/wekan into seve12-main 2025-10-07 00:19:46 +03:00
seve12 ab4e2d1ed6 fixed issue #5885 2025-10-06 16:34:49 +03:00
Lauri Ojansivu 5e90c4a142 Updated ChangeLog. 2025-10-02 20:15:35 +03:00
Lauri Ojansivu 9a7fa25ca3 Added missing screenshots from wekan.github.io history to wekan/docs/Features/ . Thanks to xet7 2025-10-02 20:05:38 +03:00
Lauri Ojansivu 65cb0641f3 Merge branch 'main' of github.com:wekan/wekan 2025-10-02 19:57:56 +03:00
Lauri Ojansivu 6f2b891214 Fixed links at docs. Thanks to xet7 2025-10-02 19:57:28 +03:00
Lauri Ojansivu 44d7ab83ec Merge pull request #5890 from wekan/dependabot/github_actions/docker/login-action-3.6.0
Bump docker/login-action from 3.5.0 to 3.6.0
2025-10-02 00:06:19 +03:00
dependabot[bot] b328cc3f6a Bump docker/login-action from 3.5.0 to 3.6.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.5.0 to 3.6.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/184bdaa0721073962dff0199f1fb9940f07167d1...5e57cd118135c172c3672efd75eb46360885c0ef)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-29 23:33:41 +00:00
seve12 836226a8d3 RICHER_CARD_COMMENT_EDITOR=true has no effect (no rich text toolbar shown) fixed! 2025-09-29 15:56:23 +03:00
Lauri Ojansivu 2083f6630a Updated translations 2025-09-28 00:42:45 +03:00
Lauri Ojansivu 25e1e9028f Updated email address 2025-09-28 00:38:04 +03:00
seve12 a5651c686f fix 22.9 Unable to download large PDF files attached to card issue fixed 2025-09-23 13:40:14 +03:00
Lauri Ojansivu 93f0e0b125 Updated ChangeLog. 2025-09-21 15:01:17 +03:00
Lauri Ojansivu 730bb6d166 Fixed find.sh script errors about unmatched single quotes and binary files that are now ignored.
Thanks to xet7 !
2025-09-21 14:59:00 +03:00
Lauri Ojansivu 855bac59bf Updated ChangeLog. 2025-09-19 23:46:32 +03:00
Lauri Ojansivu 870900b92a Merge pull request #5882 from Skylark13/main
Fix links in docker-compose.yml to point to doc in repo
2025-09-19 23:45:09 +03:00
Jean-Sébastien Guay a1d5802d36 Fix links in docker-compose.yml to point to doc in repo 2025-09-19 15:26:53 -04:00
Lauri Ojansivu 6e16fa3992 Updated ChangeLog. 2025-09-19 16:35:59 +03:00
Lauri Ojansivu ee3b49ce59 Updated ChangeLog. 2025-09-19 03:08:40 +03:00
Lauri Ojansivu 3253dd5010 Updated to MongoDB 7.0.24 at Snap Candidate.
Thanks to MongoDB developers !
2025-09-19 03:06:06 +03:00
Lauri Ojansivu a10d8e0438 Updated ChangeLog. 2025-09-19 01:56:18 +03:00
Lauri Ojansivu 5151814e98 Added missing mobile drag drop image to docs from wekan.github.io git history.
Thanks to xet7 !
2025-09-19 01:54:01 +03:00
Lauri Ojansivu 2e2a0f01f4 Updated ChangeLog. 2025-09-18 23:11:33 +03:00
Lauri Ojansivu dec9b82da0 Updated release script for new command of uploading snap package to Snap Store.
Thanks to xet7 !
2025-09-18 23:09:03 +03:00
Lauri Ojansivu 33f4db5871 v7.95 2025-09-18 21:59:50 +03:00
Lauri Ojansivu 9a086c0397 Updated translations 2025-09-18 21:43:38 +03:00
Lauri Ojansivu 013ddb8978 Updated ChangeLog. 2025-09-18 21:34:15 +03:00
Lauri Ojansivu c4161c5ce6 Updated dependencies.
Thanks to developers of dependencies !
2025-09-18 21:29:36 +03:00
seve12 392b8dd5b2 fix 17.9.2025 2025-09-17 22:34:46 +03:00
seve12 d19eb801f3 fixes 16.9.2025 2025-09-16 23:05:35 +03:00
seve12 ed416ca555 fixes 25.9 2025-09-15 19:31:18 +03:00
seve12 106009316f more fixes 2025-09-10 17:17:51 +03:00
seve12 cb10ad3a20 Fixed 9.2.4.6, 9.1.3.5 and 11.7 2025-09-10 13:04:37 +03:00
Lauri Ojansivu 2fad0099e4 Reorganized docs 2025-08-27 16:11:15 +03:00
Lauri Ojansivu 454416b0bd Updated ChangeLog. 2025-08-26 15:15:44 +03:00
Lauri Ojansivu dd80b7308b Added PGP public key for sending encrypted email.
Thanks to xet7 !
2025-08-26 15:14:19 +03:00
Lauri Ojansivu fe5c5f1bdc Updated ChangeLog. 2025-08-21 13:11:51 +03:00
Lauri Ojansivu 79074deb34 Merge pull request #5872 from sowwos/docs/windows-offline-clarify
This adds two actionable Windows notes to docs/Platforms/Propietary/Windows/Offline.md:  - Explain that WRITABLE_PATH and ATTACHMENTS_STORE_PATH must be set before start to avoid the attachments error   (“The 'path' argument must be of type string. Received undefined” from models/attachments.js). - Show how to expose the dev server on LAN on Windows (BIND_IP=0.0.0.0 + ROOT_URL=http://<LAN-IP>:4000).  Tested on Windows 11 with Meteor dev server. Follow-up to #5871 (closed as wrong file).
2025-08-21 13:07:59 +03:00
sowwos db0294c95e docs(windows): clarify WRITABLE_PATH/attachments + LAN bind instructions (nits) 2025-08-21 07:19:13 +03:00
Lauri Ojansivu 8f0fa88eac Updates 2025-08-20 03:20:49 +03:00
Lauri Ojansivu 946f7045e9 Added missing image to WeKan Multiverse page.
Thanks to xet7 !
2025-08-20 03:19:26 +03:00
Lauri Ojansivu dda8c90d44 Updated ChangeLog. 2025-08-20 02:26:20 +03:00
Lauri Ojansivu 701485d76a More info about ToroDB, FerretDB, Turso, TigerBeetle, PostgreSQL, SQLite etc.
Thanks to xet7 !
2025-08-20 02:24:38 +03:00
Lauri Ojansivu b25198b87e Updated ChangeLog. 2025-08-20 00:58:58 +03:00
Lauri Ojansivu ad7e3774c2 Fixed image link at docs.
Thanks to xet7 !
2025-08-20 00:57:40 +03:00
Lauri Ojansivu ee67dea96c Updated ChangeLog. 2025-08-20 00:43:26 +03:00
Lauri Ojansivu b3d3bf1593 Updated upgrade link.
Thanks to xet7 !
2025-08-20 00:41:22 +03:00
Lauri Ojansivu 53726fed24 Updated WeKan version to Dockerfile.
Thanks to xet7 !
2025-08-18 19:01:19 +03:00
Lauri Ojansivu 7d2ca2c811 v7.94 2025-08-18 18:54:35 +03:00
Lauri Ojansivu c0d10a0ec3 Updated ChangeLog. 2025-08-18 18:35:09 +03:00
Lauri Ojansivu 8e01e3c762 Upgraded to MongoDB 7.0.23 at Snap Candidate.
Thanks to MongoDB developers !
2025-08-18 18:30:21 +03:00
Lauri Ojansivu 04be599893 Updates 2025-08-18 18:15:51 +03:00
Lauri Ojansivu f6cb7fddf1 Updated ChangeLog. 2025-08-13 05:34:28 +03:00
Lauri Ojansivu a662283b7d Updates to design docs. 2025-08-13 05:32:26 +03:00
Lauri Ojansivu f34490fe28 Reorganized Design Docs 2025-08-13 05:30:35 +03:00
Lauri Ojansivu 1a98c86a37 Updated ChangeLog. 2025-08-12 16:55:06 +03:00
Lauri Ojansivu 97b5931a4f Updated dependencies.
Thanks to developers of dependencies !
2025-08-12 16:53:34 +03:00
Lauri Ojansivu 2a4dd9b693 Updated ChangeLog. 2025-08-12 16:04:41 +03:00
Lauri Ojansivu ecf31c3675 Updated MongoDB to 7.0.22.
Thanks to MongoDB developers !
2025-08-12 16:03:28 +03:00
Lauri Ojansivu 9b0d0a2b7c Updated ChangeLog. 2025-08-12 15:59:18 +03:00
Lauri Ojansivu e2ffca2e39 Merge pull request #5865 from wekan/dependabot/github_actions/actions/download-artifact-5
Bump actions/download-artifact from 4 to 5
2025-08-12 15:58:29 +03:00
Lauri Ojansivu a8435879cf Updated translations 2025-08-12 15:56:52 +03:00
Lauri Ojansivu e16695585a Updated ChangeLog. 2025-08-12 15:03:27 +03:00
Lauri Ojansivu a7bcfc6db7 Added docs for Windows SSL/TLS. Part 6.
Thanks to xet7 !

Related #5866
2025-08-12 15:01:56 +03:00
Lauri Ojansivu 21bed98d69 Updated ChangeLog. 2025-08-12 15:00:23 +03:00
Lauri Ojansivu 044793cc44 Added docs for Windows SSL/TLS. Part 5.
Thanks to xet7 !

Related #5866
2025-08-12 14:59:20 +03:00
Lauri Ojansivu 083a7050b9 Updated ChangeLog. 2025-08-12 14:55:13 +03:00
Lauri Ojansivu cc78e19740 Added docs for Windows SSL/TLS. Part 4.
Thanks to xet7 !

Related #5866
2025-08-12 14:53:09 +03:00
Lauri Ojansivu ee01a07b0a Added docs for Windows SSL/TLS. Part 3.
Thanks to xet7 !

Related #5866
2025-08-12 14:44:34 +03:00
Lauri Ojansivu ce528b1826 Added docs for Windows SSL/TLS. Part 2.
Thanks to xet7 !

Related #5866
2025-08-12 14:40:33 +03:00
Lauri Ojansivu 92c0543614 Added docs for Windows SSL/TLS.
Thanks to xet7 !

Related #5866
2025-08-12 14:33:55 +03:00
dependabot[bot] b6638f4a29 Bump actions/download-artifact from 4 to 5
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 5.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 07:34:46 +00:00
Lauri Ojansivu 96be56a620 Merge pull request #5864 from wekan/dependabot/github_actions/actions/checkout-5
Bump actions/checkout from 4 to 5
2025-08-12 10:33:51 +03:00
dependabot[bot] 2e555e420a Bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-12 03:02:57 +00:00
Lauri Ojansivu a849951c53 Update LXD docs 2025-08-09 23:03:09 +03:00
Lauri Ojansivu 435cb04717 Update PWA docs 2025-08-09 21:14:30 +03:00
Lauri Ojansivu 25a375261e Move docs PWA to Browsers directory 2025-08-09 21:09:27 +03:00
Lauri Ojansivu 1a1a44616f Revert accessibility changes temporarily.
Thanks to xet7 !

Related https://github.com/wekan/wekan/pull/5857
2025-08-09 14:23:40 +03:00
Lauri Ojansivu e6347a305c Updated ChangeLog. 2025-08-09 12:00:39 +03:00
Lauri Ojansivu b137108ae1 Fixed uppercase K of WeKan.
Thanks to xet7 !

Related https://github.com/wekan/wekan/pull/5857
2025-08-09 11:55:50 +03:00
Lauri Ojansivu 4ce7ff7cef Merge pull request #5857 from seve12/main
Accessibility:
- Added product name to page titles, settings and global search.
- More accessible header and layout templates, modal dialogs, DOM structure, color contrast.
2025-08-09 11:33:50 +03:00
seve12 184415a026 Enhance color contrast and accessibility for various UI elements 2025-08-09 04:29:30 +03:00
Lauri Ojansivu 3977adcb8f Updated translations 2025-08-08 12:38:15 +03:00
Lauri Ojansivu 1c47f98f59 Feature: Added brute force login protection settings to Admin Panel/People/Locked Users. Part 2.
Thanks to xet7 !
2025-08-08 12:36:17 +03:00
seve12 9f0c37352e Enhance accessibility for modal dialogs and improve DOM structure 2025-08-07 23:59:34 +03:00
seve12 7a20fa4805 Merge branch 'main' of https://github.com/seve12/wekan 2025-08-06 19:29:25 +03:00
seve12 6a713d9e8d Refactor header and layout templates for improved accessibility and consistency 2025-08-06 19:18:01 +03:00
Lauri Ojansivu 51b0544752 Updated translations. 2025-08-06 08:17:02 +03:00
seve12 1022a7aa3a Update settingBody.js 2025-08-05 20:23:52 +03:00
seve12 c048678844 Update router.js 2025-08-05 20:22:09 +03:00
seve12 f9fb02457c Merge branch 'main' of https://github.com/seve12/wekan 2025-08-05 20:07:50 +03:00
seve12 ce419ce64e Merge pull request #1 from seve12/severi
Improves accessibility by disabling custom Tab handling
2025-08-05 20:05:39 +03:00
severi12345 ecb6ff57fc Update page titles to include product name in settings and global search 2025-08-05 19:59:33 +03:00
Lauri Ojansivu 663866562f Updated ChangeLog. 2025-08-05 12:57:00 +03:00
Lauri Ojansivu dfdd0b6cbb Added info about Sandstorm Radicale Calendar and Contacts.
Thanks to xet7 !
2025-08-05 12:55:29 +03:00
Lauri Ojansivu c81ca82822 Updated translations. 2025-08-05 12:02:36 +03:00
Lauri Ojansivu 66b067eb49 Updated translations. 2025-08-05 11:26:23 +03:00
Lauri Ojansivu 4e9f2dc46a Updated ChangeLog. 2025-08-05 11:24:34 +03:00
Lauri Ojansivu f02d8b61b5 Merge pull request #5856 from wekan/dependabot/github_actions/docker/login-action-3.5.0
Bump docker/login-action from 3.4.0 to 3.5.0
2025-08-05 11:18:16 +03:00
Lauri Ojansivu 088de1877c Merge pull request #5855 from wekan/dependabot/github_actions/docker/metadata-action-5.8.0
Bump docker/metadata-action from 5.7.0 to 5.8.0
2025-08-05 11:17:59 +03:00
dependabot[bot] 4611abffed Bump docker/login-action from 3.4.0 to 3.5.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.4.0 to 3.5.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/74a5d142397b4f367a81961eba4e8cd7edddf772...184bdaa0721073962dff0199f1fb9940f07167d1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-05 03:24:07 +00:00
dependabot[bot] f206f2f45c Bump docker/metadata-action from 5.7.0 to 5.8.0
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5.7.0 to 5.8.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/902fa8ec7d6ecbf8d84d538b9b233a880e428804...c1e51972afc2121e065aed6d45c65596fe445f3f)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-05 03:17:28 +00:00
Lauri Ojansivu 9c5f5757be Updated translations. 2025-08-05 00:50:29 +03:00
Lauri Ojansivu ec2e0f6fed Updated ChangeLog. 2025-08-05 00:33:46 +03:00
Lauri Ojansivu ae0d059b6f Feature: Added brute force login protection settings to Admin Panel/People/Locked Users.
Added filtering of Admin Panel/People/People: All Users/Locked Users Only/Active/Not Active.
Added visual indicators: red lock icon for locked users, green check for active users, and red X for inactive users.
Added "Unlock All" button to quickly unlock all brute force locked users.
Added ability to toggle user active status directly from the People page.
Moved lockout settings from environment variables to database so admins can configure the lockout thresholds directly in the UI.

Thanks to xet7.
2025-08-05 00:31:43 +03:00
Lauri Ojansivu 0132b801b2 Updated translations. 2025-08-04 21:34:33 +03:00
Lauri Ojansivu 06ca564ec2 Updated ChangeLog. 2025-08-04 21:26:51 +03:00
Lauri Ojansivu d83ce5e633 Feature: Accessibility page at /accessibility. Settings at Admin Panel. When enabled, link at right sidebar.
Thanks to xet7 !
2025-08-04 21:22:14 +03:00
Lauri Ojansivu 5287319fde Updated translations. 2025-08-04 19:53:31 +03:00
Lauri Ojansivu c75a23e693 Updated ChangeLog. 2025-08-04 19:49:59 +03:00
Lauri Ojansivu 180bf930c1 Updated translations. 2025-08-04 19:46:24 +03:00
Lauri Ojansivu c16e99ba1e Added FerretDB/PostgreSQL to changelog. 2025-08-04 19:21:16 +03:00
Lauri Ojansivu b9887fc917 Fixed link at OpenShift docs.
Thanks to xet7 !

Related #5854
2025-08-04 19:17:17 +03:00
Lauri Ojansivu 96b36bd8ef Updated ChangeLog. 2025-08-04 18:24:27 +03:00
Lauri Ojansivu 0e32f666eb Comment out not used Tab code at accessibility related changes.
Thanks to xet7 !

Related https://github.com/wekan/wekan/pull/5853
2025-08-04 18:17:24 +03:00
Lauri Ojansivu 1341fc6a98 Merge pull request #5853 from seve12/severi
Improves accessibility by disabling custom Tab handling
2025-08-04 18:12:46 +03:00
seve12 81c9e96bf3 Improves accessibility by disabling custom Tab handling
Comments out custom Tab key behavior to allow default browser focus movement, enhancing accessibility for keyboard users and assistive technologies.
2025-08-04 17:45:53 +03:00
Lauri Ojansivu 20f91ef893 Updated ChangeLog. 2025-08-01 02:29:49 +03:00
Lauri Ojansivu 1187254573 Updated security email address.
Thanks to xet7 !
2025-08-01 02:28:15 +03:00
Lauri Ojansivu f22fb4f4a9 Updated translations 2025-07-28 23:28:36 +03:00
Lauri Ojansivu a14762e005 Updated ChangeLog. 2025-07-19 20:16:31 +03:00
Lauri Ojansivu 9a91214043 Update CHANGELOG.md 2025-07-19 05:14:56 +03:00
Lauri Ojansivu 30795a46cf Update CHANGELOG.md 2025-07-19 05:10:55 +03:00
Lauri Ojansivu b0865c8d06 Update CHANGELOG.md 2025-07-19 05:03:37 +03:00
Lauri Ojansivu c2aabbc6db Update CHANGELOG.md 2025-07-19 05:02:01 +03:00
Lauri Ojansivu 4aad3dafd4 Update CHANGELOG.md 2025-07-19 04:58:25 +03:00
Lauri Ojansivu 62269a9902 Update CHANGELOG.md 2025-07-19 04:54:59 +03:00
Lauri Ojansivu 4bc2fd4a7f Update CHANGELOG.md 2025-07-19 04:52:48 +03:00
Lauri Ojansivu 0e43a08689 Update CHANGELOG.md platforms info at top. Thanks to xet7! 2025-07-19 04:51:15 +03:00
Lauri Ojansivu 77b1416413 Updated ChangeLog. 2025-07-19 01:29:59 +03:00
Lauri Ojansivu 200545822c Fixes for building at Mac M1 Air.
Thanks to xet7 !
2025-07-18 22:31:17 +03:00
Lauri Ojansivu ac33f2164f Removed not needed files. 2025-07-18 19:04:25 +03:00
Lauri Ojansivu e82ca8a1f4 Updated ChangeLog. 2025-07-18 15:19:57 +03:00
Lauri Ojansivu f894f9ef6d Fixed building Docker image. Pushed new Docker image to Docker Hub, Quay and GitHub.
Thanks to xet7 !

Fixes #5835
2025-07-18 15:17:36 +03:00
Lauri Ojansivu ccfd8ef566 Updated ChangeLog. 2025-07-18 08:55:51 +03:00
Lauri Ojansivu 6c6b66c802 Updated release website script.
Thanks to xet7 !
2025-07-18 08:53:41 +03:00
Lauri Ojansivu 2cb2e7cb10 v7.93 2025-07-18 08:30:28 +03:00
Lauri Ojansivu 8744f47a0e Updated ChangeLog. 2025-07-17 01:25:27 +03:00
Lauri Ojansivu b742b25f96 Merge pull request #5832 from Atry/edit_list
Add REST API endpoint to edit a list with various properties
2025-07-17 01:21:06 +03:00
Yang, Bo 6d7c20f397 Add API endpoint to edit a list with various properties 2025-07-16 22:09:58 +00:00
Lauri Ojansivu f3d384c1dd Updated ChangeLog. 2025-07-17 00:52:34 +03:00
Lauri Ojansivu 69ce79a7b9 Fixed Snap local build script.
Thanks to xet7 !
2025-07-17 00:50:18 +03:00
Lauri Ojansivu 1cd191f76b Updated ChangeLog. 2025-07-16 01:10:59 +03:00
Lauri Ojansivu afc3c29341 Merge pull request #5829 from Atry/get_card_by_id
Avoid calling `ReactiveCache.getCard` twice
2025-07-16 01:08:27 +03:00
Lauri Ojansivu fedb3040d1 Updated ChangeLog. 2025-07-16 00:59:16 +03:00
Yang, Bo 0a969c59d3 Avoid calling ReactiveCache.getCard twice 2025-07-15 14:58:21 -07:00
Lauri Ojansivu d9fb9f49fd Merge pull request #5828 from Atry/get-card-by-id
Add a REST API endpoint to retrieve a card by ID
2025-07-16 00:53:42 +03:00
Yang, Bo fc77d0d201 Add REST API endpoint to retrieve a card by ID 2025-07-15 21:40:43 +00:00
Lauri Ojansivu 56700d1d06 Updated ChangeLog. 2025-07-10 05:22:38 +03:00
Lauri Ojansivu 84259c5116 Merge pull request #5826 from Atry/rest-api-custom-field-return-type
fix: correct return type in API documentation for custom fields
2025-07-09 19:14:13 +03:00
Yang, Bo 6cc746df8b fix: correct return type in API documentation for custom fields 2025-07-09 15:43:20 +00:00
Lauri Ojansivu b3ad06692f Updated ChangeLog. 2025-07-08 14:18:54 +03:00
Lauri Ojansivu bb0814bb11 Update Python.md 2025-07-08 14:16:49 +03:00
Lauri Ojansivu 722faf8aaa Updated ChangeLog. 2025-07-07 23:06:17 +03:00
Lauri Ojansivu 159324858b Fixed release script of building Snap locally at Ubuntu.
Thanks to xet7 !
2025-07-07 23:03:36 +03:00
Lauri Ojansivu f73a370719 Updated ChangeLog. 2025-07-07 22:42:12 +03:00
Lauri Ojansivu dfc658b910 Added release script to switch between using kvm, snapcraft.io/multipass, waydroid and virtualbox.
Thanks to xet7 !
2025-07-07 22:41:15 +03:00
Lauri Ojansivu 0a52f470b7 Updated ChangeLog. 2025-07-07 22:19:44 +03:00
Lauri Ojansivu e4822d8bc4 Updated issue template about pull requests for fixes to docs.
Thanks to xet7 !
2025-07-07 22:17:57 +03:00
Lauri Ojansivu 9d1cab3ac2 Updated ChangeLog. 2025-07-07 22:15:18 +03:00
Lauri Ojansivu 6d16ec238a Updated image links at Azure docs 2025-07-07 20:29:48 +03:00
Lauri Ojansivu e0284f225b Updated Keycloak link 2025-07-07 20:25:59 +03:00
Lauri Ojansivu a5acd0801a Updated Azure link 2025-07-07 20:24:34 +03:00
Lauri Ojansivu d59337ae47 Updated docs 2025-07-07 19:44:24 +03:00
Lauri Ojansivu 2f27a3f839 Updated docs about migrating manually. Part 2. 2025-07-07 19:27:54 +03:00
Lauri Ojansivu 450b33f98a Updated docs about migrating manually 2025-07-07 19:26:43 +03:00
Lauri Ojansivu 9adf5027fa Updated docs about importing from Asana 2025-07-07 19:21:27 +03:00
Lauri Ojansivu 1610b84845 Updated docs about importing from Trello. Part 3. 2025-07-07 19:18:29 +03:00
Lauri Ojansivu 40b2cf88fc Updated docs about importing from Trello. Part 2. 2025-07-07 19:17:41 +03:00
Lauri Ojansivu 55ef6c0c56 Updated docs about importing from Trello 2025-07-07 19:15:51 +03:00
Lauri Ojansivu a9c67ac200 Fix docs link about CSV. Part 2. 2025-07-07 19:09:57 +03:00
Lauri Ojansivu 3892be47ac Fix docs link about CSV 2025-07-07 19:07:48 +03:00
Lauri Ojansivu a2654b2e29 Removed not working link from docs 2025-07-07 19:05:49 +03:00
Lauri Ojansivu a052cc8fa5 Updated docs about Kubernetes 2025-07-07 19:04:05 +03:00
Lauri Ojansivu 5d09100cb6 Update info about hashing 2025-07-07 19:00:10 +03:00
Lauri Ojansivu 76769d1b85 Fixed not working link 2025-07-07 18:56:34 +03:00
Lauri Ojansivu 2cf57250e8 Fix link to image at docs 2025-07-07 18:54:56 +03:00
Lauri Ojansivu 5d0a0c931f Removed old link Test Edge from docs index. 2025-07-07 18:50:56 +03:00
Lauri Ojansivu fa1adfec76 Formatting 2025-07-07 18:43:26 +03:00
Lauri Ojansivu 9271b07383 Fix link to image about PWA app icon install.
Thanks to stevenroose and xet7 !

Fixes #5824
2025-07-07 18:40:52 +03:00
Lauri Ojansivu 4fe955650a Added most often used links 2025-07-07 18:36:39 +03:00
Lauri Ojansivu 6b401000b9 Updated Snap platforms setting. 2025-07-05 20:11:13 +03:00
Lauri Ojansivu aa681cde88 Updated building Snap 2025-07-05 16:59:33 +03:00
Lauri Ojansivu 0921c6ea53 Updated ChangeLog. 2025-07-05 16:09:33 +03:00
Lauri Ojansivu 2df73bedee Updated rebuild-wekan.sh install deps for macOS. Part 2. 2025-07-05 15:30:12 +03:00
Lauri Ojansivu 938fe49b2c Update rebuild-wekan.sh install deps for macOS 2025-07-05 15:27:11 +03:00
Lauri Ojansivu 7e2e649a8e Updated ChangeLog. 2025-07-05 14:16:02 +03:00
Lauri Ojansivu 6e11aa27f8 Reorganized docs to fix building WeKan 2025-07-04 04:08:41 +03:00
Lauri Ojansivu 57ee624398 Updated issue template. 2025-07-04 03:18:44 +03:00
Lauri Ojansivu 3cba95b60a Updated issue template. 2025-07-01 19:08:43 +03:00
Lauri Ojansivu bd796ce642 Updated links at readme 2025-06-28 14:22:01 +03:00
Lauri Ojansivu 3a8473e632 Fix image link 2025-06-28 14:10:41 +03:00
Lauri Ojansivu 89480b61ce wekan.github.io to wekan.fi, and blog.wekan.team to wekan.fi/blog 2025-06-28 14:08:57 +03:00
Lauri Ojansivu c5e5bb613e Moved images from wekan.github.io to docs 2025-06-28 11:50:55 +03:00
Lauri Ojansivu b52d1ca6c4 wekan.team to wekan.fi 2025-06-28 10:59:38 +03:00
Lauri Ojansivu 790bafbb09 wekan.team to wekan.fi 2025-06-27 21:37:17 +03:00
Lauri Ojansivu c256ce6968 Update Browser-compatibility-matrix.md about Servo 2025-06-26 20:29:48 +03:00
Lauri Ojansivu 85465e0dfb Updated ChangeLog. 2025-06-26 05:45:11 +03:00
Lauri Ojansivu b7409c1d0b Removed old broken snap files.
Thanks to xet7 !
2025-06-26 05:43:30 +03:00
Lauri Ojansivu 4060d33bbc Updated ChangeLog. 2025-06-26 05:42:34 +03:00
Lauri Ojansivu 6526d99123 Upgraded Snap Candidate to have base core24, MongoDB 7 and Caddy 2.
Thanks to xet7 !
2025-06-26 05:39:37 +03:00
Lauri Ojansivu 1b44c848ad Updated ChangeLog. 2025-06-26 03:47:57 +03:00
Lauri Ojansivu e6980be061 Updated dependencies.
Thanks to developers of dependencies !
2025-06-26 03:46:39 +03:00
Lauri Ojansivu 81c77650b4 Updated translations. 2025-06-26 03:08:38 +03:00
Lauri Ojansivu 791e37dd9c Updated ChangeLog. 2025-06-26 03:01:59 +03:00
Lauri Ojansivu f926448bbb Added docs about using MongoDB at unsupported CPUs with Qemu.
Thanks to xet7 !

Related #4321,
related #5814
2025-06-26 03:00:04 +03:00
Lauri Ojansivu 7128f067d0 Updated ChangeLog. 2025-06-26 02:48:12 +03:00
Lauri Ojansivu 59ab6c1cd8 Snap Candidate: If CPU does not support AVX, use Qemu that supports AVX, so that MongoDB 6 works. Part 3.
Thanks to xet7 !

Fixes #4321
2025-06-26 02:41:11 +03:00
Lauri Ojansivu 89105b6e72 Updated ChangeLog. 2025-06-26 01:20:41 +03:00
Lauri Ojansivu 51e43f1c25 Specify meteor version, otherwise the latest meteor version is not compatible with node14. Part 2.
Thanks to xet7 !
2025-06-26 01:19:06 +03:00
Lauri Ojansivu 9b24d644a4 Merge pull request #5815 from Atry/board-rest-api-doc
feat: enhance API documentation and add board-related endpoints
2025-06-26 01:09:42 +03:00
Lauri Ojansivu 2549e0e4c8 Merge pull request #5816 from dassio/main
specify meteor version, otherwise the lastes meteor version is not compatible with node14
2025-06-26 01:08:43 +03:00
xiangbin.li d974fa638c specify meteor version, otherwise the lastes version is not compatiable with node_14 2025-06-24 03:36:20 +00:00
Yang, Bo f399b5119f feat: enhance API documentation and add board-related endpoints
Previously, boards.js contains a `?.` operator, which is not supported by esprima, therefore boards.js is skipped.

This PR fixes the API generation for borads.js by removing `?.`.

See https://github.com/jquery/esprima/issues/2112
2025-06-23 20:01:38 +00:00
Lauri Ojansivu 12d3fabdb7 Updates 2025-06-18 17:12:53 +03:00
Lauri Ojansivu d5727f5b15 Updated github issue template 2025-06-18 17:08:49 +03:00
Lauri Ojansivu 1908d04fd4 Updates 2025-06-18 17:04:59 +03:00
Lauri Ojansivu c7f8c828cb Updates 2025-06-18 17:01:38 +03:00
Lauri Ojansivu 054d0913fd Updates 2025-06-18 16:56:37 +03:00
Lauri Ojansivu ca7dc69c85 Fix links and add more info 2025-06-18 16:53:29 +03:00
Lauri Ojansivu b2ff51d99c Updates 2025-06-18 16:48:33 +03:00
Lauri Ojansivu abd4b6bd87 Updates 2025-06-18 16:45:04 +03:00
Lauri Ojansivu b92d07c9a6 Updated translations 2025-06-18 11:30:37 +03:00
Lauri Ojansivu c6204417a3 Updated translations 2025-06-18 11:27:23 +03:00
Lauri Ojansivu 3a23401443 Updated ChangeLog. 2025-06-18 11:07:15 +03:00
Lauri Ojansivu 12e6e46924 Update Browser-compatibility-matrix.md 2025-06-17 00:19:33 +03:00
Lauri Ojansivu be1b959519 Update Browser-compatibility-matrix.md 2025-06-17 00:18:04 +03:00
Lauri Ojansivu 7841c9e801 Update Browser-compatibility-matrix.md 2025-06-17 00:15:23 +03:00
Lauri Ojansivu bc486e2a7d Updated ChangeLog. 2025-06-16 14:58:15 +03:00
Lauri Ojansivu d01885e2ad Updated ChangeLog. 2025-06-16 14:47:50 +03:00
Lauri Ojansivu 358ffe0601 Merge pull request #5805 from walster001/main
CleanDark Theme Fixes
2025-06-16 14:45:19 +03:00
walster001 8d0bf1b758 Fix invisible scrollbar on CleanDark cards
Found that scrollbars were blending in with the card background largely due to transparency on the main board.
2025-06-16 00:39:10 +09:30
walster001 c7961cf2f4 Fix spacing 2025-06-16 00:22:20 +09:30
walster001 2ac374e578 Fix font colour contrast CleanDark
Fixes the visibility of card titles when card is opened for CleanDark theme as was inheriting black title from CleanLight theme. 
Title now is white to match and heavily contrast theme.
2025-06-16 00:18:50 +09:30
Lauri Ojansivu ad3306e19e Update Browser-compatibility-matrix.md 2025-06-14 22:37:10 +03:00
Lauri Ojansivu f54cbac370 Updated docs 2025-06-13 23:05:01 +03:00
Lauri Ojansivu 4bfc0140be Updated docs links 2025-06-13 22:36:59 +03:00
Lauri Ojansivu cc7459b983 Updated docs: Fixed links 2025-06-13 22:15:36 +03:00
Lauri Ojansivu 11415cd517 Updated Docs: Updated deep dive link 2025-06-13 21:45:38 +03:00
Lauri Ojansivu 9aa028f32d Updates 2025-06-13 21:39:46 +03:00
Lauri Ojansivu 2a415cbd26 Updated docs: Moved content from Home.md to README.md 2025-06-13 21:38:12 +03:00
Lauri Ojansivu 40a52da2d3 Updated docs: Removed footer 2025-06-13 21:34:45 +03:00
Lauri Ojansivu 31e043c1d7 Updated docs 2025-06-13 21:32:36 +03:00
Lauri Ojansivu 985afafed0 Snap Candidate back to MongoDB 6.0.24 2025-06-12 21:22:16 +03:00
Lauri Ojansivu 3a4d20ecb2 Updates 2025-06-12 20:28:52 +03:00
Lauri Ojansivu 35b524f179 Updated ChangeLog. 2025-06-12 20:15:19 +03:00
Lauri Ojansivu d2f17ef140 Upgrade to MongoDB 7.0.21 2025-06-12 20:13:49 +03:00
Lauri Ojansivu d99864ca95 v7.92 2025-06-12 19:30:59 +03:00
Lauri Ojansivu e6e1191f8a Updated ChangeLog. 2025-06-12 15:50:06 +03:00
Lauri Ojansivu 1f10c20874 Updated dependencies.
Thanks to developers of dependencies !
2025-06-12 15:48:55 +03:00
Lauri Ojansivu c699f7a940 Updated ChangeLog. 2025-06-12 15:47:50 +03:00
Lauri Ojansivu 7115c292c7 Updated dependencies.
Thanks to developers of dependencies !
2025-06-12 15:46:44 +03:00
Lauri Ojansivu 7744b14b48 Updated ChangeLog. 2025-06-12 15:44:50 +03:00
Lauri Ojansivu 6fed9e73d8 Merge pull request #5801 from Atry/fix-login-openapi
fix: properly document `/users/login`
2025-06-12 15:43:09 +03:00
Yang, Bo e14a3f9c2d fix: properly document /users/login 2025-06-12 02:18:19 +00:00
Lauri Ojansivu 7bf75b1ff7 Updated ChangeLog. 2025-06-11 19:54:05 +03:00
Lauri Ojansivu 6905e24199 Updated to MongoDB 6.0.24 at Snap Candidate.
Thanks to MongoDB developers!
2025-06-11 19:52:13 +03:00
Lauri Ojansivu 9eb25c09da Updated ChangeLog. 2025-06-06 03:58:51 +03:00
Lauri Ojansivu c97f5ffca3 Updated ChangeLog. 2025-06-03 01:11:26 +03:00
Lauri Ojansivu eb19168b5f Merge pull request #5795 from wekan/dependabot/github_actions/docker/build-push-action-6.18.0
Bump docker/build-push-action from 6.17.0 to 6.18.0
2025-06-03 01:09:31 +03:00
dependabot[bot] e421b68766 Bump docker/build-push-action from 6.17.0 to 6.18.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.17.0 to 6.18.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/1dc73863535b631f98b2378be8619f83b136f4a0...263435318d21b8e681c14492fe198d362a7d2c83)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-02 20:16:16 +00:00
Lauri Ojansivu 6b86625c56 Updated ChangeLog. 2025-06-02 19:15:46 +03:00
Lauri Ojansivu 254b6dd5c7 Merge pull request #5793 from walster001/main
Allow selection of card text via webkit/user-select attributes
2025-06-02 19:13:18 +03:00
walster001 7b545d12c7 Allow selection of card text via webkit/user-select attributes
Fixes open issue around webkit/user-select attributes. Modifies values to allow "text" values to be selected in cards and on the board.
2025-06-02 22:03:16 +09:30
Lauri Ojansivu 01ed20b400 v7.91 2025-05-25 01:05:10 +03:00
Lauri Ojansivu fad930833a Updated translations. 2025-05-25 00:31:15 +03:00
Lauri Ojansivu 9192fbee44 Updated ChangeLog. 2025-05-25 00:30:11 +03:00
Lauri Ojansivu aaa5f9885d Reverted https://github.com/wekan/wekan/pull/5774 about due date changes.
Thanks to xet7 !

Fixes #5782
2025-05-25 00:22:47 +03:00
Lauri Ojansivu 1979b1692d Reverted date format changes.
Thanks to xet7 !
2025-05-25 00:11:09 +03:00
valhalla-creator fc31eeca88 Update cardDate.js 2025-05-22 21:20:20 +01:00
valhalla-creator 6d4de76a9a Update datepicker.js 2025-05-22 21:19:39 +01:00
valhalla-creator 9245ba4a5b Update utils.js 2025-05-22 21:18:52 +01:00
valhalla-creator 973a788c21 Update datepicker.js 2025-05-22 21:13:22 +01:00
valhalla-creator 0d5df8d3cf Update cardDate.js 2025-05-22 21:12:44 +01:00
valhalla-creator b67a236657 Update utils.js 2025-05-22 21:11:28 +01:00
Lauri Ojansivu 170617d457 Merge pull request #5780 from wekan/dependabot/github_actions/docker/build-push-action-6.17.0
Bump docker/build-push-action from 6.16.0 to 6.17.0
2025-05-19 23:12:48 +03:00
dependabot[bot] cc2f359b6c Bump docker/build-push-action from 6.16.0 to 6.17.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.16.0 to 6.17.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/14487ce63c7a62a4a324b0bfb37086795e31c6c1...1dc73863535b631f98b2378be8619f83b136f4a0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-19 20:08:52 +00:00
Lauri Ojansivu c0d44a8686 v7.90 2025-05-14 03:05:46 +03:00
Lauri Ojansivu a1700f38b1 Updated ChangeLog. 2025-05-14 01:14:08 +03:00
Lauri Ojansivu b82ba63532 Fix Lines in multiline markdown code block to not anymore have darker background.
Thanks to mueller-ma and xet7 !

Fixes #5689
2025-05-14 01:11:54 +03:00
Lauri Ojansivu dbd1324a90 Updated ChangeLog. 2025-05-14 00:15:37 +03:00
Lauri Ojansivu e52158b729 Changed start.bat default ROOT_URL=http://localhost , although it only works for local user.
Thanks to xet7 !
2025-05-14 00:13:52 +03:00
Lauri Ojansivu 68a717cf47 Updated ChangeLog. 2025-05-13 23:48:45 +03:00
Lauri Ojansivu c0a9780f80 Added missing quotes to Fix Due Date Problem in Non-English Numbers.
Thanks to xet7 !

Fixes https://github.com/wekan/wekan/pull/5774,
fixes #5752
2025-05-13 23:43:08 +03:00
Lauri Ojansivu 0520129a40 Updated ChangeLog. 2025-05-13 23:36:32 +03:00
Lauri Ojansivu 6b36e126ab Merge pull request #5774 from valhalla-creator/bugfix/fix-due-date
Bugfix/fix due date
2025-05-13 23:31:33 +03:00
valhalla-creator 3cb4662363 Update datepicker.js 2025-05-13 21:26:34 +01:00
valhalla-creator cef14f301f Update utils.js 2025-05-13 21:21:48 +01:00
valhalla-creator eca4a0c440 Update cardDate.js 2025-05-13 21:19:59 +01:00
Lauri Ojansivu cea04b2ada Updated ChangeLog. 2025-05-13 23:18:13 +03:00
Lauri Ojansivu b7d9af9ae7 v7.89 2025-05-13 22:45:44 +03:00
Lauri Ojansivu 0046b3c802 Merge pull request #5772 from oussama-madimagh/fix/css-compatibility
Fix: Add vendor prefixes for user-select and text-size-adjust for better browser compatibility
2025-05-13 22:37:32 +03:00
oussama-madimagh 25384c8ac3 Fix: Add vendor prefixes for user-select and text-size-adjust for better browser compatibility 2025-05-13 20:33:29 +01:00
Lauri Ojansivu 2eee72da8a Updated ChangeLog. 2025-05-13 22:00:50 +03:00
Lauri Ojansivu d65c422e3f Merge pull request #5769 from Adamsss001/feature/email-i18n
Add email notifications language localization feature
2025-05-13 21:50:53 +03:00
Omar Abid 2ab9bd3172 Add email notifications language localization feature 2025-05-13 19:45:08 +01:00
Lauri Ojansivu a0b5c2e88c Updated ChangeLog. 2025-05-13 19:42:41 +03:00
Lauri Ojansivu 0bd4108661 Upgraded to MongoDB 6.0.23 at Snap Candidate.
Thanks to MongoDB developers !
2025-05-13 19:37:42 +03:00
Lauri Ojansivu 7d1a1475ba Add missing code of fix insertion order of list.
Thanks to xet7 !

Fixes https://github.com/wekan/wekan/pull/5767,
fixes #5732
2025-05-13 18:54:14 +03:00
Lauri Ojansivu 6418263165 Merge branch 'correct-list-position' of github.com:valhalla-creator/wekan 2025-05-13 18:30:59 +03:00
Lauri Ojansivu b32c835cf9 Updated license 2025-05-13 17:00:54 +03:00
valhalla-creator a927685bc6 Update swimlanes.js 2025-05-12 22:33:42 +01:00
Lauri Ojansivu 6827d40e84 Updated ChangeLog. 2025-05-12 22:26:33 +03:00
Lauri Ojansivu 9aa12b0eb6 Updated Caddy docs.
Thanks to xet7 !
2025-05-12 22:25:18 +03:00
Lauri Ojansivu 8ca1a36d78 Updated ChangeLog. 2025-05-12 21:57:43 +03:00
Lauri Ojansivu 394f2eae69 Updated Browser Compatibility Matrix.
Thanks to xet7 !
2025-05-12 21:56:11 +03:00
Lauri Ojansivu 7588459c3e Updated translations. 2025-05-12 21:43:02 +03:00
Lauri Ojansivu 2c33f45a6a Updated translations. 2025-04-29 05:47:20 +03:00
Lauri Ojansivu 43cf0b66ac Merge pull request #5754 from wekan/dependabot/github_actions/docker/build-push-action-6.16.0
Bump docker/build-push-action from 6.15.0 to 6.16.0
2025-04-29 04:59:27 +03:00
dependabot[bot] c05f6fcff2 Bump docker/build-push-action from 6.15.0 to 6.16.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.15.0 to 6.16.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/471d1dc4e07e5cdedd4c2171150001c434f0b7a4...14487ce63c7a62a4a324b0bfb37086795e31c6c1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-28 21:07:25 +00:00
Lauri Ojansivu a4eb32158b Updated ChangeLog. 2025-04-25 23:33:40 +03:00
Lauri Ojansivu 072ced9d0e Added script to build snap locally and push to snapcraft.io candidate and edge.
Thanks to xet7 !
2025-04-25 23:30:57 +03:00
Lauri Ojansivu c5fefc1414 v7.88 2025-04-25 22:12:37 +03:00
Lauri Ojansivu c825895ceb Reverted translation fix that sometimes did not work.
Thanks to xet7 !

Fixes #5751,
related https://github.com/wekan/wekan/pull/5748
2025-04-25 22:06:06 +03:00
Lauri Ojansivu 9488972ed8 Merge newest changes. 2025-04-25 21:07:22 +03:00
Lauri Ojansivu 1f4bed38f7 Merge pull request #5750 from walster001/main
Fix more contrasting issues
2025-04-25 21:02:50 +03:00
Lauri Ojansivu 747112957a v7.87 2025-04-25 20:59:20 +03:00
Lauri Ojansivu 18228096f9 Updated ChangeLog. 2025-04-25 20:56:07 +03:00
Lauri Ojansivu d0ea5bf500 Fix typos.
Thanks to xet7 !
2025-04-25 20:54:02 +03:00
walster001 74fe91fa3a Fix more contrasting issues
- Fix contrast issue with CleanDark & CleanLight buttons on theme selection menu.
2025-04-26 01:40:15 +09:30
Lauri Ojansivu d4e3075fcc v7.86 2025-04-25 19:00:08 +03:00
Lauri Ojansivu 413ae5f20e Updated translations. 2025-04-25 18:38:13 +03:00
Lauri Ojansivu e23f86f542 Updated ChangeLog. 2025-04-25 18:35:12 +03:00
Lauri Ojansivu b3c47dbe84 Merge pull request #5749 from walster001/main
Fix contrast issue checkbox
2025-04-25 18:32:28 +03:00
Lauri Ojansivu b10c2b9a15 Updated ChangeLog. 2025-04-25 18:31:35 +03:00
Lauri Ojansivu bb6910acd1 Merge branch 'nourguidara-fix-i18n-init-timing' 2025-04-25 18:29:43 +03:00
Lauri Ojansivu 76b000820e Updated ChangeLog. 2025-04-25 18:28:23 +03:00
Lauri Ojansivu 50b874629c Merge branch 'Rayene123-add-toggle-watch-icon-on-card' 2025-04-25 18:25:44 +03:00
Lauri Ojansivu 4d145ab4ad Added translations to questions about are you sure.
Thanks to xet7 !

Related https://github.com/wekan/wekan/pull/5745
2025-04-25 18:24:40 +03:00
Lauri Ojansivu 4d67a4641f Merge branch 'fix-i18n-init-timing' of github.com:nourguidara/wekan into nourguidara-fix-i18n-init-timing 2025-04-25 17:54:41 +03:00
Lauri Ojansivu bc2eab6f9d Merge branch 'add-toggle-watch-icon-on-card' of github.com:Rayene123/wekan into Rayene123-add-toggle-watch-icon-on-card 2025-04-25 17:49:14 +03:00
walster001 3ccce75863 Fix contrast issue checkbox
- Fixed contrasting issue with tick check on custom fields, also fixed the ugly green tick coming up on the checklists in cards by utilisation of the !important tag to override it with the checkbox colour for custom fields also.
2025-04-25 20:15:34 +09:30
nour guidara b390b266e2 bug fix in imports\i18n\index.js 2025-04-25 11:10:46 +01:00
Lauri Ojansivu 561af8926c Merge branch 'main' of github.com:wekan/wekan 2025-04-24 22:20:06 +03:00
Lauri Ojansivu 5717dc2cf4 Updated ChangeLog. 2025-04-24 22:19:45 +03:00
Lauri Ojansivu d0e64ad6d2 Merge pull request #5746 from nourguidara/nourguidara-patch-1
Improve impersonate user documentation in docs/Admin/Impersonate-user.md
2025-04-24 22:18:07 +03:00
Lauri Ojansivu 8e1a735ec7 Updates 2025-04-24 22:17:47 +03:00
nour guidara 1301d67295 Update Impersonate-user.md
Restored direct link to the original pull request (#3280) as requested
2025-04-24 20:14:41 +01:00
nour guidara 7c239f5218 Update Impersonate-user.md 2025-04-24 19:45:29 +01:00
Rayene123 61235c953c added a watch icon button when clicked toggle if a card is watched or not 2025-04-24 11:53:08 +01:00
Lauri Ojansivu ea65a0308d Merge pull request #5741 from Firas-Git/documentation/login-and-signup
Documentation/login and signup
2025-04-21 22:57:08 +03:00
Firas Saidi f840b07c02 documentation/login-and-signup 2025-04-21 20:50:23 +01:00
Lauri Ojansivu 8af719d39e v7.85 2025-04-21 16:30:58 +03:00
Lauri Ojansivu c1a4250bd2 Updated to MongoDB 6.0.22.
Thanks to MongoDB developers !
2025-04-21 16:28:41 +03:00
Lauri Ojansivu b4b442f8a8 Updated translations. 2025-04-21 16:13:56 +03:00
Lauri Ojansivu b7e76fcfa1 Updated ChangeLog. 2025-04-21 16:10:24 +03:00
Lauri Ojansivu 01950cc796 Fix board sidebar menu doesn't open in mobile.
Thanks to mimZD and xet7 !

Fixes #5736
2025-04-21 16:07:43 +03:00
Firas Saidi 14f2022e21 fix-5434 2025-04-10 11:16:38 +01:00
Lauri Ojansivu 23bac73559 Updated translations. 2025-04-05 17:39:29 +03:00
Lauri Ojansivu 2de9b94b01 Updated ChangeLog. 2025-03-31 21:50:47 +03:00
Lauri Ojansivu 05d1736f5f Update Browser-compatibility-matrix.md 2025-03-31 21:45:22 +03:00
Lauri Ojansivu db5346fc5c Update Browser-compatibility-matrix.md 2025-03-31 21:38:06 +03:00
Lauri Ojansivu f09f5bd737 Updated translations. 2025-03-29 21:35:45 +02:00
Lauri Ojansivu d96a1a5c9e v7.84 2025-03-23 17:33:57 +02:00
Lauri Ojansivu ad3cc9c087 Updated translations. 2025-03-22 21:39:15 +02:00
Lauri Ojansivu d0c7bf65a4 Updated ChangeLog. 2025-03-22 21:30:53 +02:00
Lauri Ojansivu bb6ac70f63 Updated dependencies.
Thanks to developers of dependencies !
2025-03-22 21:26:41 +02:00
Lauri Ojansivu c38b9da7d6 Merge pull request #5718 from wekan/dependabot/github_actions/docker/login-action-3.4.0
Bump docker/login-action from 3.3.0 to 3.4.0
2025-03-18 01:37:59 +02:00
dependabot[bot] 19153ca489 Bump docker/login-action from 3.3.0 to 3.4.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.3.0 to 3.4.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/9780b0c442fbb1117ed29e0efdff1e18412f7567...74a5d142397b4f367a81961eba4e8cd7edddf772)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-17 21:00:44 +00:00
Lauri Ojansivu 8e7a5e8cb5 Updated translations. 2025-03-15 22:31:43 +02:00
Lauri Ojansivu 1407059937 Updated ChangeLog. 2025-03-15 19:33:49 +02:00
Lauri Ojansivu a2911bc9c3 Fixed building OpenAPI docs.
Thanks to xet7 !

Fixes #5512
2025-03-15 19:32:16 +02:00
Lauri Ojansivu f99b735746 Updated ChangeLog. 2025-03-11 04:23:55 +02:00
Lauri Ojansivu 4510ddda15 Fix typo at Shortcuts.
Thanks to xet7 !
2025-03-11 04:19:45 +02:00
Lauri Ojansivu 028ec46f46 Updated ChangeLog. 2025-03-09 20:05:08 +02:00
Lauri Ojansivu c0e4e01deb Added developer docs about Login code.
Thanks to xet7 !

Fixes #5714
2025-03-09 20:03:42 +02:00
Lauri Ojansivu cfcf682134 Updated ChangeLog. 2025-03-09 19:08:20 +02:00
Lauri Ojansivu 27bd9817d7 Merge pull request #5712 from Rayene123/Time-on-Minicard-for-Start/End/Due-Date
Time on minicard for start/end/due date
2025-03-09 19:03:54 +02:00
Firas Saidi dc389a68ab bug_fixing 2025-03-09 16:23:37 +01:00
Rayene123 6d982cdfd9 formatted the date when storing it 2025-03-09 14:15:17 +00:00
Rayene123 827ee615c1 added the start , due , end hour 2025-03-09 14:12:20 +00:00
Lauri Ojansivu f5f0ba721e v7.83 2025-03-08 21:32:37 +02:00
Lauri Ojansivu 1344f85986 Updated ChangeLog. 2025-03-08 21:14:12 +02:00
Lauri Ojansivu e7462ada12 Reverted Docker changes of WeKan v7.68 to get WeKan Docker version working.
Thanks to xet7 !

Fixes #5697
2025-03-08 21:09:09 +02:00
Lauri Ojansivu 11199b6225 Updated ChangeLog. 2025-03-08 19:58:05 +02:00
Lauri Ojansivu 345aece0f2 Updated ChangeLog. 2025-03-08 19:55:49 +02:00
Lauri Ojansivu 912c84422b Merge branch 'Firas-Git-CopyCard_FixError' 2025-03-08 19:52:42 +02:00
Lauri Ojansivu b402676079 Removed siteurl from manifest to fix browser console error about not matching URL.
Thanks to xet7 !
2025-03-08 19:51:23 +02:00
Lauri Ojansivu 908a5fc60d Comment out error message of non-existing Custom Field.
Thanks to xet7 !

Related #5684
2025-03-08 19:50:00 +02:00
Lauri Ojansivu 38e57d3635 Merge branch 'CopyCard_FixError' of github.com:Firas-Git/wekan into Firas-Git-CopyCard_FixError 2025-03-08 19:46:19 +02:00
Firas Saidi 14167b19b9 cards file 2025-03-08 18:02:03 +01:00
Lauri Ojansivu fc548b426d Updated translations. 2025-03-08 17:45:02 +02:00
Zegden Rayen 094f13934c added the confirm pop up before cloning or archiving a board 2025-03-08 15:38:22 +00:00
Lauri Ojansivu 71acd9603e Updated ChangeLog. 2025-03-06 01:52:46 +02:00
Lauri Ojansivu 341f655ab7 Merge pull request #5704 from wekan/dependabot/github_actions/docker/build-push-action-6.15.0
Bump docker/build-push-action from 6.14.0 to 6.15.0
2025-03-05 19:33:04 +02:00
Lauri Ojansivu 0c5323c106 Merge pull request #5703 from wekan/dependabot/github_actions/docker/metadata-action-5.7.0
Bump docker/metadata-action from 5.6.1 to 5.7.0
2025-03-05 19:30:27 +02:00
dependabot[bot] 7f871fdf30 Bump docker/build-push-action from 6.14.0 to 6.15.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.14.0 to 6.15.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/0adf9959216b96bec444f325f1e493d4aa344497...471d1dc4e07e5cdedd4c2171150001c434f0b7a4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 20:41:23 +00:00
dependabot[bot] 6347b40a02 Bump docker/metadata-action from 5.6.1 to 5.7.0
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5.6.1 to 5.7.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/369eb591f429131d6889c46b94e711f089e6ca96...902fa8ec7d6ecbf8d84d538b9b233a880e428804)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 20:41:20 +00:00
Lauri Ojansivu bce84a432a Updated ChangeLog. 2025-03-02 12:03:22 +02:00
Lauri Ojansivu b6fc4deb63 Merge pull request #5702 from adam-fu/main
Adding Development information to readme
2025-03-02 12:00:54 +02:00
adam-fu 6b48f9e259 Adding Development information to readme 2025-03-02 06:43:29 +01:00
Lauri Ojansivu 44b7666426 Merge branch 'main' of github.com:wekan/wekan 2025-02-25 14:33:34 +02:00
Lauri Ojansivu ef70ed76a2 Updated changelog 2025-02-25 14:33:06 +02:00
Lauri Ojansivu a1a1b3d1ee Merge pull request #5696 from wekan/dependabot/github_actions/docker/build-push-action-6.14.0
Bump docker/build-push-action from 6.13.0 to 6.14.0
2025-02-24 23:21:55 +02:00
dependabot[bot] 0326757399 Bump docker/build-push-action from 6.13.0 to 6.14.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.13.0 to 6.14.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/ca877d9245402d1537745e0e356eab47c3520991...0adf9959216b96bec444f325f1e493d4aa344497)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 21:18:37 +00:00
Lauri Ojansivu edbc8ed92b v7.82 2025-02-24 20:21:33 +02:00
Lauri Ojansivu 07e9ec0617 Updated ChangeLog. 2025-02-24 15:12:07 +02:00
Lauri Ojansivu 18d0fa4327 Fixed building WeKan. Updated dompurify. Forked Meteor 2.14 version of meteor-node-stubs to update elliptic.
Thanks to delopers of depedencies and xet7 !

Fixes #5694
2025-02-24 14:56:54 +02:00
Lauri Ojansivu 666ee84033 Updated dependencies.
Thanks to developes of dependencies "
2025-02-23 15:55:20 +02:00
Lauri Ojansivu 120642f47d Updated ChangeLog. 2025-02-21 21:22:14 +02:00
Lauri Ojansivu 148b81262d Updated requirements at docs.
Thanks to mueller-ma !

Fixes #5692
2025-02-21 21:12:28 +02:00
Lauri Ojansivu c41467f76e Merge pull request #5691 from wekan/dependabot/npm_and_yarn/dompurify-3.2.4
Bump dompurify from 2.5.6 to 3.2.4
2025-02-15 15:47:49 +02:00
dependabot[bot] c83e83b8b1 Bump dompurify from 2.5.6 to 3.2.4
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 2.5.6 to 3.2.4.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/2.5.6...3.2.4)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-15 08:58:49 +00:00
Lauri Ojansivu 563a508e26 Added missing ) character.
Thanks to xet7 !
2025-02-11 22:12:57 +02:00
Lauri Ojansivu 1d8347cc23 Merge pull request #5682 from salleman33/devel
Update metrics.js
2025-02-11 17:28:33 +02:00
salleman 874abf2c41 Update metrics.js
Add possibility to use a token in place of ipaddress to access metrics route
2025-02-11 12:06:23 +01:00
Lauri Ojansivu 3e18f820ea v7.81 2025-02-07 16:13:52 +02:00
Lauri Ojansivu b571f1c953 Upgraded to MongoDB 6.0.20 at Snap Candidate.
Thanks to MongoDB developers!
2025-02-07 16:11:48 +02:00
Lauri Ojansivu fd69c7ceb8 Updated ChangeLog. 2025-02-05 04:41:42 +02:00
Lauri Ojansivu d15faa1890 Updated ChangeLog. 2025-01-29 14:15:31 +02:00
Lauri Ojansivu 0c7e12c5e7 Added Meteor 3.1 learning course to docs.
Thanks to producer of learning course !
2025-01-29 14:13:21 +02:00
Lauri Ojansivu 36a3077853 Fix misspelling of hours at env variable setting LDAP_BACKGROUND_SYNC_INTERVAL.
Thanks to hubermam !

Fixes #5665
2025-01-29 14:09:31 +02:00
Lauri Ojansivu 51bc23dbd9 Updated ChangeLog. 2025-01-29 13:40:34 +02:00
Lauri Ojansivu 0b1e0bd395 Fixed env variable METRICS_ACCEPTED_IP_ADDRESS to be same as at docs.
Thanks to salleman33 !

Fixes #5671
2025-01-29 13:38:40 +02:00
Lauri Ojansivu 151aafd114 Updated ChangeLog. 2025-01-29 10:23:12 +02:00
Lauri Ojansivu 16c8a65a1c Updated ChangeLog. 2025-01-29 10:22:08 +02:00
Lauri Ojansivu f3133e9cd8 Merge pull request #5670 from wekan/dependabot/github_actions/docker/build-push-action-6.13.0
Bump docker/build-push-action from 6.12.0 to 6.13.0
2025-01-29 10:14:55 +02:00
dependabot[bot] de84aa7674 Bump docker/build-push-action from 6.12.0 to 6.13.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.12.0 to 6.13.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/67a2d409c0a876cbe6b11854e3e25193efe4e62d...ca877d9245402d1537745e0e356eab47c3520991)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-27 20:17:17 +00:00
Lauri Ojansivu 1bdbfe86bb Merge pull request #5663 from wekan/dependabot/github_actions/helm/chart-releaser-action-1.7.0
Bump helm/chart-releaser-action from 1.6.0 to 1.7.0
2025-01-21 22:44:23 +02:00
Lauri Ojansivu 397b9f439e Merge pull request #5662 from wekan/dependabot/github_actions/docker/build-push-action-6.12.0
Bump docker/build-push-action from 6.11.0 to 6.12.0
2025-01-21 22:44:12 +02:00
dependabot[bot] 261e0aee0f Bump helm/chart-releaser-action from 1.6.0 to 1.7.0
Bumps [helm/chart-releaser-action](https://github.com/helm/chart-releaser-action) from 1.6.0 to 1.7.0.
- [Release notes](https://github.com/helm/chart-releaser-action/releases)
- [Commits](https://github.com/helm/chart-releaser-action/compare/v1.6.0...v1.7.0)

---
updated-dependencies:
- dependency-name: helm/chart-releaser-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-20 20:02:15 +00:00
dependabot[bot] a6d9bde658 Bump docker/build-push-action from 6.11.0 to 6.12.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.11.0 to 6.12.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/b32b51a8eda65d6793cd0494a773d4f6bcef32dc...67a2d409c0a876cbe6b11854e3e25193efe4e62d)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-20 20:02:13 +00:00
Lauri Ojansivu 8d0a07e1a8 Updated translations. 2025-01-19 23:06:23 +02:00
Lauri Ojansivu 659615e6f2 Merge pull request #5658 from wekan/dependabot/github_actions/docker/build-push-action-6.11.0
Bump docker/build-push-action from 6.10.0 to 6.11.0
2025-01-15 12:18:49 +02:00
dependabot[bot] 9f0b82c0d5 Bump docker/build-push-action from 6.10.0 to 6.11.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.10.0 to 6.11.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/48aba3b46d1b1fec4febb7c5d0c644b249a11355...b32b51a8eda65d6793cd0494a773d4f6bcef32dc)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-13 20:55:32 +00:00
845 changed files with 115185 additions and 20705 deletions

No files matched your search

-257
View File
@@ -1,257 +0,0 @@
name: wekan
version: 0
version-script: git describe --tags | cut -c 2-
summary: The open-source kanban
description: |
Wekan is an open-source and collaborative kanban board application.
Whether you’re maintaining a personal todo list, planning your holidays with some friends, or working in a team on your next revolutionary idea, Kanban boards are an unbeatable tool to keep your things organized. They give you a visual overview of the current state of your project, and make you productive by allowing you to focus on the few items that matter the most.
Depending on target environment, some configuration settings might need to be adjusted.
For full list of configuration options call:
$ wekan.help
confinement: strict
grade: stable
architectures:
- amd64
plugs:
mongodb-plug:
interface: content
target: $SNAP_DATA/shared
hooks:
configure:
plugs:
- network
- network-bind
slots:
mongodb-slot:
interface: content
write:
- $SNAP_DATA/share
apps:
wekan:
command: wekan-control
daemon: simple
plugs: [network, network-bind]
mongodb:
command: mongodb-control
daemon: simple
plugs: [network, network-bind]
caddy:
command: caddy-control
daemon: simple
plugs: [network, network-bind]
help:
command: wekan-help
database-backup:
command: mongodb-backup
plugs: [network, network-bind]
database-list-backups:
command: ls -al $SNAP_COMMON/db-backups/
database-restore:
command: mongodb-restore
plugs: [network, network-bind]
parts:
mongodb:
source: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-4.2.6.tgz
plugin: dump
stage-packages: [libssl1.0.0, libcurl3]
filesets:
mongo:
- usr
- bin
- lib
stage:
- $mongo
prime:
- $mongo
wekan:
source: .
plugin: nodejs
node-engine: 14.21.3
node-packages:
- node-gyp
- node-pre-gyp
- fibers
build-packages:
- ca-certificates
- apt-utils
- python
- python3
- g++
- capnproto
- curl
- libcurl3
- execstack
- nodejs
- npm
stage-packages:
- libfontconfig1
override-build: |
echo "Cleaning environment first"
rm -rf ~/.meteor ~/.npm /usr/local/lib/node_modules
# Create the OpenAPI specification
rm -rf .build
## Use Meteor 1.8.x on Snap
#rm -rf .meteor
#mv .snap-meteor-1.8/.meteor .
#mv .snap-meteor-1.8/package.json .
#mv .snap-meteor-1.8/package-lock.json .
## Meteor 1.9.x has changes to Buffer() => Buffer.alloc(), so reverting those
#mv .snap-meteor-1.8/cfs_access-point.txt fix-download-unicode/
#mv .snap-meteor-1.8/export.js models/
#mv .snap-meteor-1.8/wekanCreator.js models/
#mv .snap-meteor-1.8/ldap.js packages/wekan-ldap/server/ldap.js
#mv .snap-meteor-1.8/oidc_server.js packages/wekan-oidc/oidc_server.js
rm -rf .snap-meteor-1.8
#mkdir -p .build/python
#cd .build/python
#git clone --depth 1 -b master https://github.com/Kronuz/esprima-python
#cd esprima-python
#python3 setup.py install
#cd ../../..
#mkdir -p ./public/api
#python3 ./openapi/generate_openapi.py --release $(git describe --tags --abbrev=0) > ./public/api/wekan.yml
# we temporary need api2html and mkdirp
#npm install -g api2html@0.3.0
#npm install -g mkdirp
#api2html -c ./public/logo-header.png -o ./public/api/wekan.html ./public/api/wekan.yml
#npm uninstall -g mkdirp
#npm uninstall -g api2html
# Node Fibers 100% CPU usage issue:
# https://github.com/wekan/wekan-mongodb/issues/2#issuecomment-381453161
# https://github.com/meteor/meteor/issues/9796#issuecomment-381676326
# https://github.com/sandstorm-io/sandstorm/blob/0f1fec013fe7208ed0fd97eb88b31b77e3c61f42/shell/server/00-startup.js#L99-L129
# Also see beginning of wekan/server/authentication.js
# import Fiber from "fibers";
# Fiber.poolSize = 1e9;
# OLD: Download node version 8.12.0 prerelease build => Official node 8.12.0 has been released
# Description at https://releases.wekan.team/node.txt
##echo "375bd8db50b9c692c0bbba6e96d4114cd29bee3770f901c1ff2249d1038f1348 node" >> node-SHASUMS256.txt.asc
##curl https://releases.wekan.team/node -o node
# Verify Fibers patched node authenticity
##echo "Fibers 100% CPU issue patched node authenticity:"
##grep node node-SHASUMS256.txt.asc | shasum -a 256 -c -
##rm -f node-SHASUMS256.txt.asc
##chmod +x node
##mv node `which node`
# DOES NOT WORK: paxctl fix.
# Removed from build-packages: - paxctl
#echo "Applying paxctl fix for alpine linux: https://github.com/wekan/wekan/issues/1303"
#paxctl -mC `which node`
#echo "Installing npm"
#curl -L https://www.npmjs.com/install.sh | sh
echo "Installing meteor"
curl https://install.meteor.com/ -o install_meteor.sh
#sed -i "s|RELEASE=.*|RELEASE=\"1.8.1-beta.0\"|g" install_meteor.sh
chmod +x install_meteor.sh
sh install_meteor.sh
rm install_meteor.sh
# REPOS BELOW ARE INCLUDED TO WEKAN REPO
#if [ ! -d "packages" ]; then
# mkdir packages
#fi
#if [ ! -d "packages/kadira-flow-router" ]; then
# cd packages
# git clone --depth 1 -b master https://github.com/wekan/flow-router.git kadira-flow-router
# cd ..
#fi
#if [ ! -d "packages/meteor-useraccounts-core" ]; then
# cd packages
# git clone --depth 1 -b master https://github.com/meteor-useraccounts/core.git meteor-useraccounts-core
# sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' meteor-useraccounts-core/package.js
# cd ..
#fi
#if [ ! -d "packages/meteor-accounts-cas" ]; then
# cd packages
# git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-cas.git meteor-accounts-cas
# cd ..
#fi
#if [ ! -d "packages/wekan-ldap" ]; then
# cd packages
# git clone --depth 1 -b master https://github.com/wekan/wekan-ldap.git
# cd ..
#fi
#if [ ! -d "packages/wekan-scrollbar" ]; then
# cd packages
# git clone --depth 1 -b master https://github.com/wekan/wekan-scrollbar.git
# cd ..
#fi
#if [ ! -d "packages/wekan_accounts-oidc" ]; then
# cd packages
# git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-oidc.git
# mv meteor-accounts-oidc/packages/switch_accounts-oidc wekan-accounts-oidc
# mv meteor-accounts-oidc/packages/switch_oidc wekan-oidc
# rm -rf meteor-accounts-oidc
# cd ..
#fi
#if [ ! -d "packages/markdown" ]; then
# cd packages
# git clone --depth 1 -b master --recurse-submodules https://github.com/wekan/markdown.git
# cd ..
#fi
rm -rf .build
meteor add standard-minifier-js --allow-superuser
meteor npm install --allow-superuser
meteor npm install --allow-superuser --save babel-runtime
meteor build .build --directory --allow-superuser
cp -f fix-download-unicode/cfs_access-point.txt .build/bundle/programs/server/packages/cfs_access-point.js
#Removed binary version of bcrypt because of security vulnerability that is not fixed yet.
#https://github.com/wekan/wekan/commit/4b2010213907c61b0e0482ab55abb06f6a668eac
#https://github.com/wekan/wekan/commit/7eeabf14be3c63fae2226e561ef8a0c1390c8d3c
#cd .build/bundle/programs/server/npm/node_modules/meteor/npm-bcrypt
#rm -rf node_modules/bcrypt
#meteor npm install --save bcrypt
# Change from npm-bcrypt directory back to .build/bundle/programs/server directory.
#cd ../../../../
# Change to directory .build/bundle/programs/server
cd .build/bundle/programs/server
npm install
npm install --allow-superuser --save babel-runtime
#meteor npm install --save bcrypt
# Change back to Wekan source directory
cd ../../../..
cp -r .build/bundle/* $SNAPCRAFT_PART_INSTALL/
cp .build/bundle/.node_version.txt $SNAPCRAFT_PART_INSTALL/
rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/wekan
rm -f $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/meteor/rajit_bootstrap3-datepicker/lib/bootstrap-datepicker/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs
rm -f $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/tar/lib/.mkdir.js.swp
rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-pre-gyp/node_modules/tar/lib/.mkdir.js.swp
rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-gyp/node_modules/tar/lib/.mkdir.js.swp
# Meteor 1.8.x additional .swp remove
rm -f $SNAPCRAFT_PART_INSTALL/programs/server/node_modules/node-pre-gyp/node_modules/tar/lib/.mkdir.js.swp
organize:
README: README.wekan
prime:
- -lib/node_modules/node-pre-gyp/node_modules/tar/lib/.unpack.js.swp
helpers:
source: snap-src
plugin: dump
caddy:
plugin: dump
source: https://caddyserver.com/download/linux/amd64?license=personal&telemetry=off
source-type: tar
organize:
caddy: bin/caddy
CHANGES.txt: CADDY_CHANGES.txt
EULA.txt: CADDY_EULA.txt
LICENSES.txt: CADDY_LICENSES.txt
README.txt: CADDY_README.txt
stage:
- -init
-198
View File
@@ -1,198 +0,0 @@
#!/bin/bash
echo "Note: If you use other locale than en_US.UTF-8 , you need to additionally install en_US.UTF-8"
echo " with 'sudo dpkg-reconfigure locales' , so that MongoDB works correctly."
echo " You can still use any other locale as your main locale."
#Below script installs newest node 8.x for Debian/Ubuntu/Mint.
#NODE_VERSION=12.21.0
#X64NODE="https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.gz"
function pause(){
read -p "$*"
}
function cprec(){
if [[ -d "$1" ]]; then
if [[ ! -d "$2" ]]; then
sudo mkdir -p "$2"
fi
for i in $(ls -A "$1"); do
cprec "$1/$i" "$2/$i"
done
else
sudo cp "$1" "$2"
fi
}
# sudo npm doesn't work right, so this is a workaround
function npm_call(){
TMPDIR="/tmp/tmp_npm_prefix"
if [[ -d "$TMPDIR" ]]; then
rm -rf $TMPDIR
fi
mkdir $TMPDIR
NPM_PREFIX="$(npm config get prefix)"
npm config set prefix $TMPDIR
npm "$@"
npm config set prefix "$NPM_PREFIX"
echo "Moving files to $NPM_PREFIX"
for i in $(ls -A $TMPDIR); do
cprec "$TMPDIR/$i" "$NPM_PREFIX/$i"
done
rm -rf $TMPDIR
}
#function wekan_repo_check(){
## UNCOMMENTING, IT'S NOT REQUIRED THAT /HOME/USERNAME IS /HOME/WEKAN
# git_remotes="$(git remote show 2>/dev/null)"
# res=""
# for i in $git_remotes; do
# res="$(git remote get-url $i | sed 's/.*wekan\/wekan.*/wekan\/wekan/')"
# if [[ "$res" == "wekan/wekan" ]]; then
# break
# fi
# done
#
# if [[ "$res" != "wekan/wekan" ]]; then
# echo "$PWD is not a wekan repository"
# exit;
# fi
#}
echo
PS3='Please enter your choice: '
options=("Install Wekan dependencies" "Build Wekan" "Run Meteor for dev on http://localhost:4000" "Run Meteor for dev on http://CURRENT-IP-ADDRESS:4000" "Run Meteor for dev on http://CUSTOM-IP-ADDRESS:PORT" "Quit")
select opt in "${options[@]}"
do
case $opt in
"Install Wekan dependencies")
if [[ "$OSTYPE" == "linux-gnu" ]]; then
echo "Linux";
# Debian, Ubuntu, Mint
sudo apt-get install -y build-essential gcc g++ make git curl wget
# npm nodejs
#sudo npm -g install npm
curl -0 -L https://npmjs.org/install.sh | sudo sh
sudo chown -R $(id -u):$(id -g) $HOME/.npm
sudo npm -g install n
sudo n 12.21.0
#curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -
#sudo apt-get install -y nodejs
elif [[ "$OSTYPE" == "darwin"* ]]; then
echo "macOS";
pause '1) Install XCode 2) Install Node 8.x from https://nodejs.org/en/ 3) Press [Enter] key to continue.'
elif [[ "$OSTYPE" == "cygwin" ]]; then
# POSIX compatibility layer and Linux environment emulation for Windows
echo "TODO: Add Cygwin";
exit;
elif [[ "$OSTYPE" == "msys" ]]; then
# Lightweight shell and GNU utilities compiled for Windows (part of MinGW)
echo "TODO: Add msys on Windows";
exit;
elif [[ "$OSTYPE" == "win32" ]]; then
# I'm not sure this can happen.
echo "TODO: Add Windows";
exit;
elif [[ "$OSTYPE" == "freebsd"* ]]; then
echo "TODO: Add FreeBSD";
exit;
else
echo "Unknown"
echo ${OSTYPE}
exit;
fi
## Latest npm with Meteor 1.8.x
npm_call -g install npm
npm_call -g install node-gyp
# Latest fibers for Meteor 1.8.x
sudo mkdir -p /usr/local/lib/node_modules/fibers/.node-gyp
npm_call -g install fibers
# Install Meteor, if it's not yet installed
curl https://install.meteor.com | bash
sudo chown -R $(id -u):$(id -g) $HOME/.npm $HOME/.meteor
break
;;
"Build Wekan")
echo "Building Wekan."
#wekan_repo_check
# REPOS BELOW ARE INCLUDED TO WEKAN REPO
#rm -rf packages/kadira-flow-router packages/meteor-useraccounts-core packages/meteor-accounts-cas packages/wekan-ldap packages/wekan-ldap packages/wekan-scrfollbar packages/meteor-accounts-oidc packages/markdown
#mkdir packages
#cd packages
#git clone --depth 1 -b master https://github.com/wekan/flow-router.git kadira-flow-router
#git clone --depth 1 -b master https://github.com/meteor-useraccounts/core.git meteor-useraccounts-core
#git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-cas.git
#git clone --depth 1 -b master https://github.com/wekan/wekan-ldap.git
#git clone --depth 1 -b master https://github.com/wekan/wekan-scrollbar.git
#git clone --depth 1 -b master https://github.com/wekan/meteor-accounts-oidc.git
#git clone --depth 1 -b master --recurse-submodules https://github.com/wekan/markdown.git
#mv meteor-accounts-oidc/packages/switch_accounts-oidc wekan_accounts-oidc
#mv meteor-accounts-oidc/packages/switch_oidc wekan_oidc
#rm -rf meteor-accounts-oidc
#if [[ "$OSTYPE" == "darwin"* ]]; then
# echo "sed at macOS";
# sed -i '' 's/api\.versionsFrom/\/\/api.versionsFrom/' ~/repos/wekan/packages/meteor-useraccounts-core/package.js
#else
# echo "sed at ${OSTYPE}"
# sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' ~/repos/wekan/packages/meteor-useraccounts-core/package.js
#fi
#cd ..
sudo chown -R $(id -u):$(id -g) $HOME/.npm $HOME/.meteor
rm -rf node_modules .meteor/local
npm install
rm -rf .build
meteor build .build --directory
cp -f fix-download-unicode/cfs_access-point.txt .build/bundle/programs/server/packages/cfs_access-point.js
# Remove legacy webbroser bundle, so that Wekan works also at Android Firefox, iOS Safari, etc.
rm -rf .build/bundle/programs/web.browser.legacy
#Removed binary version of bcrypt because of security vulnerability that is not fixed yet.
#https://github.com/wekan/wekan/commit/4b2010213907c61b0e0482ab55abb06f6a668eac
#https://github.com/wekan/wekan/commit/7eeabf14be3c63fae2226e561ef8a0c1390c8d3c
#cd ~/repos/wekan/.build/bundle/programs/server/npm/node_modules/meteor/npm-bcrypt
#rm -rf node_modules/bcrypt
#meteor npm install bcrypt
cd .build/bundle/programs/server
rm -rf node_modules
npm install
#meteor npm install bcrypt
cd ../../../..
echo Done.
break
;;
"Run Meteor for dev on http://localhost:4000")
WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://localhost:4000 meteor run --exclude-archs web.browser.legacy,web.cordova --port 4000
break
;;
"Run Meteor for dev on http://CURRENT-IP-ADDRESS:4000")
IPADDRESS=$(ip a | grep 'noprefixroute' | grep 'inet ' | cut -d: -f2 | awk '{ print $2}' | cut -d '/' -f 1)
echo "Your IP address is $IPADDRESS"
WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://$IPADDRESS:4000 meteor run --exclude-archs web.browser.legacy,web.cordova --port 4000
break
;;
"Run Meteor for dev on http://CUSTOM-IP-ADDRESS:PORT")
ip address
echo "From above list, what is your IP address?"
read IPADDRESS
echo "On what port you would like to run Wekan?"
read PORT
echo "ROOT_URL=http://$IPADDRESS:$PORT"
WITH_API=true RICHER_CARD_COMMENT_EDITOR=false ROOT_URL=http://$IPADDRESS:$PORT meteor run --exclude-archs web.browser.legacy,web.cordova --port $PORT
break
;;
"Quit")
break
;;
*) echo invalid option;;
esac
done
-155
View File
@@ -1,155 +0,0 @@
name: wekan
version: git
summary: The open-source kanban
description: |
Wekan is an open-source and collaborative kanban board application.
Whether you’re maintaining a personal todo list, planning your holidays with some friends, or working in a team on your next revolutionary idea, Kanban boards are an unbeatable tool to keep your things organized. They give you a visual overview of the current state of your project, and make you productive by allowing you to focus on the few items that matter the most.
Depending on target environment, some configuration settings might need to be adjusted.
For full list of configuration options call:
$ wekan.help
confinement: strict
grade: stable
base: core18
architectures:
- amd64
plugs:
mongodb-plug:
interface: content
target: $SNAP_DATA/shared
hooks:
configure:
plugs:
- network
- network-bind
slots:
mongodb-slot:
interface: content
write:
- $SNAP_DATA/share
apps:
wekan:
command: wekan-control
daemon: simple
plugs: [network, network-bind]
mongodb:
command: mongodb-control
daemon: simple
plugs: [network, network-bind]
caddy:
command: caddy-control
daemon: simple
plugs: [network, network-bind]
help:
command: wekan-help
database-backup:
command: mongodb-backup
plugs: [network, network-bind]
database-list-backups:
command: ls -al $SNAP_COMMON/db-backups/
database-restore:
command: mongodb-restore
plugs: [network, network-bind]
parts:
mongodb:
source: https://repo.mongodb.org/apt/ubuntu/dists/xenial/mongodb-org/4.2/multiverse/binary-amd64/mongodb-org-server_4.2.2_amd64.deb
#https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-4.0.14.tgz
#https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu1604-3.2.22.tgz
plugin: dump
stage-packages: [libssl1.0.0, libcurl3]
filesets:
mongo:
- usr
- bin
- lib
stage:
- $mongo
prime:
- $mongo
wekan:
source: .
plugin: nodejs
node-engine: 14.19.0
node-packages:
- node-gyp
- node-pre-gyp
- fibers
build-packages:
- ca-certificates
- apt-utils
- build-essential
- python
- python3
- g++
- capnproto
- curl
- libcurl3
- execstack
- nodejs
- npm
stage-packages:
- libfontconfig1
override-build: |
echo "Cleaning environment first"
rm -rf ~/.meteor ~/.npm /usr/local/lib/node_modules
rm -rf .build
echo "Installing meteor"
curl https://install.meteor.com/ -o install_meteor.sh
chmod +x install_meteor.sh
sh install_meteor.sh
rm install_meteor.sh
rm -rf .build
meteor add standard-minifier-js --allow-superuser
meteor npm install --allow-superuser
meteor npm install --allow-superuser --save babel-runtime
meteor build .build --directory --allow-superuser
cp -f fix-download-unicode/cfs_access-point.txt .build/bundle/programs/server/packages/cfs_access-point.js
cd .build/bundle/programs/server
npm install
npm install --allow-superuser --save babel-runtime
# Change back to Wekan source directory
cd ../../../..
cp -r .build/bundle/* $SNAPCRAFT_PART_INSTALL/
cp .build/bundle/.node_version.txt $SNAPCRAFT_PART_INSTALL/
rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/wekan
rm -f $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/meteor/rajit_bootstrap3-datepicker/lib/bootstrap-datepicker/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs
rm -f $SNAPCRAFT_PART_INSTALL/programs/server/npm/node_modules/tar/lib/.mkdir.js.swp
rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-pre-gyp/node_modules/tar/lib/.mkdir.js.swp
rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/node-gyp/node_modules/tar/lib/.mkdir.js.swp
rm -f $SNAPCRAFT_PART_INSTALL/programs/server/node_modules/node-pre-gyp/node_modules/tar/lib/.mkdir.js.swp
organize:
README: README.wekan
prime:
- -lib/node_modules/node-pre-gyp/node_modules/tar/lib/.unpack.js.swp
helpers:
source: snap-src
plugin: dump
caddy:
plugin: dump
source: https://caddyserver.com/download/linux/amd64?license=personal&telemetry=off
source-type: tar
organize:
caddy: bin/caddy
CHANGES.txt: CADDY_CHANGES.txt
EULA.txt: CADDY_EULA.txt
LICENSES.txt: CADDY_LICENSES.txt
README.txt: CADDY_README.txt
stage:
- -init
-183
View File
@@ -1,183 +0,0 @@
name: wekan
version: '6.21'
base: core20
summary: Open Source kanban
description: |
WeKan ® is an Open Source and collaborative kanban board application.
Whether you’re maintaining a personal todo list, planning your holidays with some friends, or working in a team on your next revolutionary idea, Kanban boards are an unbeatable tool to keep your things organized. They give you a visual overview of the current state of your project, and make you productive by allowing you to focus on the few items that matter the most.
Depending on target environment, some configuration settings might need to be adjusted.
For full list of configuration options call:
$ wekan.help
confinement: strict
grade: stable
architectures:
- build-on: amd64
run-on: amd64
- build-on: arm64
run-on: arm64
- build-on: ppc64el
run-on: ppc64el
- build-on: s390x
run-on: s390x
plugs:
mongodb-plug:
interface: content
target: $SNAP_DATA/shared
hooks:
configure:
plugs:
- network
- network-bind
slots:
mongodb-slot:
interface: content
write:
- $SNAP_DATA/share
apps:
wekan:
command: wekan-control
daemon: simple
plugs: [network, network-bind, mount-observe, system-observe, bluetooth-control]
restart-condition: on-failure
mongodb:
command: mongodb-control
daemon: simple
plugs: [network, network-bind, mount-observe, system-observe, bluetooth-control]
restart-condition: on-failure
caddy:
command: caddy-control
daemon: simple
plugs: [network, network-bind]
help:
command: wekan-help
database-backup:
command: mongodb-backup
plugs: [network, network-bind, mount-observe, system-observe, bluetooth-control]
database-list-backups:
command: ls -al $SNAP_COMMON/db-backups/
database-restore:
command: mongodb-restore
plugs: [network, network-bind, mount-observe, system-observe, bluetooth-control]
parts:
mongodb:
plugin: dump
source:
- on amd64: https://repo.mongodb.org/apt/ubuntu/dists/focal/mongodb-org/4.4/multiverse/binary-amd64/mongodb-org-server_4.4.13_amd64.deb
- on arm64: https://repo.mongodb.org/apt/ubuntu/dists/focal/mongodb-org/4.4/multiverse/binary-arm64/mongodb-org-server_4.4.13_arm64.deb
- on ppc64el: https://repo.mongodb.org/apt/ubuntu/dists/focal/mongodb-org/4.4/multiverse/binary-ppc64el/mongodb-org-server_4.4.13_ppc64el.deb
- on s390x: https://repo.mongodb.org/apt/ubuntu/dists/focal/mongodb-org/4.4/multiverse/binary-s390x/mongodb-org-server_4.4.13_s390x.deb
stage-packages:
- libssl1.1
- libcurl3-dev
- libcurl4-openssl-dev
filesets:
mongo:
- usr
- bin
- lib
stage:
- $mongo
prime:
- $mongo
wekan:
#plugin: npm
plugin: dump
source:
# Fixed URLs to some allowed GitHub releases URL.
# Non-GitHub build server file urls are not allowed at 2022-03-02 and later.
- on amd64: https://github.com/wekan/wekan/releases/download/v6.20/wekan-6.20-amd64.zip
- on arm64: https://github.com/wekan/wekan/releases/download/v6.20/wekan-6.20-arm64.zip
- on ppc64el: https://github.com/wekan/wekan/releases/download/v6.20/wekan-6.20-ppc64el.zip
- on s390x: https://github.com/wekan/wekan/releases/download/v6.20/wekan-6.20-s390x.zip
# npm-node-version: 14.19.1
# node-packages:
# - node-gyp
# - node-pre-gyp
# - fibers
# build-packages:
# - npm
# - build-essential
# - ca-certificates
# - apt-utils
# - python
# - python3
# - g++
# - capnproto
# - curl
# - execstack
# - nodejs
# - npm
# - p7zip-full
# stage-packages:
# - libfontconfig1
override-build: |
cp -r bundle/* $SNAPCRAFT_PART_INSTALL/
cp bundle/.node_version.txt $SNAPCRAFT_PART_INSTALL/
rm -f $SNAPCRAFT_PART_INSTALL/lib/node_modules/wekan
snapcraftctl build
organize:
README: README.wekan
prime:
- -lib/node_modules/node-pre-gyp/node_modules/tar/lib/.unpack.js.swp
- -lib/node_modules/weka*
helpers:
source: snap-src
plugin: dump
caddy:
plugin: dump
## Caddy v1 is not developed anymore. TODO: Sometime migrate to Caddy v2.
## https://caddy.community/t/caddyfile-v1-adapter/9129
## https://github.com/caddyserver/caddy/tree/v1
#source: https://caddyserver.com/download/linux/amd64?license=personal&telemetry=off
#source-type: tar
# Using last working binary that was downloaded from above URL to Wekan Snap,
# and .txt files from https://github.com/caddyserver/caddy/tree/v1/dist
source: https://wekan.github.io/caddy-v1-linux-amd64.7z
source-type: 7z
organize:
caddy: bin/caddy
CHANGES.txt: CADDY_CHANGES.txt
EULA.txt: CADDY_EULA.txt
LICENSES.txt: CADDY_LICENSES.txt
README.txt: CADDY_README.txt
stage:
- -init
caddy2:
plugin: dump
source:
# Fixed URLs to some allowed GitHub releases URL.
# Non-GitHub build server file urls are not allowed at 2022-03-02 and later.
- on amd64: https://github.com/wekan/wekan/releases/download/v6.20/caddy-v2-amd64.zip
- on arm64: https://github.com/wekan/wekan/releases/download/v6.20/caddy-v2-arm64.zip
- on ppc64el: https://github.com/wekan/wekan/releases/download/v6.20/caddy-v2-ppc64el.zip
- on s390x: https://github.com/wekan/wekan/releases/download/v6.20/caddy-v2-s390x.zip
source-type: zip
organize:
caddy: bin/caddy
CHANGES.txt: CADDY_CHANGES.txt
EULA.txt: CADDY_EULA.txt
LICENSES.txt: CADDY_LICENSES.txt
README.txt: CADDY_README.txt
stage:
- -init
+2 -1
View File
@@ -1,3 +1,4 @@
# These are supported funding model platforms
custom: ['https://wekan.team/commercial-support/']
github: wekan
custom: ['https://wekan.fi/commercial-support/']
+4 -3
View File
@@ -1,5 +1,9 @@
## Issue
UPGRADE: https://wekan.fi/upgrade/
Pull requests welcome to fix any broken links at docs directory, and organizing docs/Features and their screenshots to subdirectories of each feature.
Please report these issues elsewhere:
- SECURITY ISSUES, PGP EMAIL: https://github.com/wekan/wekan/blob/main/SECURITY.md
@@ -7,9 +11,6 @@ Please report these issues elsewhere:
If WeKan Snap is slow, try this: https://github.com/wekan/wekan/wiki/Cron
**[PLEASE UPGRADE](https://github.com/wekan/wekan/wiki/Backup)** to the newest
WeKan ® before reporting an issue, if possible.
Please search existing Open and Closed issues, most questions have already been answered.
If you can not login for any reason: https://github.com/wekan/wekan/wiki/Forgot-Password
+1 -1
View File
@@ -9,6 +9,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: 'Dependency Review'
uses: actions/dependency-review-action@v4
+4 -4
View File
@@ -32,13 +32,13 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -48,14 +48,14 @@ jobs:
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@369eb591f429131d6889c46b94e711f089e6ca96
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
uses: docker/build-push-action@48aba3b46d1b1fec4febb7c5d0c644b249a11355
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
+1 -1
View File
@@ -15,6 +15,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Build the Docker image
run: docker build . --file Dockerfile --tag wekan:$(date +%s)
+2 -2
View File
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
@@ -25,6 +25,6 @@ jobs:
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Run chart-releaser
uses: helm/chart-releaser-action@v1.6.0
uses: helm/chart-releaser-action@v1.7.0
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
+10 -10
View File
@@ -18,7 +18,7 @@ jobs:
# runs-on: ubuntu-latest
# steps:
# - name: checkout
# uses: actions/checkout@v4
# uses: actions/checkout@v6
#
# - name: setup node
# uses: actions/setup-node@v1
@@ -42,7 +42,7 @@ jobs:
# needs: [lintcode]
# steps:
# - name: checkout
# uses: actions/checkout@v4
# uses: actions/checkout@v6
#
# - name: setup node
# uses: actions/setup-node@v1
@@ -65,7 +65,7 @@ jobs:
# needs: [lintcode,lintstyle]
# steps:
# - name: checkout
# uses: actions/checkout@v4
# uses: actions/checkout@v6
#
# - name: setup node
# uses: actions/setup-node@v1
@@ -90,12 +90,12 @@ jobs:
# CHECKOUTS
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
# CACHING
- name: Install Meteor
id: cache-meteor-install
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ~/.meteor
key: v1-meteor-${{ hashFiles('.meteor/versions') }}
@@ -104,7 +104,7 @@ jobs:
- name: Cache NPM dependencies
id: cache-meteor-npm
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ~/.npm
key: v1-npm-${{ hashFiles('package-lock.json') }}
@@ -113,7 +113,7 @@ jobs:
- name: Cache Meteor build
id: cache-meteor-build
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: |
.meteor/local/resolver-result-cache.json
@@ -136,7 +136,7 @@ jobs:
run: sh ./test-wekan.sh -cv
- name: Upload coverage
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v6
with:
name: coverage-folder
path: .coverage/
@@ -147,10 +147,10 @@ jobs:
needs: [tests]
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Download coverage
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
name: coverage-folder
path: .coverage/
+3 -3
View File
@@ -52,8 +52,8 @@ ongoworks:speakingurl
raix:handlebar-helpers
http@2.0.0! # force new http package
# Datepicker
wekan-bootstrap-datepicker
# Datepicker (disabled - using native HTML inputs)
# wekan-bootstrap-datepicker
# UI components
ostrio:i18n
@@ -93,4 +93,4 @@ ejson@1.1.3
logging@1.3.3
wekan-fullcalendar
momentjs:moment@2.29.3
wekan-fontawesome
# wekan-fontawesome
-2
View File
@@ -155,8 +155,6 @@ wekan-accounts-cas@0.1.0
wekan-accounts-lockout@1.0.0
wekan-accounts-oidc@1.0.10
wekan-accounts-sandstorm@0.8.0
wekan-bootstrap-datepicker@1.10.0
wekan-fontawesome@6.4.2
wekan-fullcalendar@3.10.5
wekan-ldap@0.0.2
wekan-markdown@1.0.9
+12 -11
View File
@@ -1,12 +1,13 @@
{
"editor.tabSize": 2,
"editor.rulers": [80],
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
"files.exclude": {
"**/.git": true,
"**/.DS_Store": true,
"node_modules": true,
".meteor/local": true
}
}
"terminal.integrated.env.linux": {
"TERM": "xterm-256color"
},
"terminal.integrated.env.osx": {
"TERM": "xterm-256color"
},
"terminal.integrated.env.windows": {
"TERM": "xterm-256color"
},
"terminal.integrated.shell.linux": "/bin/bash",
"terminal.integrated.shellArgs.linux": ["-l"]
}
+1039 -10
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -11,7 +11,7 @@ For all code at WeKan GitHub Organization https://github.com/wekan
## Private reports
- Email support@wekan.team
- Email support@wekan.fi
- Security issues: [SECURITY.md](SECURITY.md)
- License violations
- Anything private, sensitive or negative
+1 -1
View File
@@ -11,7 +11,7 @@ Not paid:
Paid by customers of WeKan Team:
- Commercial Support at https://wekan.team/commercial-support/
- Commercial Support at https://wekan.fi/commercial-support/
- Support
- Private Chat
- Features
+154 -96
View File
@@ -1,76 +1,31 @@
FROM scratch AS downloads
# Download NodeJS bundle
ADD https://static.meteor.com/dev-bundle-node-os/v14.21.4/node-v14.21.4-linux-x64.tar.gz node.tar.gz
# Download Meteor bundle
ADD https://static.meteor.com/packages-bootstrap/2.16/meteor-bootstrap-os.linux.x86_64.tar.gz meteor.tar.gz
FROM ubuntu:24.04 AS build
# Install node from official archive
RUN --mount=type=bind,from=downloads,source=node.tar.gz,target=node.tar.gz \
tar xzf node.tar.gz --strip-components=1 --keep-old-files --no-same-owner
# Install meteor from official archive
RUN --mount=type=bind,from=downloads,source=meteor.tar.gz,target=meteor.tar.gz \
tar xzf meteor.tar.gz -C $HOME --no-same-owner
# Update the npm version
RUN npm install -g npm@6.14.17
# Install several build dependencies
RUN apt update && \
apt install --yes --no-install-suggests --no-install-recommends ssh git g++ curl ca-certificates && \
rm -r /var/lib/apt/lists
# Change to temporary working directory
WORKDIR /workdir
# Copy package.json and package-lock.json to install application dependencies
COPY package.json .
COPY package-lock.json .
# Install build dependencies
RUN $HOME/.meteor/meteor npm install --production
# Copy meteor application configurations
COPY .meteor .meteor
# Copy application sources
COPY packages packages
COPY imports imports
COPY config config
COPY models models
COPY public public
COPY server server
COPY client client
# Build the application
RUN $HOME/.meteor/meteor build --directory /build --allow-superuser
# Enter server bundle directory
WORKDIR /build/bundle/programs/server
# Install server dependencies
RUN $HOME/.meteor/meteor npm install --production
# Enter installation directory of fibers
WORKDIR /build/bundle/programs/server/node_modules/fibers
# Build fibers integrations
RUN node build.js
FROM ubuntu:24.04 AS wekan
# Add metadata to image
FROM ubuntu:24.04
LABEL maintainer="wekan"
LABEL org.opencontainers.image.ref.name="ubuntu"
LABEL org.opencontainers.image.version="24.04"
LABEL org.opencontainers.image.source="https://github.com/wekan/wekan"
# Set default environment variables
ENV WITH_API=true \
# 2022-04-25:
# - gyp does not yet work with Ubuntu 22.04 ubuntu:rolling,
# so changing to 21.10. https://github.com/wekan/wekan/issues/4488
# 2021-09-18:
# - Above Ubuntu base image copied from Docker Hub ubuntu:hirsute-20210825
# to Quay to avoid Docker Hub rate limits.
ARG DEBIAN_FRONTEND=noninteractive
ENV BUILD_DEPS="apt-utils gnupg gosu wget bzip2 g++ curl libarchive-tools build-essential git ca-certificates python3 unzip"
ENV \
DEBUG=false \
NODE_VERSION=v14.21.4 \
METEOR_RELEASE=METEOR@2.14 \
USE_EDGE=false \
METEOR_EDGE=1.5-beta.17 \
NPM_VERSION=6.14.17 \
FIBERS_VERSION=4.0.1 \
ARCHITECTURE=linux-x64 \
SRC_PATH=./ \
WITH_API=true \
RESULTS_PER_PAGE="" \
DEFAULT_BOARD_ID="" \
ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3 \
@@ -111,6 +66,7 @@ ENV WITH_API=true \
OAUTH2_LOGIN_STYLE=redirect \
OAUTH2_CLIENT_ID="" \
OAUTH2_SECRET="" \
OAUTH2_SECRET_FILE="" \
OAUTH2_SERVER_URL="" \
OAUTH2_AUTH_ENDPOINT="" \
OAUTH2_USERINFO_ENDPOINT="" \
@@ -136,6 +92,7 @@ ENV WITH_API=true \
LDAP_AUTHENTIFICATION=false \
LDAP_AUTHENTIFICATION_USERDN="" \
LDAP_AUTHENTIFICATION_PASSWORD="" \
LDAP_AUTHENTIFICATION_PASSWORD_FILE="" \
LDAP_LOG_ENABLED=false \
LDAP_BACKGROUND_SYNC=false \
LDAP_BACKGROUND_SYNC_INTERVAL="" \
@@ -201,38 +158,139 @@ ENV WITH_API=true \
ORACLE_OIM_ENABLED=false \
WAIT_SPINNER="" \
WRITABLE_PATH=/data \
S3=""
S3="" \
MAIL_SERVICE_PASSWORD_FILE="" \
MONGO_PASSWORD_FILE="" \
S3_SECRET_FILE=""
# Install node and application sources from build stage
RUN --mount=type=bind,from=downloads,source=node.tar.gz,target=node.tar.gz \
--mount=type=bind,from=build,source=/build,target=/build \
# Install NodeJS
tar xzf node.tar.gz --strip-components=1 --keep-old-files --no-same-owner && \
# Add the wekan user
useradd --uid 999 --user-group --system wekan && \
# Copy wekan bundle
cp -r /build/bundle /wekan && \
# Remove the web.browser.legacy platform
rm -r /wekan/programs/web.browser.legacy && \
# Change ownership of wekan directory
chown wekan:wekan -R /wekan && \
# Create the data directory
mkdir /data && \
# Change ownership of data directory
chown wekan:root -R /data
# NODE_OPTIONS="--max_old_space_size=4096"
#---------------------------------------------
# == at docker-compose.yml: AUTOLOGIN WITH OIDC/OAUTH2 ====
# https://github.com/wekan/wekan/wiki/autologin
#- OIDC_REDIRECTION_ENABLED=true
#---------------------------------------------------------------------
# Copy the app to the image
#COPY ${SRC_PATH} /home/wekan/app
# Install OS
RUN <<EOR
set -o xtrace
# Add non-root user wekan
useradd --user-group --system --home-dir /home/wekan wekan
# OS dependencies
apt-get update --assume-yes
apt-get install --assume-yes --no-install-recommends ${BUILD_DEPS}
# Meteor installer doesn't work with the default tar binary, so using bsdtar while installing.
# https://github.com/coreos/bugs/issues/1095#issuecomment-350574389
cp $(which tar) $(which tar)~
ln -sf $(which bsdtar) $(which tar)
# Install NodeJS
cd /tmp
# Download nodejs
#wget "https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz"
wget "https://github.com/wekan/node-v14-esm/releases/download/v14.21.4/node-v14.21.4-linux-x64.tar.gz"
#wget "https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/SHASUMS256.txt"
wget "https://github.com/wekan/node-v14-esm/releases/download/v14.21.4/SHASUMS256.txt"
# Verify nodejs authenticity
#grep "node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz" "SHASUMS256.txt" | shasum -a 256 -c -
grep "node-v14.21.4-linux-x64.tar.gz" "SHASUMS256.txt" | shasum -a 256 -c -
rm -f "SHASUMS256.txt"
# Install Node
#tar xzf "node-$NODE_VERSION-$ARCHITECTURE.tar.gz" -C /usr/local --strip-components=1 --no-same-owner
tar xzf "node-v14.21.4-linux-x64.tar.gz" -C /usr/local --strip-components=1 --no-same-owner
#rm "node-$NODE_VERSION-$ARCHITECTURE.tar.gz" "SHASUMS256.txt"
rm "node-v14.21.4-linux-x64.tar.gz" "SHASUMS256.txt"
ln -s "/usr/local/bin/node" "/usr/local/bin/nodejs"
#mkdir -p "/opt/nodejs/lib/node_modules/fibers/.node-gyp" "/root/.node-gyp/${NODE_VERSION} /home/wekan/.config"
#mkdir -p "/opt/nodejs/lib/node_modules/fibers/.node-gyp" "/root/.node-gyp/v14.21.4 /home/wekan/.config"
# Install node dependencies
#npm install -g npm@${NPM_VERSION} --production
npm install -g npm@$6.14.17 --production
chown --recursive wekan:wekan /home/wekan/.config
# Install Meteor
cd /home/wekan
chown --recursive wekan:wekan /home/wekan
echo "Starting meteor ${METEOR_RELEASE} installation... \n"
#gosu wekan:wekan curl https://install.meteor.com/ | /bin/sh
# Specify Meteor version 2.14 to be compatible: https://github.com/wekan/wekan/pull/5816/files
#gosu wekan:wekan npm -g install meteor@2.14 --unsafe-perm
#mv /root/.meteor /home/wekan/
#chown --recursive wekan:wekan /home/wekan/.meteor
#sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' /home/wekan/app/packages/meteor-useraccounts-core/package.js
#cd /home/wekan/.meteor
#gosu wekan:wekan /home/wekan/.meteor/meteor -- help
# Build app (Production)
#cd /home/wekan/app
mkdir -p /home/wekan/app
cd /home/wekan/app
#mkdir -p /home/wekan/.npm
#chown --recursive wekan:wekan /home/wekan/.npm
#chmod u+w *.json
#gosu wekan:wekan meteor npm install --production
#gosu wekan:wekan /home/wekan/.meteor/meteor build --directory /home/wekan/app_build
#cd /home/wekan/app_build/bundle/programs/server/
#chmod u+w *.json
#gosu wekan:wekan meteor npm install --production
#cd node_modules/fibers
#node build.js
#cd ../..
# Remove legacy webbroser bundle, so that Wekan works also at Android Firefox, iOS Safari, etc.
#rm -rf /home/wekan/app_build/bundle/programs/web.browser.legacy
#mv /home/wekan/app_build/bundle /build
wget "https://github.com/wekan/wekan/releases/download/v8.18/wekan-8.18-amd64.zip"
unzip wekan-8.18-amd64.zip
rm wekan-8.18-amd64.zip
mv /home/wekan/app/bundle /build
# Put back the original tar
mv $(which tar)~ $(which tar)
# Cleanup
apt-get remove --purge --assume-yes ${BUILD_DEPS}
#npm uninstall -g api2html
apt-get autoremove --assume-yes
apt-get clean --assume-yes
rm -Rf /tmp/*
rm -Rf /var/lib/apt/lists/*
rm -Rf /var/cache/apt
rm -Rf /var/lib/apt/lists
rm -Rf /home/wekan/app_build
rm -Rf /home/wekan/app
rm -Rf /home/wekan/.meteor
mkdir /data
chown wekan --recursive /data
EOR
# Change to the wekan user
USER wekan
# Set the port to listen on
ENV PORT=8080
EXPOSE ${PORT}
EXPOSE $PORT
# Define the stop signal for node
STOPSIGNAL SIGINT
STOPSIGNAL SIGKILL
WORKDIR /home/wekan/app
# Declare a volume on /data
VOLUME /data
# Set startup command
CMD ["node", "/wekan/main.js"]
#---------------------------------------------------------------------
# https://github.com/wekan/wekan/issues/3585#issuecomment-1021522132
# Add more Node heap:
# NODE_OPTIONS="--max_old_space_size=4096"
# Add more stack:
# bash -c "ulimit -s 65500; exec node --stack-size=65500 main.js"
#---------------------------------------------------------------------
#
# CMD ["node", "/build/main.js"]
# CMD ["bash", "-c", "ulimit -s 65500; exec node --stack-size=65500 /build/main.js"]
# CMD ["bash", "-c", "ulimit -s 65500; exec node --stack-size=65500 --max-old-space-size=8192 /build/main.js"]
CMD ["bash", "-c", "ulimit -s 65500; exec node /build/main.js"]
+6 -1
View File
@@ -53,7 +53,12 @@ ENV QEMU_ARCHITECTURE=aarch64 \
WITH_API=true \
PORT=8080 \
ROOT_URL=http://localhost \
MONGO_URL=mongodb://127.0.0.1:27017/wekan
MONGO_URL=mongodb://127.0.0.1:27017/wekan \
LDAP_AUTHENTIFICATION_PASSWORD_FILE="" \
OAUTH2_SECRET_FILE="" \
MAIL_SERVICE_PASSWORD_FILE="" \
MONGO_PASSWORD_FILE="" \
S3_SECRET_FILE=""
# Copy qemu-static to image
COPY --from=builder qemu-${QEMU_ARCHITECTURE}-static /usr/bin
+6 -1
View File
@@ -56,7 +56,12 @@ ENV QEMU_ARCHITECTURE=s390x \
WITH_API=true \
PORT=8080 \
ROOT_URL=http://localhost \
MONGO_URL=mongodb://127.0.0.1:27017/wekan
MONGO_URL=mongodb://127.0.0.1:27017/wekan \
LDAP_AUTHENTIFICATION_PASSWORD_FILE="" \
OAUTH2_SECRET_FILE="" \
MAIL_SERVICE_PASSWORD_FILE="" \
MONGO_PASSWORD_FILE="" \
S3_SECRET_FILE=""
# Copy qemu-static to image
COPY --from=builder qemu-${QEMU_ARCHITECTURE}-static /usr/bin
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2014-2024 The Wekan Team
Copyright (c) Lauri Ojansivu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+23 -26
View File
@@ -1,10 +1,8 @@
[Gitpod Ready-to-Code](https://gitpod.io/#https://github.com/wekan/wekan)
# WeKan ® - Open Source kanban
## Downloads
https://wekan.github.io / Install WeKan ® Server
https://wekan.fi/install/
## Docker Containers
@@ -16,7 +14,7 @@ docker-compose.yml at https://github.com/wekan/wekan/blob/main/docker-compose.ym
## Standards
- [WeKan and Standard for Public Code](https://wekan.github.io/standard-for-public-code/) assessment was made at 2023-11.
- [WeKan and Standard for Public Code](https://wekan.fi/standard-for-public-code/) assessment was made at 2023-11.
Currently Wekan meets 8 out of 16 criteria out of the box.
Some others could be met with small changes.
@@ -27,21 +25,19 @@ docker-compose.yml at https://github.com/wekan/wekan/blob/main/docker-compose.ym
- [Open Hub](https://www.openhub.net/p/wekan)
- [OSS Insight](https://ossinsight.io/analyze/wekan/wekan)
## [Translate WeKan ® at Transifex](https://app.transifex.com/wekan/)
## Translate WeKan ® at Transifex
Translations to non-English languages are accepted only at [Transifex](https://app.transifex.com/wekan/wekan) using webbrowser.
New English strings of new features can be added as PRs to master branch file wekan/imports/i18n/data/en.i18n.json .
## [WeKan ® feature requests and bugs](https://github.com/wekan/wekan/issues)
## WeKan ® feature requests and bugs
Please add most of your questions as GitHub issue: [WeKan ® Feature Requests and Bugs](https://github.com/wekan/wekan/issues).
It's better than at chat where details get lost when chat scrolls up.
## Chat
## Discussions
[Discussions][discussions] - WeKan Community GitHub Discussions, that are not [Feature Requests and Bugs](https://github.com/wekan/wekan/issues).
[WeKan IRC FAQ](https://github.com/wekan/wekan/wiki/IRC-FAQ)
[IRC](https://github.com/wekan/wekan/blob/main/docs/FAQ/IRC-FAQ.md)
## Docker: Latest tag has newest release
@@ -51,8 +47,8 @@ See bottom of https://github.com/wekan/wekan/issues/3874
## FAQ
**NOTE**:
- Please read the [FAQ](https://github.com/wekan/wekan/wiki/FAQ) first
- Please don't feed the [trolls](https://github.com/wekan/wekan/wiki/FAQ#why-am-i-called-a-troll) and [spammers](https://github.com/wekan/wekan/wiki/FAQ#why-am-i-called-a-spammer) that are mentioned in the FAQ :)
- Please read the [FAQ](https://github.com/wekan/wekan/blob/main/docs/FAQ/FAQ.md) first
- Please don't feed the [trolls](https://github.com/wekan/wekan/blob/main/docs/FAQ/FAQ.md#why-am-i-called-a-troll) and [spammers](https://github.com/wekan/wekan/blob/main/docs/FAQ/FAQ.md#why-am-i-called-a-spammer) that are mentioned in the FAQ :)
## About WeKan ®
@@ -72,14 +68,11 @@ that by providing one-click installation on various platforms.
- WeKan ® largest user has 30k users using WeKan ® in their company.
- WeKan ® has been [translated](https://app.transifex.com/wekan/) to about 105 languages.
- [Features][features]: WeKan ® has real-time user interface.
- [Platforms][platforms]: WeKan ® supports many platforms.
- [Platforms](https://wekan.fi/install/): WeKan ® supports many platforms.
WeKan ® is critical part of new platforms Wekan is currently being integrated to.
## Requirements
- 64bit: Linux [Snap](https://github.com/wekan/wekan-snap/wiki/Install) or [Sandstorm](https://sandstorm.io) /
[Mac](https://github.com/wekan/wekan/wiki/Mac) / [Windows](https://github.com/wekan/wekan/wiki/Install-Wekan-from-source-on-Windows).
[More Platforms](https://github.com/wekan/wekan/wiki/Platforms), bundle for RasPi3 ARM and other CPUs where Node.js and MongoDB exists.
- 1 GB RAM minimum free for WeKan ®. Production server should have minimum total 4 GB RAM.
For thousands of users, for example with [Docker](https://github.com/wekan/wekan/blob/main/docker-compose.yml): 3 frontend servers,
each having 2 CPU and 2 wekan-app containers. One backend wekan-db server with many CPUs.
@@ -89,7 +82,7 @@ that by providing one-click installation on various platforms.
WeKan ® on Sandstorm is not usually affected by any Standalone WeKan ® (Snap/Docker/Source) security issues.
- [Reporting all new bugs immediately](https://github.com/wekan/wekan/issues).
New features and fixes are added to WeKan ® [many times a day](https://github.com/wekan/wekan/blob/main/CHANGELOG.md).
- [Backups](https://github.com/wekan/wekan/wiki/Backup) of WeKan ® database once a day miminum.
- [Backups](https://github.com/wekan/wekan/blob/main/docs/Backup/Backup.md) of WeKan ® database once a day miminum.
Bugs, updates, users deleting list or card, harddrive full, harddrive crash etc can eat your data. There is no undo yet.
Some bug can cause WeKan ® board to not load at all, requiring manual fixing of database content.
@@ -102,14 +95,22 @@ that by providing one-click installation on various platforms.
- There is many companies and individuals contributing code to WeKan ®, to add features and bugfixes
[many times a day](https://github.com/wekan/wekan/blob/main/CHANGELOG.md).
- [Please add Add new Feature Requests and Bug Reports immediately](https://github.com/wekan/wekan/issues).
- [Commercial Support](https://wekan.team/commercial-support/).
- [Commercial Support](https://wekan.fi/commercial-support/).
We also welcome sponsors for features and bugfixes.
By working directly with WeKan ® you get the benefit of active maintenance and new features added by growing WeKan ® developer community.
## Getting Started with Development
The default branch uses [Meteor 2 with Node.js 14](https://wekan.fi/install/).
To contribute, [create a fork](https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Build-and-Create-Pull-Request.md#2-create-fork-of-httpsgithubcomwekanwekan-at-github-web-page) and run `./rebuild-wekan.sh` (or `./rebuild-wekan.bat` on Windows) as detailed [here](https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Build-and-Create-Pull-Request.md#3-select-option-1-to-install-dependencies-and-then-enter). Once you're ready, please test your code and [submit a pull request (PR)](https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Build-and-Create-Pull-Request.md#7-test).
Please refer to the [developer documentation](https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Developer-Documentation.md) for more information.
## Screenshot
[More screenshots at Features page](https://github.com/wekan/wekan/wiki/Features)
[More screenshots at Features page](https://github.com/wekan/wekan/tree/main/docs/Features)
[![Screenshot of WeKan ®][screenshot_wekan]][roadmap_wekan]
@@ -118,16 +119,12 @@ By working directly with WeKan ® you get the benefit of active maintenance and
WeKan ® is released under the very permissive [MIT license](LICENSE), and made
with [Meteor](https://www.meteor.com).
[platforms]: https://github.com/wekan/wekan/wiki/Platforms
[dev_docs]: https://github.com/wekan/wekan/wiki/Developer-Documentation
[screenshot_wekan]: https://wekan.github.io/wekan-dark-mode.png
[dev_docs]: https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Developer-Documentation.md
[screenshot_wekan]: https://wekan.fi/wekan-dark-mode.png
[features]: https://github.com/wekan/wekan/wiki/Features
[roadmap_wekan]: https://boards.wekan.team/b/D2SzJKZDS4Z48yeQH/wekan-open-source-kanban-board-with-mit-license
[wekan_issues]: https://github.com/wekan/wekan/issues
[wekan_issues]: https://github.com/wekan/wekan/issues
[docker_image]: https://hub.docker.com/r/wekanteam/wekan/
[wekan_wiki]: https://github.com/wekan/wekan/wiki
[translate_wekan]: https://app.transifex.com/wekan/
[translate_wekan]: https://app.transifex.com/wekan/wekan/
[open_source]: https://en.wikipedia.org/wiki/Open-source_software
[free_software]: https://en.wikipedia.org/wiki/Free_software
[discussions]: https://github.com/wekan/wekan/discussions
+113
View File
@@ -0,0 +1,113 @@
# 🔐 WeKan — Login System Overview
This document provides a detailed overview of WeKan’s **login and authentication system**, covering client-side UI, server-side logic, external authentication methods, and potential upgrade paths.
---
## 🖥️ Login Web UI
WeKan's login interface is implemented using a combination of:
- `layouts.jade` – Login HTML structure
- `layouts.js` – Login logic and interactivity
- `layouts.css` – Styling and layout
📁 Source: [`client/components/main`](https://github.com/wekan/wekan/tree/main/client/components/main)
---
## ⚙️ Server-Side Authentication
Server-side login functionality is handled in:
- [`server/authentication.js`](https://github.com/wekan/wekan/blob/main/server/authentication.js)
Other related configurations:
- 🔧 Account config: [`config/accounts.js`](https://github.com/wekan/wekan/blob/main/config/accounts.js)
- 📨 Sign-up invitations: [`models/settings.js#L275`](https://github.com/wekan/wekan/blob/main/models/settings.js#L275)
- 👤 User creation logic: [`models/users.js#L1339`](https://github.com/wekan/wekan/blob/main/models/users.js#L1339)
---
## 👥 Meteor User Accounts
WeKan utilizes Meteor’s `accounts` system. Relevant resources:
- 📚 Meteor 2.x Accounts Docs: [v2-docs.meteor.com/api/accounts](https://v2-docs.meteor.com/api/accounts)
- 🔍 Meteor Packages:
- [`packages`](https://github.com/wekan/wekan/blob/main/.meteor/packages)
- [`versions`](https://github.com/wekan/wekan/blob/main/.meteor/versions)
- 📦 Meteor 2.14 core packages: [Meteor 2.14 packages](https://github.com/meteor/meteor/tree/release/METEOR%402.14/packages)
---
## 🔐 External Authentication (OIDC, LDAP, etc.)
WeKan supports external authentication methods via internal packages.
📁 See [`packages/`](https://github.com/wekan/wekan/tree/main/packages) for:
- OpenID Connect (OIDC)
- LDAP
- OAuth and other integrations
---
## 📦 NPM & AtmosphereJS Dependencies
- 🔗 `package.json`: [Dependencies list](https://github.com/wekan/wekan/blob/main/package.json)
- 🧩 WekanTeam scoped NPM packages: [@wekanteam on npm](https://www.npmjs.com/search?q=%40wekanteam)
- ☁️ AtmosphereJS Meteor packages: [atmospherejs.com](https://atmospherejs.com)
---
## 🚧 Meteor Version & Upgrade Notes
- 📌 Current Version: **Meteor 2.14**
- [`.meteor/release`](https://github.com/wekan/wekan/blob/main/.meteor/release)
- 🔧 Maintained with only **critical fixes** until ~Summer 2025
- 🚀 Migration to **Meteor 3** or a new framework is under consideration
📘 Meteor 3 API: [docs.meteor.com/api/accounts](https://docs.meteor.com/api/accounts)
---
## 🧪 Prototypes & Examples
### 🐘 PHP Prototype Sign-Up
Used in experimental versions:
- Step 1: [`sign-up1.php`](https://github.com/wekan/php/blob/main/page/sign-up1.php)
- Step 2: [`sign-up2.php`](https://github.com/wekan/php/blob/main/page/sign-up2.php)
- Main entry: [`index.php#L72-L83`](https://github.com/wekan/php/blob/main/public/index.php#L72-L83)
---
### 🎨 WeKan Studio Prototype
Sign-up logic in the **WeKan Studio** version:
- [`signUp.fmt`](https://github.com/wekan/wekanstudio/blob/main/srv/templates/login/signUp.fmt)
---
## 📎 Future Considerations
- Upgrading to **Meteor 3.x**
- Refactoring frontend logic to fix translation rendering order
- Exploring **simplified authentication systems** in future prototypes
---
## 🔗 Project Links
- 🔧 Main Repo: [github.com/wekan/wekan](https://github.com/wekan/wekan)
- 🌐 Website: [wekan.github.io](https://wekan.github.io)
- 📚 Documentation: [Wekan Wiki](https://github.com/wekan/wekan/wiki)
---
---
+68 -18
View File
@@ -1,11 +1,20 @@
About money, see [CONTRIBUTING.md](CONTRIBUTING.md)
Security is very important to us. If you discover any issue regarding security, please disclose
the information responsibly by sending an email to security@wekan.team and not by
creating a GitHub issue. We will respond swiftly to fix verifiable security issues.
## Responsible Security Disclosure
We thank you with a place at our hall of fame page, that is
at https://wekan.github.io/hall-of-fame
- To send email, use [ProtonMail](https://proton.me) email address or use PGP key [security-at-wekan.fi.asc](security-at-wekan.fi.asc)
- Send info about security issue ONLY to security@wekan.fi (that is Protomail email address). NOT TO ANYWHERE ELSE. NO CC, NO BCC.
- Wait for new WeKan release that fixes security issue
- If you approve, we thank you by adding you to Hall of Fame: https://wekan.fi/hall-of-fame/
## Bonus Points
- If you include code for fixing security issue
## Losing Points
- If you ask about [bounty](CONTRIBUTING.md). There is no bounty. WeKan is NOT Big Tech. WeKan is FLOSS.
- If you forget to include vulnerability details.
- If you send info about security issue to somewhere else than security@wekan.fi
## How should reports be formatted?
@@ -25,7 +34,7 @@ CWSS (optional): %cwss
Anyone who reports a unique security issue in scope and does not disclose it to
a third party before we have patched and updated may be upon their approval
added to the Wekan Hall of Fame.
added to the WeKan Hall of Fame https://wekan.fi/hall-of-fame/
## Which domains are in scope?
@@ -62,11 +71,6 @@ and by by companies that have 30k users.
- If you are thinking about TLS MITM, look at https://github.com/caddyserver/caddy/issues/2530
- Let's Encrypt TLS requires publicly accessible webserver, that Let's Encrypt TLS validation servers check.
- If firewall limits to only allowed IP addresses, you may need non-Let's Encrypt TLS cert.
- For On Premise:
- https://caddyserver.com/docs/automatic-https#local-https
- https://github.com/wekan/wekan/wiki/Caddy-Webserver-Config
- https://github.com/wekan/wekan/wiki/Azure
- https://github.com/wekan/wekan/wiki/Traefik-and-self-signed-SSL-certs
## XSS
@@ -171,6 +175,57 @@ Meteor.startup(() => {
- https://github.com/wekan/wekan/blob/main/client/components/cards/attachments.js#L303-L312
- https://wekan.github.io/hall-of-fame/filebleed/
### Attachments: Forced download to prevent stored XSS
- To prevent browser-side execution of uploaded content under the app origin, all attachment downloads are served with safe headers:
- `Content-Type: application/octet-stream`
- `Content-Disposition: attachment`
- `X-Content-Type-Options: nosniff`
- A restrictive `Content-Security-Policy` with `sandbox`
- This means attachments are downloaded instead of rendered inline by default. This mitigates HTML/JS/SVG based stored XSS vectors.
- Avatars and inline images remain supported but SVG uploads are blocked and never rendered inline.
## Users: Client update restrictions
- Client-side updates to user documents are limited to safe fields only:
- `username`
- `profile.*`
- Sensitive fields are blocked from any client updates and can only be modified by server methods with authorization:
- `orgs`, `teams`, `roles`, `isAdmin`, `createdThroughApi`, `loginDisabled`, `authenticationMethod`, `services.*`, `emails.*`, `sessionData.*`
- Attempts to update forbidden fields from the client are denied.
- Admin operations like managing org/team membership or toggling flags must use server methods that check permissions.
## Voting: integrity and authorization
- Client updates to card `vote` fields are blocked to prevent forged votes and inconsistent policy enforcement.
- Voting is performed via a server method that enforces:
- Authentication and board membership, or an explicit per-card flag allowing non-members to vote.
- Only the caller's own userId is added/removed from `vote.positive`/`vote.negative`.
- This prevents members from fabricating other users' votes and ensures non-members cannot vote unless explicitly allowed.
## Planning Poker: integrity and authorization
- Client updates to card `poker` fields are blocked. All poker actions go through server methods that enforce:
- Authentication and board membership for configuration and results.
- For casting a poker vote, either board membership or an explicit per-card flag allowing non-members to participate.
- Only the caller's own userId is added/removed from the selected estimation bucket (e.g., one, two, five, etc.).
- Methods cover setting/unsetting poker question/end, casting votes, replaying, and setting final estimation.
## Attachment API: authentication and DoS prevention
- The attachment API (`/api/attachment/*`) requires proper authentication using `X-User-Id` and `X-Auth-Token` headers.
- Authentication validates tokens by hashing with `Accounts._hashLoginToken` and matching against stored login tokens, preventing identity spoofing.
- Request handlers implement:
- 30-second timeout to prevent hanging connections.
- Request body size limits (50MB for uploads, 10MB for metadata operations).
- Proper error handling and guaranteed response completion.
- Request error event handlers to clean up failed connections.
- This prevents:
- DoS attacks via concurrent unauthenticated or malformed requests.
- Identity spoofing by using arbitrary bearer tokens or user IDs.
- Resource exhaustion from hanging connections or excessive payloads.
- Access control: all attachment operations verify board membership before allowing access.
## Brute force login protection
- https://github.com/wekan/wekan/commit/23e5e1e3bd081699ce39ce5887db7e612616014d
@@ -217,9 +272,4 @@ Typical already known or "no impact" bugs such as:
- Email spoofing, SPF, DMARC & DKIM. Wekan does not include email server.
Wekan is Open Source with MIT license, and free to use also for commercial use.
We welcome all fixes to improve security by email to security@wekan.team
## Bonus Points
If your Responsible Security Disclosure includes code for fixing security issue,
you get bonus points, as seen on [Hall of Fame](https://wekan.github.io/hall-of-fame).
We welcome all fixes to improve security by email to security@wekan.fi
+1 -1
View File
@@ -1,5 +1,5 @@
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
appVersion: "v7.80.0"
appVersion: "v8.18.0"
files:
userUploads:
- README.md
+276
View File
@@ -39,6 +39,12 @@ If *nix: chmod +x api.py => ./api.py users
python3 api.py addcustomfieldtoboard AUTHORID BOARDID NAME TYPE SETTINGS SHOWONCARD AUTOMATICALLYONCARD SHOWLABELONMINICARD SHOWSUMATTOPOFLIST # Add Custom Field to Board
python3 api.py editcustomfield BOARDID LISTID CARDID CUSTOMFIELDID NEWCUSTOMFIELDVALUE # Edit Custom Field
python3 api.py listattachments BOARDID # List attachments
python3 api.py uploadattachment BOARDID SWIMLANEID LISTID CARDID FILEPATH [STORAGE_BACKEND] # Upload attachment to card
python3 api.py downloadattachment ATTACHMENTID OUTPUTPATH # Download attachment to local file
python3 api.py attachmentinfo ATTACHMENTID # Get attachment information
python3 api.py listcardattachments BOARDID SWIMLANEID LISTID CARDID # List attachments for specific card
python3 api.py copymoveattachment ATTACHMENTID TARGETBOARDID TARGETSWIMLANEID TARGETLISTID TARGETCARDID [copy|move] # Copy or move attachment
python3 api.py deleteattachment ATTACHMENTID # Delete attachment
python3 api.py cardsbyswimlane SWIMLANEID LISTID # Retrieve cards list on a swimlane
python3 api.py getcard BOARDID LISTID CARDID # Get card info
python3 api.py addlabel BOARDID LISTID CARDID LABELID # Add label to a card
@@ -750,3 +756,273 @@ if arguments == 1:
data2 = body.text.replace('}',"}\n")
print(data2)
# ------- LIST OF PUBLIC BOARDS END -----------
# ------- NEW ATTACHMENT API ENDPOINTS START -----------
if sys.argv[1] == 'uploadattachment':
# ------- UPLOAD ATTACHMENT START -----------
if arguments < 5:
print("Usage: python3 api.py uploadattachment BOARDID SWIMLANEID LISTID CARDID FILEPATH [STORAGE_BACKEND]")
print("Storage backends: fs, gridfs, s3")
exit(1)
boardid = sys.argv[2]
swimlaneid = sys.argv[3]
listid = sys.argv[4]
cardid = sys.argv[5]
filepath = sys.argv[6]
storage_backend = sys.argv[7] if arguments > 6 else None
# Read file and convert to base64
try:
with open(filepath, 'rb') as f:
file_data = f.read()
import base64
base64_data = base64.b64encode(file_data).decode('utf-8')
except FileNotFoundError:
print(f"Error: File '{filepath}' not found")
exit(1)
except Exception as e:
print(f"Error reading file: {e}")
exit(1)
# Get file info
import os
filename = os.path.basename(filepath)
import mimetypes
file_type = mimetypes.guess_type(filepath)[0] or 'application/octet-stream'
# Prepare request data
upload_data = {
'boardId': boardid,
'swimlaneId': swimlaneid,
'listId': listid,
'cardId': cardid,
'fileData': base64_data,
'fileName': filename,
'fileType': file_type
}
if storage_backend:
upload_data['storageBackend'] = storage_backend
# Make API call
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey), 'Content-Type': 'application/json'}
upload_url = wekanurl + 'api/attachment/upload'
try:
response = requests.post(upload_url, headers=headers, json=upload_data)
response.raise_for_status()
result = response.json()
print(f"Upload successful!")
print(f"Attachment ID: {result.get('attachmentId')}")
print(f"File: {result.get('fileName')}")
print(f"Size: {result.get('fileSize')} bytes")
print(f"Storage: {result.get('storageBackend')}")
except requests.exceptions.RequestException as e:
print(f"Upload failed: {e}")
if hasattr(e, 'response') and e.response is not None:
print(f"Response: {e.response.text}")
# ------- UPLOAD ATTACHMENT END -----------
if sys.argv[1] == 'downloadattachment':
# ------- DOWNLOAD ATTACHMENT START -----------
if arguments < 3:
print("Usage: python3 api.py downloadattachment ATTACHMENTID OUTPUTPATH")
exit(1)
attachmentid = sys.argv[2]
outputpath = sys.argv[3]
# Make API call
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
download_url = wekanurl + f'api/attachment/download/{attachmentid}'
try:
response = requests.get(download_url, headers=headers)
response.raise_for_status()
result = response.json()
if result.get('success'):
# Decode base64 data and save to file
import base64
file_data = base64.b64decode(result.get('base64Data'))
with open(outputpath, 'wb') as f:
f.write(file_data)
print(f"Download successful!")
print(f"File saved to: {outputpath}")
print(f"Original filename: {result.get('fileName')}")
print(f"Size: {result.get('fileSize')} bytes")
print(f"Storage: {result.get('storageBackend')}")
else:
print(f"Download failed: {result.get('message', 'Unknown error')}")
except requests.exceptions.RequestException as e:
print(f"Download failed: {e}")
if hasattr(e, 'response') and e.response is not None:
print(f"Response: {e.response.text}")
# ------- DOWNLOAD ATTACHMENT END -----------
if sys.argv[1] == 'attachmentinfo':
# ------- ATTACHMENT INFO START -----------
if arguments < 2:
print("Usage: python3 api.py attachmentinfo ATTACHMENTID")
exit(1)
attachmentid = sys.argv[2]
# Make API call
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
info_url = wekanurl + f'api/attachment/info/{attachmentid}'
try:
response = requests.get(info_url, headers=headers)
response.raise_for_status()
result = response.json()
if result.get('success'):
print("=== ATTACHMENT INFO ===")
print(f"Attachment ID: {result.get('attachmentId')}")
print(f"File Name: {result.get('fileName')}")
print(f"File Size: {result.get('fileSize')} bytes")
print(f"File Type: {result.get('fileType')}")
print(f"Storage Backend: {result.get('storageBackend')}")
print(f"Board ID: {result.get('boardId')}")
print(f"Swimlane ID: {result.get('swimlaneId')}")
print(f"List ID: {result.get('listId')}")
print(f"Card ID: {result.get('cardId')}")
print(f"Created At: {result.get('createdAt')}")
print(f"Is Image: {result.get('isImage')}")
print(f"Versions: {len(result.get('versions', []))}")
else:
print(f"Failed to get attachment info: {result.get('message', 'Unknown error')}")
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
if hasattr(e, 'response') and e.response is not None:
print(f"Response: {e.response.text}")
# ------- ATTACHMENT INFO END -----------
if sys.argv[1] == 'listcardattachments':
# ------- LIST CARD ATTACHMENTS START -----------
if arguments < 5:
print("Usage: python3 api.py listcardattachments BOARDID SWIMLANEID LISTID CARDID")
exit(1)
boardid = sys.argv[2]
swimlaneid = sys.argv[3]
listid = sys.argv[4]
cardid = sys.argv[5]
# Make API call
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
list_url = wekanurl + f'api/attachment/list/{boardid}/{swimlaneid}/{listid}/{cardid}'
try:
response = requests.get(list_url, headers=headers)
response.raise_for_status()
result = response.json()
if result.get('success'):
attachments = result.get('attachments', [])
print(f"=== CARD ATTACHMENTS ({len(attachments)}) ===")
for attachment in attachments:
print(f"ID: {attachment.get('attachmentId')}")
print(f"Name: {attachment.get('fileName')}")
print(f"Size: {attachment.get('fileSize')} bytes")
print(f"Type: {attachment.get('fileType')}")
print(f"Storage: {attachment.get('storageBackend')}")
print(f"Created: {attachment.get('createdAt')}")
print("---")
else:
print(f"Failed to list attachments: {result.get('message', 'Unknown error')}")
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
if hasattr(e, 'response') and e.response is not None:
print(f"Response: {e.response.text}")
# ------- LIST CARD ATTACHMENTS END -----------
if sys.argv[1] == 'copymoveattachment':
# ------- COPY/MOVE ATTACHMENT START -----------
if arguments < 6:
print("Usage: python3 api.py copymoveattachment ATTACHMENTID TARGETBOARDID TARGETSWIMLANEID TARGETLISTID TARGETCARDID [copy|move]")
exit(1)
attachmentid = sys.argv[2]
targetboardid = sys.argv[3]
targetswimlaneid = sys.argv[4]
targetlistid = sys.argv[5]
targetcardid = sys.argv[6]
operation = sys.argv[7] if arguments > 6 else 'copy'
if operation not in ['copy', 'move']:
print("Operation must be 'copy' or 'move'")
exit(1)
# Prepare request data
request_data = {
'attachmentId': attachmentid,
'targetBoardId': targetboardid,
'targetSwimlaneId': targetswimlaneid,
'targetListId': targetlistid,
'targetCardId': targetcardid
}
# Make API call
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey), 'Content-Type': 'application/json'}
api_url = wekanurl + f'api/attachment/{operation}'
try:
response = requests.post(api_url, headers=headers, json=request_data)
response.raise_for_status()
result = response.json()
if result.get('success'):
print(f"{operation.capitalize()} successful!")
if operation == 'copy':
print(f"Source Attachment ID: {result.get('sourceAttachmentId')}")
print(f"New Attachment ID: {result.get('newAttachmentId')}")
else:
print(f"Attachment ID: {result.get('attachmentId')}")
print(f"Source Board: {result.get('sourceBoardId')}")
print(f"Target Board: {result.get('targetBoardId')}")
print(f"File: {result.get('fileName')}")
print(f"Size: {result.get('fileSize')} bytes")
else:
print(f"{operation.capitalize()} failed: {result.get('message', 'Unknown error')}")
except requests.exceptions.RequestException as e:
print(f"{operation.capitalize()} failed: {e}")
if hasattr(e, 'response') and e.response is not None:
print(f"Response: {e.response.text}")
# ------- COPY/MOVE ATTACHMENT END -----------
if sys.argv[1] == 'deleteattachment':
# ------- DELETE ATTACHMENT START -----------
if arguments < 2:
print("Usage: python3 api.py deleteattachment ATTACHMENTID")
exit(1)
attachmentid = sys.argv[2]
# Make API call
headers = {'Accept': 'application/json', 'Authorization': 'Bearer {}'.format(apikey)}
delete_url = wekanurl + f'api/attachment/delete/{attachmentid}'
try:
response = requests.delete(delete_url, headers=headers)
response.raise_for_status()
result = response.json()
if result.get('success'):
print("Delete successful!")
print(f"Attachment ID: {result.get('attachmentId')}")
print(f"File: {result.get('fileName')}")
else:
print(f"Delete failed: {result.get('message', 'Unknown error')}")
except requests.exceptions.RequestException as e:
print(f"Delete failed: {e}")
if hasattr(e, 'response') and e.response is not None:
print(f"Response: {e.response.text}")
# ------- DELETE ATTACHMENT END -----------
# ------- NEW ATTACHMENT API ENDPOINTS END -----------
+75
View File
@@ -4,3 +4,78 @@ if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/pwa-service-worker.js');
});
}
// Import board converter for on-demand conversion
import '/client/lib/boardConverter';
import '/client/components/boardConversionProgress';
// Import migration manager and progress UI
import '/client/lib/migrationManager';
import '/client/components/migrationProgress';
// Import cron settings
import '/client/components/settings/cronSettings';
// Mirror Meteor login token into a cookie for server-side file route auth
// This enables cookie-based auth for /cdn/storage/* without leaking ROOT_URL
// Token already lives in localStorage; cookie adds same-origin send-on-request semantics
Meteor.startup(() => {
const COOKIE_NAME = 'meteor_login_token';
const cookieAttrs = () => {
const attrs = ['Path=/', 'SameSite=Lax'];
try {
if (window.location && window.location.protocol === 'https:') {
attrs.push('Secure');
}
} catch (_) {}
return attrs.join('; ');
};
const setCookie = (name, value) => {
if (!value) return;
document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; ${cookieAttrs()}`;
};
const clearCookie = (name) => {
document.cookie = `${encodeURIComponent(name)}=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; ${cookieAttrs()}`;
};
const syncCookie = () => {
try {
const token = Accounts && typeof Accounts._storedLoginToken === 'function' ? Accounts._storedLoginToken() : null;
if (token) setCookie(COOKIE_NAME, token); else clearCookie(COOKIE_NAME);
} catch (e) {
// ignore
}
};
// Initial sync on startup
syncCookie();
// Keep cookie in sync on login/logout
if (Accounts && typeof Accounts.onLogin === 'function') Accounts.onLogin(syncCookie);
if (Accounts && typeof Accounts.onLogout === 'function') Accounts.onLogout(syncCookie);
// Sync across tabs/windows when localStorage changes
window.addEventListener('storage', (ev) => {
if (ev && typeof ev.key === 'string' && ev.key.indexOf('Meteor.loginToken') !== -1) {
syncCookie();
}
});
});
// Subscribe to per-user small publications
Meteor.startup(() => {
Tracker.autorun(() => {
if (Meteor.userId()) {
Meteor.subscribe('userGreyIcons');
}
});
// Initialize mobile mode on startup for iOS devices
// This ensures mobile mode is applied correctly on page load
Tracker.afterFlush(() => {
if (typeof Utils !== 'undefined' && Utils.initializeUserSettings) {
Utils.initializeUserSettings();
}
});
});
+14 -14
View File
@@ -1,16 +1,16 @@
.activity-title {
margin: 0 0.5em 0.8em;
margin: 0 0.7vw 1vh;
display: flex;
justify-content: space-between;
}
.reactions-popup .add-comment-reaction {
display: inline-block;
cursor: pointer;
border-radius: 5px;
font-size: 22px;
border-radius: 0.7vw;
font-size: clamp(18px, 4vw, 22px);
text-align: center;
line-height: 30px;
width: 40px;
line-height: 1.3;
width: 5vw;
}
.reactions-popup .add-comment-reaction:hover {
background-color: #b0c4de;
@@ -19,13 +19,13 @@
clear: both;
}
.activities .activity {
margin: 0.5px 0;
padding: 6px 0;
margin: 0.1vh 0;
padding: 0.8vh 0;
display: flex;
}
.activities .activity .member {
width: 32px;
height: 32px;
width: 4vw;
height: 4vw;
}
.activities .activity .activity-member {
font-weight: 700;
@@ -36,18 +36,18 @@
flex: 1;
align-self: center;
margin: 0;
margin-left: 3px;
margin-left: 0.4vw;
overflow: hidden;
word-break: break-word;
}
.activities .activity .activity-desc .activity-comment {
display: block;
border-radius: 3px;
border-radius: 0.4vw;
background: #fff;
text-decoration: none;
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
margin-top: 5px;
padding: 5px;
box-shadow: 0 0.2vh 0.3vh rgba(0,0,0,0.2);
margin-top: 0.7vh;
padding: 0.7vh 0.7vw;
}
.activities .activity .activity-desc .activity-checklist {
display: block;
+8 -11
View File
@@ -1,5 +1,6 @@
import { ReactiveCache } from '/imports/reactiveCache';
import DOMPurify from 'dompurify';
import { sanitizeHTML, sanitizeText } from '/imports/lib/secureDOMPurify';
import { TAPi18n } from '/imports/i18n';
const activitiesPerPage = 500;
@@ -216,15 +217,11 @@ BlazeComponent.extendComponent({
{
href: source.url,
},
DOMPurify.sanitize(source.system, {
ALLOW_UNKNOWN_PROTOCOLS: true,
}),
sanitizeHTML(source.system),
),
);
} else {
return DOMPurify.sanitize(source.system, {
ALLOW_UNKNOWN_PROTOCOLS: true,
});
return sanitizeHTML(source.system);
}
}
return null;
@@ -248,10 +245,10 @@ BlazeComponent.extendComponent({
href: `${attachment.link()}?download=true`,
target: '_blank',
},
DOMPurify.sanitize(attachment.name),
sanitizeText(attachment.name),
),
)) ||
DOMPurify.sanitize(this.currentData().activity.attachmentName)
sanitizeText(this.currentData().activity.attachmentName)
);
},
@@ -265,7 +262,7 @@ BlazeComponent.extendComponent({
Template.activity.helpers({
sanitize(value) {
return DOMPurify.sanitize(value, { ALLOW_UNKNOWN_PROTOCOLS: true });
return sanitizeHTML(value);
},
});
@@ -336,7 +333,7 @@ function createCardLink(card, board) {
href: card.originRelativeUrl(),
class: 'action-card',
},
DOMPurify.sanitize(text, { ALLOW_UNKNOWN_PROTOCOLS: true }),
sanitizeHTML(text),
),
)
);
@@ -353,7 +350,7 @@ function createBoardLink(board, list) {
href: board.originRelativeUrl(),
class: 'action-board',
},
DOMPurify.sanitize(text, { ALLOW_UNKNOWN_PROTOCOLS: true }),
sanitizeHTML(text),
),
)
);
+5 -8
View File
@@ -108,15 +108,12 @@
text-decoration: none;
height: 24px;
}
.comments .comment .comment-desc .reactions .open-comment-reaction-popup i.fa.fa-smile-o {
font-size: 17px;
.comments .comment .comment-desc .reactions .open-comment-reaction-popup span {
display: inline-block;
font-size: clamp(14px, 2vw, 18px);
font-weight: 500;
margin-left: 2px;
}
.comments .comment .comment-desc .reactions .open-comment-reaction-popup i.fa.fa-plus {
font-size: 8px;
margin-top: -7px;
margin-left: 1px;
line-height: 1;
margin-left: 4px;
}
.comments .comment .comment-desc .reactions .reaction {
cursor: pointer;
+3 -3
View File
@@ -25,7 +25,7 @@ template(name="comment")
= text
.edit-controls
button.primary(type="submit") {{_ 'edit'}}
.fa.fa-times-thin.js-close-inlined-form
a.js-close-inlined-form(title="{{_ 'close' }}") ❌
else
.comment-text
+viewer
@@ -55,8 +55,8 @@ template(name="commentReactions")
span.reaction-count #{reaction.userIds.length}
if (currentUser.isBoardMember)
a.open-comment-reaction-popup(title="{{_ 'addReactionPopup-title'}}")
i.fa.fa-smile-o
i.fa.fa-plus
span(title="{{_ 'reaction' }}") 😀
span(title="{{_ 'add' }}") ➕
template(name="addReactionPopup")
.reactions-popup
@@ -0,0 +1,184 @@
/* Board Conversion Progress Styles */
.board-conversion-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
z-index: 9999;
display: none;
align-items: center;
justify-content: center;
}
.board-conversion-overlay.active {
display: flex;
}
.board-conversion-modal {
background: white;
border-radius: 8px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
max-width: 500px;
width: 90%;
max-height: 80vh;
overflow: hidden;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.board-conversion-header {
padding: 20px 24px 16px;
border-bottom: 1px solid #e0e0e0;
text-align: center;
}
.board-conversion-header h3 {
margin: 0 0 8px 0;
color: #333;
font-size: 20px;
font-weight: 500;
}
.board-conversion-header h3 i {
margin-right: 8px;
color: #2196F3;
}
.board-conversion-header p {
margin: 0;
color: #666;
font-size: 14px;
}
.board-conversion-content {
padding: 24px;
}
.conversion-progress {
margin-bottom: 20px;
}
.progress-bar {
width: 100%;
height: 8px;
background-color: #e0e0e0;
border-radius: 4px;
overflow: hidden;
margin-bottom: 8px;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #2196F3, #21CBF3);
border-radius: 4px;
transition: width 0.3s ease;
position: relative;
}
.progress-fill::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.3),
transparent
);
animation: shimmer 2s infinite;
}
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
.progress-text {
text-align: center;
font-weight: 600;
color: #2196F3;
font-size: 16px;
}
.conversion-status {
text-align: center;
margin-bottom: 16px;
color: #333;
font-size: 16px;
}
.conversion-status i {
margin-right: 8px;
color: #2196F3;
}
.conversion-time {
text-align: center;
color: #666;
font-size: 14px;
background-color: #f5f5f5;
padding: 8px 12px;
border-radius: 4px;
margin-bottom: 16px;
}
.conversion-time i {
margin-right: 6px;
color: #FF9800;
}
.board-conversion-footer {
padding: 16px 24px 20px;
border-top: 1px solid #e0e0e0;
background-color: #f9f9f9;
}
.conversion-info {
text-align: center;
color: #666;
font-size: 13px;
line-height: 1.4;
}
.conversion-info i {
margin-right: 6px;
color: #2196F3;
}
/* Responsive design */
@media (max-width: 600px) {
.board-conversion-modal {
width: 95%;
margin: 20px;
}
.board-conversion-header,
.board-conversion-content,
.board-conversion-footer {
padding-left: 16px;
padding-right: 16px;
}
.board-conversion-header h3 {
font-size: 18px;
}
}
@@ -0,0 +1,27 @@
template(name="boardConversionProgress")
.board-conversion-overlay(class="{{#if isConverting}}active{{/if}}")
.board-conversion-modal
.board-conversion-header
h3
| ⚙️
| {{_ 'converting-board'}}
p {{_ 'converting-board-description'}}
.board-conversion-content
.conversion-progress
.progress-bar
.progress-fill(style="width: {{conversionProgress}}%")
.progress-text {{conversionProgress}}%
.conversion-status
| ⚙️
| {{conversionStatus}}
.conversion-time(style="{{#unless conversionEstimatedTime}}display: none;{{/unless}}")
| ⏰
| {{_ 'estimated-time-remaining'}}: {{conversionEstimatedTime}}
.board-conversion-footer
.conversion-info
| ℹ️
| {{_ 'conversion-info-text'}}
@@ -0,0 +1,37 @@
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import {
boardConverter,
isConverting,
conversionProgress,
conversionStatus,
conversionEstimatedTime
} from '/client/lib/boardConverter';
Template.boardConversionProgress.helpers({
isConverting() {
return isConverting.get();
},
conversionProgress() {
return conversionProgress.get();
},
conversionStatus() {
return conversionStatus.get();
},
conversionEstimatedTime() {
return conversionEstimatedTime.get();
}
});
Template.boardConversionProgress.onCreated(function() {
// Subscribe to conversion state changes
this.autorun(() => {
isConverting.get();
conversionProgress.get();
conversionStatus.get();
conversionEstimatedTime.get();
});
});
+3 -3
View File
@@ -1,6 +1,6 @@
template(name="archivedBoards")
h2
i.fa.fa-archive
span(title="{{_ 'archived-boards'}}") 📦
| {{_ 'archived-boards'}}
ul.archived-lists
@@ -8,10 +8,10 @@ template(name="archivedBoards")
li.archived-lists-item
div.board-header-btns
button.board-header-btn.js-delete-board
i.fa.fa-trash-o
| 🗑️
| {{_ 'delete-board'}}
button.board-header-btn.js-restore-board
i.fa.fa-undo
| ↩️
| {{_ 'restore-board'}}
= title
span {{ moment archivedAt 'LLL' }}
+356 -4
View File
@@ -6,6 +6,27 @@
bottom: 0;
overflow-x: hidden;
overflow-y: hidden;
width: 100%;
min-width: 100%;
}
/* When zoom is 50% or lower, ensure full width like content */
.board-wrapper[style*="transform: scale(0.5)"] {
width: 100% !important;
max-width: 100% !important;
margin: 0 !important;
}
.board-wrapper[style*="transform: scale(0.4)"] {
width: 100% !important;
max-width: 100% !important;
margin: 0 !important;
}
.board-wrapper[style*="transform: scale(0.3)"] {
width: 100% !important;
max-width: 100% !important;
margin: 0 !important;
}
.board-wrapper .board-canvas {
position: absolute;
@@ -15,6 +36,243 @@
bottom: 0;
transition: margin 0.1s;
overflow-y: auto;
width: 100%;
min-width: 100%;
}
/* Ensure horizontal scrollbar is visible for high zoom levels */
.board-wrapper .board-canvas[style*="overflow-x: auto"] {
overflow-x: auto !important;
overflow-y: auto !important;
}
/* Style the scrollbar for better visibility */
.board-wrapper .board-canvas::-webkit-scrollbar {
height: 12px;
width: 12px;
}
.board-wrapper .board-canvas::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 6px;
}
.board-wrapper .board-canvas::-webkit-scrollbar-thumb {
background: #888;
border-radius: 6px;
}
.board-wrapper .board-canvas::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* Ensure content area can scroll horizontally for high zoom levels */
#content[style*="overflow-x: auto"] {
overflow-x: auto !important;
overflow-y: auto !important;
}
/* Style content area scrollbar */
#content::-webkit-scrollbar {
height: 12px;
width: 12px;
}
#content::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 6px;
}
#content::-webkit-scrollbar-thumb {
background: #888;
border-radius: 6px;
}
#content::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* Ensure scrollbars are always visible for high zoom levels */
#content[style*="overflow-x: auto"] {
position: relative;
}
#content[style*="overflow-x: auto"]::-webkit-scrollbar {
height: 12px;
width: 12px;
}
/* Force vertical scrollbar to always be visible */
#content[style*="overflow-y: auto"] {
overflow-y: scroll !important;
}
/* Mobile - make all text 2x bigger inside #content by default (icons stay same size) */
@media screen and (max-width: 800px),
screen and (max-device-width: 800px),
screen and (-webkit-min-device-pixel-ratio: 2) and (max-width: 800px),
screen and (max-width: 800px) and (orientation: portrait),
screen and (max-width: 800px) and (orientation: landscape) {
#content {
font-size: 2em !important; /* 2x bigger base font size for content area */
}
/* Make all text elements 2x bigger */
#content h1, #content h2, #content h3, #content h4, #content h5, #content h6,
#content p, #content span, #content div, #content a, #content button,
#content .minicard, #content .list-header-name, #content .board-header-btn,
#content .card-title, #content .card-details, #content .card-description,
#content .swimlane-header, #content .list-title, #content .card-text,
#content .member, #content .member-name, #content .member-initials,
#content .checklist-item, #content .checklist-title, #content .comment,
#content .activity, #content .activity-text, #content .activity-time,
#content .board-title, #content .board-description, #content .list-name,
#content .card-text, #content .card-title, #content .card-description,
#content .swimlane-title, #content .swimlane-description,
#content .board-header-title, #content .board-header-description,
#content .card-detail-title, #content .card-detail-description,
#content .list-header-title, #content .list-header-description,
#content .swimlane-header-title, #content .swimlane-header-description,
#content .minicard-title, #content .minicard-description,
#content .card-comment, #content .card-comment-text,
#content .checklist-item-text, #content .checklist-item-title,
#content .activity-item, #content .activity-item-text,
#content .board-member, #content .board-member-name,
#content .team-member, #content .team-member-name,
#content .org-member, #content .org-member-name,
#content .template-member, #content .template-member-name,
#content .user-name, #content .user-email, #content .user-role,
#content .setting-title, #content .setting-description,
#content .popup-title, #content .popup-description,
#content .modal-title, #content .modal-description,
#content .notification-title, #content .notification-text,
#content .announcement-title, #content .announcement-text,
#content .offline-warning-title, #content .offline-warning-text,
#content .error-title, #content .error-text,
#content .success-title, #content .success-text,
#content .info-title, #content .info-text,
#content .warning-title, #content .warning-text {
font-size: 1em !important; /* Use inherited 2x scaling */
}
/* Keep icons the same size (don't scale them) */
#content .fa, #content .icon, #content i {
font-size: 1em !important; /* Keep original icon size */
}
/* Reset specific icon sizes to prevent double scaling */
#content .fa-home, #content .fa-bars, #content .fa-search,
#content .fa-bell, #content .fa-user, #content .fa-cog,
#content .fa-plus, #content .fa-minus, #content .fa-edit,
#content .fa-trash, #content .fa-save, #content .fa-cancel,
#content .fa-arrow-left, #content .fa-arrow-right,
#content .fa-arrow-up, #content .fa-arrow-down,
#content .fa-check, #content .fa-times, #content .fa-close,
#content .fa-star, #content .fa-heart, #content .fa-thumbs-up,
#content .fa-thumbs-down, #content .fa-comment, #content .fa-reply,
#content .fa-share, #content .fa-download, #content .fa-upload,
#content .fa-copy, #content .fa-paste, #content .fa-cut,
#content .fa-undo, #content .fa-redo, #content .fa-refresh,
#content .fa-sync, #content .fa-spinner, #content .fa-loading,
#content .fa-info, #content .fa-question, #content .fa-exclamation,
#content .fa-warning, #content .fa-error, #content .fa-success,
#content .fa-check-circle, #content .fa-times-circle,
#content .fa-exclamation-circle, #content .fa-question-circle,
#content .fa-info-circle, #content .fa-warning-circle,
#content .fa-error-circle, #content .fa-success-circle {
font-size: 1em !important; /* Keep original icon size */
}
}
/* Fallback for iPhone devices using JavaScript detection */
.iphone-device #content {
font-size: 2em !important; /* 2x bigger base font size for content area */
}
.iphone-device #content h1, .iphone-device #content h2, .iphone-device #content h3, .iphone-device #content h4, .iphone-device #content h5, .iphone-device #content h6,
.iphone-device #content p, .iphone-device #content span, .iphone-device #content div, .iphone-device #content a, .iphone-device #content button,
.iphone-device #content .minicard, .iphone-device #content .list-header-name, .iphone-device #content .board-header-btn,
.iphone-device #content .card-title, .iphone-device #content .card-details, .iphone-device #content .card-description,
.iphone-device #content .swimlane-header, .iphone-device #content .list-title, .iphone-device #content .card-text,
.iphone-device #content .member, .iphone-device #content .member-name, .iphone-device #content .member-initials,
.iphone-device #content .checklist-item, .iphone-device #content .checklist-title, .iphone-device #content .comment,
.iphone-device #content .activity, .iphone-device #content .activity-text, .iphone-device #content .activity-time,
.iphone-device #content .board-title, .iphone-device #content .board-description, .iphone-device #content .list-name,
.iphone-device #content .card-text, .iphone-device #content .card-title, .iphone-device #content .card-description,
.iphone-device #content .swimlane-title, .iphone-device #content .swimlane-description,
.iphone-device #content .board-header-title, .iphone-device #content .board-header-description,
.iphone-device #content .card-detail-title, .iphone-device #content .card-detail-description,
.iphone-device #content .list-header-title, .iphone-device #content .list-header-description,
.iphone-device #content .swimlane-header-title, .iphone-device #content .swimlane-header-description,
.iphone-device #content .minicard-title, .iphone-device #content .minicard-description,
.iphone-device #content .card-comment, .iphone-device #content .card-comment-text,
.iphone-device #content .checklist-item-text, .iphone-device #content .checklist-item-title,
.iphone-device #content .activity-item, .iphone-device #content .activity-item-text,
.iphone-device #content .board-member, .iphone-device #content .board-member-name,
.iphone-device #content .team-member, .iphone-device #content .team-member-name,
.iphone-device #content .org-member, .iphone-device #content .org-member-name,
.iphone-device #content .template-member, .iphone-device #content .template-member-name,
.iphone-device #content .user-name, .iphone-device #content .user-email, .iphone-device #content .user-role,
.iphone-device #content .setting-title, .iphone-device #content .setting-description,
.iphone-device #content .popup-title, .iphone-device #content .popup-description,
.iphone-device #content .modal-title, .iphone-device #content .modal-description,
.iphone-device #content .notification-title, .iphone-device #content .notification-text,
.iphone-device #content .announcement-title, .iphone-device #content .announcement-text,
.iphone-device #content .offline-warning-title, .iphone-device #content .offline-warning-text,
.iphone-device #content .error-title, .iphone-device #content .error-text,
.iphone-device #content .success-title, .iphone-device #content .success-text,
.iphone-device #content .info-title, .iphone-device #content .info-text,
.iphone-device #content .warning-title, .iphone-device #content .warning-text {
font-size: 1em !important; /* Use inherited 2x scaling */
}
/* Keep icons the same size for iPhone devices */
.iphone-device #content .fa, .iphone-device #content .icon, .iphone-device #content i {
font-size: 1em !important; /* Keep original icon size */
}
/* Mobile iPhone: scale card details text and icons to 2x */
body.mobile-mode.iphone-device .card-details {
font-size: 2em !important;
}
body.mobile-mode.iphone-device .card-details .fa,
body.mobile-mode.iphone-device .card-details .icon,
body.mobile-mode.iphone-device .card-details i,
body.mobile-mode.iphone-device .card-details .emoji-icon,
body.mobile-mode.iphone-device .card-details a,
body.mobile-mode.iphone-device .card-details p,
body.mobile-mode.iphone-device .card-details span,
body.mobile-mode.iphone-device .card-details div,
body.mobile-mode.iphone-device .card-details button,
body.mobile-mode.iphone-device .card-details input,
body.mobile-mode.iphone-device .card-details select,
body.mobile-mode.iphone-device .card-details textarea {
font-size: inherit !important;
}
/* Section titles slightly larger than content but not as big as card title */
body.mobile-mode.iphone-device .card-details .card-details-item-title {
font-size: 1.1em !important;
font-weight: bold;
}
/* Ensure scrollbars are positioned correctly */
#content[style*="overflow-x: auto"]::-webkit-scrollbar:vertical {
width: 12px;
}
#content[style*="overflow-x: auto"]::-webkit-scrollbar:horizontal {
height: 12px;
}
/* Force both scrollbars to always be visible for high zoom levels */
#content[style*="overflow-x: auto"][style*="overflow-y: auto"] {
overflow-x: scroll !important;
overflow-y: scroll !important;
}
/* Ensure the right scrollbar is always visible on the inner right edge */
#content[style*="overflow-y: auto"] {
scrollbar-gutter: stable;
}
.board-wrapper .board-canvas .board-overlay {
position: fixed;
@@ -29,19 +287,106 @@
animation: fadeIn 0.2s;
z-index: 16;
}
/* Fix for mobile Safari: ensure overlay stays behind card details */
@media screen and (max-width: 800px) {
.board-wrapper .board-canvas .board-overlay {
z-index: 17 !important;
}
/* In desktop mode on small screens, still keep overlay behind card */
body.desktop-mode .board-wrapper .board-canvas .board-overlay {
z-index: 17 !important;
}
}
/* In mobile mode, lower the overlay z-index to stay behind card details */
body.mobile-mode .board-wrapper .board-canvas .board-overlay {
z-index: 17 !important;
}
/* iPhone in desktop mode: remove overlay to avoid blocking card */
body.desktop-mode.iphone-device .board-wrapper .board-canvas .board-overlay {
display: none !important;
pointer-events: none !important;
}
/* Desktop mode: hide overlay to allow multiple cards and board interaction */
body.desktop-mode .board-wrapper .board-canvas .board-overlay {
display: none !important;
pointer-events: none !important;
}
.board-wrapper .board-canvas.is-dragging-active .open-minicard-composer,
.board-wrapper .board-canvas.is-dragging-active .minicard-wrapper.is-checked {
display: none;
}
@media screen and (max-width: 800px) {
/* Mobile view styles - applied when isMiniScreen is true (iPhone, etc.) */
.board-wrapper.mobile-view {
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
left: 0 !important;
right: 0 !important;
overflow-x: hidden !important;
overflow-y: auto !important;
}
.board-wrapper.mobile-view .board-canvas {
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
left: 0 !important;
right: 0 !important;
overflow-x: hidden !important;
overflow-y: auto !important;
}
.board-wrapper.mobile-view .board-canvas.mobile-view .swimlane {
border-bottom: 1px solid #ccc;
display: block !important;
flex-direction: column;
margin: 0;
padding: 0;
overflow-x: hidden !important;
overflow-y: auto;
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
}
@media screen and (max-width: 800px),
screen and (max-device-width: 932px) and (-webkit-min-device-pixel-ratio: 3) {
.board-wrapper {
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
left: 0 !important;
right: 0 !important;
overflow-x: hidden !important;
overflow-y: auto !important;
}
.board-wrapper .board-canvas {
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
left: 0 !important;
right: 0 !important;
overflow-x: hidden !important;
overflow-y: auto !important;
}
.board-wrapper .board-canvas .swimlane {
border-bottom: 1px solid #ccc;
display: flex;
display: block !important;
flex-direction: column;
margin: 0;
padding: 0 0px 0px 0;
overflow-x: hidden;
padding: 0;
overflow-x: hidden !important;
overflow-y: auto;
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
}
}
.calendar-event-green {
@@ -219,3 +564,10 @@
font-size: 25px;
cursor: pointer;
}
/* Global file drag over state for board canvas */
.board-canvas.file-drag-over {
background-color: rgba(0, 123, 255, 0.05) !important;
border: 2px dashed #007bff !important;
transition: all 0.2s ease;
}
+34 -7
View File
@@ -1,5 +1,10 @@
template(name="board")
if isBoardReady.get
if isMigrating.get
+migrationProgress
else if isConverting.get
+boardConversionProgress
else if isBoardReady.get
if currentBoard
if onlyShowCurrentCard
+cardDetails(currentCard)
@@ -16,13 +21,18 @@ template(name="boardBody")
if notDisplayThisBoard
| {{_ 'tableVisibilityMode-allowPrivateOnly'}}
else
.board-wrapper(class=currentBoard.colorClass)
// Debug information (remove in production)
if debugBoardState
.debug-info(style="position: fixed; top: 0; left: 0; background: rgba(0,0,0,0.8); color: white; padding: 10px; z-index: 9999; font-size: 12px;")
| {{_ 'board'}}: {{currentBoard.title}} | {{_ 'view'}}: {{boardView}} | {{_ 'has-swimlanes'}}: {{hasSwimlanes}} | {{_ 'swimlanes'}}: {{currentBoard.swimlanes.length}}
.board-wrapper(class=currentBoard.colorClass class="{{#if isMiniScreen}}mobile-view{{/if}}")
.board-canvas.js-swimlanes(
class="{{#if hasSwimlanes}}dragscroll{{/if}}"
class="{{#if Sidebar.isOpen}}is-sibling-sidebar-open{{/if}}"
class="{{#if MultiSelection.isActive}}is-multiselection-active{{/if}}"
class="{{#if draggingActive.get}}is-dragging-active{{/if}}"
class="{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}")
class="{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}"
class="{{#if isMiniScreen}}mobile-view{{/if}}")
if showOverlay.get
.board-overlay
if currentBoard.isTemplatesBoard
@@ -33,15 +43,25 @@ template(name="boardBody")
each currentBoard.swimlanes
+swimlane(this)
else
a.js-empty-board-add-swimlane(title="{{_ 'add-swimlane'}}")
h1.big-message.quiet
| {{_ 'add-swimlane'}} +
// Fallback: If no swimlanes exist, show lists instead of empty message
+listsGroup(currentBoard)
else if isViewLists
+listsGroup(currentBoard)
else if isViewCalendar
+calendarView
else if isViewGantt
+ganttView
else
+listsGroup(currentBoard)
// Default view - show swimlanes if they exist, otherwise show lists
if hasSwimlanes
each currentBoard.swimlanes
+swimlane(this)
else
+listsGroup(currentBoard)
//- Render multiple open cards in desktop mode
unless isMiniScreen
each openCards
+cardDetails(this cardIndex=@index)
+sidebar
template(name="calendarView")
@@ -50,3 +70,10 @@ template(name="calendarView")
if currentCard
+cardDetails(currentCard)
+fullcalendar(calendarOptions)
template(name="ganttView")
if isViewGantt
.gantt-view.swimlane
if currentCard
+cardDetails(currentCard)
.gantt-container
#gantt-chart
File diff suppressed because it is too large. Load diff
+37 -23
View File
@@ -1,4 +1,4 @@
/* ===============
/* ===============
THEME - NEPHRITIS
=================*/
@@ -99,7 +99,7 @@ THEME - NEPHRITIS
/* === END NEPHRITIS THEME === */
/* ===============
/* ===============
THEME - Pomegranate
=================*/
@@ -200,7 +200,7 @@ THEME - Pomegranate
/* === END Pomegranate THEME === */
/* ===============
/* ===============
THEME - Belize
=================*/
@@ -301,7 +301,7 @@ THEME - Belize
/* === END Belize THEME === */
/* ===============
/* ===============
THEME - Wisteria
=================*/
@@ -402,7 +402,7 @@ THEME - Wisteria
/* === END Wisteria THEME === */
/* ===============
/* ===============
THEME - Midnight
=================*/
@@ -503,7 +503,7 @@ THEME - Midnight
/* === END Midnight THEME === */
/* ===============
/* ===============
THEME - Pumpkin
=================*/
@@ -604,7 +604,7 @@ THEME - Pumpkin
/* === END Pumpkin THEME === */
/* ===============
/* ===============
THEME - Moderate Pink
=================*/
@@ -705,7 +705,7 @@ THEME - Moderate Pink
/* === END Moderatepink THEME === */
/* ===============
/* ===============
THEME - Strong Cyan
=================*/
@@ -806,7 +806,7 @@ THEME - Strong Cyan
/* === END Strongcyan THEME === */
/* ===============
/* ===============
THEME - Lime Green
=================*/
@@ -907,7 +907,7 @@ THEME - Lime Green
/* === END Limegreen THEME === */
/* ===============
/* ===============
THEME - Dark
=================*/
@@ -1111,7 +1111,7 @@ THEME - Dark
/* === END Dark THEME === */
/* ===============
/* ===============
THEME - Relax
=================*/
@@ -1241,7 +1241,7 @@ THEME - Relax
/* === END Relax THEME === */
/* ===============
/* ===============
THEME - Corteza
=================*/
@@ -1342,7 +1342,7 @@ THEME - Corteza
/* === END Corteza THEME === */
/* ===============
/* ===============
THEME - Clear Blue
=================*/
@@ -1609,7 +1609,7 @@ THEME - Clear Blue
/* === END Clearblue THEME === */
/* ===============
/* ===============
THEME - Natural
=================*/
@@ -1719,7 +1719,7 @@ THEME - Natural
/* === END Natural THEME === */
/* ===============
/* ===============
THEME - Modern
=================*/
@@ -2006,7 +2006,7 @@ THEME - Modern
/* === END Modern THEME === */
/* ===============
/* ===============
THEME - Modern Dark
=================*/
@@ -2496,7 +2496,7 @@ THEME - Modern Dark
/* === END ModernDark THEME === */
/* ===============
/* ===============
THEME - Exodark
=================*/
@@ -2544,8 +2544,8 @@ THEME - Exodark
background: #1b1b1b;
}
.board-color-exodark .materialCheckBox.is-checked {
border-bottom: 2px solid #222;
border-right: 2px solid #222;
border-bottom: 2px solid #dbdbdb!important;/*Fix contrast of checkbox*/
border-right: 2px solid #dbdbdb!important;
}
.board-color-exodark .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard {
background: #e9e9e9;
@@ -2744,7 +2744,7 @@ THEME - Exodark
/* === END Exodark THEME === */
/* ===============
/* ===============
THEME - Clean Dark
=================*/
@@ -2811,6 +2811,7 @@ THEME - Clean Dark
.board-color-cleandark .card-details {
background: #23232B;
scrollbar-color: #ffffff #2e2e39;
border-radius: 20px;
box-shadow: none;
}
@@ -3179,10 +3180,17 @@ THEME - Clean Dark
margin-top: 8px;
}
/* ===============
.board-color-cleandark.background-box {
background-color:#23232B;
} /*Fixes contrast issues with background box in theme selection list*/
/* ===============
THEME - Clean Light
=================*/
/* Please note Clean Light theme elements also contain references to some cleandark theme elements so if unable to find code you're looking for under CleanDark it might be here. This should probably be cleaned up*/
.board-color-cleanlight.background-box {
background-color:#e0e0e0;
color:#010101!important;
} /*Fixes issues with text colour/background box being similar no contrast */
.board-color-cleanlight {
background: #E0E0E0;
@@ -3314,7 +3322,13 @@ THEME - Clean Light
.board-color-cleanlight .list .list-header .list-header-menu .js-collapse,
.board-color-cleandark .list .list-header .list-header-menu .js-collapse {
display: none;
/* Make collapse button visible in Clean Light / Clean Dark themes.
Previously this was hidden which caused the missing Collapse button
when using these themes. Use inline-block so it lines up with other
header controls. */
display: inline-block;
vertical-align: middle;
color: inherit;
}
.board-color-cleanlight .list-header-add,
@@ -3485,7 +3499,7 @@ THEME - Clean Light
.board-color-cleandark .card-details-header {
background: #2E2E39 !important;
color: rgba(255, 255, 255, 1);
color: #FFF !important;
}
.board-color-cleanlight .card-details-header .card-details-title,
+923 -7
View File
@@ -1,5 +1,5 @@
.integration-form {
padding: 5px;
padding: 0.7vh 0.7vw;
border-bottom: 1px solid #ccc;
}
.flex,
@@ -12,12 +12,928 @@
display: flex;
}
.option {
-webkit-border-radius: 3px;
border-radius: 3px;
-webkit-border-radius: 0.4vw;
border-radius: 0.4vw;
background: #fff;
text-decoration: none;
-webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.2);
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
margin-top: 5px;
padding: 5px;
-webkit-box-shadow: 0 0.2vh 0.3vh rgba(0,0,0,0.2);
box-shadow: 0 0.2vh 0.3vh rgba(0,0,0,0.2);
margin-top: 0.7vh;
padding: 0.7vh 0.7vw;
}
/* Zoom and Mobile Mode Controls */
.board-header-btns.center {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
}
.zoom-controls {
display: flex;
align-items: center;
gap: 0.5vw;
background: rgba(255, 255, 255, 0.9);
padding: 0.5vh 1vw;
border-radius: 0.5vw;
box-shadow: 0 0.2vh 0.5vh rgba(0,0,0,0.1);
}
.zoom-controls .board-header-btn {
padding: 0.5vh 0.8vw !important;
border-radius: 0.3vw !important;
background: #fff !important;
border: 1px solid #000 !important;
transition: all 0.2s ease !important;
color: #000 !important;
height: auto !important;
line-height: normal !important;
margin: 0 !important;
float: none !important;
overflow: visible !important;
}
.zoom-controls .board-header-btn i {
color: #000 !important;
float: none !important;
display: inline !important;
line-height: normal !important;
margin: 0 !important;
}
.zoom-controls .board-header-btn:hover {
background: #000 !important;
border-color: #000 !important;
color: #fff !important;
}
.zoom-controls .board-header-btn:hover i {
color: #fff !important;
}
.zoom-controls .board-header-btn.is-active {
background: #0079bf;
color: white;
border-color: #005a8a;
}
.zoom-controls .board-header-btn.is-active i {
color: white;
}
.zoom-level {
font-weight: bold;
color: #333;
min-width: 3vw;
text-align: center;
font-size: clamp(12px, 2vw, 14px);
cursor: pointer;
padding: 0.3vh 0.5vw;
border-radius: 0.3vw;
transition: all 0.2s ease;
}
.zoom-level:hover {
background: #f0f0f0;
color: #000;
}
/* Mobile Mode Styles */
.mobile-mode .board-wrapper {
width: 100%;
height: 100%;
}
.mobile-mode .board-canvas {
height: 100%;
}
.mobile-mode .minicard {
font-size: clamp(16px, 4vw, 20px);
padding: 1.2vh 1.5vw 0.5vh;
min-height: 3vh;
}
.mobile-mode .list-header-name {
font-size: clamp(18px, 4.5vw, 24px);
}
.mobile-mode .board-header-btn {
padding: 1vh 1.5vw;
font-size: clamp(14px, 3.5vw, 18px);
}
.mobile-mode .zoom-controls {
padding: 1vh 1.5vw;
gap: 1vw;
}
.mobile-mode .zoom-controls .board-header-btn {
padding: 1vh 1.5vw !important;
font-size: clamp(14px, 3.5vw, 18px) !important;
background: #fff !important;
border: 1px solid #000 !important;
color: #000 !important;
height: auto !important;
line-height: normal !important;
margin: 0 !important;
float: none !important;
overflow: visible !important;
}
.mobile-mode .zoom-controls .board-header-btn i {
color: #000 !important;
float: none !important;
display: inline !important;
line-height: normal !important;
margin: 0 !important;
}
.mobile-mode .zoom-controls .board-header-btn:hover {
background: #000 !important;
border-color: #000 !important;
color: #fff !important;
}
.mobile-mode .zoom-controls .board-header-btn:hover i {
color: #fff !important;
}
.mobile-mode .zoom-level {
font-size: clamp(14px, 3.5vw, 18px);
min-width: 4vw;
}
/* Comprehensive Mobile Mode Styles - Works on all screen sizes */
.mobile-mode .board-wrapper {
width: 100% !important;
height: 100% !important;
transform: none !important;
transform-origin: initial !important;
max-width: 100% !important;
}
.mobile-mode .board-canvas {
height: 100% !important;
overflow-x: hidden !important;
overflow-y: auto !important;
width: 100% !important;
max-width: 100% !important;
}
.mobile-mode .swimlane {
width: 100% !important;
min-width: 100% !important;
max-width: 100% !important;
margin-bottom: 2rem !important;
display: block !important;
float: none !important;
}
.mobile-mode .swimlane-header {
width: 100% !important;
min-width: 100% !important;
max-width: 100% !important;
font-size: clamp(18px, 2.5vw, 32px) !important;
padding: 1rem !important;
margin-bottom: 1rem !important;
display: block !important;
}
.mobile-mode .list {
width: 100% !important;
min-width: 100% !important;
max-width: 100% !important;
display: block !important;
float: none !important;
margin-bottom: 2rem !important;
border-left: none !important;
border-bottom: 2px solid #ccc !important;
clear: both !important;
}
.mobile-mode .list-header {
width: 100% !important;
min-width: 100% !important;
max-width: 100% !important;
padding: 1rem !important;
font-size: clamp(18px, 2.5vw, 32px) !important;
display: grid !important;
grid-template-columns: 30px 1fr auto auto !important;
gap: 10px !important;
align-items: center !important;
position: relative !important;
}
.mobile-mode .list-header .list-header-name {
font-size: clamp(18px, 2.5vw, 32px) !important;
font-weight: bold !important;
grid-row: 1 !important;
grid-column: 2 !important;
align-self: end !important;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
}
.mobile-mode .list-header .cardCount {
font-size: clamp(14px, 2vw, 24px) !important;
grid-row: 2 !important;
grid-column: 2 !important;
align-self: start !important;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
}
.mobile-mode .list-header .list-header-menu-icon {
position: static !important;
right: auto !important;
top: auto !important;
transform: none !important;
grid-row: 1/3 !important;
grid-column: 3 !important;
padding: 14px !important;
font-size: clamp(24px, 3vw, 48px) !important;
text-align: center !important;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
}
.mobile-mode .list-header .list-header-handle {
position: static !important;
right: auto !important;
top: auto !important;
transform: none !important;
grid-row: 1/3 !important;
grid-column: 4 !important;
padding: 14px !important;
font-size: clamp(28px, 3.5vw, 56px) !important;
text-align: center !important;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
}
.mobile-mode .list-body {
width: 100% !important;
min-width: 100% !important;
max-width: 100% !important;
padding: 1rem !important;
display: block !important;
}
.mobile-mode .minicard {
width: 100% !important;
min-width: 100% !important;
max-width: 100% !important;
font-size: clamp(16px, 2vw, 24px) !important;
padding: 1.2vh 1.5vw 0.5vh !important;
min-height: 3vh !important;
margin-bottom: 0.5rem !important;
display: block !important;
float: none !important;
}
.mobile-mode .minicard .minicard-title {
font-size: clamp(16px, 2vw, 24px) !important;
font-weight: bold !important;
}
.mobile-mode .minicard .minicard-members {
font-size: clamp(12px, 1.5vw, 18px) !important;
}
.mobile-mode .minicard .minicard-lists {
font-size: clamp(12px, 1.5vw, 18px) !important;
}
/* Desktop Mode Styles */
.desktop-mode .board-wrapper {
width: auto !important;
height: auto !important;
}
.desktop-mode .swimlane {
width: auto !important;
min-width: auto !important;
}
.desktop-mode .list {
width: auto !important;
min-width: auto !important;
display: flex !important;
float: left !important;
margin-bottom: 0 !important;
border-left: 1px solid #ccc !important;
border-bottom: none !important;
}
.desktop-mode .list-header {
width: auto !important;
min-width: auto !important;
padding: 2.5vh 1.5vw 0.5vh !important;
font-size: clamp(14px, 3vw, 18px) !important;
display: block !important;
}
.desktop-mode .list-header .list-header-name {
font-size: clamp(14px, 3vw, 18px) !important;
display: inline !important;
grid-row: auto !important;
grid-column: auto !important;
align-self: auto !important;
}
.desktop-mode .list-header .cardCount {
font-size: 12px !important;
grid-row: auto !important;
grid-column: auto !important;
align-self: auto !important;
}
.desktop-mode .list-header .list-header-menu-icon {
position: absolute !important;
right: 60px !important;
top: 50% !important;
transform: translateY(-50%) !important;
grid-row: auto !important;
grid-column: auto !important;
padding: 14px !important;
font-size: 40px !important;
}
.desktop-mode .list-header .list-header-handle {
position: absolute !important;
right: 10px !important;
top: 50% !important;
transform: translateY(-50%) !important;
grid-row: auto !important;
grid-column: auto !important;
padding: 7px !important;
font-size: clamp(16px, 3vw, 20px) !important;
}
.desktop-mode .list-body {
width: auto !important;
min-width: auto !important;
padding: 5px 11px !important;
}
.desktop-mode .minicard {
width: auto !important;
min-width: auto !important;
font-size: clamp(12px, 2.5vw, 16px) !important;
padding: 0.5vh 0.8vw !important;
min-height: auto !important;
margin-bottom: 9px !important;
}
.desktop-mode .minicard .minicard-title {
font-size: clamp(12px, 2.5vw, 16px) !important;
}
.desktop-mode .minicard .minicard-members {
font-size: 10px !important;
}
.desktop-mode .minicard .minicard-lists {
font-size: 10px !important;
}
/* Additional Mobile Mode Styles for Other Elements - Works on all screen sizes */
.mobile-mode .swimlane-header .swimlane-title {
font-size: clamp(18px, 2.5vw, 32px) !important;
font-weight: bold !important;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
}
.mobile-mode .swimlane-header .swimlane-description {
font-size: clamp(14px, 2vw, 24px) !important;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
}
.mobile-mode .board-header {
font-size: clamp(18px, 2.5vw, 32px) !important;
padding: 1rem !important;
width: 100% !important;
max-width: 100% !important;
}
.mobile-mode .board-header .board-header-title {
font-size: clamp(18px, 2.5vw, 32px) !important;
font-weight: bold !important;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
}
.mobile-mode .board-header .board-header-description {
font-size: clamp(14px, 2vw, 24px) !important;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
}
.mobile-mode .board-header .board-header-btn {
font-size: clamp(14px, 2vw, 24px) !important;
padding: 1vh 1.5vw !important;
display: inline-block !important;
visibility: visible !important;
opacity: 1 !important;
}
.mobile-mode .board-header .board-header-btn i {
font-size: clamp(14px, 2vw, 24px) !important;
display: inline !important;
visibility: visible !important;
opacity: 1 !important;
}
/* Force mobile mode visibility on all screen sizes */
.mobile-mode .list-header .fa-angle-right,
.mobile-mode .list-header .fa-arrows {
display: block !important;
visibility: visible !important;
opacity: 1 !important;
position: static !important;
right: auto !important;
top: auto !important;
transform: none !important;
}
.mobile-mode .list-header .fa-angle-right {
grid-row: 1/3 !important;
grid-column: 3 !important;
padding: 14px !important;
font-size: clamp(24px, 3vw, 48px) !important;
text-align: center !important;
}
.mobile-mode .list-header .fa-arrows {
grid-row: 1/3 !important;
grid-column: 4 !important;
padding: 14px !important;
font-size: clamp(28px, 3.5vw, 56px) !important;
text-align: center !important;
}
/* Override any media queries that might hide elements in mobile mode */
.mobile-mode * {
max-width: none !important;
}
.mobile-mode .list,
.mobile-mode .swimlane,
.mobile-mode .board-wrapper,
.mobile-mode .board-canvas {
max-width: 100% !important;
width: 100% !important;
min-width: 100% !important;
}
/* Force mobile mode list styling on all screen sizes - override desktop CSS */
.mobile-mode .board-canvas {
display: block !important;
flex-direction: column !important;
flex-wrap: nowrap !important;
align-items: stretch !important;
justify-content: flex-start !important;
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
overflow-x: hidden !important;
overflow-y: auto !important;
}
.mobile-mode .swimlane {
display: block !important;
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
margin: 0 0 2rem 0 !important;
padding: 0 !important;
float: none !important;
clear: both !important;
}
.mobile-mode .swimlane .swimlane-header {
display: block !important;
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
margin: 0 0 1rem 0 !important;
padding: 1rem !important;
font-size: clamp(18px, 2.5vw, 32px) !important;
font-weight: bold !important;
border-bottom: 2px solid #ccc !important;
}
.mobile-mode .swimlane .lists {
display: block !important;
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
margin: 0 !important;
padding: 0 !important;
flex-direction: column !important;
flex-wrap: nowrap !important;
align-items: stretch !important;
justify-content: flex-start !important;
}
.mobile-mode .list {
display: block !important;
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
margin: 0 0 2rem 0 !important;
padding: 0 !important;
float: none !important;
clear: both !important;
border-left: none !important;
border-right: none !important;
border-top: none !important;
border-bottom: 2px solid #ccc !important;
flex: none !important;
flex-basis: auto !important;
flex-grow: 0 !important;
flex-shrink: 0 !important;
position: static !important;
left: auto !important;
right: auto !important;
top: auto !important;
bottom: auto !important;
transform: none !important;
}
.mobile-mode .list:first-child {
margin-left: 0 !important;
margin-top: 0 !important;
}
.mobile-mode .list:last-child {
margin-right: 0 !important;
margin-bottom: 0 !important;
}
.mobile-mode .list.ui-sortable-helper {
display: block !important;
width: 100% !important;
max-width: 100% !important;
min-width: 100% !important;
height: auto !important;
min-height: 60px !important;
margin: 0 0 2rem 0 !important;
padding: 0 !important;
float: none !important;
clear: both !important;
border-left: none !important;
border-right: none !important;
border-top: none !important;
border-bottom: 2px solid #ccc !important;
flex: none !important;
}
.mobile-mode .list.placeholder {
display: block !important;
width: 100% !important;
max-width: 100% !important;
min-width: 100% !important;
height: auto !important;
min-height: 60px !important;
margin: 0 0 2rem 0 !important;
padding: 0 !important;
float: none !important;
clear: both !important;
border-left: none !important;
border-right: none !important;
border-top: none !important;
border-bottom: 2px solid #ccc !important;
flex: none !important;
}
/* Override any existing responsive CSS that might interfere with mobile mode */
.mobile-mode .board-canvas .swimlane .lists {
display: block !important;
width: 100% !important;
max-width: 100% !important;
min-width: 100% !important;
margin: 0 !important;
padding: 0 !important;
flex-direction: column !important;
flex-wrap: nowrap !important;
align-items: stretch !important;
justify-content: flex-start !important;
overflow: visible !important;
}
.mobile-mode .board-canvas .swimlane .lists .list {
display: block !important;
width: 100% !important;
max-width: 100% !important;
min-width: 100% !important;
margin: 0 0 2rem 0 !important;
padding: 0 !important;
float: none !important;
clear: both !important;
border-left: none !important;
border-right: none !important;
border-top: none !important;
border-bottom: 2px solid #ccc !important;
flex: none !important;
flex-basis: auto !important;
flex-grow: 0 !important;
flex-shrink: 0 !important;
position: static !important;
left: auto !important;
right: auto !important;
top: auto !important;
bottom: auto !important;
transform: none !important;
}
/* Force mobile mode to override any media query styles */
@media screen and (min-width: 801px) {
.mobile-mode .board-canvas {
display: block !important;
flex-direction: column !important;
flex-wrap: nowrap !important;
align-items: stretch !important;
justify-content: flex-start !important;
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
overflow-x: hidden !important;
overflow-y: auto !important;
}
.mobile-mode .swimlane {
display: block !important;
width: 100% !important;
max-width: 100% !important;
min-width: 100% !important;
margin: 0 0 2rem 0 !important;
padding: 0 !important;
float: none !important;
clear: both !important;
}
.mobile-mode .swimlane .lists {
display: block !important;
width: 100% !important;
max-width: 100% !important;
min-width: 100% !important;
margin: 0 !important;
padding: 0 !important;
flex-direction: column !important;
flex-wrap: nowrap !important;
align-items: stretch !important;
justify-content: flex-start !important;
}
.mobile-mode .list {
display: block !important;
width: 100% !important;
max-width: 100% !important;
min-width: 100% !important;
margin: 0 0 2rem 0 !important;
padding: 0 !important;
float: none !important;
clear: both !important;
border-left: none !important;
border-right: none !important;
border-top: none !important;
border-bottom: 2px solid #ccc !important;
flex: none !important;
flex-basis: auto !important;
flex-grow: 0 !important;
flex-shrink: 0 !important;
position: static !important;
left: auto !important;
right: auto !important;
top: auto !important;
bottom: auto !important;
transform: none !important;
}
}
/* Hide desktop-only elements in mobile mode (like mobile media queries do) */
.mobile-mode .board-header-btn i.fa + span {
display: none !important;
}
.mobile-mode .board-header-btn span {
display: none !important;
}
.mobile-mode .board-header-btn .fa + span {
display: none !important;
}
.mobile-mode .board-header-btn .fa + .board-header-btn-text {
display: none !important;
}
.mobile-mode .board-header-btn .fa + .board-header-btn-label {
display: none !important;
}
/* Show only icons in mobile mode */
.mobile-mode .board-header-btn {
width: auto !important;
min-width: auto !important;
padding: 8px !important;
text-align: center !important;
}
.mobile-mode .board-header-btn i {
display: inline-block !important;
margin: 0 !important;
}
/* Hide desktop-specific elements that shouldn't show in mobile mode */
.mobile-mode .desktop-only,
.mobile-mode .board-header .desktop-only {
display: none !important;
}
.mobile-mode .board-header .board-header-btn.desktop-only {
display: none !important;
}
/* Hide desktop-specific board header buttons in mobile mode */
.mobile-mode .board-header-btns.left {
display: none !important;
}
.mobile-mode .board-header-btns.center {
display: none !important;
}
/* Show only the right section buttons in mobile mode, but hide text labels */
.mobile-mode .board-header-btns.right {
display: block !important;
}
.mobile-mode .board-header-btns.right .board-header-btn span {
display: none !important;
}
.mobile-mode .board-header-btns.right .board-header-btn .fa + span {
display: none !important;
}
.mobile-mode .board-header-btns.right .board-header-btn .fa + .board-header-btn-text {
display: none !important;
}
.mobile-mode .board-header-btns.right .board-header-btn .fa + .board-header-btn-label {
display: none !important;
}
/* Hide specific desktop-only buttons that shouldn't show in mobile mode */
.mobile-mode .board-header-btn.js-star-board span,
.mobile-mode .board-header-btn.js-change-visibility span,
.mobile-mode .board-header-btn.js-watch-board span,
.mobile-mode .board-header-btn.js-sort-cards span {
display: none !important;
}
/* Show only icons for mobile mode buttons */
.mobile-mode .board-header-btns.right .board-header-btn {
width: auto !important;
min-width: auto !important;
padding: 8px !important;
text-align: center !important;
margin: 0 2px !important;
}
.mobile-mode .board-header-btns.right .board-header-btn i {
display: inline-block !important;
margin: 0 !important;
}
/* Ensure mobile mode looks like small screen mobile view */
.mobile-mode .board-header {
height: 40px !important;
}
.mobile-mode .board-header .board-header-btns {
margin-top: 0px !important;
}
.mobile-mode .board-header .board-header-btn {
height: 32px !important;
line-height: 32px !important;
font-size: 15px !important;
}
.mobile-mode .board-header .board-header-btn i.fa {
line-height: 32px !important;
}
/* Copy mobile media query styles to mobile mode for consistent appearance */
.mobile-mode .board-header {
height: 40px !important;
padding: 0 !important;
margin: 0 !important;
}
.mobile-mode .board-header .board-header-btns {
margin-top: 0px !important;
height: 40px !important;
display: flex !important;
align-items: center !important;
justify-content: flex-end !important;
}
.mobile-mode .board-header .board-header-btn {
height: 32px !important;
line-height: 32px !important;
font-size: 15px !important;
margin: 0 2px !important;
padding: 4px 8px !important;
border-radius: 4px !important;
background: rgba(255, 255, 255, 0.1) !important;
border: 1px solid rgba(255, 255, 255, 0.2) !important;
color: #fff !important;
text-decoration: none !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
min-width: 32px !important;
width: auto !important;
}
.mobile-mode .board-header .board-header-btn:hover {
background: rgba(255, 255, 255, 0.2) !important;
border-color: rgba(255, 255, 255, 0.3) !important;
}
.mobile-mode .board-header .board-header-btn i.fa {
line-height: 32px !important;
font-size: 15px !important;
margin: 0 !important;
padding: 0 !important;
}
.mobile-mode .board-header .board-header-btn i.fa + span {
display: none !important;
}
.mobile-mode .board-header .board-header-btn span {
display: none !important;
}
/* Hide the board title in mobile mode to match mobile view */
.mobile-mode .header-board-menu {
display: none !important;
}
/* Ensure the board header takes full width in mobile mode */
.mobile-mode .board-header {
width: 100% !important;
max-width: 100% !important;
display: flex !important;
align-items: center !important;
justify-content: space-between !important;
padding: 0 10px !important;
}
/* Additional Desktop Mode Styles for Other Elements */
.desktop-mode .swimlane-header .swimlane-title {
font-size: clamp(14px, 3vw, 18px) !important;
}
.desktop-mode .swimlane-header .swimlane-description {
font-size: 12px !important;
}
.desktop-mode .board-header {
font-size: clamp(14px, 3vw, 18px) !important;
padding: 2.5vh 1.5vw 0.5vh !important;
}
.desktop-mode .board-header .board-header-title {
font-size: clamp(14px, 3vw, 18px) !important;
}
.desktop-mode .board-header .board-header-description {
font-size: 12px !important;
}
.desktop-mode .board-header .board-header-btn {
font-size: clamp(12px, 2.5vw, 16px) !important;
padding: 0.5vh 0.8vw !important;
}
.desktop-mode .board-header .board-header-btn i {
font-size: clamp(12px, 2.5vw, 16px) !important;
}
+164 -97
View File
@@ -14,91 +14,86 @@ template(name="boardHeaderBar")
with currentBoard
if currentUser.isBoardAdmin
a.board-header-btn(class="{{#if currentUser.isBoardAdmin}}js-edit-board-title{{else}}is-disabled{{/if}}" title="{{_ 'edit'}}" value=title)
i.fa.fa-pencil-square-o
| ✏️
a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}"
title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}")
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
if showStarCounter
span
= currentBoard.stars
a.board-header-btn(
class="{{#if currentUser.isBoardAdmin}}js-change-visibility{{else}}is-disabled{{/if}}"
title="{{_ currentBoard.permission}}")
| {{#if currentBoard.isPublic}}🌐{{else}}🔒{{/if}}
span {{_ currentBoard.permission}}
a.board-header-btn(
class="{{#if currentUser.isBoardAdmin}}js-change-visibility{{else}}is-disabled{{/if}}"
title="{{_ currentBoard.permission}}")
i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
span {{_ currentBoard.permission}}
a.board-header-btn.js-watch-board(
title="{{_ watchLevel }}")
if $eq watchLevel "watching"
i.fa.fa-eye
if $eq watchLevel "tracking"
i.fa.fa-bell
if $eq watchLevel "muted"
i.fa.fa-bell-slash
span {{_ watchLevel}}
a.board-header-btn(title="{{_ 'sort-cards'}}" class="{{#if isSortActive }}emphasis{{else}} js-sort-cards {{/if}}")
i.fa.fa-sort
span {{#if isSortActive }}{{_ 'sort-is-on'}}{{else}}{{_ 'sort-cards'}}{{/if}}
a.board-header-btn.js-watch-board(
title="{{_ watchLevel }}")
if $eq watchLevel "watching"
| 👁️
if $eq watchLevel "tracking"
| 🔔
if $eq watchLevel "muted"
| 🔕
span {{_ watchLevel}}
a.board-header-btn.js-star-board(title="{{_ 'star-board'}}")
if isStarred
| ⭐
else
| ☆
if showStarCounter
span.board-star-counter {{currentBoard.stars}}
a.board-header-btn(title="{{_ 'sort-cards'}}" class="{{#if isSortActive }}emphasis{{else}} js-sort-cards {{/if}}")
| {{sortCardsIcon}}
span {{#if isSortActive }}{{_ 'sort-is-on'}}{{else}}{{_ 'sort-cards'}}{{/if}}
if isSortActive
a.board-header-btn-close.js-sort-reset(title="{{_ 'remove-sort'}}")
i.fa.fa-times-thin
| ❌
else
a.board-header-btn.js-log-in(
title="{{_ 'log-in'}}")
i.fa.fa-sign-in
| 🚪
span {{_ 'log-in'}}
.board-header-btns.center
.board-header-btns.right
if currentBoard
if isMiniScreen
if currentUser
with currentBoard
a.board-header-btn(class="{{#if currentUser.isBoardAdmin}}js-edit-board-title{{else}}is-disabled{{/if}}" title="{{_ 'edit'}}" value=title)
i.fa.fa-pencil-square-o
| ✏️
a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}"
title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}")
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
if showStarCounter
span
= currentBoard.stars
a.board-header-btn(
class="{{#if currentUser.isBoardAdmin}}js-change-visibility{{else}}is-disabled{{/if}}"
title="{{_ currentBoard.permission}}")
| {{#if currentBoard.isPublic}}🌐{{else}}🔒{{/if}}
a.board-header-btn(
class="{{#if currentUser.isBoardAdmin}}js-change-visibility{{else}}is-disabled{{/if}}"
title="{{_ currentBoard.permission}}")
i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
span {{_ currentBoard.permission}}
a.board-header-btn.js-watch-board(
title="{{_ watchLevel }}")
if $eq watchLevel "watching"
i.fa.fa-eye
if $eq watchLevel "tracking"
i.fa.fa-bell
if $eq watchLevel "muted"
i.fa.fa-bell-slash
span {{_ watchLevel}}
a.board-header-btn(title="{{_ 'sort-cards'}}" class="{{#if isSortActive }}emphasis{{else}} js-sort-cards {{/if}}")
i.fa.fa-sort
span {{#if isSortActive }}{{_ 'sort-is-on'}}{{else}}{{_ 'sort-cards'}}{{/if}}
a.board-header-btn.js-watch-board(
title="{{_ watchLevel }}")
if $eq watchLevel "watching"
| 👁️
if $eq watchLevel "tracking"
| 🔔
if $eq watchLevel "muted"
| 🔕
a.board-header-btn.js-star-board(title="{{_ 'star-board'}}")
if isStarred
| ⭐
else
| ☆
a.board-header-btn(title="{{_ 'sort-cards'}}" class="{{#if isSortActive }}emphasis{{else}} js-sort-cards {{/if}}")
| {{sortCardsIcon}}
if isSortActive
a.board-header-btn-close.js-sort-reset(title="{{_ 'remove-sort'}}")
i.fa.fa-times-thin
| ❌
else
a.board-header-btn.js-log-in(
title="{{_ 'log-in'}}")
i.fa.fa-sign-in
span {{_ 'log-in'}}
| 🚪
if isSandstorm
if currentUser
a.board-header-btn.js-open-archived-board
i.fa.fa-archive
span {{_ 'archives'}}
| 📦
//if showSort
// a.board-header-btn.js-open-sort-view(title="{{_ 'sort-desc'}}")
@@ -108,60 +103,58 @@ template(name="boardHeaderBar")
a.board-header-btn.js-open-filter-view(
title="{{#if Filter.isActive}}{{_ 'filter-on-desc'}}{{else}}{{_ 'filter'}}{{/if}}"
class="{{#if Filter.isActive}}emphasis{{/if}}")
i.fa.fa-filter
span {{#if Filter.isActive}}{{_ 'filter-on'}}{{else}}{{_ 'filter'}}{{/if}}
| 🔽
if Filter.isActive
a.board-header-btn-close.js-filter-reset(title="{{_ 'filter-clear'}}")
i.fa.fa-times-thin
| ❌
a.board-header-btn.js-open-search-view(title="{{_ 'search'}}")
i.fa.fa-search
span {{_ 'search'}}
span.emoji-icon 🔍
unless currentBoard.isTemplatesBoard
a.board-header-btn.js-toggle-board-view(
title="{{_ 'board-view'}}")
i.fa.fa-caret-down
| ▼
if $eq boardView 'board-view-swimlanes'
i.fa.fa-th-large
| 🏊
if $eq boardView 'board-view-lists'
i.fa.fa-trello
| 📋
if $eq boardView 'board-view-cal'
i.fa.fa-calendar
span {{#if boardView}}{{_ boardView}}{{else}}{{_ 'board-view-swimlanes'}}{{/if}}
| 📅
if $eq boardView 'board-view-gantt'
| 📊
if canModifyBoard
a.board-header-btn.js-multiselection-activate(
title="{{#if MultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}}"
class="{{#if MultiSelection.isActive}}emphasis{{/if}}")
i.fa.fa-check-square-o
span {{#if MultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}}
if MultiSelection.isActive
a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}")
i.fa.fa-times-thin
| ☑️
if MultiSelection.isActive
a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}")
| ❌
.separator
a.board-header-btn.js-toggle-sidebar(title="{{_ 'sidebar-open'}} {{_ 'or'}} {{_ 'sidebar-close'}}")
i.fa.fa-navicon
| ☰
template(name="boardVisibilityList")
ul.pop-over-list
li
with "private"
a.js-select-visibility
i.fa.fa-lock.colorful
| 🔒
| {{_ 'private'}}
if visibilityCheck
i.fa.fa-check
| ✅
span.sub-name {{_ 'private-desc'}}
if notAllowPrivateVisibilityOnly
li
with "public"
a.js-select-visibility
i.fa.fa-globe.colorful
| 🌐
| {{_ 'public'}}
if visibilityCheck
i.fa.fa-check
| ✅
span.sub-name {{_ 'public-desc'}}
template(name="boardChangeVisibilityPopup")
@@ -172,26 +165,26 @@ template(name="boardChangeWatchPopup")
li
with "watching"
a.js-select-watch
i.fa.fa-eye.colorful
| 👁️
| {{_ 'watching'}}
if watchCheck
i.fa.fa-check
| ✅
span.sub-name {{_ 'watching-info'}}
li
with "tracking"
a.js-select-watch
i.fa.fa-bell.colorful
| 🔔
| {{_ 'tracking'}}
if watchCheck
i.fa.fa-check
| ✅
span.sub-name {{_ 'tracking-info'}}
li
with "muted"
a.js-select-watch
i.fa.fa-bell-slash.colorful
| 🔕
| {{_ 'muted'}}
if watchCheck
i.fa.fa-check
| ✅
span.sub-name {{_ 'muted-info'}}
template(name="boardChangeViewPopup")
@@ -199,24 +192,31 @@ template(name="boardChangeViewPopup")
li
with "board-view-swimlanes"
a.js-open-swimlanes-view
i.fa.fa-th-large.colorful
| 🏊
| {{_ 'board-view-swimlanes'}}
if $eq Utils.boardView "board-view-swimlanes"
i.fa.fa-check
| ✅
li
with "board-view-lists"
a.js-open-lists-view
i.fa.fa-trello.colorful
| 📋
| {{_ 'board-view-lists'}}
if $eq Utils.boardView "board-view-lists"
i.fa.fa-check
| ✅
li
with "board-view-cal"
a.js-open-cal-view
i.fa.fa-calendar.colorful
| 📅
| {{_ 'board-view-cal'}}
if $eq Utils.boardView "board-view-cal"
i.fa.fa-check
| ✅
li
with "board-view-gantt"
a.js-open-gantt-view
| 📊
| {{_ 'board-view-gantt'}}
if $eq Utils.boardView "board-view-gantt"
| ✅
template(name="createBoard")
form
@@ -228,11 +228,70 @@ template(name="createBoard")
else
p.quiet
if $eq visibility.get 'public'
span.fa.fa-globe.colorful
span 🌐
= " "
| {{{_ 'board-public-info'}}}
else
span.fa.fa-lock.colorful
span 🔒
= " "
| {{{_ 'board-private-info'}}}
a.js-change-visibility {{_ 'change'}}.
a.flex.js-toggle-add-template-container
.materialCheckBox#add-template-container
span {{_ 'add-template-container'}}
input.primary.wide(type="submit" value="{{_ 'create'}}")
span.quiet
| {{_ 'or'}}
a.js-import-board {{_ 'import'}}
span.quiet
| /
a.js-board-template {{_ 'template'}}
template(name="createBoardPopup")
form
label
| {{_ 'title'}}
input.js-new-board-title(type="text" placeholder="{{_ 'bucket-example'}}" autofocus required)
if visibilityMenuIsOpen.get
+boardVisibilityList
else
p.quiet
if $eq visibility.get 'public'
span 🌐
= " "
| {{{_ 'board-public-info'}}}
else
span 🔒
= " "
| {{{_ 'board-private-info'}}}
a.js-change-visibility {{_ 'change'}}.
a.flex.js-toggle-add-template-container
.materialCheckBox#add-template-container
span {{_ 'add-template-container'}}
input.primary.wide(type="submit" value="{{_ 'create'}}")
span.quiet
| {{_ 'or'}}
a.js-import-board {{_ 'import'}}
span.quiet
| /
a.js-board-template {{_ 'template'}}
// New popup for Template Container creation; shares the same form content
template(name="createTemplateContainerPopup")
form
label
| {{_ 'title'}}
input.js-new-board-title(type="text" placeholder="{{_ 'bucket-example'}}" autofocus required)
if visibilityMenuIsOpen.get
+boardVisibilityList
else
p.quiet
if $eq visibility.get 'public'
span 🌐
= " "
| {{{_ 'board-public-info'}}}
else
span 🔒
= " "
| {{{_ 'board-private-info'}}}
a.js-change-visibility {{_ 'change'}}.
@@ -256,10 +315,10 @@ template(name="createBoard")
// li
// a.js-sort-by(name="{{value.name}}")
// if $eq sortby value.name
// i(class="fa {{Direction}}")
// | {{#if $eq Direction "fa-arrow-up"}}⬆️{{else}}⬇️{{/if}}
// | {{_ value.label }}{{_ value.shortLabel}}
// if $eq sortby value.name
// i(class="fa fa-check")
// | ✅
template(name="boardChangeTitlePopup")
form
@@ -279,14 +338,22 @@ template(name="boardCreateRulePopup")
template(name="cardsSortPopup")
ul.pop-over-list
li
a.js-sort-due {{_ 'due-date'}}
a.js-sort-due
| 📅
| {{_ 'due-date'}}
hr
li
a.js-sort-title {{_ 'title-alphabetically'}}
a.js-sort-title
| 🔤
| {{_ 'title-alphabetically'}}
hr
li
a.js-sort-created-desc {{_ 'created-at-newest-first'}}
a.js-sort-created-desc
| ⬇️
| {{_ 'created-at-newest-first'}}
hr
li
a.js-sort-created-asc {{_ 'created-at-oldest-first'}}
a.js-sort-created-asc
| ⬆️
| {{_ 'created-at-oldest-first'}}
+94 -5
View File
@@ -33,6 +33,8 @@ BlazeComponent.extendComponent({
return currentBoard && currentBoard.getWatchLevel(Meteor.userId());
},
isStarred() {
const boardId = Session.get('currentBoard');
const user = ReactiveCache.getCurrentUser();
@@ -70,7 +72,10 @@ BlazeComponent.extendComponent({
{
'click .js-edit-board-title': Popup.open('boardChangeTitle'),
'click .js-star-board'() {
ReactiveCache.getCurrentUser().toggleBoardStar(Session.get('currentBoard'));
const boardId = Session.get('currentBoard');
if (boardId) {
Meteor.call('toggleBoardStar', boardId);
}
},
'click .js-open-board-menu': Popup.open('boardMenu'),
'click .js-change-visibility': Popup.open('boardChangeVisibility'),
@@ -80,10 +85,37 @@ BlazeComponent.extendComponent({
},
'click .js-toggle-board-view': Popup.open('boardChangeView'),
'click .js-toggle-sidebar'() {
Sidebar.toggle();
if (process.env.DEBUG === 'true') {
console.log('Hamburger menu clicked');
}
// Use the same approach as keyboard shortcuts
if (typeof Sidebar !== 'undefined' && Sidebar && typeof Sidebar.toggle === 'function') {
if (process.env.DEBUG === 'true') {
console.log('Using Sidebar.toggle()');
}
Sidebar.toggle();
} else {
if (process.env.DEBUG === 'true') {
console.warn('Sidebar not available, trying alternative approach');
}
// Try to trigger the sidebar through the global Blaze helper
if (typeof Blaze !== 'undefined' && Blaze._globalHelpers && Blaze._globalHelpers.Sidebar) {
const sidebar = Blaze._globalHelpers.Sidebar();
if (sidebar && typeof sidebar.toggle === 'function') {
if (process.env.DEBUG === 'true') {
console.log('Using Blaze helper Sidebar.toggle()');
}
sidebar.toggle();
}
}
}
},
'click .js-open-filter-view'() {
Sidebar.setView('filter');
if (Sidebar) {
Sidebar.setView('filter');
} else {
console.warn('Sidebar not available for setView');
}
},
'click .js-sort-cards': Popup.open('cardsSort'),
/*
@@ -100,14 +132,22 @@ BlazeComponent.extendComponent({
*/
'click .js-filter-reset'(event) {
event.stopPropagation();
Sidebar.setView();
if (Sidebar) {
Sidebar.setView();
} else {
console.warn('Sidebar not available for setView');
}
Filter.reset();
},
'click .js-sort-reset'() {
Session.set('sortBy', '');
},
'click .js-open-search-view'() {
Sidebar.setView('search');
if (Sidebar) {
Sidebar.setView('search');
} else {
console.warn('Sidebar not available for setView');
}
},
'click .js-multiselection-activate'() {
const currentCard = Utils.getCurrentCardId();
@@ -126,6 +166,7 @@ BlazeComponent.extendComponent({
},
];
},
}).register('boardHeaderBar');
Template.boardHeaderBar.helpers({
@@ -135,6 +176,23 @@ Template.boardHeaderBar.helpers({
isSortActive() {
return Session.get('sortBy') ? true : false;
},
sortCardsIcon() {
const sortBy = Session.get('sortBy');
if (!sortBy) {
return '🃏'; // Card icon when nothing is selected
}
// Determine which sort option is active based on sortBy object
if (sortBy.dueAt) {
return '📅'; // Due date icon
} else if (sortBy.title) {
return '🔤'; // Alphabet icon
} else if (sortBy.createdAt) {
return sortBy.createdAt === 1 ? '⬆️' : '⬇️'; // Up/down arrow based on direction
}
return '🃏'; // Default card icon
},
});
Template.boardChangeViewPopup.events({
@@ -150,6 +208,10 @@ Template.boardChangeViewPopup.events({
Utils.setBoardView('board-view-cal');
Popup.back();
},
'click .js-open-gantt-view'() {
Utils.setBoardView('board-view-gantt');
Popup.back();
},
});
const CreateBoard = BlazeComponent.extendComponent({
@@ -201,6 +263,7 @@ const CreateBoard = BlazeComponent.extendComponent({
title: title,
permission: 'private',
type: 'template-container',
migrationVersion: 1, // Latest version - no migration needed
}),
);
@@ -235,6 +298,15 @@ const CreateBoard = BlazeComponent.extendComponent({
},
);
// Assign to space if one was selected
const spaceId = Session.get('createBoardInWorkspace');
if (spaceId) {
Meteor.call('assignBoardToWorkspace', this.boardId.get(), spaceId, (err) => {
if (err) console.error('Error assigning board to space:', err);
});
Session.set('createBoardInWorkspace', null); // Clear after use
}
Utils.goBoardId(this.boardId.get());
} else {
@@ -244,6 +316,7 @@ const CreateBoard = BlazeComponent.extendComponent({
Boards.insert({
title,
permission: visibility,
migrationVersion: 1, // Latest version - no migration needed
}),
);
@@ -252,6 +325,15 @@ const CreateBoard = BlazeComponent.extendComponent({
boardId: this.boardId.get(),
});
// Assign to space if one was selected
const spaceId = Session.get('createBoardInWorkspace');
if (spaceId) {
Meteor.call('assignBoardToWorkspace', this.boardId.get(), spaceId, (err) => {
if (err) console.error('Error assigning board to space:', err);
});
Session.set('createBoardInWorkspace', null); // Clear after use
}
Utils.goBoardId(this.boardId.get());
}
},
@@ -273,6 +355,13 @@ const CreateBoard = BlazeComponent.extendComponent({
},
}).register('createBoardPopup');
(class CreateTemplateContainerPopup extends CreateBoard {
onRendered() {
// Always pre-check the template container checkbox for this popup
$('#add-template-container').addClass('is-checked');
}
}).register('createTemplateContainerPopup');
(class HeaderBarCreateBoard extends CreateBoard {
onSubmit(event) {
super.onSubmit(event);
File diff suppressed because it is too large. Load diff
+196 -137
View File
@@ -1,147 +1,181 @@
template(name="boardList")
.wrapper
ul.AllBoardTeamsOrgs
li.AllBoardTeams
if userHasTeams
select.js-AllBoardTeams#jsAllBoardTeams("multiple")
option(value="-1") {{_ 'teams'}} :
each teamsDatas
option(value="{{teamId}}") {{_ teamDisplayName}}
.board-list-header
li.AllBoardOrgs
if userHasOrgs
select.js-AllBoardOrgs#jsAllBoardOrgs("multiple")
option(value="-1") {{_ 'organizations'}} :
each orgsDatas
option(value="{{orgId}}") {{orgDisplayName}}
.boards-layout
// Left menu
.boards-left-menu
ul.menu
li(class="menu-item {{#if isSelectedMenu 'starred'}}active{{/if}}")
a.js-select-menu(data-type="starred")
span.menu-label
span.emoji-icon ⭐
| {{_ 'allboards.starred'}}
span.menu-count {{menuItemCount 'starred'}}
li(class="menu-item {{#if isSelectedMenu 'templates'}}active{{/if}}")
a.js-select-menu(data-type="templates")
span.menu-label
span.emoji-icon 📋
| {{_ 'allboards.templates'}}
span.menu-count {{menuItemCount 'templates'}}
li(class="menu-item {{#if isSelectedMenu 'remaining'}}active{{/if}}")
a.js-select-menu(data-type="remaining")
span.menu-label
span.emoji-icon 📂
| {{_ 'allboards.remaining'}}
span.menu-count {{menuItemCount 'remaining'}}
.workspaces-header
span
span.emoji-icon 🗂️
| {{_ 'allboards.workspaces'}}
a.js-add-workspace(title="{{_ 'allboards.add-workspace'}}") +
// Workspaces tree
+workspaceTree(nodes=workspacesTree selectedWorkspaceId=selectedWorkspaceId)
//li.AllBoardTemplates
// if userHasTemplates
// select.js-AllBoardTemplates#jsAllBoardTemplates("multiple")
// option(value="-1") {{_ 'templates'}} :
// each templatesDatas
// option(value="{{templateId}}") {{_ templateDisplayName}}
// Existing filter by orgs/teams (kept)
ul.AllBoardTeamsOrgs
li.AllBoardTeams
if userHasTeams
select.js-AllBoardTeams#jsAllBoardTeams("multiple")
option(value="-1") {{_ 'teams'}} :
each teamsDatas
option(value="{{teamId}}") {{_ teamDisplayName}}
li.AllBoardBtns
div.AllBoardButtonsContainer
if userHasOrgsOrTeams
i.fa.fa-filter
input#filterBtn(type="button" value="{{_ 'filter'}}")
input#resetBtn(type="button" value="{{_ 'filter-clear'}}")
li.AllBoardOrgs
if userHasOrgs
select.js-AllBoardOrgs#jsAllBoardOrgs("multiple")
option(value="-1") {{_ 'organizations'}} :
each orgsDatas
option(value="{{orgId}}") {{orgDisplayName}}
ul.board-list.clearfix.js-boards
li.js-add-board
a.board-list-item.label(title="{{_ 'add-board'}}")
| {{_ 'add-board'}}
each boards
li(class="{{_id}}" class="{{#if isStarred}}starred{{/if}}" class=colorClass).js-board
if isInvited
.board-list-item
span.details
span.board-list-item-name= title
i.fa.js-star-board(
class="fa-star{{#if isStarred}} is-star-active{{else}}-o{{/if}}"
title="{{_ 'star-board-title'}}")
p.board-list-item-desc {{_ 'just-invited'}}
button.js-accept-invite.primary {{_ 'accept'}}
button.js-decline-invite {{_ 'decline'}}
else
if $eq type "template-container"
a.js-open-board.template-container.board-list-item(href="{{pathFor 'board' id=_id slug=slug}}")
span.details
span.board-list-item-name(title="{{_ 'template-container'}}")
+viewer
= title
i.fa.js-star-board(
class="fa-star{{#if isStarred}} is-star-active{{else}}-o{{/if}}"
title="{{_ 'star-board-title'}}")
p.board-list-item-desc
+viewer
= description
if hasSpentTimeCards
i.fa.js-has-spenttime-cards(
class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}"
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
if isTouchScreenOrShowDesktopDragHandles
i.fa.board-handle(
class="fa-arrows"
title="{{_ 'drag-board'}}")
else
if isSandstorm
i.fa.js-clone-board(
class="fa-clone"
title="{{_ 'duplicate-board'}}")
i.fa.js-archive-board(
class="fa-archive"
title="{{_ 'archive-board'}}")
else if isAdministrable
i.fa.js-clone-board(
class="fa-clone"
title="{{_ 'duplicate-board'}}")
i.fa.js-archive-board(
class="fa-archive"
title="{{_ 'archive-board'}}")
else if currentUser.isAdmin
i.fa.js-clone-board(
class="fa-clone"
title="{{_ 'duplicate-board'}}")
i.fa.js-archive-board(
class="fa-archive"
title="{{_ 'archive-board'}}")
li.AllBoardBtns
div.AllBoardButtonsContainer
if userHasOrgsOrTeams
span.emoji-icon 🔍
input#filterBtn(type="button" value="{{_ 'filter'}}")
button#resetBtn.filter-reset-btn
span.reset-icon
span.emoji-icon ❌
span {{_ 'filter-clear'}}
// Right boards grid
.boards-right-grid
.boards-path-header
.path-left
span.path-icon.emoji-icon {{currentMenuPath.icon}}
span.path-text {{currentMenuPath.text}}
if BoardMultiSelection.isActive
span.multiselection-hint
span.emoji-icon 📌
| {{_ 'multi-selection-active'}}
.path-right
if canModifyBoards
if hasBoardsSelected
button.js-archive-selected-boards.board-header-btn
span.emoji-icon 📦
span {{_ 'archive-board'}}
button.js-duplicate-selected-boards.board-header-btn
span.emoji-icon 📋
span {{_ 'duplicate-board'}}
a.board-header-btn.js-multiselection-activate(
title="{{#if BoardMultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}}"
class="{{#if BoardMultiSelection.isActive}}emphasis{{/if}}")
span.emoji-icon ☑️
if BoardMultiSelection.isActive
a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}")
span.emoji-icon ✖
ul.board-list.clearfix.js-boards(class="{{#if isMiniScreen}}mobile-view{{/if}} {{#if BoardMultiSelection.isActive}}is-multiselection-active{{/if}}")
li.js-add-board
if isSelectedMenu 'templates'
a.board-list-item.label(title="{{_ 'add-template-container'}}")
span.emoji-icon ➕
| {{_ 'add-template-container'}}
else
a.js-open-board.board-list-item(href="{{pathFor 'board' id=_id slug=slug}}")
span.details
span.board-list-item-name(title="{{_ 'board-drag-drop-reorder-or-click-open'}}")
+viewer
= title
unless currentSetting.hideBoardMemberList
if allowsBoardMemberList
.minicard-members
each member in boardMembers _id
a.name
+userAvatar(userId=member noRemove=true)
unless currentSetting.hideCardCounterList
if allowsCardCounterList
.minicard-lists.flex.flex-wrap
each list in boardLists _id
.item
| {{ list }}
i.fa.js-star-board(
class="fa-star{{#if isStarred}} is-star-active{{else}}-o{{/if}}"
title="{{_ 'star-board-title'}}")
p.board-list-item-desc
+viewer
= description
if hasSpentTimeCards
i.fa.js-has-spenttime-cards(
class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}"
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
if isTouchScreenOrShowDesktopDragHandles
i.fa.board-handle(
class="fa-arrows"
title="{{_ 'drag-board'}}")
else
if isSandstorm
i.fa.js-clone-board(
class="fa-clone"
title="{{_ 'duplicate-board'}}")
i.fa.js-archive-board(
class="fa-archive"
title="{{_ 'archive-board'}}")
else if isAdministrable
i.fa.js-clone-board(
class="fa-clone"
title="{{_ 'duplicate-board'}}")
i.fa.js-archive-board(
class="fa-archive"
title="{{_ 'archive-board'}}")
else if currentUser.isAdmin
i.fa.js-clone-board(
class="fa-clone"
title="{{_ 'duplicate-board'}}")
i.fa.js-archive-board(
class="fa-archive"
title="{{_ 'archive-board'}}")
a.board-list-item.label(title="{{_ 'add-board'}}")
span.emoji-icon ➕
| {{_ 'add-board'}}
each boards
li.js-board(class="{{_id}} {{#if isStarred}}starred{{/if}} {{colorClass}} {{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}", draggable="true")
if isInvited
.board-list-item
if BoardMultiSelection.isActive
.materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection(
class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}")
span.details
span.board-list-item-name= title
span.js-star-board(
class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}"
title="{{_ 'star-board-title'}}")
span.emoji-icon
| {{#if isStarred}}⭐{{else}}☆{{/if}}
p.board-list-item-desc {{_ 'just-invited'}}
button.js-accept-invite.primary {{_ 'accept'}}
button.js-decline-invite {{_ 'decline'}}
else
if $eq type "template-container"
.template-container.board-list-item
if BoardMultiSelection.isActive
.materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection(
class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}")
span.board-handle(title="{{_ 'drag-board'}}")
span.emoji-icon ↕️
a.js-open-board(href="{{pathFor 'board' id=_id slug=slug}}")
span.details
span.board-list-item-name(title="{{_ 'template-container'}}")
+viewer
= title
p.board-list-item-desc
+viewer
= description
if hasSpentTimeCards
span.js-has-spenttime-cards(
class="{{#if hasOvertimeCards}}has-overtime-card-active{{else}}no-overtime-card-active{{/if}}"
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
span.emoji-icon ⏱️
span.js-star-board(
class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}"
title="{{_ 'star-board-title'}}")
span.emoji-icon
| {{#if isStarred}}⭐{{else}}☆{{/if}}
else
.board-list-item
if BoardMultiSelection.isActive
.materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection(
class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}")
span.board-handle(title="{{_ 'drag-board'}}")
span.emoji-icon ↕️
a.js-open-board(href="{{pathFor 'board' id=_id slug=slug}}")
span.details
span.board-list-item-name(title="{{_ 'board-drag-drop-reorder-or-click-open'}}")
+viewer
= title
unless currentSetting.hideBoardMemberList
if allowsBoardMemberList
.minicard-members
each member in boardMembers _id
a.name
+userAvatar(userId=member noRemove=true)
unless currentSetting.hideCardCounterList
if allowsCardCounterList
.minicard-lists.flex.flex-wrap
each list in boardLists _id
.item
| {{ list }}
p.board-list-item-desc
+viewer
= description
if hasSpentTimeCards
span.js-has-spenttime-cards(
class="{{#if hasOvertimeCards}}has-overtime-card-active{{else}}no-overtime-card-active{{/if}}"
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
span.emoji-icon ⏱️
a.js-star-board(
class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}"
title="{{_ 'star-board-title'}}")
span.emoji-icon
| {{#if isStarred}}⭐{{else}}☆{{/if}}
template(name="boardListHeaderBar")
h1 {{_ title }}
@@ -152,3 +186,28 @@ template(name="boardListHeaderBar")
// a.board-header-btn(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}")
// i.fa.fa-clone
// span {{_ 'templates'}}
// Recursive template for workspaces tree
template(name="workspaceTree")
if nodes
ul.workspace-tree.js-workspace-tree
each nodes
li.workspace-node(class="{{#if $eq id selectedWorkspaceId}}active{{/if}}" data-workspace-id="{{id}}" draggable="true")
.workspace-node-content
span.workspace-drag-handle
span.emoji-icon ↕️
a.js-select-workspace(data-id="{{id}}")
span.workspace-icon
if icon
+viewer
= icon
else
span.emoji-icon 📁
span.workspace-name= name
a.js-edit-workspace(data-id="{{id}}" title="{{_ 'allboards.edit-workspace'}}")
span.emoji-icon ✏️
span.workspace-count {{workspaceCount id}}
a.js-add-subworkspace(data-id="{{id}}" title="{{_ 'allboards.add-subworkspace'}}") +
if children
+workspaceTree(nodes=children selectedWorkspaceId=selectedWorkspaceId)
+550 -57
View File
@@ -14,6 +14,9 @@ Template.boardList.helpers({
return Utils.isMiniScreen() && Session.get('currentBoard'); */
return true;
},
BoardMultiSelection() {
return BoardMultiSelection;
},
})
Template.boardListHeaderBar.events({
@@ -22,6 +25,9 @@ Template.boardListHeaderBar.events({
},
});
Template.boardList.events({
});
Template.boardListHeaderBar.helpers({
title() {
//if (FlowRouter.getRouteName() === 'template-container') {
@@ -42,6 +48,9 @@ BlazeComponent.extendComponent({
onCreated() {
Meteor.subscribe('setting');
Meteor.subscribe('tableVisibilityModeSettings');
this.selectedMenu = new ReactiveVar('starred');
this.selectedWorkspaceIdVar = new ReactiveVar(null);
this.workspacesTreeVar = new ReactiveVar([]);
let currUser = ReactiveCache.getCurrentUser();
let userLanguage;
if (currUser && currUser.profile) {
@@ -50,9 +59,72 @@ BlazeComponent.extendComponent({
if (userLanguage) {
TAPi18n.setLanguage(userLanguage);
}
// Load workspaces tree reactively
this.autorun(() => {
const u = ReactiveCache.getCurrentUser();
const tree = (u && u.profile && u.profile.boardWorkspacesTree) || [];
this.workspacesTreeVar.set(tree);
});
},
reorderWorkspaces(draggedSpaceId, targetSpaceId) {
const tree = this.workspacesTreeVar.get();
// Helper to remove a space from tree
const removeSpace = (nodes, id) => {
for (let i = 0; i < nodes.length; i++) {
if (nodes[i].id === id) {
const removed = nodes.splice(i, 1)[0];
return { tree: nodes, removed };
}
if (nodes[i].children) {
const result = removeSpace(nodes[i].children, id);
if (result.removed) {
return { tree: nodes, removed: result.removed };
}
}
}
return { tree: nodes, removed: null };
};
// Helper to insert a space after target
const insertAfter = (nodes, targetId, spaceToInsert) => {
for (let i = 0; i < nodes.length; i++) {
if (nodes[i].id === targetId) {
nodes.splice(i + 1, 0, spaceToInsert);
return true;
}
if (nodes[i].children) {
if (insertAfter(nodes[i].children, targetId, spaceToInsert)) {
return true;
}
}
}
return false;
};
// Clone the tree
const newTree = EJSON.clone(tree);
// Remove the dragged space
const { tree: treeAfterRemoval, removed } = removeSpace(newTree, draggedSpaceId);
if (removed) {
// Insert after target
insertAfter(treeAfterRemoval, targetSpaceId, removed);
// Save the new tree
Meteor.call('setWorkspacesTree', treeAfterRemoval, (err) => {
if (err) console.error(err);
});
}
},
onRendered() {
// jQuery sortable is disabled in favor of HTML5 drag-and-drop for space management
// The old sortable code has been removed to prevent conflicts
/* OLD SORTABLE CODE - DISABLED
const itemsSelector = '.js-board:not(.placeholder)';
const $boards = this.$('.js-boards');
@@ -70,27 +142,20 @@ BlazeComponent.extendComponent({
EscapeActions.executeUpTo('popup-close');
},
stop(evt, ui) {
// To attribute the new index number, we need to get the DOM element
// of the previous and the following card -- if any.
const prevBoardDom = ui.item.prev('.js-board').get(0);
const nextBoardBom = ui.item.next('.js-board').get(0);
const sortIndex = Utils.calculateIndex(prevBoardDom, nextBoardBom, 1);
const nextBoardDom = ui.item.next('.js-board').get(0);
const sortIndex = Utils.calculateIndex(prevBoardDom, nextBoardDom, 1);
const boardDomElement = ui.item.get(0);
const board = Blaze.getData(boardDomElement);
// Normally the jquery-ui sortable library moves the dragged DOM element
// to its new position, which disrupts Blaze reactive updates mechanism
// (especially when we move the last card of a list, or when multiple
// users move some cards at the same time). To prevent these UX glitches
// we ask sortable to gracefully cancel the move, and to put back the
// DOM in its initial state. The card move is then handled reactively by
// Blaze with the below query.
$boards.sortable('cancel');
board.move(sortIndex.base);
const currentUser = ReactiveCache.getCurrentUser();
if (currentUser && typeof currentUser.setBoardSortIndex === 'function') {
currentUser.setBoardSortIndex(board._id, sortIndex.base);
}
},
});
// Disable drag-dropping if the current user is not a board member or is comment only
this.autorun(() => {
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
$boards.sortable({
@@ -98,6 +163,7 @@ BlazeComponent.extendComponent({
});
}
});
*/
},
userHasTeams() {
if (ReactiveCache.getCurrentUser()?.teams?.length > 0)
@@ -129,22 +195,55 @@ BlazeComponent.extendComponent({
const ret = this.userHasOrgs() || this.userHasTeams();
return ret;
},
currentMenuPath() {
const sel = this.selectedMenu.get();
const currentUser = ReactiveCache.getCurrentUser();
// Helper to find space by id in tree
const findSpaceById = (nodes, targetId, path = []) => {
for (const node of nodes) {
if (node.id === targetId) {
return [...path, node];
}
if (node.children && node.children.length > 0) {
const result = findSpaceById(node.children, targetId, [...path, node]);
if (result) return result;
}
}
return null;
};
if (sel === 'starred') {
return { icon: '⭐', text: TAPi18n.__('allboards.starred') };
} else if (sel === 'templates') {
return { icon: '📋', text: TAPi18n.__('allboards.templates') };
} else if (sel === 'remaining') {
return { icon: '📂', text: TAPi18n.__('allboards.remaining') };
} else {
// sel is a workspaceId, build path
const tree = this.workspacesTreeVar.get();
const spacePath = findSpaceById(tree, sel);
if (spacePath && spacePath.length > 0) {
const pathText = spacePath.map(s => s.name).join(' / ');
return { icon: '🗂️', text: `${TAPi18n.__('allboards.workspaces')} / ${pathText}` };
}
return { icon: '🗂️', text: TAPi18n.__('allboards.workspaces') };
}
},
boards() {
let query = {
// { type: 'board' },
// { type: { $in: ['board','template-container'] } },
$and: [
{ archived: false },
{ type: { $in: ['board', 'template-container'] } },
{ $or: [] },
{ title: { $not: { $regex: /^\^.*\^$/ } } }
]
};
const membershipOrs = [];
let allowPrivateVisibilityOnly = TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly');
if (FlowRouter.getRouteName() === 'home') {
query.$and[2].$or.push({ 'members.userId': Meteor.userId() });
membershipOrs.push({ 'members.userId': Meteor.userId() });
if (allowPrivateVisibilityOnly !== undefined && allowPrivateVisibilityOnly.booleanValue) {
query.$and.push({ 'permission': 'private' });
@@ -159,7 +258,7 @@ BlazeComponent.extendComponent({
// }
//query.$and[2].$or.push({'orgs': {$elemMatch : {orgId: orgsIds[0]}}});
query.$and[2].$or.push({ 'orgs.orgId': { $in: orgsIds } });
membershipOrs.push({ 'orgs.orgId': { $in: orgsIds } });
}
let teamIdsUserBelongs = currUser?.teamIdsUserBelongs() || '';
@@ -169,8 +268,11 @@ BlazeComponent.extendComponent({
// query.$or[2].$or.push({'teams.teamId': teamsIds[i]});
// }
//query.$and[2].$or.push({'teams': { $elemMatch : {teamId: teamsIds[0]}}});
query.$and[2].$or.push({ 'teams.teamId': { $in: teamsIds } });
membershipOrs.push({ 'teams.teamId': { $in: teamsIds } });
}
if (membershipOrs.length) {
query.$and.splice(2, 0, { $or: membershipOrs });
}
}
else if (allowPrivateVisibilityOnly !== undefined && !allowPrivateVisibilityOnly.booleanValue) {
query = {
@@ -181,10 +283,33 @@ BlazeComponent.extendComponent({
};
}
const ret = ReactiveCache.getBoards(query, {
sort: { sort: 1 /* boards default sorting */ },
});
return ret;
const boards = ReactiveCache.getBoards(query, {});
const currentUser = ReactiveCache.getCurrentUser();
let list = boards;
// Apply left menu filtering
const sel = this.selectedMenu.get();
const assignments = (currentUser && currentUser.profile && currentUser.profile.boardWorkspaceAssignments) || {};
if (sel === 'starred') {
list = list.filter(b => currentUser && currentUser.hasStarred(b._id));
} else if (sel === 'templates') {
list = list.filter(b => b.type === 'template-container');
} else if (sel === 'remaining') {
// Show boards not in any workspace AND not templates
// Keep starred boards visible in Remaining too
list = list.filter(b =>
!assignments[b._id] &&
b.type !== 'template-container'
);
} else {
// assume sel is a workspaceId
// Keep starred boards visible in their workspace too
list = list.filter(b => assignments[b._id] === sel);
}
if (currentUser && typeof currentUser.sortBoardsForUser === 'function') {
return currentUser.sortBoardsForUser(list);
}
return list.slice().sort((a, b) => (a.title || '').localeCompare(b.title || ''));
},
boardLists(boardId) {
/* Bug Board icons random dance https://github.com/wekan/wekan/issues/4214
@@ -232,41 +357,101 @@ BlazeComponent.extendComponent({
events() {
return [
{
'click .js-add-board': Popup.open('createBoard'),
'click .js-star-board'(evt) {
const boardId = this.currentData()._id;
ReactiveCache.getCurrentUser().toggleBoardStar(boardId);
'click .js-select-menu'(evt) {
const type = evt.currentTarget.getAttribute('data-type');
this.selectedWorkspaceIdVar.set(null);
this.selectedMenu.set(type);
},
'click .js-select-workspace'(evt) {
const id = evt.currentTarget.getAttribute('data-id');
this.selectedWorkspaceIdVar.set(id);
this.selectedMenu.set(id);
},
'click .js-add-workspace'(evt) {
evt.preventDefault();
const name = prompt(TAPi18n.__('allboards.add-workspace-prompt') || 'New Space name');
if (name && name.trim()) {
Meteor.call('createWorkspace', { parentId: null, name: name.trim() }, (err, res) => {
if (err) console.error(err);
});
}
},
'click .js-add-board'(evt) {
// Store the currently selected workspace/menu for board creation
const selectedWorkspaceId = this.selectedWorkspaceIdVar.get();
const selectedMenu = this.selectedMenu.get();
if (selectedWorkspaceId) {
Session.set('createBoardInWorkspace', selectedWorkspaceId);
} else {
Session.set('createBoardInWorkspace', null);
}
// Open different popup based on context
if (selectedMenu === 'templates') {
Popup.open('createTemplateContainer')(evt);
} else {
Popup.open('createBoard')(evt);
}
},
'click .js-star-board'(evt) {
evt.preventDefault();
evt.stopPropagation();
const boardId = this.currentData()._id;
if (boardId) {
Meteor.call('toggleBoardStar', boardId);
}
},
// HTML5 DnD from boards to spaces
'dragstart .js-board'(evt) {
const boardId = this.currentData()._id;
// Support multi-drag
if (BoardMultiSelection.isActive() && BoardMultiSelection.isSelected(boardId)) {
const selectedIds = BoardMultiSelection.getSelectedBoardIds();
try {
evt.originalEvent.dataTransfer.setData('text/plain', JSON.stringify(selectedIds));
evt.originalEvent.dataTransfer.setData('application/x-board-multi', 'true');
} catch (e) {}
} else {
try { evt.originalEvent.dataTransfer.setData('text/plain', boardId); } catch (e) {}
}
},
'click .js-clone-board'(evt) {
let title = getSlug(ReactiveCache.getBoard(this.currentData()._id).title) || 'cloned-board';
Meteor.call(
'copyBoard',
this.currentData()._id,
{
sort: ReactiveCache.getBoards({ archived: false }).length,
type: 'board',
title: ReactiveCache.getBoard(this.currentData()._id).title,
},
(err, res) => {
if (err) {
console.error(err);
} else {
Session.set('fromBoard', null);
subManager.subscribe('board', res, false);
FlowRouter.go('board', {
id: res,
slug: title,
});
}
},
);
evt.preventDefault();
if (confirm(TAPi18n.__('duplicate-board-confirm'))) {
let title =
getSlug(ReactiveCache.getBoard(this.currentData()._id).title) ||
'cloned-board';
Meteor.call(
'copyBoard',
this.currentData()._id,
{
sort: ReactiveCache.getBoards({ archived: false }).length,
type: 'board',
title: ReactiveCache.getBoard(this.currentData()._id).title,
},
(err, res) => {
if (err) {
console.error(err);
} else {
Session.set('fromBoard', null);
subManager.subscribe('board', res, false);
FlowRouter.go('board', {
id: res,
slug: title,
});
}
},
);
evt.preventDefault();
}
},
'click .js-archive-board'(evt) {
const boardId = this.currentData()._id;
Meteor.call('archiveBoard', boardId);
evt.preventDefault();
if (confirm(TAPi18n.__('archive-board-confirm'))) {
const boardId = this.currentData()._id;
Meteor.call('archiveBoard', boardId);
evt.preventDefault();
}
},
'click .js-accept-invite'() {
const boardId = this.currentData()._id;
@@ -281,6 +466,58 @@ BlazeComponent.extendComponent({
}
});
},
'click .js-multiselection-activate'(evt) {
evt.preventDefault();
if (BoardMultiSelection.isActive()) {
BoardMultiSelection.disable();
} else {
BoardMultiSelection.activate();
}
},
'click .js-multiselection-reset'(evt) {
evt.preventDefault();
BoardMultiSelection.disable();
},
'click .js-toggle-board-multi-selection'(evt) {
evt.preventDefault();
evt.stopPropagation();
const boardId = this.currentData()._id;
BoardMultiSelection.toogle(boardId);
},
'click .js-archive-selected-boards'(evt) {
evt.preventDefault();
const selectedBoards = BoardMultiSelection.getSelectedBoardIds();
if (selectedBoards.length > 0 && confirm(TAPi18n.__('archive-board-confirm'))) {
selectedBoards.forEach(boardId => {
Meteor.call('archiveBoard', boardId);
});
BoardMultiSelection.reset();
}
},
'click .js-duplicate-selected-boards'(evt) {
evt.preventDefault();
const selectedBoards = BoardMultiSelection.getSelectedBoardIds();
if (selectedBoards.length > 0 && confirm(TAPi18n.__('duplicate-board-confirm'))) {
selectedBoards.forEach(boardId => {
const board = ReactiveCache.getBoard(boardId);
if (board) {
Meteor.call(
'copyBoard',
boardId,
{
sort: ReactiveCache.getBoards({ archived: false }).length,
type: 'board',
title: board.title,
},
(err, res) => {
if (err) console.error(err);
}
);
}
});
BoardMultiSelection.reset();
}
},
'click #resetBtn'(event) {
let allBoards = document.getElementsByClassName("js-board");
let currBoard;
@@ -309,15 +546,18 @@ BlazeComponent.extendComponent({
const query = {
$and: [
{ archived: false },
{ type: 'board' },
{ $or: [] }
{ type: 'board' }
]
};
const ors = [];
if (selectedTeamsValues.length > 0) {
query.$and[2].$or.push({ 'teams.teamId': { $in: selectedTeamsValues } });
ors.push({ 'teams.teamId': { $in: selectedTeamsValues } });
}
if (selectedOrgsValues.length > 0) {
query.$and[2].$or.push({ 'orgs.orgId': { $in: selectedOrgsValues } });
ors.push({ 'orgs.orgId': { $in: selectedOrgsValues } });
}
if (ors.length) {
query.$and.push({ $or: ors });
}
let filteredBoards = ReactiveCache.getBoards(query, {});
@@ -347,7 +587,260 @@ BlazeComponent.extendComponent({
}
}
},
'click .js-edit-workspace'(evt) {
evt.preventDefault();
evt.stopPropagation();
const workspaceId = evt.currentTarget.getAttribute('data-id');
// Find the space in the tree
const findSpace = (nodes, id) => {
for (const node of nodes) {
if (node.id === id) return node;
if (node.children) {
const found = findSpace(node.children, id);
if (found) return found;
}
}
return null;
};
const tree = this.workspacesTreeVar.get();
const space = findSpace(tree, workspaceId);
if (space) {
const newName = prompt(TAPi18n.__('allboards.edit-workspace-name') || 'Space name:', space.name);
const newIcon = prompt(TAPi18n.__('allboards.edit-workspace-icon') || 'Space icon (markdown):', space.icon || '📁');
if (newName !== null && newName.trim()) {
// Update space in tree
const updateSpaceInTree = (nodes, id, updates) => {
return nodes.map(node => {
if (node.id === id) {
return { ...node, ...updates };
}
if (node.children) {
return { ...node, children: updateSpaceInTree(node.children, id, updates) };
}
return node;
});
};
const updatedTree = updateSpaceInTree(tree, workspaceId, {
name: newName.trim(),
icon: newIcon || '📁'
});
Meteor.call('setWorkspacesTree', updatedTree, (err) => {
if (err) console.error(err);
});
}
}
},
'click .js-add-subworkspace'(evt) {
evt.preventDefault();
evt.stopPropagation();
const parentId = evt.currentTarget.getAttribute('data-id');
const name = prompt(TAPi18n.__('allboards.add-subworkspace-prompt') || 'Subspace name:');
if (name && name.trim()) {
Meteor.call('createWorkspace', { parentId, name: name.trim() }, (err) => {
if (err) console.error(err);
});
}
},
'dragstart .workspace-node'(evt) {
const workspaceId = evt.currentTarget.getAttribute('data-workspace-id');
evt.originalEvent.dataTransfer.effectAllowed = 'move';
evt.originalEvent.dataTransfer.setData('application/x-workspace-id', workspaceId);
// Create a better drag image
const dragImage = evt.currentTarget.cloneNode(true);
dragImage.style.position = 'absolute';
dragImage.style.top = '-9999px';
dragImage.style.opacity = '0.8';
document.body.appendChild(dragImage);
evt.originalEvent.dataTransfer.setDragImage(dragImage, 0, 0);
setTimeout(() => document.body.removeChild(dragImage), 0);
evt.currentTarget.classList.add('dragging');
},
'dragend .workspace-node'(evt) {
evt.currentTarget.classList.remove('dragging');
document.querySelectorAll('.workspace-node').forEach(el => {
el.classList.remove('drag-over');
});
},
'dragover .workspace-node'(evt) {
evt.preventDefault();
evt.stopPropagation();
const draggingEl = document.querySelector('.workspace-node.dragging');
const targetEl = evt.currentTarget;
// Allow dropping boards on any space
// Or allow dropping spaces on other spaces (but not on itself or descendants)
if (!draggingEl || (targetEl !== draggingEl && !draggingEl.contains(targetEl))) {
evt.originalEvent.dataTransfer.dropEffect = 'move';
targetEl.classList.add('drag-over');
}
},
'dragleave .workspace-node'(evt) {
evt.currentTarget.classList.remove('drag-over');
},
'drop .workspace-node'(evt) {
evt.preventDefault();
evt.stopPropagation();
const targetEl = evt.currentTarget;
targetEl.classList.remove('drag-over');
// Check what's being dropped - board or workspace
const draggedWorkspaceId = evt.originalEvent.dataTransfer.getData('application/x-workspace-id');
const isMultiBoard = evt.originalEvent.dataTransfer.getData('application/x-board-multi');
const boardData = evt.originalEvent.dataTransfer.getData('text/plain');
if (draggedWorkspaceId && !boardData) {
// This is a workspace reorder operation
const targetWorkspaceId = targetEl.getAttribute('data-workspace-id');
if (draggedWorkspaceId !== targetWorkspaceId) {
this.reorderWorkspaces(draggedWorkspaceId, targetWorkspaceId);
}
} else if (boardData) {
// This is a board assignment operation
// Get the workspace ID directly from the dropped workspace-node's data-workspace-id attribute
const workspaceId = targetEl.getAttribute('data-workspace-id');
if (workspaceId) {
if (isMultiBoard) {
// Multi-board drag
try {
const boardIds = JSON.parse(boardData);
boardIds.forEach(boardId => {
Meteor.call('assignBoardToWorkspace', boardId, workspaceId);
});
} catch (e) {
// Error parsing multi-board data
}
} else {
// Single board drag
Meteor.call('assignBoardToWorkspace', boardData, workspaceId);
}
}
}
},
'dragover .js-select-menu'(evt) {
evt.preventDefault();
evt.stopPropagation();
const menuType = evt.currentTarget.getAttribute('data-type');
// Only allow drop on "remaining" menu to unassign boards from spaces
if (menuType === 'remaining') {
evt.originalEvent.dataTransfer.dropEffect = 'move';
evt.currentTarget.classList.add('drag-over');
}
},
'dragleave .js-select-menu'(evt) {
evt.currentTarget.classList.remove('drag-over');
},
'drop .js-select-menu'(evt) {
evt.preventDefault();
evt.stopPropagation();
const menuType = evt.currentTarget.getAttribute('data-type');
evt.currentTarget.classList.remove('drag-over');
// Only handle drops on "remaining" menu
if (menuType !== 'remaining') return;
const isMultiBoard = evt.originalEvent.dataTransfer.getData('application/x-board-multi');
const boardData = evt.originalEvent.dataTransfer.getData('text/plain');
if (boardData) {
if (isMultiBoard) {
// Multi-board drag - unassign all from workspaces
try {
const boardIds = JSON.parse(boardData);
boardIds.forEach(boardId => {
Meteor.call('unassignBoardFromWorkspace', boardId);
});
} catch (e) {
// Error parsing multi-board data
}
} else {
// Single board drag - unassign from workspace
Meteor.call('unassignBoardFromWorkspace', boardData);
}
}
},
},
];
},
// Helpers for templates
workspacesTree() {
return this.workspacesTreeVar.get();
},
selectedWorkspaceId() {
return this.selectedWorkspaceIdVar.get();
},
isSelectedMenu(type) {
return this.selectedMenu.get() === type;
},
isSpaceSelected(id) {
return this.selectedWorkspaceIdVar.get() === id;
},
menuItemCount(type) {
const currentUser = ReactiveCache.getCurrentUser();
const assignments = (currentUser && currentUser.profile && currentUser.profile.boardWorkspaceAssignments) || {};
// Get all boards for counting
let query = {
$and: [
{ archived: false },
{ type: { $in: ['board', 'template-container'] } },
{ $or: [{ 'members.userId': Meteor.userId() }] },
{ title: { $not: { $regex: /^\^.*\^$/ } } }
]
};
const allBoards = ReactiveCache.getBoards(query, {});
if (type === 'starred') {
return allBoards.filter(b => currentUser && currentUser.hasStarred(b._id)).length;
} else if (type === 'templates') {
return allBoards.filter(b => b.type === 'template-container').length;
} else if (type === 'remaining') {
// Count boards not in any workspace AND not templates
// Include starred boards (they appear in both Starred and Remaining)
return allBoards.filter(b =>
!assignments[b._id] &&
b.type !== 'template-container'
).length;
}
return 0;
},
workspaceCount(workspaceId) {
const currentUser = ReactiveCache.getCurrentUser();
const assignments = (currentUser && currentUser.profile && currentUser.profile.boardWorkspaceAssignments) || {};
// Get all boards for counting
let query = {
$and: [
{ archived: false },
{ type: { $in: ['board', 'template-container'] } },
{ $or: [{ 'members.userId': Meteor.userId() }] },
{ title: { $not: { $regex: /^\^.*\^$/ } } }
]
};
const allBoards = ReactiveCache.getBoards(query, {});
// Count boards directly assigned to this space (not including children)
return allBoards.filter(b => assignments[b._id] === workspaceId).length;
},
canModifyBoards() {
const currentUser = ReactiveCache.getCurrentUser();
return currentUser && !currentUser.isCommentOnly();
},
hasBoardsSelected() {
return BoardMultiSelection.count() > 0;
},
}).register('boardList');
+178
View File
@@ -0,0 +1,178 @@
/* Gantt View Styles */
.gantt-view {
width: 100%;
height: auto;
overflow: visible;
background-color: #fff;
}
.gantt-view.swimlane {
background-color: #fff;
padding: 10px;
}
.gantt-container {
overflow-x: auto;
overflow-y: visible;
background-color: #fff;
display: block;
width: 100%;
}
.gantt-container table,
.gantt-table {
border-collapse: collapse;
width: 100%;
min-width: 800px;
border: 2px solid #666;
font-family: sans-serif;
font-size: 13px;
background-color: #fff;
}
.gantt-container thead {
background-color: #e8e8e8;
border-bottom: 2px solid #666;
font-weight: bold;
position: sticky;
top: 0;
z-index: 10;
}
.gantt-container thead th,
.gantt-container thead tr > td:first-child {
border-right: 2px solid #666;
padding: 4px; /* half of 8px */
width: 100px; /* half of 200px */
text-align: left;
font-weight: bold;
background-color: #e8e8e8;
min-width: 100px; /* half of 200px */
}
.gantt-container thead td {
border-right: 1px solid #999;
padding: 2px 1px; /* half */
text-align: center;
background-color: #f5f5f5;
font-size: 11px;
min-width: 15px; /* half of 30px */
font-weight: bold;
height: auto;
line-height: 1.2;
white-space: normal;
word-break: break-word;
}
.gantt-container tbody tr {
border-bottom: 1px solid #999;
height: 32px;
}
.gantt-container tbody tr:hover {
background-color: #f9f9f9;
}
.gantt-container tbody tr:hover td {
background-color: #f9f9f9 !important;
}
.gantt-container tbody td {
border-right: 1px solid #ccc;
padding: 1px; /* half */
text-align: center;
min-width: 15px; /* half of 30px */
height: 32px;
vertical-align: middle;
line-height: 28px;
background-color: #ffffff;
font-size: 18px;
font-weight: bold;
}
.gantt-container tbody td:nth-child(even) {
background-color: #fafafa;
}
.gantt-container tbody td:first-child {
border-right: 2px solid #666;
padding: 4px; /* half of 8px */
font-weight: 500;
cursor: pointer;
background-color: #fafafa !important;
text-align: left;
width: 100px; /* half of 200px */
min-width: 100px; /* half of 200px */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
height: auto;
line-height: normal;
}
.gantt-container tbody td:first-child:hover {
background-color: #f0f0f0 !important;
text-decoration: underline;
}
.js-gantt-task-cell {
cursor: pointer;
}
.js-gantt-date-icon {
cursor: pointer;
}
.gantt-container .ganttview-weekend {
background-color: #efefef;
}
.gantt-container .ganttview-today {
background-color: #fcf8e3;
border-right: 2px solid #ffb347;
}
/* Task bar styling - VERY VISIBLE */
.gantt-container tbody td.ganttview-block {
background-color: #4CAF50 !important;
color: #fff !important;
font-size: 18px !important;
font-weight: bold !important;
padding: 2px !important;
border-radius: 2px;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.gantt-container table {
font-size: 11px;
}
.gantt-container thead td {
min-width: 20px;
padding: 2px;
}
.gantt-container tbody td {
min-width: 20px;
padding: 1px;
height: 20px;
}
.gantt-container tbody td:first-child {
width: 100px;
font-size: 12px;
}
}
/* Print styles */
@media print {
.gantt-container {
overflow: visible;
}
.gantt-container table {
page-break-inside: avoid;
}
}
+1 -1
View File
@@ -3,6 +3,6 @@ template(name="miniboard")
class="minicard-{{colorClass}}")
.minicard-title
.handle
.fa.fa-arrows
span.drag-handle(title="{{_ 'dragBoard'}}") ↕️
+viewer
= title
@@ -0,0 +1,263 @@
/* Original Positions View Styles */
.original-positions-view {
margin: 10px 0;
padding: 15px;
background-color: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 6px;
}
.original-positions-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 15px;
}
.original-positions-header .btn {
display: flex;
align-items: center;
gap: 5px;
}
.original-positions-content {
background-color: white;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 15px;
}
.original-positions-loading {
text-align: center;
padding: 20px;
color: #6c757d;
font-style: italic;
}
.original-positions-loading i {
margin-right: 8px;
}
.original-positions-filters {
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #dee2e6;
}
.original-positions-filters .btn-group {
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.original-positions-filters .btn {
display: flex;
align-items: center;
gap: 5px;
white-space: nowrap;
}
.original-positions-list {
max-height: 400px;
overflow-y: auto;
}
.original-position-item {
background-color: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 4px;
margin-bottom: 10px;
padding: 12px;
transition: all 0.2s ease;
}
.original-position-item:hover {
background-color: #e9ecef;
border-color: #ced4da;
}
.original-position-item:last-child {
margin-bottom: 0;
}
.original-position-item-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
font-weight: 600;
color: #495057;
}
.original-position-item-header i {
color: #6c757d;
width: 16px;
text-align: center;
}
.entity-type {
background-color: #007bff;
color: white;
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
font-weight: 500;
text-transform: uppercase;
}
.entity-name {
color: #212529;
font-weight: 600;
}
.entity-id {
color: #6c757d;
font-size: 11px;
font-family: monospace;
}
.original-position-item-details {
margin-left: 24px;
}
.original-position-description {
color: #495057;
margin-bottom: 6px;
font-size: 13px;
}
.original-title {
color: #6c757d;
font-size: 12px;
margin-bottom: 6px;
padding: 4px 6px;
background-color: #e9ecef;
border-radius: 3px;
}
.original-title strong {
color: #495057;
}
.original-position-date {
color: #6c757d;
font-size: 11px;
}
.no-original-positions {
text-align: center;
padding: 40px 20px;
color: #6c757d;
font-style: italic;
}
.no-original-positions i {
font-size: 24px;
margin-bottom: 10px;
display: block;
color: #adb5bd;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.original-positions-view {
margin: 5px 0;
padding: 10px;
}
.original-positions-header {
flex-direction: column;
align-items: stretch;
gap: 8px;
}
.original-positions-header .btn {
justify-content: center;
}
.original-positions-filters .btn-group {
justify-content: center;
}
.original-position-item-header {
flex-wrap: wrap;
gap: 6px;
}
.entity-name {
flex: 1;
min-width: 0;
word-break: break-word;
}
.original-position-item-details {
margin-left: 0;
margin-top: 8px;
}
}
/* Dark theme support */
@media (prefers-color-scheme: dark) {
.original-positions-view {
background-color: #2d3748;
border-color: #4a5568;
color: #e2e8f0;
}
.original-positions-content {
background-color: #1a202c;
border-color: #4a5568;
}
.original-position-item {
background-color: #2d3748;
border-color: #4a5568;
color: #e2e8f0;
}
.original-position-item:hover {
background-color: #4a5568;
border-color: #718096;
}
.original-position-item-header {
color: #e2e8f0;
}
.original-position-item-header i {
color: #a0aec0;
}
.entity-name {
color: #e2e8f0;
}
.entity-id {
color: #a0aec0;
}
.original-position-description {
color: #e2e8f0;
}
.original-title {
background-color: #4a5568;
color: #a0aec0;
}
.original-title strong {
color: #e2e8f0;
}
.original-position-date {
color: #a0aec0;
}
.no-original-positions {
color: #a0aec0;
}
.no-original-positions i {
color: #718096;
}
}
@@ -0,0 +1,82 @@
<template name="originalPositionsView">
<div class="original-positions-view">
<div class="original-positions-header">
<button class="btn btn-sm btn-outline-secondary" onclick="{{toggleOriginalPositions}}">
<i class="fa fa-history"></i>
{{#if isShowingOriginalPositions}}Hide{{else}}Show{{/if}} Original Positions
</button>
{{#if isShowingOriginalPositions}}
<button class="btn btn-sm btn-outline-primary" onclick="{{refreshHistory}}">
<i class="fa fa-refresh"></i> Refresh
</button>
{{/if}}
</div>
{{#if isShowingOriginalPositions}}
<div class="original-positions-content">
{{#if isLoading}}
<div class="original-positions-loading">
<i class="fa fa-spinner fa-spin"></i> Loading original positions...
</div>
{{else}}
<div class="original-positions-filters">
<div class="btn-group btn-group-sm" role="group">
<button type="button"
class="btn {{#if isFilterType 'all'}}btn-primary{{else}}btn-outline-secondary{{/if}}"
onclick="{{setFilterType 'all'}}">
All
</button>
<button type="button"
class="btn {{#if isFilterType 'swimlane'}}btn-primary{{else}}btn-outline-secondary{{/if}}"
onclick="{{setFilterType 'swimlane'}}">
<i class="fa fa-bars"></i> Swimlanes
</button>
<button type="button"
class="btn {{#if isFilterType 'list'}}btn-primary{{else}}btn-outline-secondary{{/if}}"
onclick="{{setFilterType 'list'}}">
<i class="fa fa-columns"></i> Lists
</button>
<button type="button"
class="btn {{#if isFilterType 'card'}}btn-primary{{else}}btn-outline-secondary{{/if}}"
onclick="{{setFilterType 'card'}}">
<i class="fa fa-sticky-note"></i> Cards
</button>
</div>
</div>
<div class="original-positions-list">
{{#each getFilteredHistory}}
<div class="original-position-item">
<div class="original-position-item-header">
<i class="fa {{getEntityTypeIcon entityType}}"></i>
<span class="entity-type">{{getEntityTypeLabel entityType}}</span>
<span class="entity-name">{{getEntityDisplayName this}}</span>
<span class="entity-id">({{entityId}})</span>
</div>
<div class="original-position-item-details">
<div class="original-position-description">
{{getEntityOriginalPositionDescription this}}
</div>
{{#if originalTitle}}
<div class="original-title">
<strong>Original title:</strong> {{originalTitle}}
</div>
{{/if}}
<div class="original-position-date">
<small class="text-muted">Created: {{formatDate createdAt}}</small>
</div>
</div>
</div>
{{else}}
<div class="no-original-positions">
<i class="fa fa-info-circle"></i>
No original position data available for this board.
</div>
{{/each}}
</div>
{{/if}}
</div>
{{/if}}
</div>
</template>
@@ -0,0 +1,148 @@
import { BlazeComponent } from 'meteor/peerlibrary:blaze-components';
import { ReactiveVar } from 'meteor/reactive-var';
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import './originalPositionsView.html';
/**
* Component to display original positions for all entities on a board
*/
class OriginalPositionsViewComponent extends BlazeComponent {
onCreated() {
super.onCreated();
this.showOriginalPositions = new ReactiveVar(false);
this.boardHistory = new ReactiveVar([]);
this.isLoading = new ReactiveVar(false);
this.filterType = new ReactiveVar('all'); // 'all', 'swimlane', 'list', 'card'
}
onRendered() {
super.onRendered();
this.loadBoardHistory();
}
loadBoardHistory() {
const boardId = Session.get('currentBoard');
if (!boardId) return;
this.isLoading.set(true);
Meteor.call('positionHistory.getBoardHistory', boardId, (error, result) => {
this.isLoading.set(false);
if (error) {
console.error('Error loading board history:', error);
this.boardHistory.set([]);
} else {
this.boardHistory.set(result);
}
});
}
toggleOriginalPositions() {
this.showOriginalPositions.set(!this.showOriginalPositions.get());
}
isShowingOriginalPositions() {
return this.showOriginalPositions.get();
}
isLoading() {
return this.isLoading.get();
}
getBoardHistory() {
return this.boardHistory.get();
}
getFilteredHistory() {
const history = this.getBoardHistory();
const filterType = this.filterType.get();
if (filterType === 'all') {
return history;
}
return history.filter(item => item.entityType === filterType);
}
getSwimlanesHistory() {
return this.getBoardHistory().filter(item => item.entityType === 'swimlane');
}
getListsHistory() {
return this.getBoardHistory().filter(item => item.entityType === 'list');
}
getCardsHistory() {
return this.getBoardHistory().filter(item => item.entityType === 'card');
}
setFilterType(type) {
this.filterType.set(type);
}
getFilterType() {
return this.filterType.get();
}
getEntityDisplayName(entity) {
const position = entity.originalPosition || {};
return position.title || `Entity ${entity.entityId}`;
}
getEntityOriginalPositionDescription(entity) {
const position = entity.originalPosition || {};
let description = `Position: ${position.sort || 0}`;
if (entity.entityType === 'list' && entity.originalSwimlaneId) {
description += ` in swimlane ${entity.originalSwimlaneId}`;
} else if (entity.entityType === 'card') {
if (entity.originalSwimlaneId) {
description += ` in swimlane ${entity.originalSwimlaneId}`;
}
if (entity.originalListId) {
description += ` in list ${entity.originalListId}`;
}
}
return description;
}
getEntityTypeIcon(entityType) {
switch (entityType) {
case 'swimlane':
return 'fa-bars';
case 'list':
return 'fa-columns';
case 'card':
return 'fa-sticky-note';
default:
return 'fa-question';
}
}
getEntityTypeLabel(entityType) {
switch (entityType) {
case 'swimlane':
return 'Swimlane';
case 'list':
return 'List';
case 'card':
return 'Card';
default:
return 'Unknown';
}
}
formatDate(date) {
return new Date(date).toLocaleString();
}
refreshHistory() {
this.loadBoardHistory();
}
}
OriginalPositionsViewComponent.register('originalPositionsView');
export default OriginalPositionsViewComponent;
+169
View File
@@ -106,6 +106,142 @@
color: white;
cursor: pointer;
font-size: 4em;
}
/* Upload progress indicators for drag-and-drop uploads */
.minicard-upload-progress,
.card-details-upload-progress {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 4px;
padding: 12px;
margin: 8px 0;
font-size: 14px;
}
.upload-progress-header {
display: flex;
align-items: center;
margin-bottom: 8px;
font-weight: bold;
color: #495057;
}
.upload-progress-header i {
margin-right: 8px;
color: #007bff;
}
.upload-progress-item {
display: flex;
flex-direction: column;
margin-bottom: 8px;
padding: 8px;
background: white;
border-radius: 3px;
border: 1px solid #dee2e6;
}
.upload-progress-item.upload-error {
border-color: #dc3545;
background: #f8d7da;
}
.upload-progress-filename {
font-weight: 500;
margin-bottom: 4px;
color: #495057;
word-break: break-all;
}
.upload-progress-bar {
width: 100%;
height: 6px;
background: #e9ecef;
border-radius: 3px;
overflow: hidden;
margin-bottom: 4px;
}
.upload-progress-fill {
height: 100%;
background: linear-gradient(90deg, #007bff, #0056b3);
transition: width 0.3s ease;
border-radius: 3px;
}
.upload-progress-item.upload-error .upload-progress-fill {
background: #dc3545;
}
.upload-progress-error,
.upload-progress-success {
display: flex;
align-items: center;
font-size: 12px;
font-weight: 500;
}
.upload-progress-error {
color: #dc3545;
}
.upload-progress-success {
color: #28a745;
}
.upload-progress-error i,
.upload-progress-success i {
margin-right: 4px;
}
/* Minicard specific styles */
.minicard-upload-progress {
margin: 4px 0;
padding: 8px;
font-size: 12px;
}
.minicard-upload-progress .upload-progress-item {
padding: 6px;
margin-bottom: 6px;
}
.minicard-upload-progress .upload-progress-filename {
font-size: 11px;
}
/* Card details specific styles */
.card-details-upload-progress {
margin: 12px 0;
padding: 16px;
}
.card-details-upload-progress .upload-progress-header {
font-size: 16px;
margin-bottom: 12px;
}
.card-details-upload-progress .upload-progress-item {
padding: 12px;
margin-bottom: 10px;
}
.card-details-upload-progress .upload-progress-filename {
font-size: 14px;
}
/* Drag over state for minicards */
.minicard.is-dragging-over {
border: 2px dashed #007bff !important;
background: rgba(0, 123, 255, 0.1) !important;
}
/* Drag over state for card details */
.js-card-details.is-dragging-over {
border: 2px dashed #007bff !important;
background: rgba(0, 123, 255, 0.05) !important;
}
top: 0;
right: 8px;
position: absolute;
@@ -200,3 +336,36 @@
margin-top: 10px;
}
}
/* Attachment migration styles */
.attachment-item.migrating {
position: relative;
opacity: 0.7;
}
.attachment-migration-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.9);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 10;
border-radius: 4px;
}
.migration-spinner {
font-size: 24px;
color: #007cba;
margin-bottom: 8px;
}
.migration-text {
font-size: 12px;
color: #666;
text-align: center;
}
+25 -18
View File
@@ -34,10 +34,10 @@ template(name="attachmentViewer")
#viewer-overlay.hidden
#viewer-top-bar
span#attachment-name
a#viewer-close.fa.fa-times-thin
a#viewer-close ❌
#viewer-container
i.fa.fa-chevron-left.attachment-arrow#prev-attachment
| ◀️
#viewer-content
img#image-viewer.hidden
video#video-viewer.hidden(controls="true")
@@ -45,7 +45,7 @@ template(name="attachmentViewer")
object#pdf-viewer.hidden(type="application/pdf")
span.pdf-preview-error {{_ 'preview-pdf-not-supported' }}
object#txt-viewer.hidden(type="text/plain")
i.fa.fa-chevron-right.attachment-arrow#next-attachment
| ▶️
template(name="attachmentGallery")
@@ -53,11 +53,11 @@ template(name="attachmentGallery")
if canModifyCard
a.attachment-item.add-attachment.js-add-attachment
i.fa.fa-plus.icon
| ➕
each attachments
.attachment-item
.attachment-item(class="{{#if isAttachmentMigrating _id}}migrating{{/if}}")
.attachment-thumbnail-container.open-preview(data-attachment-id="{{_id}}" data-card-id="{{ meta.cardId }}")
if link
if(isImage)
@@ -86,25 +86,32 @@ template(name="attachmentGallery")
= name
span.file-size ({{fileSize size}})
.attachment-actions
a.js-download(href="{{link}}?download=true", download="{{name}}")
i.fa.fa-download.icon(title="{{_ 'download'}}")
a.js-download(href="{{link}}?download=true", download="{{name}}", title="{{_ 'download'}}")
| ⬇️
if currentUser.isBoardMember
unless currentUser.isCommentOnly
unless currentUser.isWorker
a.js-rename
i.fa.fa-pencil-square-o.icon(title="{{_ 'rename'}}")
a.js-confirm-delete
i.fa.fa-trash.icon(title="{{_ 'delete'}}")
a.fa.fa-navicon.icon.js-open-attachment-menu(data-attachment-link="{{link}}" title="{{_ 'attachmentActionsPopup-title'}}")
a.js-rename(title="{{_ 'rename'}}")
| ✏️
a.js-confirm-delete(title="{{_ 'delete'}}")
| 🗑️
a.js-open-attachment-menu(data-attachment-link="{{link}}", title="{{_ 'attachmentActionsPopup-title'}}")
| ☰
// Migration spinner overlay
if isAttachmentMigrating _id
.attachment-migration-overlay
.migration-spinner
| ⚙️
.migration-text {{_ 'migrating-attachment'}}
template(name="attachmentActionsPopup")
ul.pop-over-list
li
if isImage
a(class="{{#if isCover}}js-remove-cover{{else}}js-add-cover{{/if}}")
i.fa.fa-book
i.fa.fa-picture-o
| 📖
| 🖼️
if isCover
| {{_ 'remove-cover'}}
else
@@ -112,7 +119,7 @@ template(name="attachmentActionsPopup")
if currentUser.isBoardAdmin
if isImage
a(class="{{#if isBackgroundImage}}js-remove-background-image{{else}}js-add-background-image{{/if}}")
i.fa.fa-picture-o
| 🖼️
if isBackgroundImage
| {{_ 'remove-background-image'}}
else
@@ -120,19 +127,19 @@ template(name="attachmentActionsPopup")
if $neq versions.original.storage "fs"
a.js-move-storage-fs
i.fa.fa-arrow-right
| ▶️
| {{_ 'attachment-move-storage-fs'}}
if $neq versions.original.storage "gridfs"
if versions.original.storage
a.js-move-storage-gridfs
i.fa.fa-arrow-right
| ▶️
| {{_ 'attachment-move-storage-gridfs'}}
if $neq versions.original.storage "s3"
if versions.original.storage
a.js-move-storage-s3
i.fa.fa-arrow-right
| ▶️
| {{_ 'attachment-move-storage-s3'}}
template(name="attachmentRenamePopup")
+124 -37
View File
@@ -1,6 +1,9 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { ObjectID } from 'bson';
import DOMPurify from 'dompurify';
import { sanitizeHTML, sanitizeText } from '/imports/lib/secureDOMPurify';
import uploadProgressManager from '../../lib/uploadProgressManager';
import { attachmentMigrationManager } from '/client/lib/attachmentMigrationManager';
const filesize = require('filesize');
const prettyMilliseconds = require('pretty-ms');
@@ -268,7 +271,7 @@ Template.attachmentGallery.helpers({
return ret;
},
sanitize(value) {
return DOMPurify.sanitize(value);
return sanitizeHTML(value);
},
});
@@ -296,44 +299,13 @@ Template.cardAttachmentsPopup.events({
const files = event.currentTarget.files;
if (files) {
let uploads = [];
for (const file of files) {
const fileId = new ObjectID().toString();
let fileName = DOMPurify.sanitize(file.name);
const uploaders = handleFileUpload(card, files);
// If sanitized filename is not same as original filename,
// it could be XSS that is already fixed with sanitize,
// or just normal mistake, so it is not a problem.
// That is why here is no warning.
if (fileName !== file.name) {
// If filename is empty, only in that case add some filename
if (fileName.length === 0) {
fileName = 'Empty-filename-after-sanitize.txt';
}
}
const config = {
file: file,
fileId: fileId,
fileName: fileName,
meta: Utils.getCommonAttachmentMetaFrom(card),
chunkSize: 'dynamic',
};
config.meta.fileId = fileId;
const uploader = Attachments.insert(
config,
false,
);
uploaders.forEach(uploader => {
uploader.on('start', function() {
uploads.push(this);
templateInstance.uploads.set(uploads);
});
uploader.on('uploaded', (error, fileRef) => {
if (!error) {
if (fileRef.isImage) {
card.setCover(fileRef._id);
}
}
});
uploader.on('end', (error, fileRef) => {
uploads = uploads.filter(_upload => _upload.config.fileId != fileRef._id);
templateInstance.uploads.set(uploads);
@@ -341,8 +313,7 @@ Template.cardAttachmentsPopup.events({
Popup.back();
}
});
uploader.start();
}
});
}
},
'click .js-computer-upload'(event, templateInstance) {
@@ -356,6 +327,105 @@ const MAX_IMAGE_PIXEL = Utils.MAX_IMAGE_PIXEL;
const COMPRESS_RATIO = Utils.IMAGE_COMPRESS_RATIO;
let pastedResults = null;
// Shared upload logic for drag-and-drop functionality
export function handleFileUpload(card, files) {
if (!files || files.length === 0) {
return [];
}
// Check if board allows attachments
const board = card.board();
if (!board || !board.allowsAttachments) {
if (process.env.DEBUG === 'true') {
console.warn('Attachments not allowed on this board');
}
return [];
}
// Check if user can modify the card
if (!Utils.canModifyCard()) {
if (process.env.DEBUG === 'true') {
console.warn('User does not have permission to modify this card');
}
return [];
}
const uploads = [];
for (const file of files) {
// Basic file validation
if (!file || !file.name) {
if (process.env.DEBUG === 'true') {
console.warn('Invalid file object');
}
continue;
}
const fileId = new ObjectID().toString();
let fileName = sanitizeText(file.name);
// If sanitized filename is not same as original filename,
// it could be XSS that is already fixed with sanitize,
// or just normal mistake, so it is not a problem.
// That is why here is no warning.
if (fileName !== file.name) {
// If filename is empty, only in that case add some filename
if (fileName.length === 0) {
fileName = 'Empty-filename-after-sanitize.txt';
}
}
const config = {
file: file,
fileId: fileId,
fileName: fileName,
meta: Utils.getCommonAttachmentMetaFrom(card),
chunkSize: 'dynamic',
};
config.meta.fileId = fileId;
try {
const uploader = Attachments.insert(
config,
false,
);
// Add to progress manager for tracking
const uploadId = uploadProgressManager.addUpload(card._id, uploader, file);
uploader.on('uploaded', (error, fileRef) => {
if (!error) {
if (fileRef.isImage) {
card.setCover(fileRef._id);
if (process.env.DEBUG === 'true') {
console.log(`Set cover image for card ${card._id}: ${fileRef.name}`);
}
}
} else {
if (process.env.DEBUG === 'true') {
console.error('Upload error:', error);
}
}
});
uploader.on('error', (error) => {
if (process.env.DEBUG === 'true') {
console.error('Upload error:', error);
}
});
uploads.push(uploader);
uploader.start();
} catch (error) {
if (process.env.DEBUG === 'true') {
console.error('Failed to create uploader:', error);
}
}
}
return uploads;
}
Template.previewClipboardImagePopup.onRendered(() => {
// we can paste image from clipboard
const handle = results => {
@@ -498,7 +568,7 @@ BlazeComponent.extendComponent({
const name = this.$('.js-edit-attachment-name')[0]
.value
.trim() + this.data().extensionWithDot;
if (name === DOMPurify.sanitize(name)) {
if (name === sanitizeText(name)) {
Meteor.call('renameAttachment', this.data()._id, name);
}
Popup.back();
@@ -507,3 +577,20 @@ BlazeComponent.extendComponent({
]
}
}).register('attachmentRenamePopup');
// Template helpers for attachment migration status
Template.registerHelper('attachmentMigrationStatus', function(attachmentId) {
return attachmentMigrationManager.getAttachmentMigrationStatus(attachmentId);
});
Template.registerHelper('isAttachmentMigrating', function(attachmentId) {
return attachmentMigrationManager.isAttachmentBeingMigrated(attachmentId);
});
Template.registerHelper('attachmentMigrationProgress', function() {
return attachmentMigrationManager.attachmentMigrationProgress.get();
});
Template.registerHelper('attachmentMigrationStatusText', function() {
return attachmentMigrationManager.attachmentMigrationStatus.get();
});
+9 -10
View File
@@ -6,10 +6,10 @@ template(name="cardCustomFieldsPopup")
span.full-name
= name
if hasCustomField
i.fa.fa-check
| ✅
hr
a.quiet-button.full.js-settings
i.fa.fa-cog
| ⚙️
span {{_ 'settings'}}
template(name="cardCustomField")
@@ -22,7 +22,7 @@ template(name="cardCustomField-text")
= value
.edit-controls.clearfix
button.primary(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
a.js-close-inlined-form
else
a.js-open-inlined-form
if value
@@ -41,7 +41,7 @@ template(name="cardCustomField-number")
input(type="number" value=data.value)
.edit-controls.clearfix
button.primary(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
a.js-close-inlined-form
else
a.js-open-inlined-form
if value
@@ -55,10 +55,9 @@ template(name="cardCustomField-number")
template(name="cardCustomField-checkbox")
.js-checklist-item.checklist-item(class="{{#if data.value }}is-checked{{/if}}")
if canModifyCard
.check-box-container
.check-box.materialCheckBox(class="{{#if data.value }}is-checked{{/if}}")
span.check-box-unicode {{#if data.value }}✅{{else}}⬜{{/if}}
else
.materialCheckBox(class="{{#if data.value }}is-checked{{/if}}")
span.check-box-unicode {{#if data.value }}✅{{else}}⬜{{/if}}
template(name="cardCustomField-currency")
if canModifyCard
@@ -66,7 +65,7 @@ template(name="cardCustomField-currency")
input(type="text" value=data.value autofocus)
.edit-controls.clearfix
button.primary(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
a.js-close-inlined-form
else
a.js-open-inlined-form
if value
@@ -113,7 +112,7 @@ template(name="cardCustomField-dropdown")
= name
.edit-controls.clearfix
button.primary(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
a.js-close-inlined-form
else
a.js-open-inlined-form
if value
@@ -134,7 +133,7 @@ template(name="cardCustomField-stringtemplate")
input.js-card-customfield-stringtemplate-item.last(type="text" value="" placeholder="{{_ 'custom-field-stringtemplate-item-placeholder'}}" autofocus)
.edit-controls.clearfix
button.primary(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
a.js-close-inlined-form
else
a.js-open-inlined-form
if value
+40 -16
View File
@@ -1,6 +1,27 @@
import moment from 'moment/min/moment-with-locales';
import { TAPi18n } from '/imports/i18n';
import { DatePicker } from '/client/lib/datepicker';
import { ReactiveCache } from '/imports/reactiveCache';
import {
formatDateTime,
formatDate,
formatDateByUserPreference,
formatTime,
getISOWeek,
isValidDate,
isBefore,
isAfter,
isSame,
add,
subtract,
startOf,
endOf,
format,
parseDate,
now,
createDate,
fromNow,
calendar
} from '/imports/lib/dateUtils';
import Cards from '/models/cards';
import { CustomFieldStringTemplate } from '/client/lib/customFields'
@@ -91,6 +112,7 @@ CardCustomField.register('cardCustomField');
events() {
return [
{
'click .js-checklist-item .check-box-unicode': this.toggleItem,
'click .js-checklist-item .check-box-container': this.toggleItem,
},
];
@@ -134,31 +156,33 @@ CardCustomField.register('cardCustomField');
super.onCreated();
const self = this;
self.date = ReactiveVar();
self.now = ReactiveVar(moment());
self.now = ReactiveVar(now());
window.setInterval(() => {
self.now.set(moment());
self.now.set(now());
}, 60000);
self.autorun(() => {
self.date.set(moment(self.data().value));
self.date.set(new Date(self.data().value));
});
}
showWeek() {
return this.date.get().week().toString();
return getISOWeek(this.date.get()).toString();
}
showWeekOfYear() {
return ReactiveCache.getCurrentUser().isShowWeekOfYear();
const user = ReactiveCache.getCurrentUser();
if (!user) {
// For non-logged-in users, week of year is not shown
return false;
}
return user.isShowWeekOfYear();
}
showDate() {
// this will start working once mquandalle:moment
// is updated to at least moment.js 2.10.5
// until then, the date is displayed in the "L" format
return this.date.get().calendar(null, {
sameElse: 'llll',
});
const currentUser = ReactiveCache.getCurrentUser();
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
return formatDateByUserPreference(this.date.get(), dateFormat, true);
}
showISODate() {
@@ -167,8 +191,8 @@ CardCustomField.register('cardCustomField');
classes() {
if (
this.date.get().isBefore(this.now.get(), 'minute') &&
this.now.get().isBefore(this.data().value)
isBefore(this.date.get(), this.now.get(), 'minute') &&
isBefore(this.now.get(), this.data().value, 'minute')
) {
return 'current';
}
@@ -176,7 +200,7 @@ CardCustomField.register('cardCustomField');
}
showTitle() {
return `${TAPi18n.__('card-start-on')} ${this.date.get().format('LLLL')}`;
return `${TAPi18n.__('card-start-on')} ${this.date.get().toLocaleString()}`;
}
events() {
@@ -195,7 +219,7 @@ CardCustomField.register('cardCustomField');
const self = this;
self.card = Utils.getCurrentCard();
self.customFieldId = this.data()._id;
this.data().value && this.date.set(moment(this.data().value));
this.data().value && this.date.set(new Date(this.data().value));
}
_storeDate(date) {
+103 -26
View File
@@ -8,57 +8,134 @@
.card-date.is-active {
background-color: #b3b3b3;
}
.card-date.current,
.card-date.almost-due,
.card-date.due,
.card-date.long-overdue {
/* Date status colors - red = overdue, amber = due soon, no shade = not due */
.card-date.overdue {
background-color: #ff4444; /* Red for overdue */
color: #fff;
}
.card-date.overdue:hover,
.card-date.overdue.is-active {
background-color: #cc3333;
}
.card-date.due-soon {
background-color: #ffaa00; /* Amber for due soon */
color: #000;
}
.card-date.due-soon:hover,
.card-date.due-soon.is-active {
background-color: #e69900;
}
.card-date.not-due {
/* No special background - uses default date type colors */
}
.card-date.current {
background-color: #5ba639;
background-color: #5ba639; /* Green for current/active */
color: #fff;
}
.card-date.current:hover,
.card-date.current.is-active {
background-color: #46802c;
}
.card-date.almost-due {
background-color: #edc909;
.card-date.completed {
background-color: #90ee90; /* Light green for completed */
color: #000;
}
.card-date.almost-due:hover,
.card-date.almost-due.is-active {
background-color: #bc9f07;
.card-date.completed:hover,
.card-date.completed.is-active {
background-color: #7dd87d;
}
.card-date.due {
background-color: #fa3f00;
.card-date.completed-early {
background-color: #4caf50; /* Green for completed early */
color: #fff;
}
.card-date.due:hover,
.card-date.due.is-active {
background-color: #c73200;
.card-date.completed-early:hover,
.card-date.completed-early.is-active {
background-color: #45a049;
}
.card-date.long-overdue {
background-color: #fd5d47;
.card-date.completed-late {
background-color: #ff9800; /* Orange for completed late */
color: #fff;
}
.card-date.long-overdue:hover,
.card-date.long-overdue.is-active {
background-color: #fd3e24;
.card-date.completed-late:hover,
.card-date.completed-late.is-active {
background-color: #f57c00;
}
.card-date.completed-on-time {
background-color: #2196f3; /* Blue for completed on time */
color: #fff;
}
.card-date.completed-on-time:hover,
.card-date.completed-on-time.is-active {
background-color: #1976d2;
}
/* Date type specific colors */
.card-date.received-date {
background-color: #dbdbdb; /* Light grey for received */
}
.card-date.received-date:hover,
.card-date.received-date.is-active {
background-color: #b3b3b3;
}
.card-date.start-date {
background-color: #90ee90; /* Light green for start */
color: #000; /* Black text for start */
}
.card-date.start-date:hover,
.card-date.start-date.is-active {
background-color: #7dd87d;
}
.card-date.due-date {
background-color: #ffd700; /* Yellow for due */
color: #000; /* Black text for due */
}
.card-date.due-date:hover,
.card-date.due-date.is-active {
background-color: #e6c200;
}
.card-date.end-date {
background-color: #ffb3b3; /* Light red for end */
color: #000; /* Black text for end */
}
.card-date.end-date:hover,
.card-date.end-date.is-active {
background-color: #ff9999;
}
.card-date.end-date time::before {
content: "\f253";
content: "🏁"; /* Finish flag - represents end/completion */
}
.card-date.due-date time::before {
content: "\f090";
content: "⏰"; /* Alarm clock - represents due/deadline */
}
.card-date.start-date time::before {
content: "\f251";
content: "🚀"; /* Rocket - represents start/launch */
}
.card-date.received-date time::before {
content: "\f08b";
content: "📥"; /* Inbox tray - represents received/incoming */
}
/* Generic date badge and custom field date */
.card-date:not(.received-date):not(.start-date):not(.due-date):not(.end-date) time::before {
/*content: "📅"; // Calendar - represents generic date */
}
.card-date time::before {
font: normal normal normal 14px/1 FontAwesome;
font-size: inherit;
-webkit-font-smoothing: antialiased;
margin-right: 0.3em;
display: inline-block;
}
.customfield-date {
display: block;
+129
View File
@@ -21,3 +21,132 @@ template(name="dateCustomField")
if showWeekOfYear
b
| {{showWeek}}
template(name="minicardReceivedDate")
if canModifyCard
a.js-edit-date.card-date.received-date(title="{{_ 'card-received'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
else
a.card-date.received-date(title="{{_ 'card-received'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
template(name="minicardStartDate")
if canModifyCard
a.js-edit-date.card-date.start-date(title="{{_ 'card-start'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
else
a.card-date.start-date(title="{{_ 'card-start'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
template(name="minicardDueDate")
if canModifyCard
a.js-edit-date.card-date.due-date(title="{{_ 'card-due'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
else
a.card-date.due-date(title="{{_ 'card-due'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
template(name="minicardEndDate")
if canModifyCard
a.js-edit-date.card-date.end-date(title="{{_ 'card-end'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
else
a.card-date.end-date(title="{{_ 'card-end'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
template(name="minicardCustomFieldDate")
a(title="{{_ 'date'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
template(name="editCardReceivedDatePopup")
form.edit-card-received-date
.datepicker
// Date input field (existing)
// Insert calendar selector right after date input
.calendar-selector
label(for="calendar-received") 🗓️
input#calendar-received.js-calendar-date(type="date")
// Time input field (if present)
.clear-date
a.js-clear-date {{_ 'clear'}}
.datepicker-actions
button.primary.wide.left(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right {{_ 'delete'}}
template(name="editCardStartDatePopup")
form.edit-card-start-date
.datepicker
// Date input field (existing)
.calendar-selector
label(for="calendar-start") 🗓️
input#calendar-start.js-calendar-date(type="date")
// Time input field (if present)
.clear-date
a.js-clear-date {{_ 'clear'}}
.datepicker-actions
button.primary.wide.left(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right {{_ 'delete'}}
template(name="editCardDueDatePopup")
form.edit-card-due-date
.datepicker
// Date input field (existing)
.calendar-selector
label(for="calendar-due") 🗓️
input#calendar-due.js-calendar-date(type="date")
// Time input field (if present)
.clear-date
a.js-clear-date {{_ 'clear'}}
.datepicker-actions
button.primary.wide.left(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right {{_ 'delete'}}
template(name="editCardEndDatePopup")
form.edit-card-end-date
.datepicker
// Date input field (existing)
.calendar-selector
label(for="calendar-end") 🗓️
input#calendar-end.js-calendar-date(type="date")
// Time input field (if present)
.clear-date
a.js-clear-date {{_ 'clear'}}
.datepicker-actions
button.primary.wide.left(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right {{_ 'delete'}}
+197 -84
View File
@@ -1,17 +1,38 @@
import moment from 'moment/min/moment-with-locales';
import { TAPi18n } from '/imports/i18n';
import { DatePicker } from '/client/lib/datepicker';
import {
formatDateTime,
formatDate,
formatDateByUserPreference,
formatTime,
getISOWeek,
isValidDate,
isBefore,
isAfter,
isSame,
add,
subtract,
startOf,
endOf,
format,
parseDate,
now,
createDate,
fromNow,
calendar,
diff
} from '/imports/lib/dateUtils';
// editCardReceivedDatePopup
(class extends DatePicker {
onCreated() {
super.onCreated(moment().format('YYYY-MM-DD HH:mm'));
super.onCreated(formatDateTime(now()));
this.data().getReceived() &&
this.date.set(moment(this.data().getReceived()));
this.date.set(new Date(this.data().getReceived()));
}
_storeDate(date) {
this.card.setReceived(date);
this.card.setReceived(formatDateTime(date));
}
_deleteDate() {
@@ -22,22 +43,28 @@ import { DatePicker } from '/client/lib/datepicker';
// editCardStartDatePopup
(class extends DatePicker {
onCreated() {
super.onCreated(moment().format('YYYY-MM-DD HH:mm'));
this.data().getStart() && this.date.set(moment(this.data().getStart()));
super.onCreated(formatDateTime(now()));
this.data().getStart() && this.date.set(new Date(this.data().getStart()));
}
onRendered() {
super.onRendered();
if (moment.isDate(this.card.getReceived())) {
this.$('.js-datepicker').datepicker(
'setStartDate',
this.card.getReceived(),
);
}
// DatePicker base class handles initialization with native HTML inputs
const self = this;
this.$('.js-calendar-date').on('change', function(evt) {
const currentUser = ReactiveCache.getCurrentUser && ReactiveCache.getCurrentUser();
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
const value = evt.target.value;
if (value) {
// Format date according to user preference
const formatted = formatDateByUserPreference(new Date(value), dateFormat, true);
self._storeDate(new Date(value));
}
});
}
_storeDate(date) {
this.card.setStart(date);
this.card.setStart(formatDateTime(date));
}
_deleteDate() {
@@ -49,18 +76,16 @@ import { DatePicker } from '/client/lib/datepicker';
(class extends DatePicker {
onCreated() {
super.onCreated('1970-01-01 17:00:00');
this.data().getDue() && this.date.set(moment(this.data().getDue()));
this.data().getDue() && this.date.set(new Date(this.data().getDue()));
}
onRendered() {
super.onRendered();
if (moment.isDate(this.card.getStart())) {
this.$('.js-datepicker').datepicker('setStartDate', this.card.getStart());
}
// DatePicker base class handles initialization with native HTML inputs
}
_storeDate(date) {
this.card.setDue(date);
this.card.setDue(formatDateTime(date));
}
_deleteDate() {
@@ -71,19 +96,17 @@ import { DatePicker } from '/client/lib/datepicker';
// editCardEndDatePopup
(class extends DatePicker {
onCreated() {
super.onCreated(moment().format('YYYY-MM-DD HH:mm'));
this.data().getEnd() && this.date.set(moment(this.data().getEnd()));
super.onCreated(formatDateTime(now()));
this.data().getEnd() && this.date.set(new Date(this.data().getEnd()));
}
onRendered() {
super.onRendered();
if (moment.isDate(this.card.getStart())) {
this.$('.js-datepicker').datepicker('setStartDate', this.card.getStart());
}
// DatePicker base class handles initialization with native HTML inputs
}
_storeDate(date) {
this.card.setEnd(date);
this.card.setEnd(formatDateTime(date));
}
_deleteDate() {
@@ -100,27 +123,29 @@ const CardDate = BlazeComponent.extendComponent({
onCreated() {
const self = this;
self.date = ReactiveVar();
self.now = ReactiveVar(moment());
self.now = ReactiveVar(now());
window.setInterval(() => {
self.now.set(moment());
self.now.set(now());
}, 60000);
},
showWeek() {
return this.date.get().week().toString();
return getISOWeek(this.date.get()).toString();
},
showWeekOfYear() {
return ReactiveCache.getCurrentUser().isShowWeekOfYear();
const user = ReactiveCache.getCurrentUser();
if (!user) {
// For non-logged-in users, week of year is not shown
return false;
}
return user.isShowWeekOfYear();
},
showDate() {
// this will start working once mquandalle:moment
// is updated to at least moment.js 2.10.5
// until then, the date is displayed in the "L" format
return this.date.get().calendar(null, {
sameElse: 'llll',
});
const currentUser = ReactiveCache.getCurrentUser();
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
return formatDateByUserPreference(this.date.get(), dateFormat, true);
},
showISODate() {
@@ -133,7 +158,7 @@ class CardReceivedDate extends CardDate {
super.onCreated();
const self = this;
self.autorun(() => {
self.date.set(moment(self.data().getReceived()));
self.date.set(new Date(self.data().getReceived()));
});
}
@@ -143,21 +168,26 @@ class CardReceivedDate extends CardDate {
const endAt = this.data().getEnd();
const startAt = this.data().getStart();
const theDate = this.date.get();
// if dueAt, endAt and startAt exist & are > receivedAt, receivedAt doesn't need to be flagged
const now = this.now.get();
// Received date logic: if received date is after start, due, or end dates, it's overdue
if (
(startAt && theDate.isAfter(startAt)) ||
(endAt && theDate.isAfter(endAt)) ||
(dueAt && theDate.isAfter(dueAt))
)
classes += 'long-overdue';
else classes += 'current';
(startAt && isAfter(theDate, startAt)) ||
(endAt && isAfter(theDate, endAt)) ||
(dueAt && isAfter(theDate, dueAt))
) {
classes += 'overdue';
} else {
classes += 'not-due';
}
return classes;
}
showTitle() {
return `${TAPi18n.__('card-received-on')} ${this.date
.get()
.format('LLLL')}`;
const currentUser = ReactiveCache.getCurrentUser();
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
const formattedDate = formatDateByUserPreference(this.date.get(), dateFormat, true);
return `${TAPi18n.__('card-received-on')} ${formattedDate}`;
}
events() {
@@ -173,26 +203,35 @@ class CardStartDate extends CardDate {
super.onCreated();
const self = this;
self.autorun(() => {
self.date.set(moment(self.data().getStart()));
self.date.set(new Date(self.data().getStart()));
});
}
classes() {
let classes = 'start-date' + ' ';
let classes = 'start-date ';
const dueAt = this.data().getDue();
const endAt = this.data().getEnd();
const theDate = this.date.get();
const now = this.now.get();
// if dueAt or endAt exist & are > startAt, startAt doesn't need to be flagged
if ((endAt && theDate.isAfter(endAt)) || (dueAt && theDate.isAfter(dueAt)))
classes += 'long-overdue';
else if (theDate.isAfter(now)) classes += '';
else classes += 'current';
// Start date logic: if start date is after due or end dates, it's overdue
if ((endAt && isAfter(theDate, endAt)) || (dueAt && isAfter(theDate, dueAt))) {
classes += 'overdue';
} else if (isAfter(theDate, now)) {
// Start date is in the future - not due yet
classes += 'not-due';
} else {
// Start date is today or in the past - current/active
classes += 'current';
}
return classes;
}
showTitle() {
return `${TAPi18n.__('card-start-on')} ${this.date.get().format('LLLL')}`;
const currentUser = ReactiveCache.getCurrentUser();
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
const formattedDate = formatDateByUserPreference(this.date.get(), dateFormat, true);
return `${TAPi18n.__('card-start-on')} ${formattedDate}`;
}
events() {
@@ -208,27 +247,48 @@ class CardDueDate extends CardDate {
super.onCreated();
const self = this;
self.autorun(() => {
self.date.set(moment(self.data().getDue()));
self.date.set(new Date(self.data().getDue()));
});
}
classes() {
let classes = 'due-date' + ' ';
let classes = 'due-date ';
const endAt = this.data().getEnd();
const theDate = this.date.get();
const now = this.now.get();
// if the due date is after the end date, green - done early
if (endAt && theDate.isAfter(endAt)) classes += 'current';
// if there is an end date, don't need to flag the due date
else if (endAt) classes += '';
else if (now.diff(theDate, 'days') >= 2) classes += 'long-overdue';
else if (now.diff(theDate, 'minute') >= 0) classes += 'due';
else if (now.diff(theDate, 'days') >= -1) classes += 'almost-due';
// If there's an end date and it's before the due date, task is completed early
if (endAt && isBefore(endAt, theDate)) {
classes += 'completed-early';
}
// If there's an end date, don't show due date status since task is completed
else if (endAt) {
classes += 'completed';
}
// Due date logic based on current time
else {
const daysDiff = diff(theDate, now, 'days');
if (daysDiff < 0) {
// Due date is in the past - overdue
classes += 'overdue';
} else if (daysDiff <= 1) {
// Due today or tomorrow - due soon
classes += 'due-soon';
} else {
// Due date is more than 1 day away - not due yet
classes += 'not-due';
}
}
return classes;
}
showTitle() {
return `${TAPi18n.__('card-due-on')} ${this.date.get().format('LLLL')}`;
const currentUser = ReactiveCache.getCurrentUser();
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
const formattedDate = formatDateByUserPreference(this.date.get(), dateFormat, true);
return `${TAPi18n.__('card-due-on')} ${formattedDate}`;
}
events() {
@@ -244,22 +304,33 @@ class CardEndDate extends CardDate {
super.onCreated();
const self = this;
self.autorun(() => {
self.date.set(moment(self.data().getEnd()));
self.date.set(new Date(self.data().getEnd()));
});
}
classes() {
let classes = 'end-date' + ' ';
let classes = 'end-date ';
const dueAt = this.data().getDue();
const theDate = this.date.get();
if (!dueAt) classes += '';
else if (theDate.isBefore(dueAt)) classes += 'current';
else if (theDate.isAfter(dueAt)) classes += 'due';
if (!dueAt) {
// No due date set - just show as completed
classes += 'completed';
} else if (isBefore(theDate, dueAt)) {
// End date is before due date - completed early
classes += 'completed-early';
} else if (isAfter(theDate, dueAt)) {
// End date is after due date - completed late
classes += 'completed-late';
} else {
// End date equals due date - completed on time
classes += 'completed-on-time';
}
return classes;
}
showTitle() {
return `${TAPi18n.__('card-end-on')} ${this.date.get().format('LLLL')}`;
return `${TAPi18n.__('card-end-on')} ${format(this.date.get(), 'LLLL')}`;
}
events() {
@@ -279,16 +350,21 @@ class CardCustomFieldDate extends CardDate {
super.onCreated();
const self = this;
self.autorun(() => {
self.date.set(moment(self.data().value));
self.date.set(new Date(self.data().value));
});
}
showWeek() {
return this.date.get().week().toString();
return getISOWeek(this.date.get()).toString();
}
showWeekOfYear() {
return ReactiveCache.getCurrentUser().isShowWeekOfYear();
const user = ReactiveCache.getCurrentUser();
if (!user) {
// For non-logged-in users, week of year is not shown
return false;
}
return user.isShowWeekOfYear();
}
showDate() {
@@ -301,7 +377,10 @@ class CardCustomFieldDate extends CardDate {
}
showTitle() {
return `${this.date.get().format('LLLL')}`;
const currentUser = ReactiveCache.getCurrentUser();
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
const formattedDate = formatDateByUserPreference(this.date.get(), dateFormat, true);
return `${formattedDate}`;
}
classes() {
@@ -315,32 +394,62 @@ class CardCustomFieldDate extends CardDate {
CardCustomFieldDate.register('cardCustomFieldDate');
(class extends CardReceivedDate {
template() {
return 'minicardReceivedDate';
}
showDate() {
return this.date.get().format('L');
const currentUser = ReactiveCache.getCurrentUser();
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
return formatDateByUserPreference(this.date.get(), dateFormat, true);
}
}.register('minicardReceivedDate'));
(class extends CardStartDate {
template() {
return 'minicardStartDate';
}
showDate() {
return this.date.get().format('L');
const currentUser = ReactiveCache.getCurrentUser();
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
return formatDateByUserPreference(this.date.get(), dateFormat, true);
}
}.register('minicardStartDate'));
(class extends CardDueDate {
template() {
return 'minicardDueDate';
}
showDate() {
return this.date.get().format('L');
const currentUser = ReactiveCache.getCurrentUser();
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
return formatDateByUserPreference(this.date.get(), dateFormat, true);
}
}.register('minicardDueDate'));
(class extends CardEndDate {
template() {
return 'minicardEndDate';
}
showDate() {
return this.date.get().format('L');
const currentUser = ReactiveCache.getCurrentUser();
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
return formatDateByUserPreference(this.date.get(), dateFormat, true);
}
}.register('minicardEndDate'));
(class extends CardCustomFieldDate {
template() {
return 'minicardCustomFieldDate';
}
showDate() {
return this.date.get().format('L');
const currentUser = ReactiveCache.getCurrentUser();
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
return formatDateByUserPreference(this.date.get(), dateFormat, true);
}
}.register('minicardCustomFieldDate'));
@@ -349,7 +458,7 @@ class VoteEndDate extends CardDate {
super.onCreated();
const self = this;
self.autorun(() => {
self.date.set(moment(self.data().getVoteEnd()));
self.date.set(new Date(self.data().getVoteEnd()));
});
}
classes() {
@@ -357,10 +466,12 @@ class VoteEndDate extends CardDate {
return classes;
}
showDate() {
return this.date.get().format('L LT');
const currentUser = ReactiveCache.getCurrentUser();
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
return formatDateByUserPreference(this.date.get(), dateFormat, true);
}
showTitle() {
return `${TAPi18n.__('card-end-on')} ${this.date.get().format('LLLL')}`;
return `${TAPi18n.__('card-end-on')} ${this.date.get().toLocaleString()}`;
}
events() {
@@ -376,7 +487,7 @@ class PokerEndDate extends CardDate {
super.onCreated();
const self = this;
self.autorun(() => {
self.date.set(moment(self.data().getPokerEnd()));
self.date.set(new Date(self.data().getPokerEnd()));
});
}
classes() {
@@ -384,10 +495,12 @@ class PokerEndDate extends CardDate {
return classes;
}
showDate() {
return this.date.get().format('l LT');
const currentUser = ReactiveCache.getCurrentUser();
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
return formatDateByUserPreference(this.date.get(), dateFormat, true);
}
showTitle() {
return `${TAPi18n.__('card-end-on')} ${this.date.get().format('LLLL')}`;
return `${TAPi18n.__('card-end-on')} ${format(this.date.get(), 'LLLL')}`;
}
events() {
+291 -15
View File
@@ -1,10 +1,38 @@
/* Date Format Selector */
.card-details-item-date-format {
margin-bottom: 10px;
}
.card-details-item-date-format .card-details-item-title {
font-size: 14px;
font-weight: bold;
margin-bottom: 5px;
color: #333;
}
.card-details-item-date-format .js-date-format-selector {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
background-color: #fff;
font-size: 14px;
cursor: pointer;
}
.card-details-item-date-format .js-date-format-selector:focus {
outline: none;
border-color: #007cba;
box-shadow: 0 0 0 2px rgba(0, 124, 186, 0.2);
}
.assignee {
border-radius: 3px;
display: block;
position: relative;
float: left;
height: 30px;
width: 30px;
height: clamp(24px, 3.5vw, 36px);
width: clamp(24px, 3.5vw, 36px);
margin: .3vh;
cursor: pointer;
user-select: none;
@@ -70,32 +98,91 @@
}
.copied-tooltip {
display: none;
padding: 0px 10px;
padding: 0 1.3vw;
background-color: rgba(0,0,0,0.875);
color: #fff;
border-radius: 5px;
border-radius: 0.7vw;
}
.card-details {
padding: 0;
flex-shrink: 0;
flex-basis: 600px;
flex-basis: min(600px, 80vw);
will-change: flex-basis;
overflow-y: scroll;
overflow-x: hidden;
background: #f7f7f7;
border-radius: bottom 3px;
border-radius: bottom 0.4vw;
z-index: 30;
animation: flexGrowIn 0.1s;
box-shadow: 0 0 7px 0 #b3b3b3;
box-shadow: 0 0 0.9vh 0 #b3b3b3;
transition: flex-basis 0.1s;
box-sizing: border-box;
}
/* Desktop mode: position card below board header */
body.desktop-mode .card-details:not(.card-details-popup) {
position: fixed;
width: auto;
max-width: 800px;
flex-basis: auto;
border-radius: 8px;
z-index: 100;
}
/* Default position for first card or when dragged */
body.desktop-mode .card-details:not(.card-details-popup):not([style*="left"]):not([style*="top"]) {
top: 50px;
left: 20px;
right: 20px;
bottom: 20px;
}
/* Stagger positions for multiple cards using nth-of-type */
body.desktop-mode .card-details:not(.card-details-popup):nth-of-type(1) {
top: 50px;
left: 20px;
}
body.desktop-mode .card-details:not(.card-details-popup):nth-of-type(2) {
top: 80px;
left: 50px;
}
body.desktop-mode .card-details:not(.card-details-popup):nth-of-type(3) {
top: 110px;
left: 80px;
}
body.desktop-mode .card-details:not(.card-details-popup):nth-of-type(4) {
top: 140px;
left: 110px;
}
body.desktop-mode .card-details:not(.card-details-popup):nth-of-type(5) {
top: 170px;
left: 140px;
}
/* For expanded cards, set dimensions */
body.desktop-mode .card-details:not(.card-details-popup):not(.card-details-collapsed) {
right: 20px;
bottom: 20px;
}
/* Collapsed card state - hide content and set height to title row only */
.card-details.card-details-collapsed .card-details-canvas > *:not(.card-details-header) {
display: none;
}
.card-details.card-details-collapsed {
height: auto !important;
bottom: auto !important;
overflow: visible;
}
body.desktop-mode .card-details.card-details-collapsed {
bottom: auto !important;
}
.card-details .mCustomScrollBox {
padding-left: 0;
}
.card-details .card-details-canvas {
width: auto;
padding: 0 20px;
padding: 0 2.5vw;
}
.card-details .card-details-header {
margin: 0 -20px 5px;
@@ -111,6 +198,49 @@
display: inline-block;
margin-right: 5px;
}
/* Collapse toggle triangle */
.card-details .card-details-header .card-collapse-toggle {
float: left;
font-size: 20px;
padding: 7px 10px;
margin-left: -10px;
margin-right: 5px;
cursor: pointer;
user-select: none;
color: #000;
}
/* Bring to front / Send to back buttons */
.card-details .card-details-header .card-bring-to-front,
.card-details .card-details-header .card-send-to-back {
float: right;
font-size: 18px;
padding: 7px 8px;
margin-right: 5px;
cursor: pointer;
user-select: none;
color: #333;
}
.card-details .card-details-header .card-bring-to-front:hover,
.card-details .card-details-header .card-send-to-back:hover {
color: #000;
background: rgba(0,0,0,0.05);
border-radius: 3px;
}
/* Drag handle */
.card-details .card-details-header .card-drag-handle {
font-size: 20px;
padding: 8px 10px;
margin-right: 10px;
cursor: move;
user-select: none;
display: inline-block;
float: right;
}
.card-details .card-details-header .close-card-details,
.card-details .card-details-header .maximize-card-details,
.card-details .card-details-header .minimize-card-details,
@@ -128,11 +258,16 @@
font-size: 24px;
padding: 5px 10px 5px 10px;
margin-right: -8px;
cursor: pointer;
user-select: none;
}
.card-details .card-details-header .close-card-details-mobile-web {
.card-details .card-details-header .close-card-details-mobile-web,
.card-details .card-details-header .card-mobile-desktop-toggle {
font-size: 24px;
padding: 5px;
margin-right: 40px;
margin-right: 5px;
cursor: pointer;
user-select: none;
}
.card-details .card-details-header .card-copy-button {
font-size: 17px;
@@ -153,6 +288,36 @@
padding: 10px;
margin-right: 30px;
}
.card-details .card-details-header .card-mobile-desktop-toggle,
.card-details .card-details-header .card-zoom-in,
.card-details .card-details-header .card-zoom-out {
font-size: 24px;
padding: 5px 10px 5px 10px;
margin-right: 5px;
cursor: pointer;
user-select: none;
float: right;
}
/* Unify all card text to match title size */
.card-details {
font-size: 1em;
}
.card-details p,
.card-details span,
.card-details div,
.card-details a,
.card-details label,
.card-details input,
.card-details textarea,
.card-details select,
.card-details button,
.card-details .card-details-item-title,
.card-details .card-label,
.card-details .viewer {
font-size: inherit;
line-height: 1.4;
}
.card-details .card-details-header .card-details-watch {
font-size: 17px;
padding-left: 7px;
@@ -256,6 +421,19 @@
position: fixed;
resize: both;
}
/* Override for mobile mode even on larger screens */
body.mobile-mode .card-details {
width: 100vw !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
height: 100vh !important;
max-height: 100vh !important;
resize: none !important;
}
.card-details-maximized {
padding: 0;
flex-shrink: 0;
@@ -307,19 +485,53 @@ input[type="submit"].attachment-add-link-submit {
}
@media screen and (max-width: 800px) {
.card-details {
width: calc(100% - 1px);
padding: 0px 20px 0px 20px;
margin: 0px;
width: 100% !important;
padding: 0px 0px 0px 0px !important;
margin: 0px !important;
transition: none;
overflow-y: revert;
overflow-x: revert;
overflow-y: auto;
overflow-x: hidden;
/* iOS Safari specific fixes */
-webkit-overflow-scrolling: touch;
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
z-index: 100 !important;
height: 100vh !important;
max-height: 100vh !important;
border-radius: 0 !important;
box-shadow: none !important;
}
/* Ensure card details are above everything on mobile */
body.mobile-mode .card-details {
z-index: 100 !important;
width: 100vw !important;
left: 0 !important;
right: 0 !important;
}
.card-details .card-details-canvas {
width: 100%;
padding-left: 0px;
padding: 0 15px;
}
.card-details .card-details-header .close-card-details {
margin-right: 0px;
display: block !important;
}
.card-details .card-details-header .close-card-details-mobile-web {
display: block !important;
margin-right: 5px !important;
}
.card-details .card-details-header .card-mobile-desktop-toggle {
display: block !important;
margin-right: 5px !important;
}
.card-details .card-details-header .card-mobile-desktop-toggle {
display: block !important;
margin-right: 5px !important;
}
.card-details .card-details-header .card-details-menu {
margin-right: 40px;
@@ -345,6 +557,62 @@ input[type="submit"].attachment-add-link-submit {
.pop-over > .content-wrapper > .popup-container-depth-0 .card-details-header {
margin: 0;
}
/* iPhone mobile: enlarge header buttons and increase spacing */
body.mobile-mode.iphone-device .card-details .card-details-header {
padding-right: 16px;
}
body.mobile-mode.iphone-device .card-details .card-details-header .close-card-details,
body.mobile-mode.iphone-device .card-details .card-details-header .maximize-card-details,
body.mobile-mode.iphone-device .card-details .card-details-header .minimize-card-details,
body.mobile-mode.iphone-device .card-details .card-details-header .card-details-menu-mobile-web,
body.mobile-mode.iphone-device .card-details .card-details-header .card-copy-mobile-button,
body.mobile-mode.iphone-device .card-details .card-details-header .card-mobile-desktop-toggle,
body.mobile-mode.iphone-device .card-details .card-details-header .card-zoom-in,
body.mobile-mode.iphone-device .card-details .card-details-header .card-zoom-out {
font-size: 2em !important; /* 2x bigger */
padding: 0.3em !important;
margin-right: 0.75em !important; /* 2x space compared to default */
margin-left: 0 !important;
}
/* Avoid clipping of the close button on the right edge */
body.mobile-mode.iphone-device .card-details .card-details-header .close-card-details {
margin-right: 0.75em !important;
}
/* Enlarge the header title too */
body.mobile-mode.iphone-device .card-details .card-details-header .card-details-title {
font-size: 1.2em !important;
font-weight: bold;
}
}
/* Mobile mode styles - apply when body has mobile-mode class regardless of screen size */
body.mobile-mode .card-details {
width: 100vw !important;
padding: 0px !important;
margin: 0px !important;
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
z-index: 100 !important;
height: 100vh !important;
max-height: 100vh !important;
border-radius: 0 !important;
box-shadow: none !important;
overflow-y: auto !important;
overflow-x: hidden !important;
-webkit-overflow-scrolling: touch;
}
body.mobile-mode .card-details .card-details-canvas {
width: 100% !important;
padding: 0 15px !important;
}
body.mobile-mode .card-details .card-details-header .close-card-details,
body.mobile-mode .card-details .card-details-header .close-card-details-mobile-web {
display: block !important;
}
.card-details-white {
background: #fff !important;
@@ -596,3 +864,11 @@ input[type="submit"].attachment-add-link-submit {
overflow: hidden;
background-color: #cecece;
}
/* Drag and drop file upload visual feedback */
.js-card-details.is-dragging-over {
border: 2px dashed #0079bf;
background-color: #e3f2fd !important;
transform: scale(1.01);
transition: all 0.2s ease;
}
+160 -90
View File
@@ -5,39 +5,70 @@ template(name="cardDetails")
+attachmentViewer
section.card-details.js-card-details.nodragscroll(class='{{#if cardMaximized}}card-details-maximized{{/if}}' class='{{#if isPopup}}card-details-popup{{/if}}' class='{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}'): .card-details-canvas
section.card-details.js-card-details.nodragscroll(class='{{#if cardMaximized}}card-details-maximized{{/if}}' class='{{#if isPopup}}card-details-popup{{/if}}' class='{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}' class='{{#if cardCollapsed}}card-details-collapsed{{/if}}'): .card-details-canvas
.card-details-header(class='{{#if colorClass}}card-details-{{colorClass}}{{/if}}')
+inlinedForm(classNames="js-card-details-title")
+editCardTitleForm
else
unless isMiniScreen
unless isPopup
a.fa.fa-times-thin.close-card-details.js-close-card-details(title="{{_ 'close-card'}}")
if canModifyCard
if cardMaximized
a.fa.fa-window-minimize.minimize-card-details.js-minimize-card-details(title="{{_ 'minimize-card'}}")
span.card-collapse-toggle.js-card-collapse-toggle(title="{{_ 'collapse-card'}}")
if cardCollapsed
| ▶
else
a.fa.fa-window-maximize.maximize-card-details.js-maximize-card-details(title="{{_ 'maximize-card'}}")
| 🔽
a.close-card-details.js-close-card-details(title="{{_ 'close-card'}}")
| ❌
if canModifyCard
a.card-bring-to-front.js-card-bring-to-front(title="Bring to front")
| ⏫
a.card-send-to-back.js-card-send-to-back(title="Send to back")
| ⏬
if cardMaximized
a.minimize-card-details.js-minimize-card-details(title="{{_ 'minimize-card'}}")
| 🔽
else
a.maximize-card-details.js-maximize-card-details(title="{{_ 'maximize-card'}}")
| 🔼
if canModifyCard
a.fa.fa-navicon.card-details-menu.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
a.fa.fa-link.card-copy-button.js-copy-link(
a.card-details-menu.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
| ☰
a.card-copy-button.js-copy-link(
id="cardURL_copy"
class="fa-link"
title="{{_ 'copy-card-link-to-clipboard'}}"
href="{{ originRelativeUrl }}"
)
span.emoji-icon 🔗
span.card-drag-handle.js-card-drag-handle(title="Drag card")
| ↕️
span.copied-tooltip {{_ 'copied'}}
else
unless isPopup
a.fa.fa-times-thin.close-card-details.js-close-card-details(title="{{_ 'close-card'}}")
a.close-card-details.js-close-card-details(title="{{_ 'close-card'}}")
| ❌
a.card-zoom-out.js-card-zoom-out(title="{{_ 'zoom-out'}}")
| 🔍➖
a.card-zoom-in.js-card-zoom-in(title="{{_ 'zoom-in'}}")
| 🔍➕
a.card-mobile-desktop-toggle.js-card-mobile-desktop-toggle(title="{{_ 'mobile-desktop-toggle'}}")
if mobileMode
| 🖥️
else
| 📱
if canModifyCard
a.fa.fa-navicon.card-details-menu-mobile-web.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
a.fa.fa-link.card-copy-mobile-button.js-copy-link(
if cardMaximized
a.minimize-card-details.js-minimize-card-details(title="{{_ 'minimize-card'}}")
| 🔽
else
a.maximize-card-details.js-maximize-card-details(title="{{_ 'maximize-card'}}")
| 🔼
a.card-details-menu-mobile-web.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
| ☰
a.card-copy-mobile-button.js-copy-link(
id="cardURL_copy"
class="fa-link"
title="{{_ 'copy-card-link-to-clipboard'}}"
href="{{ originRelativeUrl }}"
)
span.emoji-icon 🔗
span.copied-tooltip {{_ 'copied'}}
h2.card-details-title.js-card-title(
class="{{#if canModifyCard}}js-open-inlined-form is-editable{{/if}}")
@@ -47,7 +78,8 @@ template(name="cardDetails")
| ##{getCardNumber}
= getTitle
if isWatching
i.card-details-watch.fa.fa-eye
i.card-details-watch
| 👁️
.card-details-path
each parentList
| &nbsp; &gt; &nbsp;
@@ -65,13 +97,33 @@ template(name="cardDetails")
else
p.warning {{_ 'card-archived'}}
// Upload progress indicator for drag-and-drop uploads
if hasActiveUploads
.card-details-upload-progress
.upload-progress-header
| 📤
span {{_ 'uploading-files'}} ({{uploadCount}})
each uploads
.upload-progress-item(class="{{#if $eq status 'error'}}upload-error{{/if}}")
.upload-progress-filename {{file.name}}
.upload-progress-bar
.upload-progress-fill(style="width: {{progress}}%")
if $eq status 'error'
.upload-progress-error
| ⚠️
span {{_ 'upload-failed'}}
else if $eq status 'completed'
.upload-progress-success
| ✅
span {{_ 'upload-completed'}}
.card-details-left
.card-details-items
if currentBoard.allowsLabels
.card-details-item.card-details-item-labels
h3.card-details-item-title
i.fa.fa-tags
| 🏷️
| {{_ 'labels'}}
a(class="{{#if canModifyCard}}js-add-labels{{else}}is-disabled{{/if}}" title="{{_ 'card-labels-title'}}")
each labels
@@ -81,15 +133,25 @@ template(name="cardDetails")
if canModifyCard
unless currentUser.isWorker
a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}")
i.fa.fa-plus
| ➕
if currentBoard.hasAnyAllowsDate
hr
.card-details-item.card-details-item-date-format
h3.card-details-item-title
| 📅
| {{_ '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.allowsReceivedDate
.card-details-item.card-details-item-received
h3.card-details-item-title
i.fa.fa-sign-out
| 📥
| {{_ 'card-received'}}
if getReceived
+cardReceivedDate
@@ -97,12 +159,12 @@ template(name="cardDetails")
if canModifyCard
unless currentUser.isWorker
a.card-label.add-label.js-received-date
i.fa.fa-plus
| ➕
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
@@ -110,12 +172,12 @@ template(name="cardDetails")
if canModifyCard
unless currentUser.isWorker
a.card-label.add-label.js-start-date
i.fa.fa-plus
| ➕
if currentBoard.allowsDueDate
.card-details-item.card-details-item-due
h3.card-details-item-title
i.fa.fa-sign-in
| ⏰
| {{_ 'card-due'}}
if getDue
+cardDueDate
@@ -123,12 +185,12 @@ template(name="cardDetails")
if canModifyCard
unless currentUser.isWorker
a.card-label.add-label.js-due-date
i.fa.fa-plus
| ➕
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
@@ -136,7 +198,7 @@ template(name="cardDetails")
if canModifyCard
unless currentUser.isWorker
a.card-label.add-label.js-end-date
i.fa.fa-plus
| ➕
if currentBoard.hasAnyAllowsUser
hr
@@ -144,7 +206,7 @@ template(name="cardDetails")
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)
@@ -154,7 +216,7 @@ template(name="cardDetails")
if currentBoard.allowsMembers
.card-details-item.card-details-item-members
h3.card-details-item-title
i.fa.fa-users
| &#x1F465;
| {{_ 'members'}}
each userId in getMembers
+userAvatar(userId=userId cardId=_id)
@@ -162,30 +224,30 @@ template(name="cardDetails")
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 assigneeSelected
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
| ➕
//.card-details-items
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
@@ -205,7 +267,7 @@ template(name="cardDetails")
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
@@ -228,7 +290,7 @@ template(name="cardDetails")
if currentBoard.allowsCardSortingByNumber
.card-details-item.card-details-sort-order
h3.card-details-item-title
i.fa.fa-sort
| 🔢
| {{_ 'sort'}}
if canModifyCard
+inlinedForm(classNames="js-card-details-sort")
@@ -241,7 +303,7 @@ template(name="cardDetails")
if currentBoard.allowsShowLists
.card-details-item.card-details-show-lists
h3.card-details-item-title
i.fa.fa-list
| 📋
| {{_ 'list'}}
select.js-select-card-details-lists(disabled="{{#unless canModifyCard}}disabled{{/unless}}")
each currentBoard.lists
@@ -267,7 +329,7 @@ template(name="cardDetails")
hr
.card-details-item.card-details-item-customfield
h3.card-details-item-title
i.fa.fa-list-alt
| 📋
= definition.name
+cardCustomField
@@ -278,14 +340,14 @@ template(name="cardDetails")
else
input.toggle-switch(type="checkbox" id="toggleCustomFieldsGridButton")
label.toggle-label(for="toggleCustomFieldsGridButton")
a.fa.fa-plus.js-custom-fields.card-details-item.custom-fields(title="{{_ 'custom-fields'}}")
a.js-custom-fields.card-details-item.custom-fields(title="{{_ 'custom-fields'}}")
if getVoteQuestion
hr
.vote-title
div.flex
h3
i.fa.fa-thumbs-up
| 👍
| {{_ 'vote-question'}}
if getVoteEnd
+voteEndDate
@@ -303,11 +365,11 @@ template(name="cardDetails")
if showVotingButtons
button.card-details-green.js-vote.js-vote-positive(class="{{#if voteState}}voted{{/if}}")
if voteState
i.fa.fa-thumbs-up
| 👍
| {{_ 'vote-for-it'}}
button.card-details-red.js-vote.js-vote-negative(class="{{#if $eq voteState false}}voted{{/if}}")
if $eq voteState false
i.fa.fa-thumbs-down
| 👎
| {{_ 'vote-against'}}
if getPokerQuestion
@@ -315,7 +377,7 @@ template(name="cardDetails")
.poker-title
div.flex
h3
i.fa.fa-thumbs-up
| 👍
| {{_ 'poker-question'}}
if getPokerEnd
+pokerEndDate
@@ -330,52 +392,52 @@ template(name="cardDetails")
.poker-card
span.inner.js-poker.js-poker-vote-one(class="{{#if $eq pokerState 'one'}}poker-voted{{/if}}") {{_ 'poker-one'}}
if $eq pokerState "one"
i.fa.fa-check
| ✅
.poker-deck
.poker-card
span.inner.js-poker.js-poker-vote-two(class="{{#if $eq pokerState 'two'}}poker-voted{{/if}}") {{_ 'poker-two'}}
if $eq pokerState "two"
i.fa.fa-check
| ✅
.poker-deck
.poker-card
span.inner.js-poker.js-poker-vote-three(class="{{#if $eq pokerState 'three'}}poker-voted{{/if}}") {{_ 'poker-three'}}
if $eq pokerState "three"
i.fa.fa-check
| ✅
.poker-deck
.poker-card
span.inner.js-poker.js-poker-vote-five(class="{{#if $eq pokerState 'five'}}poker-voted{{/if}}") {{_ 'poker-five'}}
if $eq pokerState "five"
i.fa.fa-check
| ✅
.poker-deck
.poker-card
span.inner.js-poker.js-poker-vote-eight(class="{{#if $eq pokerState 'eight'}}poker-voted{{/if}}") {{_ 'poker-eight'}}
if $eq pokerState "eight"
i.fa.fa-check
| ✅
.poker-deck
.poker-card
span.inner.js-poker.js-poker-vote-thirteen(class="{{#if $eq pokerState 'thirteen'}}poker-voted{{/if}}") {{_ 'poker-thirteen'}}
if $eq pokerState "thirteen"
i.fa.fa-check
| ✅
.poker-deck
.poker-card
span.inner.js-poker.js-poker-vote-twenty(class="{{#if $eq pokerState 'twenty'}}poker-voted{{/if}}") {{_ 'poker-twenty'}}
if $eq pokerState "twenty"
i.fa.fa-check
| ✅
.poker-deck
.poker-card
span.inner.js-poker.js-poker-vote-forty(class="{{#if $eq pokerState 'forty'}}poker-voted{{/if}}") {{_ 'poker-forty'}}
if $eq pokerState "forty"
i.fa.fa-check
| ✅
.poker-deck
.poker-card
span.inner.js-poker.js-poker-vote-one-hundred(class="{{#if $eq pokerState 'oneHundred'}}poker-voted{{/if}}") {{_ 'poker-oneHundred'}}
if $eq pokerState "oneHundred"
i.fa.fa-check
| ✅
.poker-deck
.poker-card
span.inner.js-poker.js-poker-vote-unsure(class="{{#if $eq pokerState 'unsure'}}poker-voted{{/if}}") {{_ 'poker-unsure'}}
if $eq pokerState "unsure"
i.fa.fa-check
| ✅
if currentUser.isBoardAdmin
button.card-details-blue.js-poker-finish(class="{{#if $eq voteState false}}poker-voted{{/if}}") {{_ 'poker-finish'}}
@@ -505,7 +567,7 @@ template(name="cardDetails")
button.card-details-red.js-poker-replay(class="{{#if $eq voteState false}}voted{{/if}}") {{_ 'poker-replay'}}
div.estimation-add
button.js-poker-estimation
i.fa.fa-plus
| ➕
| {{_ 'set-estimation'}}
input(type=text,autofocus value=getPokerEstimation,id="pokerEstimation")
@@ -515,18 +577,18 @@ template(name="cardDetails")
if currentBoard.allowsDescriptionTitle
hr
h3.card-details-item-title
i.fa.fa-align-left
| 📝
| {{_ 'description'}}
if currentBoard.allowsDescriptionText
+inlinedCardDescription(classNames="card-description js-card-description")
+descriptionForm
.edit-controls.clearfix
button.primary(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
a.js-close-inlined-form
else
if currentBoard.allowsDescriptionText
a.js-open-inlined-form(title="{{_ 'edit'}}" value=title)
i.fa.fa-pencil-square-o
| ✏️
a.js-open-inlined-form(title="{{_ 'edit'}}" value=title)
if getDescription
+viewer
@@ -556,7 +618,7 @@ template(name="cardDetails")
if currentBoard.allowsAttachments
hr
h3.card-details-item-title
i.fa.fa-paperclip
| 📎
| {{_ 'attachments'}}
if Meteor.settings.public.attachmentsUploadMaxSize
| {{_ 'max-upload-filesize'}} {{Meteor.settings.public.attachmentsUploadMaxSize}}
@@ -572,7 +634,7 @@ template(name="cardDetails")
unless currentUser.isNoComments
.comment-title
h3.card-details-item-title
i.fa.fa-comment-o
| 💬
| {{_ 'comments'}}
if currentBoard.allowsComments
@@ -587,7 +649,7 @@ template(name="cardDetails")
unless currentUser.isNoComments
.activity-title
h3.card-details-item-title
i.fa.fa-history
| 📜
| {{ _ 'activities'}}
if currentUser.isBoardMember
.material-toggle-switch(title="{{_ 'show-activities'}}")
@@ -607,41 +669,41 @@ template(name="cardDetails")
+activities(card=this mode="card")
template(name="editCardTitleForm")
a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}")
a(title="{{_ 'copy-text-to-clipboard'}}")
span.copied-tooltip {{_ 'copied'}}
textarea.js-edit-card-title(rows='1' autofocus dir="auto")
= getTitle
.edit-controls.clearfix
button.primary.confirm.js-submit-edit-card-title-form(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
a.js-close-inlined-form
template(name="editCardRequesterForm")
input.js-edit-card-requester(type='text' autofocus value=getRequestedBy dir="auto")
.edit-controls.clearfix
button.primary.confirm.js-submit-edit-card-requester-form(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
a.js-close-inlined-form
template(name="editCardAssignerForm")
input.js-edit-card-assigner(type='text' autofocus value=getAssignedBy dir="auto")
.edit-controls.clearfix
button.primary.confirm.js-submit-edit-card-assigner-form(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
a.js-close-inlined-form
template(name="editCardSortOrderForm")
input.js-edit-card-sort(type='text' autofocus value=sort dir="auto")
.edit-controls.clearfix
button.primary.confirm.js-submit-edit-card-sort-form(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
a.js-close-inlined-form
template(name="cardDetailsActionsPopup")
ul.pop-over-list
li
a.js-toggle-watch-card
if isWatching
i.fa.fa-eye
| 👁️
| {{_ 'unwatch'}}
else
i.fa.fa-eye-slash
| 👁️
| {{_ 'watch'}}
hr
if canModifyCard
@@ -652,16 +714,16 @@ template(name="cardDetailsActionsPopup")
//li: a.js-attachments {{_ 'card-edit-attachments'}}
li
a.js-start-voting
i.fa.fa-thumbs-up
| 👍
| {{_ 'card-edit-voting'}}
li
a.js-start-planning-poker
i.fa.fa-thumbs-up
| 👍
| {{_ 'card-edit-planning-poker'}}
if currentUser.isBoardAdmin
li
a.js-custom-fields
i.fa.fa-list-alt
| 📋
| {{_ 'card-edit-custom-fields'}}
//li: a.js-received-date {{_ 'editCardReceivedDatePopup-title'}}
//li: a.js-start-date {{_ 'editCardStartDatePopup-title'}}
@@ -669,67 +731,75 @@ template(name="cardDetailsActionsPopup")
//li: a.js-end-date {{_ 'editCardEndDatePopup-title'}}
li
a.js-spent-time
i.fa.fa-clock-o
| 🕐
| {{_ 'editCardSpentTimePopup-title'}}
li
a.js-set-card-color
i.fa.fa-paint-brush
| 🎨
| {{_ 'setCardColorPopup-title'}}
li
a.js-toggle-show-list-on-minicard
if showListOnMinicard
| 👁️
| {{_ 'hide-list-on-minicard'}}
else
| 👁️
| {{_ 'show-list-on-minicard'}}
hr
ul.pop-over-list
li
a.js-export-card
i.fa.fa-share-alt
| 📤
| {{_ 'export-card'}}
hr
ul.pop-over-list
li
a.js-move-card-to-top
i.fa.fa-arrow-up
| ⬆️
| {{_ 'moveCardToTop-title'}}
li
a.js-move-card-to-bottom
i.fa.fa-arrow-down
| ⬇️
| {{_ 'moveCardToBottom-title'}}
hr
ul.pop-over-list
if currentUser.isBoardAdmin
li
a.js-move-card
i.fa.fa-arrow-right
| ➡️
| {{_ 'moveCardPopup-title'}}
unless currentUser.isWorker
li
a.js-copy-card
i.fa.fa-copy
| 📋
| {{_ 'copyCardPopup-title'}}
unless currentUser.isWorker
ul.pop-over-list
li
a.js-copy-checklist-cards
i.fa.fa-copy
i.fa.fa-copy
| 📋
| 📋
| {{_ 'copyManyCardsPopup-title'}}
unless archived
hr
ul.pop-over-list
li
a.js-archive
i.fa.fa-arrow-right
i.fa.fa-archive
| ➡️
| 📦
| {{_ 'archive-card'}}
hr
ul.pop-over-list
li
a.js-more
i.fa.fa-link
span.emoji-icon 🔗
| {{_ 'cardMorePopup-title'}}
template(name="exportCardPopup")
ul.pop-over-list
li
a(href="{{exportUrlCardPDF}}",, download="{{exportFilenameCardPDF}}")
i.fa.fa-share-alt
| 📤
| {{_ 'export-card-pdf'}}
template(name="moveCardPopup")
@@ -784,7 +854,7 @@ template(name="cardMembersPopup")
= user.profile.fullname
| (<span class="username">{{ user.username }}</span>)
if isCardMember
i.fa.fa-check
| ✅
template(name="cardAssigneesPopup")
input.card-assignees-filter(type="text" placeholder="{{_ 'search'}}")
@@ -798,7 +868,7 @@ template(name="cardAssigneesPopup")
= user.profile.fullname
| (<span class="username">{{ user.username }}</span>)
if isCardAssignee
i.fa.fa-check
| ✅
if currentUser.isWorker
ul.pop-over-list.js-card-assignee-list
li.item(class="{{#if currentUser.isCardAssignee}}active{{/if}}")
@@ -808,7 +878,7 @@ template(name="cardAssigneesPopup")
= currentUser.profile.fullname
| (<span class="username">{{ currentUser.username }}</span>)
if currentUser.isCardAssignee
i.fa.fa-check
| ✅
template(name="cardAssigneePopup")
.board-assignee-menu
@@ -832,7 +902,7 @@ template(name="cardMorePopup")
span.clearfix
span {{_ 'link-card'}}
= ' '
i.fa.colorful(class="{{#if board.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
| {{#if board.isPublic}}🌐{{else}}🔒{{/if}}
input.inline-input(type="text" id="cardURL" readonly value="{{ originRelativeUrl }}" autofocus="autofocus")
button.js-copy-card-link-to-clipboard(class="btn" id="clipboard") {{_ 'copy-card-link-to-clipboard'}}
.copied-tooltip {{_ 'copied'}}
@@ -874,7 +944,7 @@ template(name="setCardColorPopup")
unless $eq color 'white'
span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
if(isSelected color)
i.fa.fa-check
| ✅
button.primary.confirm.js-submit {{_ 'save'}}
button.js-remove-color.negate.wide.right {{_ 'unset-color'}}
@@ -908,12 +978,12 @@ template(name="cardStartVotingPopup")
.materialCheckBox#vote-public(name="vote-public" class="{{#if votePublic}}is-checked{{/if}}")
span {{_ 'vote-public'}}
.check-div.flex
i.fa.fa-hourglass-end
| ⏰
a.js-end-date
span
| {{_ 'card-end'}}
unless getVoteEnd
i.fa.fa-plus
| ➕
if getVoteEnd
+voteEndDate
@@ -954,12 +1024,12 @@ template(name="cardStartPlanningPokerPopup")
.materialCheckBox#poker-allow-non-members(name="poker-allow-non-members" class="{{#if pokerAllowNonBoardMembers}}is-checked{{/if}}")
span {{_ 'allowNonBoardMembers'}}
.check-div.flex
i.fa.fa-hourglass-end
| ⏰
a.js-end-date
span
| {{_ 'card-end'}}
unless getPokerEnd
i.fa.fa-plus
| ➕
if getPokerEnd
+pokerEndDate
+322 -90
View File
@@ -1,7 +1,26 @@
import { ReactiveCache } from '/imports/reactiveCache';
import moment from 'moment/min/moment-with-locales';
import { TAPi18n } from '/imports/i18n';
import { DatePicker } from '/client/lib/datepicker';
import {
formatDateTime,
formatDate,
formatTime,
getISOWeek,
isValidDate,
isBefore,
isAfter,
isSame,
add,
subtract,
startOf,
endOf,
format,
parseDate,
now,
createDate,
fromNow,
calendar
} from '/imports/lib/dateUtils';
import Cards from '/models/cards';
import Boards from '/models/boards';
import Checklists from '/models/checklists';
@@ -12,6 +31,8 @@ import CardComments from '/models/cardComments';
import { ALLOWED_COLORS } from '/config/const';
import { UserAvatar } from '../users/userAvatar';
import { DialogWithBoardSwimlaneList } from '/client/lib/dialogWithBoardSwimlaneList';
import { handleFileUpload } from './attachments';
import uploadProgressManager from '../../lib/uploadProgressManager';
const subManager = new SubsManager();
const { calculateIndexData } = Utils;
@@ -42,7 +63,11 @@ BlazeComponent.extendComponent({
const boardBody = this.parentComponent().parentComponent();
//in Miniview parent is Board, not BoardBody.
if (boardBody !== null) {
boardBody.showOverlay.set(true);
// Only show overlay in mobile mode, not in desktop mode
const isMobile = Utils.getMobileMode();
if (isMobile) {
boardBody.showOverlay.set(true);
}
boardBody.mouseHasEnterCardDetails = false;
}
}
@@ -72,6 +97,18 @@ BlazeComponent.extendComponent({
return !Utils.getPopupCardId() && ReactiveCache.getCurrentUser().hasCardMaximized();
},
cardCollapsed() {
const user = ReactiveCache.getCurrentUser();
if (user && user.profile) {
return !!user.profile.cardCollapsed;
}
if (Users.getPublicCardCollapsed) {
const stored = Users.getPublicCardCollapsed();
if (typeof stored === 'boolean') return stored;
}
return false;
},
presentParentTask() {
let result = this.currentBoard.presentParentTask;
if (result === null || result === undefined) {
@@ -275,8 +312,99 @@ BlazeComponent.extendComponent({
return [
{
...events,
'click .js-card-collapse-toggle'() {
const user = ReactiveCache.getCurrentUser();
const currentState = user && user.profile ? !!user.profile.cardCollapsed : !!Users.getPublicCardCollapsed();
if (user) {
Meteor.call('setCardCollapsed', !currentState);
} else if (Users.setPublicCardCollapsed) {
Users.setPublicCardCollapsed(!currentState);
}
},
'click .js-card-bring-to-front'(event) {
event.preventDefault();
const $card = $(event.target).closest('.card-details');
// Find the highest z-index among all cards
let maxZ = 100;
$('.card-details').each(function() {
const z = parseInt($(this).css('z-index')) || 100;
if (z > maxZ) maxZ = z;
});
// Set this card's z-index to be higher
$card.css('z-index', maxZ + 1);
},
'click .js-card-send-to-back'(event) {
event.preventDefault();
const $card = $(event.target).closest('.card-details');
// Find the lowest z-index among all cards
let minZ = 100;
$('.card-details').each(function() {
const z = parseInt($(this).css('z-index')) || 100;
if (z < minZ) minZ = z;
});
// Set this card's z-index to be lower
$card.css('z-index', minZ - 1);
},
'mousedown .js-card-drag-handle'(event) {
event.preventDefault();
const $card = $(event.target).closest('.card-details');
const startX = event.clientX;
const startY = event.clientY;
const startLeft = $card.offset().left;
const startTop = $card.offset().top;
const onMouseMove = (e) => {
const deltaX = e.clientX - startX;
const deltaY = e.clientY - startY;
$card.css({
left: startLeft + deltaX + 'px',
top: startTop + deltaY + 'px'
});
};
const onMouseUp = () => {
$(document).off('mousemove', onMouseMove);
$(document).off('mouseup', onMouseUp);
};
$(document).on('mousemove', onMouseMove);
$(document).on('mouseup', onMouseUp);
},
'click .js-close-card-details'() {
Utils.goBoardId(this.data().boardId);
// Get board ID from either the card data or current board in session
const card = this.currentData() || this.data();
const boardId = (card && card.boardId) || Utils.getCurrentBoard()._id;
const cardId = card && card._id;
if (boardId) {
// In desktop mode, remove from openCards array
const isMobile = Utils.getMobileMode();
if (!isMobile && cardId) {
const openCards = Session.get('openCards') || [];
const filtered = openCards.filter(id => id !== cardId);
Session.set('openCards', filtered);
// If this was the current card, clear it
if (Session.get('currentCard') === cardId) {
Session.set('currentCard', null);
}
// Don't navigate away in desktop mode - just close the card
return;
}
// Mobile mode: Clear the current card session to close the card
Session.set('currentCard', null);
// Navigate back to board without card
const board = ReactiveCache.getBoard(boardId);
if (board) {
FlowRouter.go('board', {
id: board._id,
slug: board.slug,
});
}
}
},
'click .js-copy-link'(event) {
event.preventDefault();
@@ -285,7 +413,39 @@ BlazeComponent.extendComponent({
const $tooltip = this.$('.card-details-header .copied-tooltip');
Utils.showCopied(promise, $tooltip);
},
'change .js-date-format-selector'(event) {
const dateFormat = event.target.value;
Meteor.call('changeDateFormat', dateFormat);
},
'click .js-open-card-details-menu': Popup.open('cardDetailsActions'),
// Mobile: switch to desktop popup view (maximize)
'click .js-mobile-switch-to-desktop'(event) {
event.preventDefault();
// Switch global mode to desktop so the card appears as desktop popup
Utils.setMobileMode(false);
},
'click .js-card-zoom-in'(event) {
event.preventDefault();
const current = Utils.getCardZoom();
const newZoom = Math.min(3.0, current + 0.1);
Utils.setCardZoom(newZoom);
},
'click .js-card-zoom-out'(event) {
event.preventDefault();
const current = Utils.getCardZoom();
const newZoom = Math.max(0.5, current - 0.1);
Utils.setCardZoom(newZoom);
},
'click .js-card-mobile-desktop-toggle'(event) {
event.preventDefault();
const currentMode = Utils.getMobileMode();
Utils.setMobileMode(!currentMode);
},
'click .js-card-mobile-desktop-toggle'(event) {
event.preventDefault();
const currentMode = Utils.getMobileMode();
Utils.setMobileMode(!currentMode);
},
'submit .js-card-description'(event) {
event.preventDefault();
const description = this.currentComponent().getValue();
@@ -405,56 +565,57 @@ BlazeComponent.extendComponent({
) {
newState = forIt;
}
this.data().setVote(Meteor.userId(), newState);
// Use secure server method; direct client updates to vote are blocked
Meteor.call('cards.vote', this.data()._id, newState);
},
'click .js-poker'(e) {
let newState = null;
if ($(e.target).hasClass('js-poker-vote-one')) {
newState = 'one';
this.data().setPoker(Meteor.userId(), newState);
Meteor.call('cards.pokerVote', this.data()._id, newState);
}
if ($(e.target).hasClass('js-poker-vote-two')) {
newState = 'two';
this.data().setPoker(Meteor.userId(), newState);
Meteor.call('cards.pokerVote', this.data()._id, newState);
}
if ($(e.target).hasClass('js-poker-vote-three')) {
newState = 'three';
this.data().setPoker(Meteor.userId(), newState);
Meteor.call('cards.pokerVote', this.data()._id, newState);
}
if ($(e.target).hasClass('js-poker-vote-five')) {
newState = 'five';
this.data().setPoker(Meteor.userId(), newState);
Meteor.call('cards.pokerVote', this.data()._id, newState);
}
if ($(e.target).hasClass('js-poker-vote-eight')) {
newState = 'eight';
this.data().setPoker(Meteor.userId(), newState);
Meteor.call('cards.pokerVote', this.data()._id, newState);
}
if ($(e.target).hasClass('js-poker-vote-thirteen')) {
newState = 'thirteen';
this.data().setPoker(Meteor.userId(), newState);
Meteor.call('cards.pokerVote', this.data()._id, newState);
}
if ($(e.target).hasClass('js-poker-vote-twenty')) {
newState = 'twenty';
this.data().setPoker(Meteor.userId(), newState);
Meteor.call('cards.pokerVote', this.data()._id, newState);
}
if ($(e.target).hasClass('js-poker-vote-forty')) {
newState = 'forty';
this.data().setPoker(Meteor.userId(), newState);
Meteor.call('cards.pokerVote', this.data()._id, newState);
}
if ($(e.target).hasClass('js-poker-vote-one-hundred')) {
newState = 'oneHundred';
this.data().setPoker(Meteor.userId(), newState);
Meteor.call('cards.pokerVote', this.data()._id, newState);
}
if ($(e.target).hasClass('js-poker-vote-unsure')) {
newState = 'unsure';
this.data().setPoker(Meteor.userId(), newState);
Meteor.call('cards.pokerVote', this.data()._id, newState);
}
},
'click .js-poker-finish'(e) {
if ($(e.target).hasClass('js-poker-finish')) {
e.preventDefault();
const now = moment().format('YYYY-MM-DD HH:mm');
this.data().setPokerEnd(now);
const now = new Date();
Meteor.call('cards.setPokerEnd', this.data()._id, now);
}
},
@@ -462,9 +623,9 @@ BlazeComponent.extendComponent({
if ($(e.target).hasClass('js-poker-replay')) {
e.preventDefault();
this.currentCard = this.currentData();
this.currentCard.replayPoker();
this.data().unsetPokerEnd();
this.data().unsetPokerEstimation();
Meteor.call('cards.replayPoker', this.currentCard._id);
Meteor.call('cards.unsetPokerEnd', this.currentCard._id);
Meteor.call('cards.unsetPokerEstimation', this.currentCard._id);
}
},
'click .js-poker-estimation'(event) {
@@ -475,9 +636,65 @@ BlazeComponent.extendComponent({
this.find('#pokerEstimation').value = '';
if (ruleTitle) {
this.data().setPokerEstimation(parseInt(ruleTitle, 10));
Meteor.call('cards.setPokerEstimation', this.data()._id, parseInt(ruleTitle, 10));
} else {
this.data().setPokerEstimation('');
Meteor.call('cards.unsetPokerEstimation', this.data()._id);
}
}
},
// Drag and drop file upload handlers
'dragover .js-card-details'(event) {
// Only prevent default for file drags to avoid interfering with other drag operations
const dataTransfer = event.originalEvent.dataTransfer;
if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) {
event.preventDefault();
event.stopPropagation();
}
},
'dragenter .js-card-details'(event) {
const dataTransfer = event.originalEvent.dataTransfer;
if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) {
event.preventDefault();
event.stopPropagation();
const card = this.data();
const board = card.board();
// Only allow drag-and-drop if user can modify card and board allows attachments
if (Utils.canModifyCard() && board && board.allowsAttachments) {
$(event.currentTarget).addClass('is-dragging-over');
}
}
},
'dragleave .js-card-details'(event) {
const dataTransfer = event.originalEvent.dataTransfer;
if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) {
event.preventDefault();
event.stopPropagation();
$(event.currentTarget).removeClass('is-dragging-over');
}
},
'drop .js-card-details'(event) {
const dataTransfer = event.originalEvent.dataTransfer;
if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) {
event.preventDefault();
event.stopPropagation();
$(event.currentTarget).removeClass('is-dragging-over');
const card = this.data();
const board = card.board();
// Check permissions
if (!Utils.canModifyCard() || !board || !board.allowsAttachments) {
return;
}
// Check if this is a file drop (not a checklist item reorder)
if (!dataTransfer.files || dataTransfer.files.length === 0) {
return;
}
const files = dataTransfer.files;
if (files && files.length > 0) {
handleFileUpload(card, files);
}
}
},
@@ -490,6 +707,21 @@ Template.cardDetails.helpers({
isPopup() {
let ret = !!Utils.getPopupCardId();
return ret;
},
isDateFormat(format) {
const currentUser = ReactiveCache.getCurrentUser();
if (!currentUser) return format === 'YYYY-MM-DD';
return currentUser.getDateFormat() === format;
},
// Upload progress helpers
hasActiveUploads() {
return uploadProgressManager.hasActiveUploads(this._id);
},
uploads() {
return uploadProgressManager.getUploadsForCard(this._id);
},
uploadCount() {
return uploadProgressManager.getUploadCountForCard(this._id);
}
});
Template.cardDetailsPopup.onDestroyed(() => {
@@ -592,6 +824,10 @@ Template.cardDetailsActionsPopup.helpers({
isBoardAdmin() {
return ReactiveCache.getCurrentUser().isBoardAdmin();
},
showListOnMinicard() {
return this.showListOnMinicard;
},
});
Template.cardDetailsActionsPopup.events({
@@ -637,6 +873,12 @@ Template.cardDetailsActionsPopup.events({
if (!err && ret) Popup.close();
});
},
'click .js-toggle-show-list-on-minicard'() {
const currentCard = this;
const newValue = !currentCard.showListOnMinicard;
Cards.update(currentCard._id, { $set: { showListOnMinicard: newValue } });
Popup.close();
},
});
BlazeComponent.extendComponent({
@@ -999,20 +1241,15 @@ BlazeComponent.extendComponent({
'is-checked',
);
const endString = this.currentCard.getVoteEnd();
this.currentCard.setVoteQuestion(
voteQuestion,
publicVote,
allowNonBoardMembers,
);
Meteor.call('cards.setVoteQuestion', this.currentCard._id, voteQuestion, publicVote, allowNonBoardMembers);
if (endString) {
this.currentCard.setVoteEnd(endString);
Meteor.call('cards.setVoteEnd', this.currentCard._id, endString);
}
Popup.back();
},
'click .js-remove-vote': Popup.afterConfirm('deleteVote', () => {
event.preventDefault();
this.currentCard.unsetVote();
Meteor.call('cards.unsetVote', this.currentCard._id);
Popup.back();
}),
'click a.js-toggle-vote-public'(event) {
@@ -1031,8 +1268,8 @@ BlazeComponent.extendComponent({
// editVoteEndDatePopup
(class extends DatePicker {
onCreated() {
super.onCreated(moment().format('YYYY-MM-DD HH:mm'));
this.data().getVoteEnd() && this.date.set(moment(this.data().getVoteEnd()));
super.onCreated(formatDateTime(now()));
this.data().getVoteEnd() && this.date.set(new Date(this.data().getVoteEnd()));
}
events() {
return [
@@ -1043,12 +1280,12 @@ BlazeComponent.extendComponent({
// if no time was given, init with 12:00
const time =
evt.target.time.value ||
moment(new Date().setHours(12, 0, 0)).format('LT');
formatTime(new Date().setHours(12, 0, 0));
const dateString = `${evt.target.date.value} ${time}`;
/*
const newDate = moment(dateString, 'L LT', true);
const newDate = parseDate(dateString, ['L LT'], true);
if (newDate.isValid()) {
// if active vote - store it
if (this.currentData().getVoteQuestion()) {
@@ -1062,28 +1299,27 @@ BlazeComponent.extendComponent({
*/
// Try to parse different date formats of all languages.
// This code is same for vote and planning poker.
const usaDate = moment(dateString, 'L LT', true);
const euroAmDate = moment(dateString, 'DD.MM.YYYY LT', true);
const euro24hDate = moment(dateString, 'DD.MM.YYYY HH.mm', true);
const eurodotDate = moment(dateString, 'DD.MM.YYYY HH:mm', true);
const minusDate = moment(dateString, 'YYYY-MM-DD HH:mm', true);
const slashDate = moment(dateString, 'DD/MM/YYYY HH.mm', true);
const dotDate = moment(dateString, 'DD/MM/YYYY HH:mm', true);
const brezhonegDate = moment(dateString, 'DD/MM/YYYY h[e]mm A', true);
const hrvatskiDate = moment(dateString, 'DD. MM. YYYY H:mm', true);
const latviaDate = moment(dateString, 'YYYY.MM.DD. H:mm', true);
const nederlandsDate = moment(dateString, 'DD-MM-YYYY HH:mm', true);
// greekDate does not work: el Greek Ελληνικά ,
// it has date format DD/MM/YYYY h:mm MM like 20/06/2021 11:15 MM
// where MM is maybe some text like AM/PM ?
// Also some other languages that have non-ascii characters in dates
// do not work.
const greekDate = moment(dateString, 'DD/MM/YYYY h:mm A', true);
const macedonianDate = moment(dateString, 'D.MM.YYYY H:mm', true);
// Try to parse different date formats using native Date parsing
const formats = [
'YYYY-MM-DD HH:mm',
'MM/DD/YYYY HH:mm',
'DD.MM.YYYY HH:mm',
'DD/MM/YYYY HH:mm',
'DD-MM-YYYY HH:mm'
];
let parsedDate = null;
for (const format of formats) {
parsedDate = parseDate(dateString, [format], true);
if (parsedDate) break;
}
// Fallback to native Date parsing
if (!parsedDate) {
parsedDate = new Date(dateString);
}
if (usaDate.isValid()) {
if (isValidDate(parsedDate)) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(usaDate.toDate());
@@ -1212,10 +1448,10 @@ BlazeComponent.extendComponent({
];
}
_storeDate(newDate) {
this.card.setVoteEnd(newDate);
Meteor.call('cards.setVoteEnd', this.card._id, newDate);
}
_deleteDate() {
this.card.unsetVoteEnd();
Meteor.call('cards.unsetVoteEnd', this.card._id);
}
}.register('editVoteEndDatePopup'));
@@ -1237,17 +1473,14 @@ BlazeComponent.extendComponent({
);
const endString = this.currentCard.getPokerEnd();
this.currentCard.setPokerQuestion(
pokerQuestion,
allowNonBoardMembers,
);
Meteor.call('cards.setPokerQuestion', this.currentCard._id, pokerQuestion, allowNonBoardMembers);
if (endString) {
this.currentCard.setPokerEnd(endString);
Meteor.call('cards.setPokerEnd', this.currentCard._id, new Date(endString));
}
Popup.back();
},
'click .js-remove-poker': Popup.afterConfirm('deletePoker', (event) => {
this.currentCard.unsetPoker();
Meteor.call('cards.unsetPoker', this.currentCard._id);
Popup.back();
}),
'click a.js-toggle-poker-allow-non-members'(event) {
@@ -1262,9 +1495,9 @@ BlazeComponent.extendComponent({
// editPokerEndDatePopup
(class extends DatePicker {
onCreated() {
super.onCreated(moment().format('YYYY-MM-DD HH:mm'));
super.onCreated(formatDateTime(now()));
this.data().getPokerEnd() &&
this.date.set(moment(this.data().getPokerEnd()));
this.date.set(new Date(this.data().getPokerEnd()));
}
/*
@@ -1282,7 +1515,7 @@ BlazeComponent.extendComponent({
return moment.localeData().longDateFormat('LT');
}
const newDate = moment(dateString, dateformat() + ' ' + timeformat(), true);
const newDate = parseDate(dateString, [dateformat() + ' ' + timeformat()], true);
*/
events() {
@@ -1294,7 +1527,7 @@ BlazeComponent.extendComponent({
// if no time was given, init with 12:00
const time =
evt.target.time.value ||
moment(new Date().setHours(12, 0, 0)).format('LT');
formatTime(new Date().setHours(12, 0, 0));
const dateString = `${evt.target.date.value} ${time}`;
@@ -1305,7 +1538,7 @@ BlazeComponent.extendComponent({
Maybe client/components/lib/datepicker.jade could have hidden input field for
datepicker format that could be used to detect date format?
const newDate = moment(dateString, dateformat() + ' ' + timeformat(), true);
const newDate = parseDate(dateString, [dateformat() + ' ' + timeformat()], true);
if (newDate.isValid()) {
// if active poker - store it
@@ -1318,28 +1551,27 @@ BlazeComponent.extendComponent({
}
*/
// Try to parse different date formats of all languages.
// This code is same for vote and planning poker.
const usaDate = moment(dateString, 'L LT', true);
const euroAmDate = moment(dateString, 'DD.MM.YYYY LT', true);
const euro24hDate = moment(dateString, 'DD.MM.YYYY HH.mm', true);
const eurodotDate = moment(dateString, 'DD.MM.YYYY HH:mm', true);
const minusDate = moment(dateString, 'YYYY-MM-DD HH:mm', true);
const slashDate = moment(dateString, 'DD/MM/YYYY HH.mm', true);
const dotDate = moment(dateString, 'DD/MM/YYYY HH:mm', true);
const brezhonegDate = moment(dateString, 'DD/MM/YYYY h[e]mm A', true);
const hrvatskiDate = moment(dateString, 'DD. MM. YYYY H:mm', true);
const latviaDate = moment(dateString, 'YYYY.MM.DD. H:mm', true);
const nederlandsDate = moment(dateString, 'DD-MM-YYYY HH:mm', true);
// greekDate does not work: el Greek Ελληνικά ,
// it has date format DD/MM/YYYY h:mm MM like 20/06/2021 11:15 MM
// where MM is maybe some text like AM/PM ?
// Also some other languages that have non-ascii characters in dates
// do not work.
const greekDate = moment(dateString, 'DD/MM/YYYY h:mm A', true);
const macedonianDate = moment(dateString, 'D.MM.YYYY H:mm', true);
// Try to parse different date formats using native Date parsing
const formats = [
'YYYY-MM-DD HH:mm',
'MM/DD/YYYY HH:mm',
'DD.MM.YYYY HH:mm',
'DD/MM/YYYY HH:mm',
'DD-MM-YYYY HH:mm'
];
let parsedDate = null;
for (const format of formats) {
parsedDate = parseDate(dateString, [format], true);
if (parsedDate) break;
}
// Fallback to native Date parsing
if (!parsedDate) {
parsedDate = new Date(dateString);
}
if (usaDate.isValid()) {
if (isValidDate(parsedDate)) {
// if active poker - store it
if (this.currentData().getPokerQuestion()) {
this._storeDate(usaDate.toDate());
@@ -1469,10 +1701,10 @@ BlazeComponent.extendComponent({
];
}
_storeDate(newDate) {
this.card.setPokerEnd(newDate);
Meteor.call('cards.setPokerEnd', this.card._id, newDate);
}
_deleteDate() {
this.card.unsetPokerEnd();
Meteor.call('cards.unsetPokerEnd', this.card._id);
}
}.register('editPokerEndDatePopup'));
+4 -4
View File
@@ -15,8 +15,8 @@ template(name="editCardSpentTime")
template(name="timeBadge")
if canModifyCard
a.js-edit-time.card-time(title="{{showTitle}}" class="{{#if getIsOvertime}}card-label-red{{else}}card-label-green{{/if}}")
| {{showTime}}
a.js-edit-time.card-time(title="{{_ 'time'}}" class="{{#if getIsOvertime}}card-label-red{{else}}card-label-green{{/if}}")
| ⏱️ {{showTime}}
else
a.card-time(title="{{showTitle}}" class="{{#if getIsOvertime}}card-label-red{{else}}card-label-green{{/if}}")
| {{showTime}}
a.card-time(title="{{_ 'time'}}" class="{{#if getIsOvertime}}card-label-red{{else}}card-label-green{{/if}}")
| ⏱️ {{showTime}}
+61 -3
View File
@@ -37,14 +37,23 @@ textarea.js-edit-checklist-item {
.checklist-progress-bar-container .checklist-progress-bar {
width: 80%;
height: 10px;
background-color: #d6ebff !important;
border-radius: 16px;
}
.checklist-progress-bar-container .checklist-progress-bar .checklist-progress {
color: #fff !important;
background-color: #2196f3 !important;
background-color: #3cb500 !important;
padding: 0.01em 16px;
border-radius: 16px;
height: 100%;
}
/* Grey progress bar when grey icons setting is enabled */
body.grey-icons-enabled .checklist-progress-bar-container .checklist-progress-bar {
background-color: #d9d9d9;
}
body.grey-icons-enabled .checklist-progress-bar-container .checklist-progress-bar .checklist-progress {
background-color: #7a7a7a !important;
}
.checklist-title {
padding: 10px;
}
@@ -67,10 +76,14 @@ textarea.js-edit-checklist-item {
.checklist-title .checklist-stat.is-finished {
color: #3cb500;
}
.checklist-title span.fa.checklist-handle {
.checklist-title span.checklist-handle {
padding-right: 20px;
padding-top: 3px;
float: left;
display: inline-block;
width: 1.2em;
text-align: center;
color: #999;
}
#card-details-overlay {
top: 0;
@@ -101,6 +114,25 @@ textarea.js-edit-checklist-item {
height: auto;
overflow: hidden;
}
/* iPhone mobile: larger checklist titles and more spacing between items */
body.mobile-mode.iphone-device .checklist-title .title {
font-size: 1.3em !important;
font-weight: bold;
}
body.mobile-mode.iphone-device .checklist-item {
margin-top: 12px !important;
margin-bottom: 8px !important;
padding: 8px 4px !important;
min-height: 44px; /* iOS recommended touch target size */
}
body.mobile-mode.iphone-device .checklist-item span.checklistitem-handle {
font-size: 1.5em !important;
padding-right: 15px !important;
width: 1.5em !important;
}
.checklist-item.is-checked.invisible {
opacity: 0;
height: 0;
@@ -130,6 +162,27 @@ textarea.js-edit-checklist-item {
border-bottom: 2px solid #3cb500;
border-right: 2px solid #3cb500;
}
/* Unicode checkbox icons styling */
.checklist-item .check-box-unicode,
.cardCustomField-checkbox .check-box-unicode {
font-size: 1.3em;
margin-right: 8px;
cursor: pointer;
display: inline-block;
vertical-align: middle;
line-height: 1;
}
/* Grey checkmarks when grey icons setting is enabled */
body.grey-icons-enabled .checklist-item .check-box.is-checked {
border-bottom: 2px solid #7a7a7a;
border-right: 2px solid #7a7a7a;
}
body.grey-icons-enabled .checklist-item .check-box-unicode,
body.grey-icons-enabled .cardCustomField-checkbox .check-box-unicode {
filter: grayscale(100%);
-webkit-filter: grayscale(100%);
opacity: 0.85;
}
.checklist-item .item-title {
flex: 1;
}
@@ -144,9 +197,14 @@ textarea.js-edit-checklist-item {
word-wrap: break-word;
max-width: 420px;
}
.checklist-item span.fa.checklistitem-handle {
.checklist-item span.checklistitem-handle {
padding-top: 2px;
padding-right: 10px;
display: inline-block;
width: 1.2em;
text-align: center;
color: #999;
cursor: pointer;
}
.js-delete-checklist-item,
.js-convert-checklist-item-to-card {
+22 -22
View File
@@ -1,14 +1,14 @@
template(name="checklists")
.checklists-title
h3.card-details-item-title
i.fa.fa-check
| ✅
| {{_ 'checklists'}}
if canModifyCard
+inlinedForm(autoclose=false classNames="js-add-checklist" cardId = cardId position="top")
+addChecklistItemForm
else
a.add-checklist-top.js-open-inlined-form(title="{{_ 'add-checklist'}}")
i.fa.fa-plus
| ➕
if currentUser.isBoardMember
.material-toggle-switch(title="{{_ 'hide-finished-checklist'}}")
//span.toggle-switch-title
@@ -28,7 +28,7 @@ template(name="checklists")
+addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=false)
else
a.add-checklist.js-open-inlined-form(title="{{_ 'add-checklist'}}")
i.fa.fa-plus
| ➕
template(name="checklistDetail")
.js-checklist.checklist.nodragscroll
@@ -38,12 +38,12 @@ template(name="checklistDetail")
.checklist-title
span
if canModifyCard
a.fa.fa-navicon.checklist-details-menu.js-open-checklist-details-menu(title="{{_ 'checklistActionsPopup-title'}}")
a.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.checklist-handle(title="{{_ 'dragChecklist'}}") ↕️
+viewer
= checklist.title
else
@@ -63,12 +63,13 @@ template(name="checklistDeletePopup")
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
template(name="addChecklistItemForm")
a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}")
a(title="{{_ 'copy-text-to-clipboard'}}")
span.copied-tooltip {{_ 'copied'}}
textarea.js-add-checklist-item(rows='1' autofocus)
.edit-controls.clearfix
button.primary.confirm.js-submit-add-checklist-item-form(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form(title="{{_ 'close-add-checklist-item'}}")
a.js-close-inlined-form(title="{{_ 'close-add-checklist-item'}}")
| ❌
if showNewlineBecomesNewChecklistItem
.material-toggle-switch(title="{{_ 'newlineBecomesNewChecklistItem'}}")
input.toggle-switch(type="checkbox" id="toggleNewlineBecomesNewChecklistItem")
@@ -81,7 +82,7 @@ template(name="addChecklistItemForm")
| {{_ 'originOrder'}}
template(name="editChecklistItemForm")
a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}")
a(title="{{_ 'copy-text-to-clipboard'}}")
span.copied-tooltip {{_ 'copied'}}
textarea.js-edit-checklist-item(rows='1' autofocus dir="auto")
if $eq type 'item'
@@ -90,12 +91,13 @@ template(name="editChecklistItemForm")
= checklist.title
.edit-controls.clearfix
button.primary.confirm.js-submit-edit-checklist-item-form(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form(title="{{_ 'close-edit-checklist-item'}}")
a.js-close-inlined-form(title="{{_ 'close-edit-checklist-item'}}")
| ❌
span(title=createdAt) {{ moment createdAt }}
if canModifyCard
a.js-delete-checklist-item {{_ "delete"}}...
a.js-convert-checklist-item-to-card
i.fa.fa-copy
| 📋
| {{_ 'convertChecklistItemToCardPopup-title'}}
template(name="checklistItems")
@@ -105,7 +107,7 @@ template(name="checklistItems")
+addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=true position="top")
else
a.add-checklist-item.js-open-inlined-form(title="{{_ 'add-checklist-item'}}")
i.fa.fa-plus
| ➕
.checklist-items.js-checklist-items
each item in checklist.items
+inlinedForm(classNames="js-edit-checklist-item" item = item checklist = checklist)
@@ -117,21 +119,19 @@ template(name="checklistItems")
+addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=true)
else
a.add-checklist-item.js-open-inlined-form(title="{{_ 'add-checklist-item'}}")
i.fa.fa-plus
| ➕
template(name='checklistItemDetail')
.js-checklist-item.checklist-item(class="{{#if item.isFinished }}is-checked{{#if checklist.hideCheckedChecklistItems}} invisible{{/if}}{{/if}}{{#if checklist.hideAllChecklistItems}} is-checked invisible{{/if}}"
role="checkbox" aria-checked="{{#if item.isFinished }}true{{else}}false{{/if}}" tabindex="0")
if canModifyCard
.check-box-container
.check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
if isTouchScreenOrShowDesktopDragHandles
span.fa.checklistitem-handle(class="fa-arrows" title="{{_ 'dragChecklistItem'}}")
span.check-box-unicode {{#if item.isFinished }}✅{{else}}⬜{{/if}}
span.checklistitem-handle(title="{{_ 'dragChecklistItem'}}") ↕️
.item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}")
+viewer
= item.title
else
.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
span.check-box-unicode {{#if item.isFinished }}✅{{else}}⬜{{/if}}
.item-title(class="{{#if item.isFinished }}is-checked{{/if}}")
+viewer
= item.title
@@ -140,16 +140,16 @@ template(name="checklistActionsPopup")
ul.pop-over-list
li
a.js-delete-checklist.delete-checklist
i.fa.fa-trash
| 🗑️
| {{_ "delete"}} ...
a.js-move-checklist.move-checklist
i.fa.fa-arrow-right
| ➡️
| {{_ "moveChecklist"}} ...
a.js-copy-checklist.copy-checklist
i.fa.fa-copy
| 📋
| {{_ "copyChecklist"}} ...
a.js-hide-checked-checklist-items
i.fa.fa-eye-slash
| 🙈
| {{_ "hideCheckedChecklistItems"}} ...
.material-toggle-switch(title="{{_ 'hide-checked-items'}}")
if checklist.hideCheckedChecklistItems
@@ -158,7 +158,7 @@ template(name="checklistActionsPopup")
input.toggle-switch(type="checkbox" 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
+3 -2
View File
@@ -65,7 +65,7 @@ BlazeComponent.extendComponent({
$(self.itemsDom).sortable('option', 'disabled', !userIsMember());
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
$(self.itemsDom).sortable({
handle: 'span.fa.checklistitem-handle',
handle: 'span.checklistitem-handle',
});
}
}
@@ -305,7 +305,7 @@ BlazeComponent.extendComponent({
{
'click .js-delete-checklist': Popup.afterConfirm('checklistDelete', function () {
Popup.back(2);
const checklist = this.checklist;
const checklist = this.data().checklist;
if (checklist && checklist._id) {
Checklists.remove(checklist._id);
}
@@ -360,6 +360,7 @@ BlazeComponent.extendComponent({
events() {
return [
{
'click .js-checklist-item .check-box-unicode': this.toggleItem,
'click .js-checklist-item .check-box-container': this.toggleItem,
},
];
+7 -2
View File
@@ -38,6 +38,7 @@
.palette-colors {
display: flex;
flex-wrap: wrap;
justify-content: flex-start; /* left-align color chips in wider popovers */
}
.palette-colors .palette-color {
flex-grow: 1;
@@ -222,9 +223,13 @@
.card-label-edit-button:hover {
background: #dbdbdb;
}
ul.edit-labels-pop-over span.fa.label-handle {
ul.edit-labels-pop-over span.label-handle {
padding-right: 10px;
display: inline-block;
width: 1.2em;
text-align: center;
color: #999;
}
ul.edit-labels-pop-over span.fa.label-handle + .card-label {
ul.edit-labels-pop-over span.label-handle + .card-label {
max-width: 180px;
}
+5 -4
View File
@@ -6,7 +6,7 @@ template(name="formLabel")
.palette-colors: each labels
span.card-label.palette-color.js-palette-color(class="card-label-{{color}}")
if(isSelected color)
i.fa.fa-check
| ✅
template(name="createLabelPopup")
form.create-label
@@ -28,13 +28,14 @@ template(name="cardLabelsPopup")
ul.edit-labels-pop-over
each board.labels
li.js-card-label-item
a.card-label-edit-button.fa.fa-pencil.js-edit-label
a.card-label-edit-button.js-edit-label
| ✏️
if isTouchScreenOrShowDesktopDragHandles
span.fa.label-handle(class="fa-arrows" title="{{_ 'dragLabel'}}")
span.label-handle(title="{{_ 'dragLabel'}}") ↕️
span.card-label.card-label-selectable.js-select-label.card-label-wrapper(class="card-label-{{color}}"
class="{{# if isLabelSelected ../_id }}active{{/if}}")
+viewer
= name
if(isLabelSelected ../_id)
i.card-label-selectable-icon.fa.fa-check
| ✅
a.quiet-button.full.js-add-label {{_ 'label-create'}}
+26 -4
View File
@@ -125,8 +125,19 @@ Template.createLabelPopup.events({
.$('#labelName')
.val()
.trim();
const color = Blaze.getData(templateInstance.find('.fa-check')).color;
board.addLabel(name, color);
// Find the selected color by looking for the palette color that contains the checkmark
let selectedColor = null;
templateInstance.$('.js-palette-color').each(function() {
if ($(this).text().includes('✅')) {
selectedColor = Blaze.getData(this).color;
return false; // break out of loop
}
});
if (selectedColor) {
board.addLabel(name, selectedColor);
}
Popup.back();
},
});
@@ -144,8 +155,19 @@ Template.editLabelPopup.events({
.$('#labelName')
.val()
.trim();
const color = Blaze.getData(templateInstance.find('.fa-check')).color;
board.editLabel(this._id, name, color);
// Find the selected color by looking for the palette color that contains the checkmark
let selectedColor = null;
templateInstance.$('.js-palette-color').each(function() {
if ($(this).text().includes('✅')) {
selectedColor = Blaze.getData(this).color;
return false; // break out of loop
}
});
if (selectedColor) {
board.editLabel(this._id, name, selectedColor);
}
Popup.back();
},
});
+211 -61
View File
@@ -3,11 +3,11 @@
position: relative;
display: flex;
align-items: center;
margin-bottom: 9px;
margin-bottom: 1.2vh;
}
.minicard-wrapper.placeholder {
background: #ccc;
border-radius: 9px;
border-radius: 1.2vw;
}
.minicard-wrapper.ui-sortable-helper {
cursor: grabbing;
@@ -16,28 +16,28 @@
}
.minicard-wrapper.ui-sortable-helper .and-n-other {
width: 100%;
height: 16px;
padding: 4px;
height: 2.5vh;
padding: 0.5vh;
background-color: #f2f2f2;
text-align: center;
border-radius: 3px;
border-radius: 0.4vw;
}
.minicard-wrapper.ui-sortable-helper .multi-selection-checkbox {
display: none;
}
.minicard-wrapper .multi-selection-checkbox + .minicard {
margin-left: 8px;
margin-left: 1vw;
}
@media only screen {
.minicard {
padding: 6px 8px 2px;
padding: 0.8vh 1vw 0.3vh;
position: relative;
flex: 1;
flex-wrap: wrap;
background-color: #fff;
min-height: 20px;
box-shadow: 0 1px 2px rgba(0,0,0,0.15);
border-radius: 2px;
min-height: 2.5vh;
box-shadow: 0 0.2vh 0.3vh rgba(0,0,0,0.15);
border-radius: 0.3vw;
color: #4d4d4d;
overflow: hidden;
transition: transform 0.2s, border-radius 0.2s;
@@ -45,14 +45,14 @@
}
.minicard-details-menu-with-handle {
float: right;
font-size: 18px;
padding-right: 30px;
padding-left: 5px;
font-size: clamp(14px, 3vw, 18px);
padding-right: 4vw;
padding-left: 0.7vw;
}
.minicard-details-menu {
float: right;
font-size: 18px;
padding-left: 5px;
font-size: clamp(14px, 3vw, 18px);
padding-left: 0.7vw;
}
@media print {
.minicard-details-menu,
@@ -63,7 +63,7 @@
.minicard.linked-board .linked-icon,
.minicard.linked-card .linked-icon {
display: inline-block;
margin-right: 11px;
margin-right: 1.5vw;
vertical-align: baseline;
font-size: 0.9em;
}
@@ -72,11 +72,11 @@
color: #937760;
}
.is-selected .minicard {
transform: translateX(11px);
transform: translateX(1.5vw);
border-bottom-right-radius: 0;
border-top-right-radius: 0;
z-index: 25;
box-shadow: -2px 1px 2px rgba(0,0,0,0.2);
box-shadow: -0.3vw 0.2vh 0.3vh rgba(0,0,0,0.2);
}
.minicard:hover:not(.minicard-composer),
.is-selected .minicard,
@@ -90,20 +90,20 @@
background-position: center;
background-repeat: no-repeat;
background-size: contain;
height: 145px;
height: 20vh;
user-select: none;
margin: 6px -8px 6px -8px;
border-radius: top 2px;
margin: 0.8vh -1vw 0.8vh -1vw;
border-radius: top 0.3vw;
}
.minicard .minicard-labels {
float: none;
}
.minicard .minicard-labels .minicard-label {
width: 11px;
height: 11px;
border-radius: 2px;
margin-right: 3px;
margin-bottom: 3px;
width: clamp(12px, 1.5vw, 16px);
height: clamp(12px, 1.5vw, 16px);
border-radius: 0.3vw;
margin-right: 0.4vw;
margin-bottom: 0.4vh;
}
.minicard .minicard-labels-no-text {
display: flex;
@@ -119,22 +119,22 @@
flex-grow: 1;
display: block;
word-wrap: break-word;
max-width: 100px;
margin-right: 4px;
max-width: 13vw;
margin-right: 0.5vw;
}
.minicard .minicard-custom-field-item-fullwidth {
flex-grow: 1;
display: block;
word-wrap: break-word;
max-width: 100%;
margin-right: 4px;
margin-right: 0.5vw;
}
.minicard .handle {
width: 20px;
height: 20px;
width: clamp(20px, 2.5vw, 28px);
height: clamp(20px, 2.5vw, 28px);
position: absolute;
right: 5px;
top: 5px;
right: 0.7vw;
top: 0.7vh;
display: none;
}
@media only screen {
@@ -142,14 +142,17 @@
display: block;
}
}
.minicard .handle .fa-arrows {
font-size: 20px;
.minicard .handle .drag-handle {
font-size: clamp(16px, 3vw, 20px);
color: #ccc;
display: inline-block;
width: 1.4em;
text-align: center;
}
.minicard .minicard-title .card-number {
color: #b3b3b3;
display: inline-block;
margin-right: 5px;
margin-right: 0.7vw;
}
@media only screen {
.minicard .minicard-title p:last-child {
@@ -166,11 +169,140 @@
flex-wrap: wrap;
}
.minicard .date {
margin-right: 3px;
margin-right: 0.4vw;
}
/* Unicode icons for minicard dates - matching cardDate.css */
.minicard .card-date.end-date time::before {
content: "🏁"; /* Finish flag - represents end/completion */
}
.minicard .card-date.due-date time::before {
content: "⏰"; /* Alarm clock - represents due/deadline */
}
.minicard .card-date.start-date time::before {
content: "🚀"; /* Rocket - represents start/launch */
}
.minicard .card-date.received-date time::before {
content: "📥"; /* Inbox tray - represents received/incoming */
}
.minicard .card-date time::before {
font-size: inherit;
margin-right: 0.3em;
display: inline-block;
}
/* Date type specific colors for minicards - matching cardDate.css */
.minicard .card-date.received-date {
background-color: #dbdbdb; /* Grey for received - same as base card-date */
}
.minicard .card-date.received-date:hover,
.minicard .card-date.received-date.is-active {
background-color: #b3b3b3;
}
.minicard .card-date.start-date {
background-color: #90ee90; /* Light green for start */
color: #000; /* Black text for start */
}
.minicard .card-date.start-date:hover,
.minicard .card-date.start-date.is-active {
background-color: #7dd87d;
}
.minicard .card-date.due-date {
background-color: #ffd700; /* Yellow for due */
color: #000; /* Black text for due */
}
.minicard .card-date.due-date:hover,
.minicard .card-date.due-date.is-active {
background-color: #e6c200;
}
.minicard .card-date.end-date {
background-color: #ffb3b3; /* Light red for end */
color: #000; /* Black text for end */
}
.minicard .card-date.end-date:hover,
.minicard .card-date.end-date.is-active {
background-color: #ff9999;
}
/* Date status colors for minicards - matching cardDate.css */
.minicard .card-date.overdue {
background-color: #ff4444 !important; /* Red for overdue */
color: #fff !important;
}
.minicard .card-date.overdue:hover,
.minicard .card-date.overdue.is-active {
background-color: #cc3333 !important;
}
.minicard .card-date.due-soon {
background-color: #ffaa00 !important; /* Amber for due soon */
color: #000 !important;
}
.minicard .card-date.due-soon:hover,
.minicard .card-date.due-soon.is-active {
background-color: #e69900 !important;
}
.minicard .card-date.not-due {
/* No special background - uses default date type colors */
}
.minicard .card-date.current {
background-color: #5ba639 !important; /* Green for current/active */
color: #fff !important;
}
.minicard .card-date.current:hover,
.minicard .card-date.current.is-active {
background-color: #46802c !important;
}
.minicard .card-date.completed {
background-color: #90ee90 !important; /* Light green for completed */
color: #000 !important;
}
.minicard .card-date.completed:hover,
.minicard .card-date.completed.is-active {
background-color: #7dd87d !important;
}
.minicard .card-date.completed-early {
background-color: #4caf50 !important; /* Green for completed early */
color: #fff !important;
}
.minicard .card-date.completed-early:hover,
.minicard .card-date.completed-early.is-active {
background-color: #45a049 !important;
}
.minicard .card-date.completed-late {
background-color: #ff9800 !important; /* Orange for completed late */
color: #fff !important;
}
.minicard .card-date.completed-late:hover,
.minicard .card-date.completed-late.is-active {
background-color: #f57c00 !important;
}
.minicard .card-date.completed-on-time {
background-color: #2196f3 !important; /* Blue for completed on time */
color: #fff !important;
}
.minicard .card-date.completed-on-time:hover,
.minicard .card-date.completed-on-time.is-active {
background-color: #1976d2 !important;
}
.minicard .badges {
float: left;
margin-top: 7px;
margin-top: 1vh;
color: #808080;
}
.minicard .badges:empty {
@@ -178,14 +310,14 @@
}
.minicard .badges .badge {
float: left;
margin-right: 11px;
margin-bottom: 3px;
margin-right: 1.5vw;
margin-bottom: 0.4vh;
font-size: 0.9em;
}
.minicard .badges .badge.is-finished {
background: #3cb500;
padding: 0px 3px;
border-radius: 3px;
padding: 0 0.4vw;
border-radius: 0.4vw;
color: #fff;
}
.minicard .badges .badge:last-of-type {
@@ -201,41 +333,41 @@
}
.minicard .badges .badge .badge-text {
font-size: 0.9em;
padding-left: 2px;
line-height: 14px;
padding-left: 0.3vw;
line-height: 1.2;
}
.minicard .badges .badge .check-list-text {
padding-left: 0px;
line-height: 12px;
line-height: 1.1;
}
.minicard .minicard-members,
.minicard .minicard-assignees,
.minicard .minicard-creator {
float: right;
margin-left: 5px;
margin-bottom: 4px;
margin-left: 0.7vw;
margin-bottom: 0.5vh;
}
.minicard .minicard-members .member,
.minicard .minicard-assignees .member,
.minicard .minicard-creator .member {
float: right;
border-radius: 50%;
height: 28px;
width: 28px;
margin-bottom: 4px;
height: clamp(24px, 3.5vw, 32px);
width: clamp(24px, 3.5vw, 32px);
margin-bottom: 0.5vh;
}
.minicard .minicard-members .assignee,
.minicard .minicard-assignees .assignee,
.minicard .minicard-creator .assignee {
float: right;
border-radius: 50%;
height: 28px;
width: 28px;
height: clamp(24px, 3.5vw, 32px);
width: clamp(24px, 3.5vw, 32px);
}
.minicard .minicard-members + .badges,
.minicard .minicard-assignees + .badges,
.minicard .minicard-creator + .badges {
margin-top: 5px;
margin-top: 0.7vh;
}
.minicard .minicard-assignees {
border-bottom: 1px solid #f00;
@@ -248,13 +380,13 @@
display: none;
}
.minicard .minicard-description {
padding: 6px 0 0 8px;
padding: 0.8vh 0 0 1vw;
color: #000;
background-color: #eee;
width: 100%;
margin-bottom: 2px;
margin-left: -4px;
border-radius: 3px;
margin-bottom: 0.3vh;
margin-left: -0.5vw;
border-radius: 0.4vw;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
@@ -262,7 +394,7 @@
-webkit-box-orient: vertical;
}
.minicard.minicard-composer {
margin-bottom: 10px;
margin-bottom: 1.3vh;
}
.minicard.minicard-composer textarea.minicard-composer-textarea,
.minicard.minicard-composer textarea.minicard-composer-textarea:focus {
@@ -273,9 +405,9 @@
height: auto;
margin: 0;
padding: 0;
max-height: 162px;
min-height: 36px;
margin-bottom: 20px;
max-height: 22vh;
min-height: 5vh;
margin-bottom: 2.5vh;
overflow-y: auto;
}
.parent-prefix {
@@ -580,3 +712,21 @@
.text-green {
color: #008000;
}
/* Drag and drop file upload visual feedback */
.minicard.is-dragging-over {
border: 2px dashed #0079bf;
background-color: #e3f2fd !important;
transform: scale(1.02);
transition: all 0.2s ease;
}
/* List name display on minicard */
.minicard-list-name {
font-size: 0.75em;
color: #8c8c8c;
margin-top: 0.2vh;
display: flex;
align-items: center;
gap: 0.3vw;
}
+68 -39
View File
@@ -4,19 +4,14 @@ template(name="minicard")
class="{{#if isLinkedBoard}}linked-board{{/if}}"
class="{{#if colorClass}}minicard-{{colorClass}}{{/if}}")
if canModifyCard
if isTouchScreenOrShowDesktopDragHandles
a.fa.fa-navicon.minicard-details-menu-with-handle.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
.handle
.fa.fa-arrows
else
a.fa.fa-navicon.minicard-details-menu.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
a.minicard-details-menu-with-handle.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}") ☰
if canMoveCard
.handle
| ↕️
.dates
if getReceived
unless getStart
unless getDue
unless getEnd
.date
+minicardReceivedDate
.date
+minicardReceivedDate
if getStart
.date
+minicardStartDate
@@ -31,6 +26,27 @@ template(name="minicard")
if cover
if currentBoard.allowsCoverAttachmentOnMinicard
.minicard-cover(style="background-image: url('{{cover.link 'original'}}?dummyReloadAfterSessionEstablished={{sess}}');")
// Upload progress indicator for drag-and-drop uploads
if hasActiveUploads
.minicard-upload-progress
.upload-progress-header
| 📤
span {{_ 'uploading-files'}} ({{uploadCount}})
each uploads
.upload-progress-item(class="{{#if $eq status 'error'}}upload-error{{/if}}")
.upload-progress-filename {{file.name}}
.upload-progress-bar
.upload-progress-fill(style="width: {{progress}}%")
if $eq status 'error'
.upload-progress-error
| ⚠️
span {{_ 'upload-failed'}}
else if $eq status 'completed'
.upload-progress-success
| ✅
span {{_ 'upload-completed'}}
.minicard-title
if $eq 'prefix-with-full-path' currentBoard.presentParentTask
.parent-prefix
@@ -40,23 +56,17 @@ template(name="minicard")
| {{ parentCardName }}
if isLinkedBoard
a.js-linked-link
span.linked-icon.fa.fa-folder
span.linked-icon | 📁
else if isLinkedCard
a.js-linked-link
span.linked-icon.fa.fa-id-card
span.linked-icon | 🃏
if getArchived
span.linked-icon.linked-archived.fa.fa-archive
span.linked-icon.linked-archived | 📦
+viewer
if currentBoard.allowsCardNumber
span.card-number
| ##{getCardNumber}
= getTitle
if $eq 'subtext-with-full-path' currentBoard.presentParentTask
.parent-subtext
| {{ parentString ' > ' }}
if $eq 'subtext-with-parent' currentBoard.presentParentTask
.parent-subtext
| {{ parentCardName }}
if labels
.minicard-labels(class="{{#if hiddenMinicardLabelText}}minicard-labels-no-text{{/if}}")
each labels
@@ -132,7 +142,7 @@ template(name="minicard")
if canModifyCard
if comments.length
.badge(title="{{_ 'card-comments-title' comments.length }}")
span.badge-icon.fa.fa-comment-o.badge-comment.badge-text
span.badge-icon.badge-comment.badge-text 💬
= ' '
= comments.length
//span.badge-comment.badge-text
@@ -140,42 +150,52 @@ template(name="minicard")
if getDescription
unless currentBoard.allowsDescriptionTextOnMinicard
.badge.badge-state-image-only(title=getDescription)
span.badge-icon.fa.fa-align-left
span.badge-icon 📝
if getVoteQuestion
.badge.badge-state-image-only(title=getVoteQuestion)
span.badge-icon.fa.fa-thumbs-up(class="{{#if voteState}}text-green{{/if}}")
span.badge-icon(class="{{#if voteState}}text-green{{/if}}") 👍
span.badge-text {{ voteCountPositive }}
span.badge-icon.fa.fa-thumbs-down(class="{{#if $eq voteState false}}text-red{{/if}}")
span.badge-icon(class="{{#if $eq voteState false}}text-red{{/if}}") 👎
span.badge-text {{ voteCountNegative }}
if getPokerQuestion
.badge.badge-state-image-only(title=getPokerQuestion)
span.badge-icon.fa.fa-check(class="{{#if pokerState}}text-green{{/if}}")
span.badge-icon(class="{{#if pokerState}}text-green{{/if}}") ✅
if expiredPoker
span.badge-text {{ getPokerEstimation }}
if attachments.length
if currentBoard.allowsBadgeAttachmentOnMinicard
.badge
span.badge-icon.fa.fa-paperclip
span.badge-icon 📎
span.badge-text= attachments.length
if checklists.length
.badge(class="{{#if checklistFinished}}is-finished{{/if}}")
span.badge-icon.fa.fa-check-square-o
span.badge-icon ☑️
span.badge-text.check-list-text {{checklistFinishedCount}}/{{checklistItemCount}}
if allSubtasks.count
.badge
span.badge-icon.fa.fa-sitemap
span.badge-icon 🌐
span.badge-text.check-list-text {{subtasksFinishedCount}}/{{allSubtasksCount}}
//{{subtasksFinishedCount}}/{{subtasksCount}} does not work because when a subtaks is archived, the count goes down
if currentBoard.allowsCardSortingByNumber
if currentBoard.allowsCardSortingByNumberOnMinicard
.badge
span.badge-icon.fa.fa-sort
span.badge-icon 🔢
span.badge-text.check-list-sort {{ sort }}
if currentBoard.allowsDescriptionTextOnMinicard
if getDescription
.minicard-description
+viewer
| {{ getDescription }}
if shouldShowListOnMinicard
.minicard-list-name
| 📋
| {{ listName }}
if $eq 'subtext-with-full-path' currentBoard.presentParentTask
.parent-subtext
| {{ parentString ' > ' }}
if $eq 'subtext-with-parent' currentBoard.presentParentTask
.parent-subtext
| {{ parentCardName }}
template(name="editCardSortOrderPopup")
input.js-edit-card-sort-popup(type='text' autofocus value=sort dir="auto")
@@ -187,41 +207,50 @@ template(name="minicardDetailsActionsPopup")
if canModifyCard
li
a.js-move-card
i.fa.fa-arrow-right
| ➡️
| {{_ 'moveCardPopup-title'}}
li
a.js-copy-card
i.fa.fa-copy
| 📋
| {{_ 'copyCardPopup-title'}}
hr
li
a.js-archive
i.fa.fa-arrow-right
i.fa.fa-archive
| ➡️
| 📦
| {{_ 'archive-card'}}
hr
li
a.js-move-card-to-top
i.fa.fa-arrow-up
| ⬆️
| {{_ 'moveCardToTop-title'}}
li
a.js-move-card-to-bottom
i.fa.fa-arrow-down
| ⬇️
| {{_ 'moveCardToBottom-title'}}
hr
li
a.js-add-labels
i.fa.fa-tags
| 🏷️
| {{_ 'card-edit-labels'}}
li
a.js-due-date
i.fa.fa-sign-in
| 📥
| {{_ 'editCardDueDatePopup-title'}}
li
a.js-set-card-color
i.fa.fa-paint-brush
| 🎨
| {{_ 'setCardColorPopup-title'}}
li
a.js-link
i.fa.fa-link
| 🔗
| {{_ 'link-card'}}
li
a.js-toggle-watch-card
if isWatching
| 👁️
| {{_ 'unwatch'}}
else
| 👁️
| {{_ 'watch'}}
+96 -1
View File
@@ -1,6 +1,8 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import { CustomFieldStringTemplate } from '/client/lib/customFields'
import { CustomFieldStringTemplate } from '/client/lib/customFields';
import { handleFileUpload } from './attachments';
import uploadProgressManager from '../../lib/uploadProgressManager';
// Template.cards.events({
// 'click .member': Popup.open('cardMember')
@@ -46,6 +48,10 @@ BlazeComponent.extendComponent({
}
return ret;
},
isWatching() {
const card = this.currentData();
return card.findWatcher(Meteor.userId());
},
showMembers() {
// cache "board" to reduce the mini-mongodb access
@@ -103,6 +109,62 @@ BlazeComponent.extendComponent({
'click span.badge-icon.fa.fa-sort, click span.badge-text.check-list-sort' : Popup.open("editCardSortOrder"),
'click .minicard-labels' : this.cardLabelsPopup,
'click .js-open-minicard-details-menu': Popup.open('minicardDetailsActions'),
// Drag and drop file upload handlers
'dragover .minicard'(event) {
// Only prevent default for file drags to avoid interfering with sortable
const dataTransfer = event.originalEvent.dataTransfer;
if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) {
event.preventDefault();
event.stopPropagation();
}
},
'dragenter .minicard'(event) {
const dataTransfer = event.originalEvent.dataTransfer;
if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) {
event.preventDefault();
event.stopPropagation();
const card = this.data();
const board = card.board();
// Only allow drag-and-drop if user can modify card and board allows attachments
if (Utils.canModifyCard() && board && board.allowsAttachments) {
$(event.currentTarget).addClass('is-dragging-over');
}
}
},
'dragleave .minicard'(event) {
const dataTransfer = event.originalEvent.dataTransfer;
if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) {
event.preventDefault();
event.stopPropagation();
$(event.currentTarget).removeClass('is-dragging-over');
}
},
'drop .minicard'(event) {
const dataTransfer = event.originalEvent.dataTransfer;
if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) {
event.preventDefault();
event.stopPropagation();
$(event.currentTarget).removeClass('is-dragging-over');
const card = this.data();
const board = card.board();
// Check permissions
if (!Utils.canModifyCard() || !board || !board.allowsAttachments) {
return;
}
// Check if this is a file drop (not a card reorder)
if (!dataTransfer.files || dataTransfer.files.length === 0) {
return;
}
const files = dataTransfer.files;
if (files && files.length > 0) {
handleFileUpload(card, files);
}
}
},
}
];
},
@@ -125,6 +187,32 @@ Template.minicard.helpers({
? Meteor.connection._lastSessionId
: null;
},
isWatching() {
return this.findWatcher(Meteor.userId());
},
// Upload progress helpers
hasActiveUploads() {
return uploadProgressManager.hasActiveUploads(this._id);
},
uploads() {
return uploadProgressManager.getUploadsForCard(this._id);
},
uploadCount() {
return uploadProgressManager.getUploadCountForCard(this._id);
},
listName() {
const list = this.list();
return list ? list.title : '';
},
shouldShowListOnMinicard() {
// Show list name if either:
// 1. Board-wide setting is enabled, OR
// 2. This specific card has the setting enabled
const currentBoard = this.currentBoard;
if (!currentBoard) return false;
return currentBoard.allowsShowListsOnMinicard || this.showListOnMinicard;
}
});
BlazeComponent.extendComponent({
@@ -178,4 +266,11 @@ Template.minicardDetailsActionsPopup.events({
this.archive();
Utils.goBoardId(this.boardId);
}),
'click .js-toggle-watch-card'() {
const currentCard = this;
const level = currentCard.findWatcher(Meteor.userId()) ? null : 'watching';
Meteor.call('watch', 'card', currentCard._id, level, (err, ret) => {
if (!err && ret) Popup.back();
});
},
});
+6 -6
View File
@@ -11,9 +11,9 @@ template(name="resultCard")
= getBoard.title
else
.broken-cards-null
| NULL
| {{_ 'no-name'}}
if getBoard.archived
i.fa.fa-archive
| 📦
li.result-card-context.result-card-context-separator
= ' '
| {{_ 'context-separator'}}
@@ -25,9 +25,9 @@ template(name="resultCard")
= getSwimlane.title
else
.broken-cards-null
| NULL
| {{_ 'no-name'}}
if getSwimlane.archived
i.fa.fa-archive
| 📦
li.result-card-context.result-card-context-separator
= ' '
| {{_ 'context-separator'}}
@@ -39,6 +39,6 @@ template(name="resultCard")
= getList.title
else
.broken-cards-null
| NULL
| {{_ 'no-name'}}
if getList.archived
i.fa.fa-archive
| 📦
+28
View File
@@ -87,6 +87,15 @@ textarea.js-edit-subtask-item {
top: 0;
bottom: -600px;
right: 0;
z-index: 15;
}
/* Fix for mobile Safari: ensure this doesn't block card interaction */
@media screen and (max-width: 800px) {
#card-details-overlay {
z-index: 15;
pointer-events: none;
}
}
.subtasks {
background: #f7f7f7;
@@ -127,6 +136,25 @@ textarea.js-edit-subtask-item {
border-bottom: 2px solid #3cb500;
border-right: 2px solid #3cb500;
}
/* Unicode checkbox icons styling */
.subtasks-item .check-box-unicode {
font-size: 1.3em;
margin-right: 8px;
cursor: pointer;
display: inline-block;
vertical-align: middle;
line-height: 1;
}
/* Grey checkmarks when grey icons setting is enabled */
body.grey-icons-enabled .subtasks-item .check-box.is-checked {
border-bottom: 2px solid #7a7a7a;
border-right: 2px solid #7a7a7a;
}
body.grey-icons-enabled .subtasks-item .check-box-unicode {
filter: grayscale(100%);
-webkit-filter: grayscale(100%);
opacity: 0.85;
}
.subtasks-item .item-title {
flex: 1;
padding-left: 10px;
+10 -10
View File
@@ -1,6 +1,6 @@
template(name="subtasks")
h3.card-details-item-title
i.fa.fa-sitemap
| 🌐
| {{_ 'subtasks'}}
if currentUser.isBoardAdmin
if toggleDeleteDialog.get
@@ -16,7 +16,7 @@ template(name="subtasks")
+addSubtaskItemForm
else
a.js-open-inlined-form(title="{{_ 'add-subtask'}}")
i.fa.fa-plus
| ➕
template(name="subtaskDetail")
.js-subtasks.subtask
@@ -26,7 +26,7 @@ template(name="subtaskDetail")
.subtask-title
span
if canModifyCard
a.fa.fa-navicon.subtask-details-menu.js-open-subtask-details-menu(title="{{_ 'subtaskActionsPopup-title'}}")
a.subtask-details-menu.js-open-subtask-details-menu(title="{{_ 'subtaskActionsPopup-title'}}")
if canModifyCard
h2.title.js-open-inlined-form.is-editable
+viewer
@@ -40,7 +40,7 @@ template(name="addSubtaskItemForm")
textarea.js-add-subtask-item(rows='1' autofocus dir="auto")
.edit-controls.clearfix
button.primary.confirm.js-submit-add-subtask-item-form(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
a.js-close-inlined-form
template(name="editSubtaskItemForm")
textarea.js-edit-subtask-item(rows='1' autofocus dir="auto")
@@ -50,7 +50,7 @@ template(name="editSubtaskItemForm")
= subtask.title
.edit-controls.clearfix
button.primary.confirm.js-submit-edit-subtask-item-form(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
a.js-close-inlined-form
span(title=createdAt) {{ moment createdAt }}
if canModifyCard
if currentUser.isBoardAdmin
@@ -68,18 +68,18 @@ template(name="subtasksItems")
+addSubtaskItemForm
else
a.add-subtask-item.js-open-inlined-form
i.fa.fa-plus
| ➕
| {{_ 'add-subtask-item'}}...
template(name='subtaskItemDetail')
.js-subtasks-item.subtasks-item
if canModifyCard
.check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
span.check-box-unicode {{#if item.isFinished }}✅{{else}}⬜{{/if}}
.item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}")
+viewer
= item.title
else
.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
span.check-box-unicode {{#if item.isFinished }}✅{{else}}⬜{{/if}}
.item-title(class="{{#if item.isFinished }}is-checked{{/if}}")
+viewer
= item.title
@@ -92,10 +92,10 @@ template(name="subtaskActionsPopup")
ul.pop-over-list
li
a.js-view-subtask(title="{{ subtask.title }}")
i.fa.fa-eye
| 👁️
| {{_ "view-it"}}
if currentUser.isBoardAdmin
a.js-delete-subtask.delete-subtask
i.fa.fa-trash
| 🗑️
| {{_ "delete"}} ...
+13 -1
View File
@@ -104,7 +104,19 @@ BlazeComponent.extendComponent({
}).register('subtasks');
BlazeComponent.extendComponent({
// ...
toggleItem() {
const item = this.currentData().item;
if (item && item._id) {
item.toggleItem();
}
},
events() {
return [
{
'click .js-subtasks-item .check-box-unicode': this.toggleItem,
},
];
},
}).register('subtaskItemDetail');
BlazeComponent.extendComponent({
@@ -0,0 +1,123 @@
/* Original Position Component Styles */
.original-position-info {
margin: 5px 0;
padding: 8px;
border-radius: 4px;
font-size: 12px;
line-height: 1.4;
}
.original-position-loading {
color: #666;
font-style: italic;
}
.original-position-loading i {
margin-right: 5px;
}
.original-position-details {
background-color: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 3px;
padding: 6px 8px;
}
.original-position-moved {
color: #856404;
background-color: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 3px;
padding: 4px 6px;
margin-bottom: 4px;
}
.original-position-moved i {
color: #f39c12;
margin-right: 5px;
}
.original-position-unchanged {
color: #155724;
background-color: #d4edda;
border: 1px solid #c3e6cb;
border-radius: 3px;
padding: 4px 6px;
margin-bottom: 4px;
}
.original-position-unchanged i {
color: #28a745;
margin-right: 5px;
}
.original-position-text {
font-weight: 500;
}
.original-title {
color: #6c757d;
font-size: 11px;
margin-top: 4px;
padding-top: 4px;
border-top: 1px solid #e9ecef;
}
.original-title strong {
color: #495057;
}
/* Integration with existing Wekan styles */
.swimlane .original-position-info,
.list .original-position-info,
.card .original-position-info {
margin: 2px 0;
padding: 4px 6px;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.original-position-info {
font-size: 11px;
padding: 6px;
}
.original-position-details {
padding: 4px 6px;
}
.original-position-moved,
.original-position-unchanged {
padding: 3px 5px;
}
}
/* Dark theme support */
@media (prefers-color-scheme: dark) {
.original-position-details {
background-color: #2d3748;
border-color: #4a5568;
color: #e2e8f0;
}
.original-position-moved {
background-color: #744210;
border-color: #b7791f;
color: #fbd38d;
}
.original-position-unchanged {
background-color: #22543d;
border-color: #38a169;
color: #9ae6b4;
}
.original-title {
color: #a0aec0;
border-color: #4a5568;
}
.original-title strong {
color: #e2e8f0;
}
}
@@ -0,0 +1,27 @@
<template name="originalPosition">
<div class="original-position-info">
{{#if isLoading}}
<div class="original-position-loading">
⏳ Loading original position...
</div>
{{else if showOriginalPosition}}
<div class="original-position-details">
{{#if hasMovedFromOriginal}}
<div class="original-position-moved">
<span class="original-position-text">ℹ️ {{getOriginalPositionDescription}}</span>
</div>
{{else}}
<div class="original-position-unchanged">
<span class="original-position-text">✅ In original position</span>
</div>
{{/if}}
{{#if getOriginalTitle}}
<div class="original-title">
<strong>Original title:</strong> {{getOriginalTitle}}
</div>
{{/if}}
</div>
{{/if}}
</div>
</template>
@@ -0,0 +1,98 @@
import { BlazeComponent } from 'meteor/peerlibrary:blaze-components';
import { ReactiveVar } from 'meteor/reactive-var';
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import './originalPosition.html';
/**
* Component to display original position information for swimlanes, lists, and cards
*/
class OriginalPositionComponent extends BlazeComponent {
onCreated() {
super.onCreated();
this.originalPosition = new ReactiveVar(null);
this.isLoading = new ReactiveVar(false);
this.hasMoved = new ReactiveVar(false);
this.autorun(() => {
const data = this.data();
if (data && data.entityId && data.entityType) {
this.loadOriginalPosition(data.entityId, data.entityType);
}
});
}
loadOriginalPosition(entityId, entityType) {
this.isLoading.set(true);
const methodName = `positionHistory.get${entityType.charAt(0).toUpperCase() + entityType.slice(1)}OriginalPosition`;
Meteor.call(methodName, entityId, (error, result) => {
this.isLoading.set(false);
if (error) {
console.error('Error loading original position:', error);
this.originalPosition.set(null);
} else {
this.originalPosition.set(result);
// Check if the entity has moved
const movedMethodName = `positionHistory.has${entityType.charAt(0).toUpperCase() + entityType.slice(1)}Moved`;
Meteor.call(movedMethodName, entityId, (movedError, movedResult) => {
if (!movedError) {
this.hasMoved.set(movedResult);
}
});
}
});
}
getOriginalPosition() {
return this.originalPosition.get();
}
isLoading() {
return this.isLoading.get();
}
hasMovedFromOriginal() {
return this.hasMoved.get();
}
getOriginalPositionDescription() {
const position = this.getOriginalPosition();
if (!position) return 'No original position data';
if (position.originalPosition) {
const entityType = this.data().entityType;
let description = `Original position: ${position.originalPosition.sort || 0}`;
if (entityType === 'list' && position.originalSwimlaneId) {
description += ` in swimlane ${position.originalSwimlaneId}`;
} else if (entityType === 'card') {
if (position.originalSwimlaneId) {
description += ` in swimlane ${position.originalSwimlaneId}`;
}
if (position.originalListId) {
description += ` in list ${position.originalListId}`;
}
}
return description;
}
return 'No original position data';
}
getOriginalTitle() {
const position = this.getOriginalPosition();
return position ? position.originalTitle : '';
}
showOriginalPosition() {
return this.getOriginalPosition() !== null;
}
}
OriginalPositionComponent.register('originalPosition');
export default OriginalPositionComponent;
+2 -3
View File
@@ -4,11 +4,10 @@ template(name="datepicker")
.fields
.left
label(for="date") {{_ 'date'}}
input.js-date-field#date(type="text" name="date" value=showDate placeholder=dateFormat autofocus)
input.js-date-field#date(type="date" name="date" value=showDate autofocus)
.right
label(for="time") {{_ 'time'}}
input.js-time-field#time(type="text" name="time" value=showTime placeholder=timeFormat)
.js-datepicker
input.js-time-field#time(type="time" name="time" value=showTime)
if error.get
.warning {{_ error.get}}
button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}}
+51 -39
View File
@@ -5,11 +5,11 @@ button {
box-sizing: border-box;
background-color: #ebebeb;
border: 1px solid #ccc;
border-radius: 3px;
border-radius: 0.4vw;
display: block;
margin-bottom: 12px;
min-height: 34px;
padding: 7px;
margin-bottom: 1.5vh;
min-height: 4.5vh;
padding: 1vh 1vw;
}
select.full,
textarea.full,
@@ -31,17 +31,18 @@ button:focus {
outline: 0;
}
input[type="file"] {
margin-bottom: 16px;
margin-bottom: 2vh;
}
input[type="radio"] {
-webkit-appearance: radio;
appearance: radio;
min-height: inherit;
}
input[type="text"],
input[type="password"],
input[type="email"] {
transition: background 85ms ease-in, border-color 85ms ease-in;
width: 250px;
width: min(250px, 30vw);
}
input[type="text"].inline-input,
input[type="password"].inline-input,
@@ -49,10 +50,10 @@ input[type="email"].inline-input {
background: none;
border: 0;
margin: 0;
padding: 2px;
padding: 0.3vh;
min-height: 0;
height: 18px;
width: 200px;
height: 2.5vh;
width: min(200px, 25vw);
}
input[type="text"].full-line,
input[type="password"].full-line,
@@ -96,30 +97,31 @@ input[type="email"]:disabled,
textarea:disabled {
background-color: #dcdcdc;
border-color: #bfbfbf;
color: #8c8c8c;
color: #222;
-webkit-touch-callout: none;
-webkit-user-select: none;
user-select: none;
}
select {
max-height: 300px;
width: 256px;
margin-bottom: 8px;
max-height: 40vh;
width: min(256px, 32vw);
margin-bottom: 1vh;
}
select.inline {
width: 100%;
}
option[disabled] {
color: #8c8c8c;
color: #222;
}
textarea {
height: 150px;
height: 20vh;
transition: background 85ms ease-in, border-color 85ms ease-in;
resize: vertical;
width: 100%;
}
textarea.editor {
resize: none;
padding-bottom: 22px;
padding-bottom: 3vh;
}
.button {
border-radius: 3px;
@@ -134,14 +136,14 @@ button {
cursor: pointer;
display: inline-block;
font-weight: 700;
line-height: 22px;
padding: 7px 20px;
line-height: 1.3;
padding: 1vh 2.5vw;
text-align: center;
}
input[type="submit"] .wide,
button .wide {
padding-left: 30px;
padding-right: 30px;
padding-left: 4vw;
padding-right: 4vw;
}
input[type="submit"]:hover,
button:hover,
@@ -222,8 +224,8 @@ button.disabled:active,
}
fieldset {
border: 1px solid #bfbfbf;
padding: 15px;
margin-bottom: 15px;
padding: 2vh 2vw;
margin-bottom: 2vh;
}
input[type="hidden"] {
display: none;
@@ -231,18 +233,18 @@ input[type="hidden"] {
.radio-div,
.check-div {
display: block;
margin: 0 0 4px 20px;
min-height: 20px;
margin: 0 0 0.5vh 2.5vw;
min-height: 2.5vh;
position: relative;
}
.radio-div input,
.check-div input {
left: -18px;
left: -2.5vw;
min-height: 0;
margin: 0;
padding: 0;
position: absolute;
top: 2px;
top: 0.3vh;
}
.radio-div label,
.check-div label {
@@ -251,23 +253,23 @@ input[type="hidden"] {
label {
display: block;
font-weight: 700;
margin-bottom: 4px;
margin-bottom: 0.5vh;
}
label.form-error {
color: #ba1212;
color: #d32f2f;
}
input::-webkit-input-placeholder,
textarea::-webkit-input-placeholder,
input::-moz-placeholder,
textarea::-moz-placeholder {
color: #8c8c8c;
color: #333 !important;
}
.edit-controls,
.add-controls {
display: flex;
align-items: center;
margin-top: 0px;
margin-bottom: 12px;
margin-bottom: 1.5vh;
}
@media print {
.add-controls {
@@ -279,13 +281,13 @@ textarea::-moz-placeholder {
.edit-controls input[type=button],
.add-controls input[type=button] {
float: left;
height: 32px;
height: 4.5vh;
margin-bottom: 0px;
}
.edit-controls .fa-times-thin,
.add-controls .fa-times-thin {
font-size: 26px;
margin: 3px 10px;
font-size: clamp(20px, 4vw, 26px);
margin: 0.5vh 1.5vw;
}
[type="checkbox"]:not(:checked),
[type="checkbox"]:checked {
@@ -313,15 +315,24 @@ textarea::-moz-placeholder {
margin-right: 6px;
border-top: 2px solid transparent;
border-left: 2px solid transparent;
border-bottom: 2px solid #3cb500;
border-right: 2px solid #3cb500;
transform: rotate(40deg);
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
transform-origin: 100% 100%;
}
/* Grey checkmarks when grey icons setting is enabled */
body.grey-icons-enabled .materialCheckBox.is-checked {
border-bottom: 2px solid #7a7a7a;
border-right: 2px solid #7a7a7a;
}
.button-link {
background: #fff;
background: linear-gradient(#fff, #f5f5f5);
border-radius: 3px;
box-sizing: border-box;
-webkit-user-select: none;
user-select: none;
border: 1px solid #e3e3e3;
border-bottom-color: #c2c2c2;
@@ -374,14 +385,14 @@ textarea::-moz-placeholder {
.button-link.setting.disabled {
background: #fff;
border-color: #e9e9e9;
color: #8c8c8c;
color: #222;
cursor: default;
}
.button-link.setting.disabled select {
display: none;
}
.button-link.setting.disabled:hover .label {
color: #8c8c8c;
color: #222;
}
.button-link.setting.disabled,
.button-link.setting.disabled:hover,
@@ -397,7 +408,7 @@ textarea::-moz-placeholder {
color: #a8a8a8;
}
.button-link.setting .label {
color: #8c8c8c;
color: #222;
display: block;
font-size: 12px;
line-height: 14px;
@@ -505,8 +516,9 @@ button.loud-text-button:hover {
.emphasis-button,
.quiet-button {
border-radius: 3px;
-webkit-user-select: none;
user-select: none;
color: #8c8c8c;
color: #222;
display: block;
margin: 2px 0;
padding: 6px 8px;
@@ -571,7 +583,7 @@ button.loud-text-button:hover {
top: 6px;
}
.big-link.none {
color: #8c8c8c;
color: #222;
text-decoration: none;
}
.big-link.none:hover {
@@ -601,7 +613,7 @@ button.loud-text-button:hover {
}
.show-more {
border-radius: 3px;
color: #8c8c8c;
color: #222;
display: block;
padding: 16px 8px 16px 40px;
margin: 8px 0;
+8 -8
View File
@@ -5,17 +5,17 @@
.map-members .mapping-list {
display: flex;
flex-wrap: wrap;
margin: 0 -4px;
margin: 0 -0.5vw;
}
.map-members .mapping-list .mapping-item {
max-width: 300px;
min-width: 200px;
padding: 6px;
margin: 5px;
max-width: min(300px, 40vw);
min-width: min(200px, 26vw);
padding: 0.8vh 0.8vw;
margin: 0.7vh;
flex: 1;
background: #fff;
border-radius: 3px;
box-shadow: 0 1px 2px rgba(0,0,0,0.15);
border-radius: 0.4vw;
box-shadow: 0 0.2vh 0.3vh rgba(0,0,0,0.15);
}
.map-members .mapping-list .mapping-item:hover {
background: #f2f2f2;
@@ -37,7 +37,7 @@
}
.map-members .mapping-list .wekan {
display: inline-block;
width: 35px;
width: 4.5vw;
}
.map-members .mapping-list .wekan .member {
float: none;
+8 -8
View File
@@ -1,7 +1,7 @@
template(name="importHeaderBar")
h1
a.back-btn(href="{{pathFor 'home'}}")
i.fa.fa-chevron-left
| ⬅️
| {{_ title}}
template(name="import")
@@ -36,7 +36,7 @@ template(name="importMapMembers")
+userAvatar(userId=wekanId)
else
a.member.add-member
i.fa.fa-plus
| ➕
//-
Due to the way the flewbox layout is working, we need to set some
invisible items so that the last row items have a consistent width.
@@ -56,17 +56,17 @@ template(name="importMapMembersAddPopup")
p
| {{_ 'import-user-select'}}
.js-map-member
+EasySearch.Input(index=searchIndex)
input.js-search-member-input(type="text" placeholder="{{_ 'search-users'}}")
ul.pop-over-list
+EasySearch.Each(index=searchIndex)
each searchResults
li.item.js-member-item
a.name.js-select-import(title="{{profile.fullname}} ({{username}})" data-id="{{__originalId}}")
+userAvatar(userId=__originalId)
a.name.js-select-import(title="{{profile.fullname}} ({{username}})" data-id="{{_id}}")
+userAvatar(userId=_id)
span.full-name
= profile.fullname
| (<span class="username">{{username}}</span>)
+EasySearch.IfSearching(index=searchIndex)
if searching.get
+spinner
+EasySearch.IfNoResults(index=searchIndex)
if noResults.get
.manage-member-section
p.quiet {{_ 'no-results'}}
+68 -1
View File
@@ -311,6 +311,73 @@ BlazeComponent.extendComponent({
},
}).register('importMapMembersAddPopup');
// Global reactive variables for import member popup
const importMemberPopupState = {
searching: new ReactiveVar(false),
searchResults: new ReactiveVar([]),
noResults: new ReactiveVar(false),
searchTimeout: null
};
BlazeComponent.extendComponent({
onCreated() {
// Use global state
this.searching = importMemberPopupState.searching;
this.searchResults = importMemberPopupState.searchResults;
this.noResults = importMemberPopupState.noResults;
this.searchTimeout = importMemberPopupState.searchTimeout;
},
onRendered() {
this.find('.js-search-member-input').focus();
},
performSearch(query) {
if (!query || query.length < 2) {
this.searchResults.set([]);
this.noResults.set(false);
return;
}
this.searching.set(true);
this.noResults.set(false);
const results = UserSearchIndex.search(query, { limit: 20 }).fetch();
this.searchResults.set(results);
this.searching.set(false);
if (results.length === 0) {
this.noResults.set(true);
}
},
events() {
return [
{
'keyup .js-search-member-input'(event) {
const query = event.target.value.trim();
if (this.searchTimeout) {
clearTimeout(this.searchTimeout);
}
this.searchTimeout = setTimeout(() => {
this.performSearch(query);
}, 300);
},
},
];
},
}).register('importMapMembersAddPopupSearch');
Template.importMapMembersAddPopup.helpers({
searchIndex: () => UserSearchIndex,
searchResults() {
return importMemberPopupState.searchResults.get();
},
searching() {
return importMemberPopupState.searching;
},
noResults() {
return importMemberPopupState.noResults;
}
})
+825 -40
View File
@@ -8,8 +8,230 @@
padding: 0;
float: left;
}
/* List resize handle */
.list-resize-handle {
position: absolute;
top: 0;
right: -3px;
width: 6px;
height: 100%;
cursor: col-resize;
z-index: 10;
background: transparent;
transition: background-color 0.2s ease;
border-radius: 2px;
/* Ensure the handle is clickable */
pointer-events: auto;
}
.list-resize-handle:hover {
background: rgba(0, 123, 255, 0.4);
box-shadow: 0 0 4px rgba(0, 123, 255, 0.3);
}
.list-resize-handle:active {
background: rgba(0, 123, 255, 0.6);
box-shadow: 0 0 6px rgba(0, 123, 255, 0.4);
}
/* Show resize handle only on hover */
.list:hover .list-resize-handle {
background: rgba(0, 0, 0, 0.1);
}
.list:hover .list-resize-handle:hover {
background: rgba(0, 123, 255, 0.4);
box-shadow: 0 0 4px rgba(0, 123, 255, 0.3);
}
/* Add a subtle indicator line */
.list-resize-handle::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 2px;
height: 20px;
background: rgba(0, 0, 0, 0.2);
border-radius: 1px;
opacity: 0;
transition: opacity 0.2s ease;
}
.list-resize-handle:hover::before {
opacity: 1;
}
/* Disable resize handle for collapsed lists and mobile view */
.list.list-collapsed .list-resize-handle,
.list.mobile-view .list-resize-handle {
display: none;
}
/* Disable resize handle for auto-width lists */
.list.list-auto-width .list-resize-handle {
display: none;
}
/* Visual feedback during resize */
.list.list-resizing {
transition: none !important;
box-shadow: 0 0 10px rgba(0, 123, 255, 0.3);
/* Ensure the list maintains its new width during resize */
flex: none !important;
flex-basis: auto !important;
flex-grow: 0 !important;
flex-shrink: 0 !important;
/* Override any conflicting layout properties */
float: left !important;
display: block !important;
position: relative !important;
/* Force width to be respected */
width: var(--list-width, auto) !important;
min-width: var(--list-width, auto) !important;
max-width: var(--list-width, auto) !important;
/* Ensure the width is applied immediately */
overflow: visible !important;
}
body.list-resizing-active {
cursor: col-resize !important;
}
body.list-resizing-active * {
cursor: col-resize !important;
}
/* Ensure swimlane container doesn't interfere with list resizing */
.swimlane .list.list-resizing {
/* Override any swimlane flex properties */
flex: none !important;
flex-basis: auto !important;
flex-grow: 0 !important;
flex-shrink: 0 !important;
/* Ensure width is respected */
width: var(--list-width, auto) !important;
min-width: var(--list-width, auto) !important;
max-width: var(--list-width, auto) !important;
}
/* More aggressive override for any container that might interfere */
.js-swimlane .list.list-resizing,
.dragscroll .list.list-resizing,
[id^="swimlane-"] .list.list-resizing {
/* Force the width to be applied */
width: var(--list-width, auto) !important;
min-width: var(--list-width, auto) !important;
max-width: var(--list-width, auto) !important;
flex: none !important;
flex-basis: auto !important;
flex-grow: 0 !important;
flex-shrink: 0 !important;
float: left !important;
display: block !important;
}
/* Ensure the width persists after resize is complete */
.js-swimlane .list[style*="--list-width"],
.dragscroll .list[style*="--list-width"],
[id^="swimlane-"] .list[style*="--list-width"] {
/* Maintain the width after resize */
width: var(--list-width, auto) !important;
min-width: var(--list-width, auto) !important;
max-width: var(--list-width, auto) !important;
flex: none !important;
flex-basis: auto !important;
flex-grow: 0 !important;
flex-shrink: 0 !important;
float: left !important;
display: block !important;
}
/* Ensure consistent header height for all lists */
.list-header {
/* Maintain consistent height and padding for all lists */
min-height: 2.5vh !important;
height: auto !important;
padding: 2.5vh 1.5vw 0.5vh !important;
/* Make sure the background covers the full height */
background-color: #e4e4e4 !important;
border-bottom: 0.8vh solid #e4e4e4 !important;
/* Use original display for consistent button positioning */
display: block !important;
position: relative !important;
/* Prevent vertical expansion but allow normal height */
overflow: hidden !important;
}
/* Ensure title text doesn't cause height changes for all lists */
.list-header .list-header-name {
/* Prevent text wrapping to maintain consistent height */
white-space: nowrap !important;
/* Truncate text with ellipsis if too long */
text-overflow: ellipsis !important;
/* Ensure proper line height */
line-height: 1.2 !important;
/* Ensure it doesn't overflow */
overflow: hidden !important;
/* Add margin to prevent overlap with buttons */
margin-right: 120px !important;
}
/* Position drag handle at top-right corner for ALL lists */
.list-header .list-header-handle {
/* Position at top-right corner, aligned with title text top */
position: absolute !important;
top: 2.5vh !important;
right: 1.5vw !important;
/* Ensure it's above other elements */
z-index: 15 !important;
/* Remove margin since it's absolutely positioned */
margin-right: 0 !important;
/* Ensure proper display */
display: inline-block !important;
/* Ensure it's clickable and shows proper cursor */
cursor: move !important;
pointer-events: auto !important;
/* Add some padding for better clickability */
padding: 4px !important;
}
/* Ensure buttons maintain original positioning */
.js-swimlane .list[style*="--list-width"] .list-header .list-header-plus-top,
.js-swimlane .list[style*="--list-width"] .list-header .js-collapse,
.js-swimlane .list[style*="--list-width"] .list-header .js-open-list-menu,
.dragscroll .list[style*="--list-width"] .list-header .list-header-plus-top,
.dragscroll .list[style*="--list-width"] .list-header .js-collapse,
.dragscroll .list[style*="--list-width"] .list-header .js-open-list-menu,
[id^="swimlane-"] .list[style*="--list-width"] .list-header .list-header-plus-top,
[id^="swimlane-"] .list[style*="--list-width"] .list-header .js-collapse,
[id^="swimlane-"] .list[style*="--list-width"] .list-header .js-open-list-menu {
/* Use original positioning to maintain layout */
position: relative !important;
/* Maintain original spacing */
margin-right: 15px !important;
/* Ensure proper display */
display: inline-block !important;
}
/* Ensure watch icon and card count maintain original positioning */
.js-swimlane .list[style*="--list-width"] .list-header .list-header-watch-icon,
.dragscroll .list[style*="--list-width"] .list-header .list-header-watch-icon,
[id^="swimlane-"] .list[style*="--list-width"] .list-header .list-header-watch-icon,
.js-swimlane .list[style*="--list-width"] .list-header .cardCount,
.dragscroll .list[style*="--list-width"] .list-header .cardCount,
[id^="swimlane-"] .list[style*="--list-width"] .list-header .cardCount {
/* Use original positioning to maintain layout */
position: relative !important;
/* Maintain original spacing */
margin-right: 15px !important;
/* Ensure proper display */
display: inline-block !important;
}
[id^="swimlane-"] .list:first-child {
min-width: 20px;
min-width: 2.5vw;
}
.list.list-auto-width {
flex: 1;
@@ -33,11 +255,79 @@
background-color: rgba(0,0,0,0.2);
border-color: transparent;
box-shadow: none;
height: 100px;
height: 15vh;
}
.list.list-collapsed {
flex: none;
min-width: 60px;
max-width: 80px;
width: 60px;
min-height: 60vh;
height: 60vh;
overflow: visible;
position: relative;
}
.list.list-collapsed .list-header {
padding: 1vh 1.5vw 0.5vh;
min-height: 2.5vh !important;
height: auto !important;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
position: relative;
overflow: visible !important;
width: 100%;
max-width: 60px;
margin: 0 auto;
}
.list.list-collapsed .list-header .js-collapse {
margin: 0 auto 0 auto;
z-index: 10;
padding: 8px 12px;
font-size: 12px;
white-space: nowrap;
display: block;
width: fit-content;
}
.list.list-collapsed .list-header .list-header-handle {
position: absolute !important;
top: 30px !important;
right: 1.5vw !important;
z-index: 15 !important;
}
.list.list-collapsed .list-header .list-rotated {
width: auto !important;
height: auto !important;
margin: 20px 0 0 0 !important;
position: relative !important;
overflow: visible !important;
}
.list.list-collapsed .list-header .list-rotated h2.list-header-name {
text-align: left;
overflow: visible;
white-space: nowrap;
display: block !important;
font-size: 12px;
line-height: 1.2;
color: #333;
background-color: rgba(255, 255, 255, 0.95);
border: 1px solid #ddd;
padding: 0;
border-radius: 4px;
margin: 0;
width: 100vh;
height: 30px;
position: absolute;
left: 40px;
top: 50%;
transform: translateY(calc(-50% + 20px)) rotate(0deg);
z-index: 10;
visibility: visible !important;
opacity: 1 !important;
pointer-events: none;
}
.list.list-composer .open-list-composer,
.list .list-composer .open-list-composer {
color: #8c8c8c;
@@ -45,24 +335,24 @@
.list.list-composer .list-name-input,
.list .list-composer .list-name-input {
background: #fff;
margin: -3px 0 8px;
margin: -0.4vh 0 1vh;
}
.list-header-add {
flex: 0 0 auto;
padding: 12px;
padding: 1.5vh 1.5vw;
position: relative;
min-height: 20px;
min-height: 2.5vh;
}
.list-header {
flex: 0 0 auto;
padding: 20px 12px 4px;
padding: 2.5vh 1.5vw 0.5vh;
position: relative;
min-height: 20px;
min-height: 2.5vh;
background-color: #e4e4e4;
border-bottom: 6px solid #e4e4e4;
border-bottom: 0.8vh solid #e4e4e4;
}
.list-header.list-header-card-count {
min-height: 35px;
min-height: 4.5vh;
height: auto;
}
.list-header.ui-sortable-handle {
@@ -73,29 +363,38 @@
}
.list-header .list-header-name {
display: inline;
font-size: 16px;
line-height: 17px;
font-size: clamp(14px, 3vw, 18px);
line-height: 1.2;
margin: 0;
font-weight: bold;
min-height: 9px;
min-width: 30px;
min-height: 1.2vh;
min-width: 4vw;
overflow: hidden;
text-overflow: ellipsis;
word-wrap: break-word;
}
/* Sum badge shown before list title */
.list-header .list-sum-badge {
display: inline-block;
margin-right: 8px;
padding: 0;
border-radius: 0;
background: transparent;
color: #8c8c8c;
font-weight: bold;
font-size: 12px;
vertical-align: middle;
}
.list-rotated {
width: 10px;
height: 250px;
margin-top: -90px;
margin-left: -110px;
width: 1.3vw;
height: 35vh;
margin-top: -12vh;
margin-left: -14vw;
margin-right: 0;
transform: rotate(90deg);
position: relative;
text-overflow: ellipsis;
white-space: nowrap;
}
.list-header .list-rotated {
}
.list-header .list-header-watch-icon {
padding-left: 10px;
@@ -121,11 +420,175 @@
color: #a6a6a6;
margin-right: 15px;
}
.list-header .list-header-uncollapse-left {
color: #a6a6a6;
/* List header collapse button styling */
.list-header .list-header-collapse-container {
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 10px;
flex: 1;
min-width: 0;
}
.list-header .list-header-uncollapse-right {
.list-header .js-collapse {
color: #a6a6a6;
display: inline-block;
vertical-align: middle;
padding: 5px 8px;
border: none;
border-radius: 0;
background-color: transparent;
cursor: pointer;
font-size: 18px;
line-height: 1;
min-width: 30px;
text-align: center;
flex-shrink: 0;
text-decoration: none;
margin: 0;
}
.list-header .js-collapse:hover {
background-color: transparent;
color: #333;
}
.list-header .list-header-collapse-container > div {
flex: 1;
min-width: 0;
}
.list.list-collapsed .list-header .js-collapse {
display: inline-block !important;
visibility: visible !important;
opacity: 1 !important;
}
/* Responsive adjustments for collapsed lists */
@media (min-width: 768px) {
.list.list-collapsed {
min-width: 60px;
max-width: 80px;
width: 60px;
min-height: 60vh;
height: 60vh;
}
.list.list-collapsed .list-header {
max-width: 60px;
margin: 0 auto;
min-height: 2.5vh !important;
height: auto !important;
}
.list.list-collapsed .list-header .list-rotated {
width: auto !important;
height: auto !important;
margin: 20px 0 0 0 !important;
position: relative !important;
}
.list.list-collapsed .list-header .list-rotated h2.list-header-name {
width: 100vh;
font-size: 12px;
height: 30px;
line-height: 1.2;
padding: 0;
margin: 0;
overflow: visible;
position: absolute;
left: 40px;
top: 50%;
transform: translateY(calc(-50% + 120px)) rotate(0deg);
text-align: center;
visibility: visible !important;
opacity: 1 !important;
display: block !important;
background-color: rgba(255, 255, 255, 0.95);
border: 1px solid #ddd;
color: #333;
z-index: 10;
}
.list.list-collapsed .list-header .js-collapse {
margin: 0 auto 20px auto;
}
}
@media (min-width: 1024px) {
.list.list-collapsed {
min-height: 60vh;
height: 60vh;
}
.list.list-collapsed .list-header {
min-height: 2.5vh !important;
height: auto !important;
}
.list.list-collapsed .list-header .list-rotated {
width: auto !important;
height: auto !important;
margin: 20px 0 0 0 !important;
position: relative !important;
}
.list.list-collapsed .list-header .list-rotated h2.list-header-name {
width: 100vh;
font-size: 12px;
height: 30px;
line-height: 1.2;
padding: 0;
margin: 0;
overflow: visible;
position: absolute;
left: 40px;
top: 50%;
transform: translateY(calc(-50% + 120px)) rotate(0deg);
text-align: center;
visibility: visible !important;
opacity: 1 !important;
display: block !important;
background-color: rgba(255, 255, 255, 0.95);
border: 1px solid #ddd;
color: #333;
z-index: 10;
}
.list.list-collapsed .list-header .js-collapse {
margin: 0 auto 20px auto;
}
}
@media (min-width: 1200px) {
.list.list-collapsed {
min-height: 60vh;
height: 60vh;
}
.list.list-collapsed .list-header {
min-height: 2.5vh !important;
height: auto !important;
}
.list.list-collapsed .list-header .list-rotated {
width: auto !important;
height: auto !important;
margin: 20px 0 0 0 !important;
position: relative !important;
}
.list.list-collapsed .list-header .list-rotated h2.list-header-name {
width: 100vh;
font-size: 12px;
height: 30px;
line-height: 1.2;
padding: 0;
margin: 0;
overflow: visible;
position: absolute;
left: 40px;
top: 50%;
transform: translateY(calc(-50% + 40px)) rotate(0deg);
text-align: left;
visibility: visible !important;
opacity: 1 !important;
display: block !important;
background-color: rgba(255, 255, 255, 0.95);
border: 1px solid #ddd;
color: #333;
z-index: 10;
}
.list.list-collapsed .list-header .js-collapse {
margin: 0 auto 20px auto;
}
}
.list-header .list-header-collapse {
color: #a6a6a6;
@@ -214,19 +677,166 @@
#js-list-width-edit .list-width-error {
display: none;
}
@media screen and (max-width: 800px) {
/* Mobile view styles - applied when isMiniScreen is true (iPhone, etc.) */
.mini-list.mobile-view {
flex: 0 0 60px;
height: auto;
width: 100vw;
max-width: 100vw;
min-width: 100vw;
border-left: 0px !important;
border-bottom: 1px solid #ccc;
display: block !important;
}
.list.mobile-view {
display: block !important;
flex-basis: auto;
width: 100vw;
max-width: 100vw;
min-width: 100vw;
border-left: 0px !important;
margin: 0 !important;
padding: 0 !important;
}
.list.mobile-view:first-child {
margin-left: 0px;
}
.list.mobile-view.ui-sortable-helper {
flex: 0 0 60px;
height: 60px;
width: 100vw;
max-width: 100vw;
border-left: 0px !important;
border-bottom: 1px solid #ccc;
display: block !important;
}
.list.mobile-view.ui-sortable-helper .list-header.ui-sortable-handle {
cursor: grabbing;
}
.list.mobile-view.placeholder {
flex: 0 0 60px;
height: 60px;
width: 100vw;
max-width: 100vw;
border-left: 0px !important;
border-bottom: 1px solid #ccc;
display: block !important;
}
.list.mobile-view .list-body {
padding: 15px 19px;
width: 100vw;
max-width: 100vw;
min-width: 100vw;
}
.list.mobile-view .list-header {
/*Updated padding values for mobile devices, this should fix text grouping issue*/
padding: 20px 0px 20px 0px;
border-bottom: 0px solid #e4e4e4;
min-height: 30px;
margin-top: 10px;
align-items: center;
width: 100vw;
max-width: 100vw;
min-width: 100vw;
/* Force grid layout for iPhone */
display: grid !important;
grid-template-columns: 30px 1fr auto auto !important;
gap: 10px !important;
}
.list.mobile-view .list-header .list-header-left-icon {
padding: 7px;
padding-right: 27px;
margin-top: 1px;
top: -7px;
left: -7px;
}
.list.mobile-view .list-header .list-header-menu-icon {
padding: 14px;
font-size: 40px !important;
text-align: center;
/* Force positioning for iPhone */
position: absolute !important;
right: 60px !important;
top: 50% !important;
transform: translateY(-50%) !important;
z-index: 10;
}
.list.mobile-view .list-header .list-header-handle {
padding: 14px;
font-size: 48px !important;
text-align: center;
/* Force positioning for iPhone */
position: absolute !important;
right: 10px !important;
top: 50% !important;
transform: translateY(-50%) !important;
z-index: 10;
}
.list.mobile-view .list-header .list-header-left-icon {
display: grid;
grid-row: 1/3;
grid-column: 1;
}
.list.mobile-view .list-header .list-header-name {
grid-row: 1;
grid-column: 2;
align-self: end;
font-size: 20px !important;
font-weight: bold;
line-height: 1.2;
padding-bottom: 2px;
}
.list.mobile-view .list-header .cardCount {
grid-row: 2;
grid-column: 2;
align-self: start;
text-align: left;
padding-left: 0;
margin-left: 0;
font-size: 16px !important;
line-height: 1.2;
}
.list.mobile-view .list-header .list-header-menu {
grid-row: 1/3;
grid-column: 3;
}
.list.mobile-view .list-header .list-header-menu-icon {
grid-row: 1/3;
grid-column: 3;
}
.list.mobile-view .list-header .list-header-handle {
grid-row: 1/3;
grid-column: 4;
}
.list.mobile-view .list-header .inlined-form {
grid-row: 1/3;
grid-column: 1/4;
}
.list.mobile-view .list-header .edit-controls {
align-items: initial;
}
@media screen and (max-width: 800px),
screen and (max-device-width: 932px) and (-webkit-min-device-pixel-ratio: 3) {
.mini-list {
flex: 0 0 60px;
height: auto;
width: 100%;
border-left: 0px;
width: 100vw;
max-width: 100vw;
min-width: 100vw;
border-left: 0px !important;
border-bottom: 1px solid #ccc;
display: block !important;
}
.list {
display: contents;
display: block !important;
flex-basis: auto;
width: 100%;
border-left: 0px;
width: 100vw;
max-width: 100vw;
min-width: 100vw;
border-left: 0px !important;
margin: 0 !important;
padding: 0 !important;
}
.list:first-child {
margin-left: 0px;
@@ -234,9 +844,11 @@
.list.ui-sortable-helper {
flex: 0 0 60px;
height: 60px;
width: 100%;
border-left: 0px;
width: 100vw;
max-width: 100vw;
border-left: 0px !important;
border-bottom: 1px solid #ccc;
display: block !important;
}
.list.ui-sortable-helper .list-header.ui-sortable-handle {
cursor: grabbing;
@@ -244,12 +856,17 @@
.list.placeholder {
flex: 0 0 60px;
height: 60px;
width: 100%;
border-left: 0px;
width: 100vw;
max-width: 100vw;
border-left: 0px !important;
border-bottom: 1px solid #ccc;
display: block !important;
}
.list-body {
padding: 15px 19px;
width: 100vw;
max-width: 100vw;
min-width: 100vw;
}
.list-header {
/*Updated padding values for mobile devices, this should fix text grouping issue*/
@@ -258,6 +875,9 @@
min-height: 30px;
margin-top: 10px;
align-items: center;
width: 100vw;
max-width: 100vw;
min-width: 100vw;
}
.list-header .list-header-left-icon {
padding: 7px;
@@ -267,24 +887,29 @@
left: -7px;
}
.list-header .list-header-menu-icon {
padding: 14px;
font-size: 40px;
text-align: center;
/* iOS Safari fallback positioning */
position: absolute;
padding: 7px;
right: 60px;
top: 50%;
transform: translateY(-50%);
right: 47px;
font-size: 20px;
}
.list-header .list-header-handle {
padding: 14px;
font-size: 48px;
text-align: center;
/* iOS Safari fallback positioning */
position: absolute;
padding: 7px;
right: 10px;
top: 50%;
transform: translateY(-50%);
right: 10px;
font-size: 24px;
}
.list-header {
display: grid;
grid-template-columns: 30px 5fr 1fr;
grid-template-columns: 30px 1fr auto auto;
gap: 10px;
}
.list-header .list-header-left-icon {
display: grid;
@@ -295,16 +920,30 @@
grid-row: 1;
grid-column: 2;
align-self: end;
font-size: 20px;
font-weight: bold;
line-height: 1.2;
padding-bottom: 2px;
}
.list-header .cardCount {
grid-row: 2;
grid-column: 2;
align-self: start;
font-size: 16px;
line-height: 1.2;
}
.list-header .list-header-menu {
grid-row: 1/3;
grid-column: 3;
}
.list-header .list-header-menu-icon {
grid-row: 1/3;
grid-column: 3;
}
.list-header .list-header-handle {
grid-row: 1/3;
grid-column: 4;
}
.list-header .inlined-form {
grid-row: 1/3;
grid-column: 1/4;
@@ -313,6 +952,152 @@
align-items: initial;
}
}
/* iPhone 12 Mini specific - fix icon positioning in stacked lists view */
@media screen and (device-width: 375px) and (device-height: 812px), /* iPhone 12 Mini exact */
screen and (max-width: 375px) and (max-height: 812px), /* iPhone 12 Mini viewport */
screen and (-webkit-min-device-pixel-ratio: 3) and (max-width: 375px) /* iPhone 12 Mini Retina */ {
.list.mobile-view .list-header {
/* Force grid layout for iPhone 12 Mini */
display: grid !important;
grid-template-columns: 30px 1fr auto auto !important;
gap: 10px !important;
align-items: center !important;
}
.list.mobile-view .list-header .list-header-menu-icon {
/* Remove absolute positioning for iPhone 12 Mini */
position: static !important;
right: auto !important;
top: auto !important;
transform: none !important;
/* Use grid positioning */
grid-row: 1/3 !important;
grid-column: 3 !important;
padding: 14px !important;
font-size: 40px !important;
text-align: center !important;
}
.list.mobile-view .list-header .list-header-handle {
/* Remove absolute positioning for iPhone 12 Mini */
position: static !important;
right: auto !important;
top: auto !important;
transform: none !important;
/* Use grid positioning */
grid-row: 1/3 !important;
grid-column: 4 !important;
padding: 14px !important;
font-size: 48px !important;
text-align: center !important;
}
.list.mobile-view .list-header .list-header-name {
grid-row: 1 !important;
grid-column: 2 !important;
align-self: end !important;
font-size: 20px !important;
font-weight: bold !important;
line-height: 1.2 !important;
padding-bottom: 2px !important;
}
.list.mobile-view .list-header .cardCount {
grid-row: 2 !important;
grid-column: 2 !important;
align-self: start !important;
text-align: left !important;
padding-left: 0 !important;
margin-left: 0 !important;
font-size: 16px !important;
line-height: 1.2 !important;
}
.list.mobile-view .list-header .list-header-left-icon {
display: grid !important;
grid-row: 1/3 !important;
grid-column: 1 !important;
}
}
/* iPhone device JavaScript detection fallback - fix icon positioning */
.iphone-device .list.mobile-view .list-header {
/* Force grid layout for iPhone devices */
display: grid !important;
grid-template-columns: 30px 1fr auto auto !important;
gap: 10px !important;
align-items: center !important;
}
.iphone-device .list.mobile-view .list-header .list-header-menu-icon {
/* Remove absolute positioning for iPhone devices */
position: static !important;
right: auto !important;
top: auto !important;
transform: none !important;
/* Use grid positioning */
grid-row: 1/3 !important;
grid-column: 3 !important;
padding: 14px !important;
font-size: 40px !important;
text-align: center !important;
}
.iphone-device .list.mobile-view .list-header .list-header-handle {
/* Remove absolute positioning for iPhone devices */
position: static !important;
right: auto !important;
top: auto !important;
transform: none !important;
/* Use grid positioning */
grid-row: 1/3 !important;
grid-column: 4 !important;
padding: 14px !important;
font-size: 48px !important;
text-align: center !important;
}
.iphone-device .list.mobile-view .list-header .list-header-name {
grid-row: 1 !important;
grid-column: 2 !important;
align-self: end !important;
font-size: 20px !important;
font-weight: bold !important;
line-height: 1.2 !important;
padding-bottom: 2px !important;
}
.iphone-device .list.mobile-view .list-header .cardCount {
grid-row: 2 !important;
grid-column: 2 !important;
align-self: start !important;
font-size: 16px !important;
line-height: 1.2 !important;
}
.iphone-device .list.mobile-view .list-header .list-header-left-icon {
display: grid !important;
grid-row: 1/3 !important;
grid-column: 1 !important;
}
/* Allow long list titles to expand on desktop (non-mobile, non-collapsed) */
.list:not(.mobile-view):not(.list-collapsed) .list-header {
overflow: visible !important;
}
.list:not(.mobile-view):not(.list-collapsed) .list-header .list-header-name {
/* Permit wrapping and full visibility */
white-space: normal !important;
overflow: visible !important;
text-overflow: clip !important;
display: inline-block !important;
/* Reserve space for right-side controls (menu, handle, count) */
max-width: calc(100% - 120px) !important;
/* Break long words to avoid overflow */
word-break: break-word !important;
}
.link-board-wrapper {
display: flex;
align-items: baseline;
+5 -3
View File
@@ -1,10 +1,12 @@
template(name='list')
.list.js-list(id="js-list-{{_id}}"
style="{{#unless collapsed}}min-width:{{listWidth}}px;max-width:{{listConstraint}}px;{{/unless}}"
class="{{#if collapsed}}list-collapsed{{/if}} {{#if autoWidth}}list-auto-width{{/if}}")
class="{{#if collapsed}}list-collapsed{{/if}} {{#if autoWidth}}list-auto-width{{/if}} {{#if isMiniScreen}}mobile-view{{/if}}")
+listHeader
+listBody
unless collapsed
+listBody
.list-resize-handle.js-list-resize-handle.nodragscroll
template(name='miniList')
a.mini-list.js-select-list.js-list(id="js-list-{{_id}}")
a.mini-list.js-select-list.js-list(id="js-list-{{_id}}" class="{{#if isMiniScreen}}mobile-view{{/if}}")
+listHeader
+258 -12
View File
@@ -24,6 +24,9 @@ BlazeComponent.extendComponent({
onRendered() {
const boardComponent = this.parentComponent().parentComponent();
// Initialize list resize functionality immediately
this.initializeListResize();
const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)';
const $cards = this.$('.js-minicards');
@@ -147,17 +150,13 @@ BlazeComponent.extendComponent({
});
this.autorun(() => {
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
$cards.sortable({
handle: '.handle',
});
} else {
$cards.sortable({
handle: '.minicard',
});
}
if ($cards.data('uiSortable') || $cards.data('sortable')) {
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
$cards.sortable('option', 'handle', '.handle');
} else {
$cards.sortable('option', 'handle', '.minicard');
}
$cards.sortable(
'option',
'disabled',
@@ -198,22 +197,269 @@ BlazeComponent.extendComponent({
listWidth() {
const user = ReactiveCache.getCurrentUser();
const list = Template.currentData();
return user.getListWidth(list.boardId, list._id);
if (!list) return 270; // Return default width if list is not available
if (user) {
// For logged-in users, get from user profile
return user.getListWidthFromStorage(list.boardId, list._id);
} else {
// For non-logged-in users, get from localStorage
try {
const stored = localStorage.getItem('wekan-list-widths');
if (stored) {
const widths = JSON.parse(stored);
if (widths[list.boardId] && widths[list.boardId][list._id]) {
return widths[list.boardId][list._id];
}
}
} catch (e) {
console.warn('Error reading list width from localStorage:', e);
}
return 270; // Return default width if not found
}
},
listConstraint() {
const user = ReactiveCache.getCurrentUser();
const list = Template.currentData();
return user.getListConstraint(list.boardId, list._id);
if (!list) return 550; // Return default constraint if list is not available
if (user) {
// For logged-in users, get from user profile
return user.getListConstraintFromStorage(list.boardId, list._id);
} else {
// For non-logged-in users, get from localStorage
try {
const stored = localStorage.getItem('wekan-list-constraints');
if (stored) {
const constraints = JSON.parse(stored);
if (constraints[list.boardId] && constraints[list.boardId][list._id]) {
return constraints[list.boardId][list._id];
}
}
} catch (e) {
console.warn('Error reading list constraint from localStorage:', e);
}
return 550; // Return default constraint if not found
}
},
autoWidth() {
const user = ReactiveCache.getCurrentUser();
const list = Template.currentData();
if (!user) {
// For non-logged-in users, auto-width is disabled
return false;
}
return user.isAutoWidth(list.boardId);
},
initializeListResize() {
// Check if we're still in a valid template context
if (!Template.currentData()) {
console.warn('No current template data available for list resize initialization');
return;
}
const list = Template.currentData();
const $list = this.$('.js-list');
const $resizeHandle = this.$('.js-list-resize-handle');
// Check if elements exist
if (!$list.length || !$resizeHandle.length) {
console.warn('List or resize handle not found, retrying in 100ms');
Meteor.setTimeout(() => {
if (!this.isDestroyed) {
this.initializeListResize();
}
}, 100);
return;
}
// Only enable resize for non-collapsed, non-auto-width lists
const isAutoWidth = this.autoWidth();
const isCollapsed = Utils.getListCollapseState(list);
if (isCollapsed || isAutoWidth) {
$resizeHandle.hide();
return;
}
let isResizing = false;
let startX = 0;
let startWidth = 0;
let minWidth = 100; // Minimum width as defined in the existing code
let maxWidth = this.listConstraint() || 1000; // Use constraint as max width
let listConstraint = this.listConstraint(); // Store constraint value for use in event handlers
const component = this; // Store reference to component for use in event handlers
const startResize = (e) => {
isResizing = true;
startX = e.pageX || e.originalEvent.touches[0].pageX;
startWidth = $list.outerWidth();
// Add visual feedback
$list.addClass('list-resizing');
$('body').addClass('list-resizing-active');
// Prevent text selection during resize
$('body').css('user-select', 'none');
e.preventDefault();
e.stopPropagation();
};
const doResize = (e) => {
if (!isResizing) {
return;
}
const currentX = e.pageX || e.originalEvent.touches[0].pageX;
const deltaX = currentX - startX;
const newWidth = Math.max(minWidth, Math.min(maxWidth, startWidth + deltaX));
// Apply the new width immediately for real-time feedback
$list[0].style.setProperty('--list-width', `${newWidth}px`);
$list[0].style.setProperty('width', `${newWidth}px`);
$list[0].style.setProperty('min-width', `${newWidth}px`);
$list[0].style.setProperty('max-width', `${newWidth}px`);
$list[0].style.setProperty('flex', 'none');
$list[0].style.setProperty('flex-basis', 'auto');
$list[0].style.setProperty('flex-grow', '0');
$list[0].style.setProperty('flex-shrink', '0');
e.preventDefault();
e.stopPropagation();
};
const stopResize = (e) => {
if (!isResizing) return;
isResizing = false;
// Calculate final width
const currentX = e.pageX || e.originalEvent.touches[0].pageX;
const deltaX = currentX - startX;
const finalWidth = Math.max(minWidth, Math.min(maxWidth, startWidth + deltaX));
// Ensure the final width is applied
$list[0].style.setProperty('--list-width', `${finalWidth}px`);
$list[0].style.setProperty('width', `${finalWidth}px`);
$list[0].style.setProperty('min-width', `${finalWidth}px`);
$list[0].style.setProperty('max-width', `${finalWidth}px`);
$list[0].style.setProperty('flex', 'none');
$list[0].style.setProperty('flex-basis', 'auto');
$list[0].style.setProperty('flex-grow', '0');
$list[0].style.setProperty('flex-shrink', '0');
// Remove visual feedback but keep the width
$list.removeClass('list-resizing');
$('body').removeClass('list-resizing-active');
$('body').css('user-select', '');
// Keep the CSS custom property for persistent width
// The CSS custom property will remain on the element to maintain the width
// Save the new width using the existing system
const boardId = list.boardId;
const listId = list._id;
// Use the new storage method that handles both logged-in and non-logged-in users
if (process.env.DEBUG === 'true') {
}
const currentUser = ReactiveCache.getCurrentUser();
if (currentUser) {
// For logged-in users, use server method
Meteor.call('applyListWidthToStorage', boardId, listId, finalWidth, listConstraint, (error, result) => {
if (error) {
console.error('Error saving list width:', error);
} else {
if (process.env.DEBUG === 'true') {
}
}
});
} else {
// For non-logged-in users, save to localStorage directly
try {
// Save list width
const storedWidths = localStorage.getItem('wekan-list-widths');
let widths = storedWidths ? JSON.parse(storedWidths) : {};
if (!widths[boardId]) {
widths[boardId] = {};
}
widths[boardId][listId] = finalWidth;
localStorage.setItem('wekan-list-widths', JSON.stringify(widths));
// Save list constraint
const storedConstraints = localStorage.getItem('wekan-list-constraints');
let constraints = storedConstraints ? JSON.parse(storedConstraints) : {};
if (!constraints[boardId]) {
constraints[boardId] = {};
}
constraints[boardId][listId] = listConstraint;
localStorage.setItem('wekan-list-constraints', JSON.stringify(constraints));
if (process.env.DEBUG === 'true') {
}
} catch (e) {
console.warn('Error saving list width/constraint to localStorage:', e);
}
}
e.preventDefault();
};
// Mouse events
$resizeHandle.on('mousedown', startResize);
$(document).on('mousemove', doResize);
$(document).on('mouseup', stopResize);
// Touch events for mobile
$resizeHandle.on('touchstart', startResize, { passive: false });
$(document).on('touchmove', doResize, { passive: false });
$(document).on('touchend', stopResize, { passive: false });
// Prevent dragscroll interference
$resizeHandle.on('mousedown', (e) => {
e.stopPropagation();
});
// Reactively update resize handle visibility when auto-width or collapse changes
component.autorun(() => {
const collapsed = Utils.getListCollapseState(list);
if (component.autoWidth() || collapsed) {
$resizeHandle.hide();
} else {
$resizeHandle.show();
}
});
// Clean up on component destruction
component.onDestroyed(() => {
$(document).off('mousemove', doResize);
$(document).off('mouseup', stopResize);
$(document).off('touchmove', doResize);
$(document).off('touchend', stopResize);
});
},
}).register('list');
Template.list.helpers({
collapsed() {
return Utils.getListCollapseState(this);
},
});
Template.miniList.events({
'click .js-select-list'() {
const listId = this._id;
+5 -2
View File
@@ -32,7 +32,7 @@ template(name="listBody")
+addCardForm(listId=_id position="bottom")
else
a.open-minicard-composer.js-card-composer.js-open-inlined-form(title="{{_ 'add-card-to-bottom-of-list'}}")
i.fa.fa-plus
| ➕
template(name="spinnerList")
.sk-spinner.sk-spinner-list(
@@ -54,7 +54,7 @@ template(name="addCardForm")
.add-controls.clearfix
button.primary.confirm(type="submit") {{_ 'add'}}
a.fa.fa-times-thin.js-close-inlined-form
a.js-close-inlined-form | ❌
.add-controls.clearfix
unless currentBoard.isTemplatesBoard
unless currentBoard.isTemplateBoard
@@ -85,16 +85,19 @@ template(name="linkCardPopup")
label {{_ 'swimlanes'}}:
select.js-select-swimlanes
option(value="") {{_ 'custom-field-dropdown-none'}}
each swimlanes
option(value="{{_id}}") {{isTitleDefault title}}
label {{_ 'lists'}}:
select.js-select-lists
option(value="") {{_ 'custom-field-dropdown-none'}}
each lists
option(value="{{_id}}") {{isTitleDefault title}}
label {{_ 'cards'}}:
select.js-select-cards
option(value="") {{_ 'custom-field-dropdown-none'}}
each cards
option(value="{{getRealId}}") {{getTitle}}
+88 -22
View File
@@ -16,11 +16,50 @@ BlazeComponent.extendComponent({
},
customFieldsSum() {
const ret = ReactiveCache.getCustomFields({
boardIds: { $in: [Session.get('currentBoard')] },
const list = Template.currentData();
if (!list) return [];
const boardId = Session.get('currentBoard');
const fields = ReactiveCache.getCustomFields({
boardIds: { $in: [boardId] },
showSumAtTopOfList: true,
});
return ret;
if (!fields || !fields.length) return [];
const cards = ReactiveCache.getCards({
listId: list._id,
archived: false,
});
const result = fields.map(field => {
let sum = 0;
if (cards && cards.length) {
cards.forEach(card => {
const cfs = (card.customFields || []);
const cf = cfs.find(f => f && f._id === field._id);
if (!cf || cf.value === null || cf.value === undefined) return;
let v = cf.value;
if (typeof v === 'string') {
// try to parse string numbers, accept comma decimal
const parsed = parseFloat(v.replace(',', '.'));
if (isNaN(parsed)) return;
v = parsed;
}
if (typeof v === 'number' && isFinite(v)) {
sum += v;
}
});
}
return {
_id: field._id,
name: field.name,
type: field.type,
settings: field.settings || {},
value: sum,
};
});
return result;
},
openForm(options) {
@@ -254,6 +293,22 @@ BlazeComponent.extendComponent({
},
}).register('listBody');
// Helpers for listBody template context
Template.listBody.helpers({
formattedCurrencyCustomFieldValue(val) {
// `this` is the custom field sum object from customFieldsSum each-iteration
const field = this || {};
const code = (field.settings && field.settings.currencyCode) || 'USD';
try {
const n = typeof val === 'number' ? val : parseFloat(val);
if (!isFinite(n)) return val;
return new Intl.NumberFormat(undefined, { style: 'currency', currency: code }).format(n);
} catch (e) {
return `${code} ${val}`;
}
},
});
function toggleValueInReactiveArray(reactiveValue, value) {
const array = reactiveValue.get();
const valueIndex = array.indexOf(value);
@@ -314,7 +369,8 @@ BlazeComponent.extendComponent({
// Pressing Tab should open the form of the next column, and Maj+Tab go
// in the reverse order
} else if (evt.keyCode === 9) {
evt.preventDefault();
// Prevent custom focus movement on Tab key for accessibility
// evt.preventDefault();
const isReverse = evt.shiftKey;
const list = $(`#js-list-${this.data().listId}`);
const listSelector = '.js-list:not(.js-list-composer)';
@@ -413,13 +469,16 @@ BlazeComponent.extendComponent({
// or `Enter` to validation the auto-completion. We also need to stop the
// event propagation to prevent the card from submitting (on `Enter`) or
// going on the next column (on `Tab`).
/*
onKeydown(evt, commands) {
if (evt.keyCode === 9 || evt.keyCode === 13) {
evt.stopPropagation();
return commands.KEY_ENTER;
}
// Prevent custom focus movement on Tab key for accessibility
// if (evt.keyCode === 9 || evt.keyCode === 13) {
// evt.stopPropagation();
// return commands.KEY_ENTER;
//}
return null;
},
*/
},
);
},
@@ -468,6 +527,14 @@ BlazeComponent.extendComponent({
if (!this.selectedBoardId.get()) {
return [];
}
const board = ReactiveCache.getBoard(this.selectedBoardId.get());
if (!board) {
return [];
}
// Ensure default swimlane exists
board.getDefaultSwimline();
const swimlanes = ReactiveCache.getSwimlanes(
{
boardId: this.selectedBoardId.get()
@@ -475,8 +542,6 @@ BlazeComponent.extendComponent({
{
sort: { sort: 1 },
});
if (swimlanes.length)
this.selectedSwimlaneId.set(swimlanes[0]._id);
return swimlanes;
},
@@ -491,7 +556,6 @@ BlazeComponent.extendComponent({
{
sort: { sort: 1 },
});
if (lists.length) this.selectedListId.set(lists[0]._id);
return lists;
},
@@ -500,19 +564,17 @@ BlazeComponent.extendComponent({
return [];
}
const ownCardsIds = this.board.cards().map(card => card.getRealId());
const ret = ReactiveCache.getCards(
{
boardId: this.selectedBoardId.get(),
swimlaneId: this.selectedSwimlaneId.get(),
listId: this.selectedListId.get(),
const selector = {
archived: false,
linkedId: { $nin: ownCardsIds },
_id: { $nin: ownCardsIds },
type: { $nin: ['template-card'] },
},
{
sort: { sort: 1 },
});
};
if (this.selectedBoardId.get()) selector.boardId = this.selectedBoardId.get();
if (this.selectedSwimlaneId.get()) selector.swimlaneId = this.selectedSwimlaneId.get();
if (this.selectedListId.get()) selector.listId = this.selectedListId.get();
const ret = ReactiveCache.getCards(selector, { sort: { sort: 1 } });
return ret;
},
@@ -533,8 +595,12 @@ BlazeComponent.extendComponent({
return [
{
'change .js-select-boards'(evt) {
subManager.subscribe('board', $(evt.currentTarget).val(), false);
this.selectedBoardId.set($(evt.currentTarget).val());
const val = $(evt.currentTarget).val();
subManager.subscribe('board', val, false);
// Clear selections to allow linking only board or re-choose swimlane/list
this.selectedSwimlaneId.set('');
this.selectedListId.set('');
this.selectedBoardId.set(val);
},
'change .js-select-swimlanes'(evt) {
this.selectedSwimlaneId.set($(evt.currentTarget).val());
+55 -44
View File
@@ -7,12 +7,10 @@ template(name="listHeader")
else
if isMiniScreen
if currentList
a.list-header-left-icon.fa.fa-angle-left.js-unselect-list
a.list-header-left-icon.js-unselect-list
| ◀️
else
if collapsed
a.js-collapse(title="{{_ 'uncollapse'}}")
i.fa.fa-arrow-left.list-header-uncollapse-left
i.fa.fa-arrow-right.list-header-uncollapse-right
if showCardsCountForList cards.length
br
span.cardCount {{cardsCount}}
@@ -28,111 +26,124 @@ template(name="listHeader")
|/#{wipLimit.value})
if showCardsCountForList cards.length
span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}}
if hasNumberFieldsSum
| &nbsp;
span.list-sum-badge(title="{{_ 'sum-of-number-fields'}}") ∑ {{numberFieldsSum}}
else
div(class="{{#if collapsed}}list-rotated{{/if}}")
h2.list-header-name(
title="{{ moment modifiedAt 'LLL' }}"
class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}")
+viewer
= title
if wipLimit.enabled
|&nbsp;(
span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.length}}
|/#{wipLimit.value})
div.list-header-collapse-container
a.list-collapse-indicator.js-collapse(title="{{_ 'collapse'}}")
if collapsed
| ▶
else
| 🔽
div(class="{{#if collapsed}}list-rotated{{/if}}")
h2.list-header-name(
title="{{ moment modifiedAt 'LLL' }}"
class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}")
+viewer
= title
if wipLimit.enabled
|&nbsp;(
span(class="{{#if exceededWipLimit}}highlight{{/if}}") {{cards.length}}
|/#{wipLimit.value})
unless collapsed
if showCardsCountForList cards.length
span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}}
if hasNumberFieldsSum
| &nbsp;
span.list-sum-badge(title="{{_ 'sum-of-number-fields'}}") ∑ {{numberFieldsSum}}
if isMiniScreen
if currentList
if isWatching
i.list-header-watch-icon.fa.fa-eye
i.list-header-watch-icon | 👁️
div.list-header-menu
unless currentUser.isCommentOnly
if canSeeAddCard
a.js-add-card.fa.fa-plus.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}")
a.fa.fa-navicon.js-open-list-menu(title="{{_ 'listActionPopup-title'}}")
a.js-add-card.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}") ➕
a.js-open-list-menu(title="{{_ 'listActionPopup-title'}}") ☰
else
a.list-header-menu-icon.fa.fa-angle-right.js-select-list
a.list-header-handle.handle.fa.fa-arrows.js-list-handle
a.list-header-menu-icon.js-select-list ▶️
unless currentUser.isWorker
a.list-header-handle.handle.js-list-handle ↕️
else if currentUser.isBoardMember
if currentUser.isBoardMember
unless currentUser.isCommentOnly
unless currentUser.isWorker
a.list-header-handle.handle.js-list-handle ↕️
if isWatching
i.list-header-watch-icon.fa.fa-eye
i.list-header-watch-icon | 👁️
unless collapsed
div.list-header-menu
unless currentUser.isCommentOnly
//if isBoardAdmin
// a.fa.js-list-star.list-header-plus-top(class="fa-star{{#unless starred}}-o{{/unless}}")
if canSeeAddCard
a.js-add-card.fa.fa-plus.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}")
a.js-collapse(title="{{_ 'collapse'}}")
i.fa.fa-arrow-right.list-header-collapse-right
i.fa.fa-arrow-left.list-header-collapse-left
a.fa.fa-navicon.js-open-list-menu(title="{{_ 'listActionPopup-title'}}")
if currentUser.isBoardAdmin
if isTouchScreenOrShowDesktopDragHandles
a.list-header-handle.handle.fa.fa-arrows.js-list-handle
a.js-add-card.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}") ➕
a.js-open-list-menu(title="{{_ 'listActionPopup-title'}}") ☰
template(name="editListTitleForm")
.list-composer
input.list-name-input.full-line(type="text" value=title autofocus)
.edit-controls.clearfix
button.primary.confirm(type="submit") {{_ 'save'}}
a.fa.fa-times-thin.js-close-inlined-form
a.js-close-inlined-form
| ❌
template(name="listActionPopup")
ul.pop-over-list
li
a.js-add-card.list-header-plus-bottom
i.fa.fa-plus
i.fa.fa-arrow-down
| ➕
| ⬇️
| {{_ 'add-card-to-bottom-of-list'}}
hr
ul.pop-over-list
li
a.js-set-list-width
i.fa.fa-arrows-h
| ↔️
| {{_ 'set-list-width'}}
ul.pop-over-list
li
a.js-toggle-watch-list
if isWatching
i.fa.fa-eye
| 👁️
| {{_ 'unwatch'}}
else
i.fa.fa-eye-slash
| 🙈
| {{_ 'watch'}}
unless currentUser.isCommentOnly
unless currentUser.isWorker
ul.pop-over-list
li
a.js-set-color-list
i.fa.fa-paint-brush
| 🎨
| {{_ 'set-color-list'}}
ul.pop-over-list
if cards.length
li
a.js-select-cards
i.fa.fa-check-square
| ☑️
| {{_ 'list-select-cards'}}
if currentUser.isBoardAdmin
ul.pop-over-list
li
a.js-set-wip-limit
i.fa.fa-ban
| 🚫
| {{#if isWipLimitEnabled }}{{_ 'edit-wip-limit'}}{{else}}{{_ 'setWipLimitPopup-title'}}{{/if}}
unless currentUser.isWorker
hr
ul.pop-over-list
li
a.js-close-list
i.fa.fa-arrow-right
i.fa.fa-archive
| ➡️
| 📦
| {{_ 'archive-list'}}
hr
ul.pop-over-list
li
a.js-more
i.fa.fa-link
| 🔗
| {{_ 'listMorePopup-title'}}
template(name="boardLists")
@@ -149,7 +160,7 @@ template(name="listMorePopup")
span.clearfix
span {{_ 'link-list'}}
= ' '
i.fa.colorful(class="{{#if board.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
| {{#if board.isPublic}}🌐{{else}}🔒{{/if}}
input.inline-input(type="text" readonly value="{{ rootUrl }}")
| {{_ 'added'}}
span.date(title=list.createdAt) {{ moment createdAt 'LLL' }}
@@ -169,7 +180,7 @@ template(name="setWipLimitPopup")
ul.pop-over-list
li: a.js-enable-wip-limit {{_ 'enable-wip-limit'}}
if isWipLimitEnabled
i.fa.fa-check
| ✅
if isWipLimitEnabled
p
input.wip-limit-value(type="number" value="{{ wipLimitValue }}" min="1" max="99")
@@ -197,7 +208,7 @@ template(name="setListWidthPopup")
br
a.js-auto-width-board(
title="{{#if isAutoWidth}}{{_ 'click-to-disable-auto-width'}}{{else}}{{_ 'click-to-enable-auto-width'}}{{/if}}")
i.fa(class="fa-solid fa-{{#if isAutoWidth}}compress{{else}}expand{{/if}}")
| {{#if isAutoWidth}}🗜️{{else}}📏{{/if}}
span {{_ 'auto-list-width'}}
template(name="listWidthErrorPopup")
@@ -211,6 +222,6 @@ template(name="setListColorPopup")
// note: we use the swimlane palette to have more than just the border
span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
if(isSelected color)
i.fa.fa-check
| ✅
button.primary.confirm.js-submit {{_ 'save'}}
button.js-remove-color.negate.wide.right {{_ 'unset-color'}}
+46 -4
View File
@@ -34,13 +34,14 @@ BlazeComponent.extendComponent({
},
collapsed(check = undefined) {
const list = Template.currentData();
const status = list.isCollapsed();
const status = Utils.getListCollapseState(list);
if (check === undefined) {
// just check
return status;
} else {
list.collapse(!status);
return !status;
const next = typeof check === 'boolean' ? check : !status;
Utils.setListCollapseState(list, next);
return next;
}
},
editTitle(event) {
@@ -142,7 +143,48 @@ BlazeComponent.extendComponent({
Template.listHeader.helpers({
isBoardAdmin() {
return ReactiveCache.getCurrentUser().isBoardAdmin();
}
},
numberFieldsSum() {
const list = Template.currentData();
if (!list) return 0;
const boardId = Session.get('currentBoard');
const fields = ReactiveCache.getCustomFields({
boardIds: { $in: [boardId] },
showSumAtTopOfList: true,
type: 'number',
});
if (!fields || !fields.length) return 0;
const cards = ReactiveCache.getCards({ listId: list._id, archived: false });
let total = 0;
if (cards && cards.length) {
cards.forEach(card => {
const cfs = (card.customFields || []);
fields.forEach(field => {
const cf = cfs.find(f => f && f._id === field._id);
if (!cf || cf.value === null || cf.value === undefined) return;
let v = cf.value;
if (typeof v === 'string') {
const parsed = parseFloat(v.replace(',', '.'));
if (isNaN(parsed)) return;
v = parsed;
}
if (typeof v === 'number' && isFinite(v)) {
total += v;
}
});
});
}
return total;
},
hasNumberFieldsSum() {
const boardId = Session.get('currentBoard');
const fields = ReactiveCache.getCustomFields({
boardIds: { $in: [boardId] },
showSumAtTopOfList: true,
type: 'number',
});
return !!(fields && fields.length);
},
});
Template.listActionPopup.helpers({
+1 -1
View File
@@ -3,6 +3,6 @@ template(name="minilist")
class="minicard-{{colorClass}}")
.minicard-title
.handle
.fa.fa-arrows
span.drag-handle(title="{{_ 'dragList'}}") ↕️
+viewer
= title
+43 -18
View File
@@ -1,27 +1,27 @@
.my-cards-board-wrapper {
border-radius: 0 0 4px 4px;
min-width: 400px;
margin-bottom: 2rem;
border-radius: 0 0 0.5vw 0.5vw;
min-width: min(400px, 52vw);
margin-bottom: 2.5vh;
margin-right: auto;
margin-left: auto;
border-width: 2px;
border-width: 0.3vw;
border-style: solid;
border-color: #a2a2a2;
}
.my-cards-board-title {
font-size: 1.4rem;
font-size: clamp(1.2rem, 3vw, 1.6rem);
font-weight: bold;
padding: 0.5rem;
padding: 0.7vh 0.7vw;
background-color: #808080;
color: #fff;
}
.my-cards-swimlane-title {
font-size: 1.1rem;
font-size: clamp(1rem, 2.5vw, 1.3rem);
font-weight: bold;
padding: 0.5rem;
padding-bottom: 0.4rem;
padding: 0.7vh 0.7vw;
padding-bottom: 0.5vh;
margin-top: 0;
margin-bottom: 0.5rem;
margin-bottom: 0.7vh;
text-align: center;
}
.swimlane-default-color {
@@ -29,23 +29,23 @@
}
.my-cards-list-title {
font-weight: bold;
font-size: 1.1rem;
font-size: clamp(1rem, 2.5vw, 1.3rem);
text-align: center;
margin-bottom: 0.7rem;
margin-bottom: 0.9vh;
}
.my-cards-list-wrapper {
margin: 1rem;
border-radius: 5px;
margin: 1.3vh 1.3vw;
border-radius: 0.7vw;
display: inline-grid;
min-width: 250px;
max-width: 350px;
min-width: min(250px, 32vw);
max-width: min(350px, 45vw);
}
.my-cards-card-wrapper {
margin-top: 0;
margin-bottom: 10px;
margin-bottom: 1.3vh;
}
.my-cards-dueat-list-wrapper {
max-width: 500px;
max-width: min(500px, 65vw);
margin-right: auto;
margin-left: auto;
}
@@ -72,3 +72,28 @@
border-radius: 5px;
margin-right: 5px;
}
/* Accessibility page styles */
.accessibility-page {
padding: 20px;
max-width: 800px;
margin: 0 auto;
}
.accessibility-page h2 {
font-size: 24px;
margin-bottom: 20px;
color: #4d4d4d;
}
.accessibility-page-content {
background-color: #fff;
padding: 20px;
border-radius: 3px;
box-shadow: 0 1px 2px rgba(0,0,0,0.15);
}
.accessibility-page-content p {
margin-bottom: 16px;
line-height: 1.6;
}
+12 -2
View File
@@ -1,8 +1,18 @@
template(name="accessibilityHeaderBar")
if currentUser
h1
| {{_ 'accessibility-title'}}
if isAccessibilityEnabled
= accessibilityTitle
else
| {{_ 'accessibility'}}
template(name="accessibility")
if currentUser
| {{_ 'accessibility-content'}}
.accessibility-page
if isAccessibilityEnabled
.accessibility-page-content
+viewer
| {{accessibilityContent}}
else
.accessibility-page-content
| {{_ 'accessibility-info-not-added-yet'}}
+27
View File
@@ -1,11 +1,38 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
// Shared helpers for both accessibility templates
const accessibilityHelpers = {
accessibilityTitle() {
const setting = AccessibilitySettings.findOne({});
return setting && setting.title ? setting.title : TAPi18n.__('accessibility-title');
},
accessibilityContent() {
const setting = AccessibilitySettings.findOne({});
return setting && setting.body ? setting.body : TAPi18n.__('accessibility-content');
},
isAccessibilityEnabled() {
const setting = AccessibilitySettings.findOne({});
return setting && setting.enabled;
}
};
// Main accessibility page component
BlazeComponent.extendComponent({
onCreated() {
this.error = new ReactiveVar('');
this.loading = new ReactiveVar(false);
Meteor.subscribe('setting');
Meteor.subscribe('accessibilitySettings');
},
...accessibilityHelpers
}).register('accessibility');
// Header bar component
BlazeComponent.extendComponent({
onCreated() {
Meteor.subscribe('accessibilitySettings');
},
...accessibilityHelpers
}).register('accessibilityHeaderBar');
+29
View File
@@ -0,0 +1,29 @@
template(name="bookmarks")
.panel
h2 {{_ 'bookmarks'}}
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'}}")
| ⭐
else
p {{_ 'no-starred-boards'}}
else
p {{_ 'please-sign-in'}}
// Desktop popup
template(name="bookmarksPopup")
ul.pop-over-list
if hasStarredBoards
each starredBoards
li
a(href="{{pathFor 'board' id=_id slug=slug}}")
| ⭐
| #{title}
a.js-toggle-star.right(title="{{_ 'star-board-short-unstar'}}")
| ⭐
else
li {{_ 'no-starred-boards'}}
+55
View File
@@ -0,0 +1,55 @@
Template.bookmarks.helpers({
hasStarredBoards() {
const user = ReactiveCache.getCurrentUser();
if (!user) return false;
const { starredBoards = [] } = user.profile || {};
return Array.isArray(starredBoards) && starredBoards.length > 0;
},
starredBoards() {
const user = ReactiveCache.getCurrentUser();
if (!user) return [];
const { starredBoards = [] } = user.profile || {};
if (!Array.isArray(starredBoards) || starredBoards.length === 0) return [];
return Boards.find({ _id: { $in: starredBoards } }, { sort: { sort: 1 } });
},
});
Template.bookmarks.events({
'click .js-toggle-star'(e) {
e.preventDefault();
const boardId = this._id;
const user = ReactiveCache.getCurrentUser();
if (user && boardId) {
user.toggleBoardStar(boardId);
}
},
});
Template.bookmarksPopup.helpers({
hasStarredBoards() {
const user = ReactiveCache.getCurrentUser();
if (!user) return false;
const { starredBoards = [] } = user.profile || {};
return Array.isArray(starredBoards) && starredBoards.length > 0;
},
starredBoards() {
const user = ReactiveCache.getCurrentUser();
if (!user) return [];
const { starredBoards = [] } = user.profile || {};
if (!Array.isArray(starredBoards) || starredBoards.length === 0) return [];
return Boards.find({ _id: { $in: starredBoards } }, { sort: { sort: 1 } });
},
});
Template.bookmarksPopup.events({
'click .js-toggle-star'(e) {
e.preventDefault();
const boardId = this._id;
const user = ReactiveCache.getCurrentUser();
if (user && boardId) {
user.toggleBoardStar(boardId);
}
},
});
+5 -5
View File
@@ -1,15 +1,15 @@
.broken-cards-card-wrapper {
margin-top: 0;
margin-bottom: 10px;
border-width: 3px !important;
margin-bottom: 1.3vh;
border-width: 0.4vw !important;
border-color: #808080 !important;
border-style: solid;
border-radius: 5px;
padding: 1.5rem;
border-radius: 0.7vw;
padding: 2vh 2vw;
background-color: #fff;
}
.broken-cards-wrapper {
max-width: 500px;
max-width: min(500px, 65vw);
margin-right: auto;
margin-left: auto;
}
+1 -1
View File
@@ -1,5 +1,5 @@
.due-cards-dueat-list-wrapper {
max-width: 500px;
max-width: min(500px, 65vw);
margin-right: auto;
margin-left: auto;
}
+19 -10
View File
@@ -1,23 +1,23 @@
template(name="dueCardsHeaderBar")
if currentUser
h1
i.fa.fa-calendar
| 📅
| {{_ 'dueCards-title'}}
.board-header-btns.left
a.board-header-btn.js-due-cards-view-change(title="{{_ 'dueCardsViewChange-title'}}")
i.fa.fa-caret-down
| ▼
if $eq dueCardsView 'me'
i.fa.fa-user
| 👤
| {{_ 'dueCardsViewChange-choice-me'}}
if $eq dueCardsView 'all'
i.fa.fa-users
| 👥
| {{_ 'dueCardsViewChange-choice-all'}}
template(name="dueCardsModalTitle")
if currentUser
h2
i.fa.fa-keyboard-o
| ⌨️
| {{_ 'dueCards-title'}}
template(name="dueCards")
@@ -32,7 +32,16 @@ template(name="dueCards")
span.global-search-error-messages
= msg
else
+resultsPaged(this)
.due-cards-results-header
h1
= resultsText
each card in dueCardsList
+resultCard(card)
else
.global-search-results-list-wrapper
.no-results
h3 {{_ 'dueCards-noResults-title'}}
p {{_ 'dueCards-noResults-description'}}
template(name="dueCardsViewChangePopup")
if currentUser
@@ -40,18 +49,18 @@ template(name="dueCardsViewChangePopup")
li
with "dueCardsViewChange-choice-me"
a.js-due-cards-view-me
i.fa.fa-user.colorful
| 👤
| {{_ 'dueCardsViewChange-choice-me'}}
if $eq Utils.dueCardsView "me"
i.fa.fa-check
| ✅
hr
li
with "dueCardsViewChange-choice-all"
a.js-due-cards-view-all
i.fa.fa-users.colorful
| 👥
| {{_ 'dueCardsViewChange-choice-all'}}
span.sub-name
+viewer
| {{_ 'dueCardsViewChange-choice-all-description' }}
if $eq Utils.dueCardsView "all"
i.fa.fa-check
| ✅
+199 -42
View File
@@ -1,13 +1,6 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { CardSearchPagedComponent } from '../../lib/cardSearch';
import {
OPERATOR_HAS,
OPERATOR_SORT,
OPERATOR_USER,
ORDER_ASCENDING,
PREDICATE_DUE_AT,
} from '../../../config/search-const';
import { QueryParams } from '../../../config/query-classes';
import { BlazeComponent } from 'meteor/peerlibrary:blaze-components';
import { TAPi18n } from '/imports/i18n';
// const subManager = new SubsManager();
@@ -15,7 +8,7 @@ BlazeComponent.extendComponent({
dueCardsView() {
// eslint-disable-next-line no-console
// console.log('sort:', Utils.dueCardsView());
return Utils.dueCardsView();
return Utils && Utils.dueCardsView ? Utils.dueCardsView() : 'me';
},
events() {
@@ -31,6 +24,47 @@ Template.dueCards.helpers({
userId() {
return Meteor.userId();
},
dueCardsList() {
const component = BlazeComponent.getComponentForElement(this.firstNode);
if (component && component.dueCardsList) {
return component.dueCardsList();
}
return [];
},
hasResults() {
const component = BlazeComponent.getComponentForElement(this.firstNode);
if (component && component.hasResults) {
return component.hasResults.get();
}
return false;
},
searching() {
const component = BlazeComponent.getComponentForElement(this.firstNode);
if (component && component.isLoading) {
return component.isLoading.get();
}
return true; // Show loading by default
},
hasQueryErrors() {
return false; // No longer using search, so always false
},
errorMessages() {
return []; // No longer using search, so always empty
},
cardsCount() {
const component = BlazeComponent.getComponentForElement(this.firstNode);
if (component && component.cardsCount) {
return component.cardsCount();
}
return 0;
},
resultsText() {
const component = BlazeComponent.getComponentForElement(this.firstNode);
if (component && component.resultsText) {
return component.resultsText();
}
return '';
},
});
BlazeComponent.extendComponent({
@@ -38,12 +72,16 @@ BlazeComponent.extendComponent({
return [
{
'click .js-due-cards-view-me'() {
Utils.setDueCardsView('me');
if (Utils && Utils.setDueCardsView) {
Utils.setDueCardsView('me');
}
Popup.back();
},
'click .js-due-cards-view-all'() {
Utils.setDueCardsView('all');
if (Utils && Utils.setDueCardsView) {
Utils.setDueCardsView('all');
}
Popup.back();
},
},
@@ -51,61 +89,180 @@ BlazeComponent.extendComponent({
},
}).register('dueCardsViewChangePopup');
class DueCardsComponent extends CardSearchPagedComponent {
class DueCardsComponent extends BlazeComponent {
onCreated() {
super.onCreated();
const queryParams = new QueryParams();
queryParams.addPredicate(OPERATOR_HAS, {
field: PREDICATE_DUE_AT,
exists: true,
});
// queryParams[OPERATOR_LIMIT] = 5;
queryParams.addPredicate(OPERATOR_SORT, {
name: PREDICATE_DUE_AT,
order: ORDER_ASCENDING,
this._cachedCards = null;
this._cachedTimestamp = null;
this.subscriptionHandle = null;
this.isLoading = new ReactiveVar(true);
this.hasResults = new ReactiveVar(false);
this.searching = new ReactiveVar(false);
// Subscribe to the optimized due cards publication
this.autorun(() => {
const allUsers = this.dueCardsView() === 'all';
if (this.subscriptionHandle) {
this.subscriptionHandle.stop();
}
this.subscriptionHandle = Meteor.subscribe('dueCards', allUsers);
// Update loading state based on subscription
this.autorun(() => {
if (this.subscriptionHandle && this.subscriptionHandle.ready()) {
if (process.env.DEBUG === 'true') {
console.log('dueCards: subscription ready, loading data...');
}
this.isLoading.set(false);
const cards = this.dueCardsList();
this.hasResults.set(cards && cards.length > 0);
} else {
if (process.env.DEBUG === 'true') {
console.log('dueCards: subscription not ready, showing loading...');
}
this.isLoading.set(true);
this.hasResults.set(false);
}
});
});
}
if (Utils.dueCardsView() !== 'all') {
queryParams.addPredicate(OPERATOR_USER, ReactiveCache.getCurrentUser().username);
onDestroyed() {
super.onDestroyed();
if (this.subscriptionHandle) {
this.subscriptionHandle.stop();
}
this.runGlobalSearch(queryParams);
}
dueCardsView() {
// eslint-disable-next-line no-console
//console.log('sort:', Utils.dueCardsView());
return Utils.dueCardsView();
return Utils && Utils.dueCardsView ? Utils.dueCardsView() : 'me';
}
sortByBoard() {
return this.dueCardsView() === 'board';
}
hasResults() {
return this.hasResults.get();
}
cardsCount() {
const cards = this.dueCardsList();
return cards ? cards.length : 0;
}
resultsText() {
const count = this.cardsCount();
if (count === 1) {
return TAPi18n.__('one-card-found');
} else {
// Get the translated text and manually replace %s with the count
const baseText = TAPi18n.__('n-cards-found');
const result = baseText.replace('%s', count);
if (process.env.DEBUG === 'true') {
console.log('dueCards: base text:', baseText, 'count:', count, 'result:', result);
}
return result;
}
}
dueCardsList() {
const results = this.getResults();
console.log('results:', results);
const cards = [];
if (results) {
results.forEach(card => {
cards.push(card);
// Check if subscription is ready
if (!this.subscriptionHandle || !this.subscriptionHandle.ready()) {
if (process.env.DEBUG === 'true') {
console.log('dueCards client: subscription not ready');
}
return [];
}
// Use cached results if available to avoid expensive re-sorting
if (this._cachedCards && this._cachedTimestamp && (Date.now() - this._cachedTimestamp < 5000)) {
if (process.env.DEBUG === 'true') {
console.log('dueCards client: using cached results,', this._cachedCards.length, 'cards');
}
return this._cachedCards;
}
// Get cards directly from the subscription (already sorted by the publication)
const cards = ReactiveCache.getCards({
type: 'cardType-card',
archived: false,
dueAt: { $exists: true, $nin: [null, ''] }
});
if (process.env.DEBUG === 'true') {
console.log('dueCards client: found', cards.length, 'cards with due dates');
console.log('dueCards client: cards details:', cards.map(c => ({
id: c._id,
title: c.title,
dueAt: c.dueAt,
boardId: c.boardId,
members: c.members,
assignees: c.assignees,
userId: c.userId
})));
}
// Filter cards based on user view preference
const allUsers = this.dueCardsView() === 'all';
const currentUser = ReactiveCache.getCurrentUser();
let filteredCards = cards;
if (process.env.DEBUG === 'true') {
console.log('dueCards client: current user:', currentUser ? currentUser._id : 'none');
console.log('dueCards client: showing all users:', allUsers);
}
if (!allUsers && currentUser) {
filteredCards = cards.filter(card => {
const isMember = card.members && card.members.includes(currentUser._id);
const isAssignee = card.assignees && card.assignees.includes(currentUser._id);
const isAuthor = card.userId === currentUser._id;
const matches = isMember || isAssignee || isAuthor;
if (process.env.DEBUG === 'true' && matches) {
console.log('dueCards client: card matches user:', card.title, { isMember, isAssignee, isAuthor });
}
return matches;
});
}
cards.sort((a, b) => {
const x = a.dueAt === null ? new Date('2100-12-31') : a.dueAt;
const y = b.dueAt === null ? new Date('2100-12-31') : b.dueAt;
// Normalize dueAt to timestamps for stable client-side ordering
const future = new Date('2100-12-31').getTime();
const toTime = v => {
if (v === null || v === undefined || v === '') return future;
if (v instanceof Date) return v.getTime();
const t = new Date(v);
if (!isNaN(t.getTime())) return t.getTime();
return future;
};
filteredCards.sort((a, b) => {
const x = toTime(a.dueAt);
const y = toTime(b.dueAt);
if (x > y) return 1;
else if (x < y) return -1;
if (x < y) return -1;
return 0;
});
// eslint-disable-next-line no-console
console.log('cards:', cards);
return cards;
if (process.env.DEBUG === 'true') {
console.log('dueCards client: filtered to', filteredCards.length, 'cards');
}
// Cache the results for 5 seconds to avoid re-filtering on every render
this._cachedCards = filteredCards;
this._cachedTimestamp = Date.now();
// Update reactive variables
this.hasResults.set(filteredCards && filteredCards.length > 0);
this.isLoading.set(false);
return filteredCards;
}
}
Loaded 100 of 845 files, more files were not shown because too many files have changed in this diff. Show more