Compare commits

..
273 Commits
Author SHA1 Message Date
Lauri Ojansivu 6b2759f97d v8.78 2026-04-09 04:52:05 +03:00
Lauri Ojansivu adf4312e5a v8.77 2026-04-09 04:40:21 +03:00
Lauri Ojansivu b1707b8691 Upgraded to Meteor 3.5-beta.7
Thanks to Meteor developers !
2026-04-09 04:37:36 +03:00
Lauri Ojansivu 25dff3f2da v8.76 2026-04-09 04:19:51 +03:00
Lauri Ojansivu bc88c0dfc2 Fix GitHub Actions builds.
Thanks to xet7 !
2026-04-09 04:14:46 +03:00
Lauri Ojansivu 1ca2dfd1b1 v8.75 2026-04-09 03:58:01 +03:00
Lauri Ojansivu bdb9274485 Updated to Node.js 24.x at release scripts.
Thanks to xet7 !
2026-04-09 03:56:06 +03:00
Lauri Ojansivu 14f9ac39ad Upgraded to Meteor 3.5-beta.7 and Node.js 24.14.1. Now prefers MongoDB Change Streams at Snap Candidate and docker-compose.yml.
Thanks to Meteor developers and xet7 !
2026-04-09 03:44:58 +03:00
Lauri Ojansivu d4b52f7de9 Updated ChangeLog. 2026-04-09 02:19:44 +03:00
Lauri Ojansivu bdc65ec6f4 Merge pull request #6261 from KhaoulaMaleh/fix/translation-refinement
Refine translations
2026-04-08 23:30:39 +03:00
KhaoulaMaleh 7fb31cecd5 fix refinement 2026-04-08 00:45:27 -11:00
KhaoulaMaleh 317c82a9f0 fixed after review 2026-04-08 00:16:18 -11:00
KhaoulaMaleh 9b5c89d235 Enhance translations with new key:value pairs 2026-04-07 23:36:43 -11:00
Lauri Ojansivu c23e190e8e Updated ChangeLog. 2026-04-08 02:16:21 +03:00
Lauri Ojansivu 4a73371499 Merge pull request #6260 from KhaoulaMaleh/fix/Card_setting_checkbox
fixed Card settings checkboxes
2026-04-08 02:14:28 +03:00
KhaoulaMaleh 6f8af55489 fixed Card settings checkboxes 2026-04-08 00:04:48 -11:00
Lauri Ojansivu 2a05f62bf9 Updated ChangeLog. 2026-04-08 01:49:33 +03:00
Lauri Ojansivu d6eb1eb76f Merge pull request #6259 from KhaoulaMaleh/fix/enhance_translation
Enhance translations with new key:value pairs
2026-04-08 01:46:33 +03:00
Lauri Ojansivu 27542f3a51 v8.74 2026-04-08 00:34:49 +03:00
Lauri Ojansivu 0328e70d5f Fix can not save Card Data. Part 2.
Thanks to xet7 !

Fixes #6254
2026-04-08 00:31:40 +03:00
Lauri Ojansivu c46932ee30 v8.73 2026-04-07 23:18:14 +03:00
Lauri Ojansivu c7408ad8d1 Fix board and card activities. Part 2.
Thanks to xet7 !

Related #6252
2026-04-07 23:12:26 +03:00
Lauri Ojansivu 05dc8f8f93 Updated ChangeLog. 2026-04-07 22:01:59 +03:00
Lauri Ojansivu aea8c285c2 Fix Change list inside or card.
Thanks to mimZD and xet7 !

Fixes #6161
2026-04-07 22:00:11 +03:00
Lauri Ojansivu a859137971 Updated ChangeLog. 2026-04-07 21:53:11 +03:00
Lauri Ojansivu 08ebad0fe4 Fix Checklists add and Subtasks reorder. Fix Board Activity part 1.
Thanks to xet7 !

Related #6252
2026-04-07 21:49:26 +03:00
Lauri Ojansivu d54ebfa0e1 Updated ChangeLog. 2026-04-07 20:39:55 +03:00
Lauri Ojansivu 304a007d07 Fix Search All Boards result opened card missing Copy Card Link etc that can be used to open correct card.
Thanks bcook-konza and xet7 !

Fixes #6251
2026-04-07 20:35:20 +03:00
Lauri Ojansivu b6b58c9d95 Updated ChangeLog. 2026-04-07 20:10:38 +03:00
Lauri Ojansivu 7f0c42cca5 Fix Error in choosing member or assignee in a card.
Thanks to hmeunier95 and xet7 !

Fixes #6257
2026-04-07 20:08:04 +03:00
Lauri Ojansivu 9508d46ebd Updated ChangeLog. 2026-04-07 19:25:15 +03:00
Lauri Ojansivu bcbf4771ea Fix 8.72 Bug: LDAP login does not work. ReactiveCache is not defined.
Thanks to poc-sm and xet7 !

Fixes #6255
2026-04-07 19:23:06 +03:00
Lauri Ojansivu 9da5ec25c6 Updated ChangeLog. 2026-04-07 19:21:20 +03:00
Lauri Ojansivu 028b404377 Removed exclude-arch from rebuild-wekan.sh, it is not necessary anymore.
Thanks to xet7 !
2026-04-07 19:19:53 +03:00
Lauri Ojansivu 8da0f34ee6 Fix can not save Card Data.
Thanks to xet7 !

Fixes #6254
2026-04-07 19:17:35 +03:00
Lauri Ojansivu cac2628b19 Updated ChangeLog. 2026-04-07 11:40:46 +03:00
Lauri Ojansivu 9c69530256 Fix Link to Windows release doesn't work.
Thanks to hmeunier95 and xet7 !

Fixes #6253
2026-04-07 11:37:50 +03:00
Lauri Ojansivu 6e108358d7 v8.72 2026-04-07 10:44:37 +03:00
Lauri Ojansivu 87f592c67f Updated release scripts.
Thanks to xet7 !
2026-04-07 10:42:45 +03:00
Lauri Ojansivu fc95abd587 v8.72 2026-04-07 09:52:41 +03:00
Lauri Ojansivu ce3e51b147 Fix Impersonate User. Show/Edit Avatar at Admin Panel/People/People.
Thanks to ahlgrimma, DnT1mmer, robbagithub, norrig and xet7 !

Fixes #6226,
fixes #6101,
fixes #3327,
fixes #4748
2026-04-07 09:52:41 +03:00
Lauri Ojansivu bb48b23266 Merge pull request #6250 from wekan/dependabot/github_actions/docker/login-action-4.1.0
Bump docker/login-action from 4.0.0 to 4.1.0
2026-04-07 09:26:54 +03:00
Lauri Ojansivu fdf00e06f5 Merge pull request #6249 from wekan/dependabot/github_actions/actions/cache-5
Bump actions/cache from 4 to 5
2026-04-07 09:26:38 +03:00
Lauri Ojansivu 950b246da8 Merge pull request #6248 from wekan/dependabot/github_actions/actions/upload-artifact-7
Bump actions/upload-artifact from 4 to 7
2026-04-07 09:26:25 +03:00
Lauri Ojansivu 6ac0df8448 Merge pull request #6247 from wekan/dependabot/github_actions/actions/download-artifact-8
Bump actions/download-artifact from 4 to 8
2026-04-07 09:25:51 +03:00
dependabot[bot] 80ae3a18a8 Bump docker/login-action from 4.0.0 to 4.1.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/b45d80f862d83dbcd57f89517bcf500b2ab88fb2...4907a6ddec9925e35a0a9e82d7399ccc52663121)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-06 20:33:05 +00:00
dependabot[bot] 972bfb9677 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>
2026-04-06 20:33:01 +00:00
dependabot[bot] b1149e4d6e Bump actions/upload-artifact from 4 to 7
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4...v7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-06 20:32:55 +00:00
dependabot[bot] 3cd2ba847b Bump actions/download-artifact from 4 to 8
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 8.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-06 20:32:49 +00:00
Lauri Ojansivu 8bf869654c Updated ChangeLog. 2026-04-06 05:09:38 +03:00
Lauri Ojansivu 3b98f862bc Fix Admin Panel / Version not showing name of MongoDB storage engine and status of MongoDB Oplog enabled.
Thanks to xet7 !
2026-04-06 05:05:19 +03:00
Lauri Ojansivu 9bc889e274 v8.71 2026-04-06 03:40:47 +03:00
Lauri Ojansivu 5df924f416 Fix: Added missing null check to notification.
Thanks to xet7 !
2026-04-06 03:35:28 +03:00
Lauri Ojansivu 5ef94b7079 v8.70 2026-04-06 02:43:39 +03:00
Lauri Ojansivu dfcaaf0ae7 Convert upsert calls to async in authentication.js .
Thanks to snowsky and xet7 !

Fixes https://github.com/wekan/wekan/pull/6243,
fixes https://github.com/wekan/wekan/pull/6245
2026-04-06 02:36:45 +03:00
Lauri Ojansivu 34e7ff532f v8.69 2026-04-06 01:32:30 +03:00
Lauri Ojansivu fbdfcead50 Try to fix Snap to start MongoDB OpLog correctly.
Thanks to xet7.
2026-04-06 01:28:46 +03:00
Lauri Ojansivu b8bbe4431b v8.68 2026-04-06 00:26:18 +03:00
Lauri Ojansivu 9e31666280 Try to fix Snap to start MongoDB OpLog correctly.
Thanks to xet7 !
2026-04-06 00:21:39 +03:00
Lauri Ojansivu 5846d9c9b0 v8.67 2026-04-05 23:22:53 +03:00
Lauri Ojansivu 7707a5b7fc Try to fix GitHub Actions release bundle builds.
Thanks to xet7.
2026-04-05 23:20:35 +03:00
Lauri Ojansivu 18c495940e v8.65 2026-04-05 23:11:32 +03:00
Lauri Ojansivu d3155d24c6 Try to fix GitHub Actions release bundle builds.
Thanks to xet7.
2026-04-05 23:09:36 +03:00
Lauri Ojansivu c0661506c4 Fix Docker Actions Test suite.
Thanks to xet7 !
2026-04-05 22:56:29 +03:00
Lauri Ojansivu fa2b34764f v8.64 2026-04-05 22:51:36 +03:00
Lauri Ojansivu 7a7909b49f Try to fix GitHub Actions release bundle builds.
Thanks to xet7 !
2026-04-05 22:49:33 +03:00
Lauri Ojansivu 17d3b3088f v8.63 2026-04-05 22:38:05 +03:00
Lauri Ojansivu 6a78a178e2 Try to fix GitHub Actions release bundle builds.
Thanks to xet7 !
2026-04-05 22:35:50 +03:00
Lauri Ojansivu 749de39e2a v8.62 2026-04-05 22:20:33 +03:00
Lauri Ojansivu cd55c0e12e Try to fix GitHub Actions bundle builds.
Thanks to xet7 !
2026-04-05 22:17:12 +03:00
Lauri Ojansivu d04037739e v8.61 2026-04-05 21:58:49 +03:00
Lauri Ojansivu f3a97b9938 Delete all GitHub Actions Caches, so that old stuff does not break builds.
Thanks to xet7 !
2026-04-05 21:55:09 +03:00
Lauri Ojansivu 104fbef37a Reverted disabling GitHub workflows. Still trying to fix them.
Thanks to xet7 !
2026-04-05 21:28:19 +03:00
Lauri Ojansivu 98b3dc71af Updated ChangeLog. 2026-04-05 21:15:52 +03:00
Lauri Ojansivu 7b28e4d589 Merge pull request #6246 from KhaoulaMaleh/fix/comment
Fix: Server crash during comment deletion
2026-04-05 21:14:09 +03:00
KhaoulaMaleh 57291dbe50 fixing deletion comment 2026-04-05 17:51:29 -11:00
Lauri Ojansivu 9f9eefce27 Updated ChangeLog. 2026-04-05 21:13:50 +03:00
Lauri Ojansivu af3361b35b Disabled not working GitHub workflows.
Thanks to xet7 !
2026-04-05 21:12:08 +03:00
Lauri Ojansivu 24d65e1e72 Disabled GitHub workflows that produce broken builds.
Thanks to xet7 !
2026-04-05 21:06:49 +03:00
Lauri Ojansivu 29922fca6a Updated ChangeLog. 2026-04-05 18:46:12 +03:00
Lauri Ojansivu 81319fc48b Updated Docker build script to check directories and repos exist.
Thanks to xet7 !
2026-04-05 18:44:07 +03:00
Lauri Ojansivu 28b7209407 v8.60 2026-04-05 18:14:31 +03:00
Lauri Ojansivu 0b59f87887 Updated Docker build script.
Thanks to xet7 !
2026-04-05 18:10:57 +03:00
Lauri Ojansivu b22df9e93d v8.59 2026-04-05 18:09:10 +03:00
Lauri Ojansivu 1fa72fd34a Fix Snap to use OpLog.
Thanks to xet7 !
2026-04-05 18:05:29 +03:00
Lauri Ojansivu a2f38e7494 Updated ChangeLog. 2026-04-05 17:41:52 +03:00
Lauri Ojansivu 30a1792c92 Fix docker-compose.yml to use OpLog.
Thanks to xet7 !
2026-04-05 17:40:01 +03:00
Lauri Ojansivu 3d70f24a78 v8.58 2026-04-05 16:45:54 +03:00
Lauri Ojansivu b3d14fb5e8 Updated build docs.
Thanks to xet7 !
2026-04-05 16:39:25 +03:00
Lauri Ojansivu 59956ca720 Try to fix .zip Bundle, Docker and Snap.
Thanks to xet7 !
2026-04-05 16:34:58 +03:00
Lauri Ojansivu aaf7690b9a v8.57 2026-04-05 15:18:00 +03:00
Lauri Ojansivu 9f549dfcd2 Try to fix Snap.
Thanks to xet7 !
2026-04-05 15:11:40 +03:00
Lauri Ojansivu f191392e3a v8.56 2026-04-05 07:36:06 +03:00
Lauri Ojansivu 56f26418ea Try to fix Snap.
Thanks to xet7 !
2026-04-05 07:33:22 +03:00
Lauri Ojansivu 118e0e5d5c v8.55 2026-04-05 06:58:46 +03:00
Lauri Ojansivu 264dcca42b Updated ChangeLog. 2026-04-05 06:48:47 +03:00
Lauri Ojansivu 73d69f4631 Updated dependencies.
Thanks to developers of dependencies !
2026-04-05 06:47:10 +03:00
Lauri Ojansivu 485b773338 Updated ChangeLog. 2026-04-05 06:39:23 +03:00
Lauri Ojansivu 8443e3511d Try to fix Snap.
Thanks to xet7 !
2026-04-05 06:38:16 +03:00
Lauri Ojansivu b2a349aec5 Updated ChangeLog. 2026-04-05 06:18:30 +03:00
Lauri Ojansivu faf8453c6d Fix Search All Boards.
Thanks to YosserDerbali and xet7 !

Fixes #6229,
fixes #6225
2026-04-05 06:13:53 +03:00
Lauri Ojansivu 7484c8cce3 Updated ChangeLog. 2026-04-05 01:34:55 +03:00
Lauri Ojansivu 2b02e7712e Fix Cards Reports is not working.
Thanks to titver968 and xet7 !

Fixes #6209
2026-04-05 01:32:04 +03:00
Lauri Ojansivu 8a3a7b19aa Updated ChangeLog. 2026-04-04 23:03:59 +03:00
Lauri Ojansivu 9fa6574c80 Docs: IMPORTANT: Do NOT npm audit fix --force, it breaks builds.
Thanks to xet7 !

Fixes #6224
2026-04-04 23:00:29 +03:00
Lauri Ojansivu 2bad17010c Docs: IMPORTANT: Do NOT npm audit fix --force, it breaks builds. 2026-04-04 22:59:16 +03:00
Lauri Ojansivu 4168385607 Updated ChangeLog. 2026-04-04 22:54:48 +03:00
Lauri Ojansivu c0aa1ef375 v8.54 2026-04-04 18:20:00 +03:00
Lauri Ojansivu 811b79b641 Revert v8.53 Refactor upsert calls to use async method.
Thanks to xet7 !
2026-04-04 18:17:26 +03:00
Lauri Ojansivu dab9755dd4 v8.53 2026-04-04 18:07:57 +03:00
Lauri Ojansivu 6863479c09 Updated ChangeLog. 2026-04-04 18:04:57 +03:00
Lauri Ojansivu e05eebb8b0 Merge pull request #6243 from snowsky/main
Refactor upsert calls to use async method
2026-04-04 18:02:03 +03:00
Hao 28ef0e6e89 Refactor upsert calls to use async method 2026-04-04 08:34:32 -05:00
Lauri Ojansivu 09e65ccac2 v8.52 2026-04-03 20:38:13 +03:00
Lauri Ojansivu 03e0cfdc15 Try to fix Docker and Snap.
Thanks to xet7 !
2026-04-03 20:35:54 +03:00
Lauri Ojansivu 7634cd6c2e v8.51 2026-04-03 20:26:08 +03:00
Lauri Ojansivu 4594711be4 Updated release scripts.
Thanks to xet7 !
2026-04-03 20:21:18 +03:00
Lauri Ojansivu c267933bf7 Updated ChangeLog. 2026-04-03 20:20:03 +03:00
Lauri Ojansivu edc6b18b5f Fix Bug: WeKan v8.50 WRITABLE_PATH.
Thanks to snowsky and xet7 !

Fixes #6242
2026-04-03 20:16:32 +03:00
Lauri Ojansivu 60657c6e94 v8.50 2026-04-03 04:40:19 +03:00
Lauri Ojansivu 3c189f9c7f Updated ChangeLog. 2026-04-03 04:32:15 +03:00
Lauri Ojansivu bf02c9a453 Updated dependencies.
Thanks to developers of dependencies.
2026-04-03 04:23:09 +03:00
Lauri Ojansivu e6b6cd3036 Updated ChangeLog. 2026-04-03 03:43:24 +03:00
Lauri Ojansivu 6e8589b1f9 Updated npm package @wekanteam/exceljs.
Thanks to xet7 !
2026-04-03 03:38:22 +03:00
Lauri Ojansivu 1354f84a70 Updated ChangeLog. 2026-04-03 00:31:11 +03:00
Lauri Ojansivu d07cf92f7c Merge pull request #6240 from snowsky/main
Fix indentation for release-all.yml workflow
2026-04-03 00:28:26 +03:00
Hao Wang e079644ed6 Fix indentation for release-all.yml workflow 2026-04-02 17:16:00 -04:00
Lauri Ojansivu 6b393178dc Updated translations. 2026-04-02 23:10:21 +03:00
Lauri Ojansivu 26e1c9ab69 Updated ChangeLog. 2026-04-01 00:57:36 +03:00
Lauri Ojansivu 145a17fee5 Uncommented build steps. Not yet tested are required dependencies included.
Thanks to xet7 !
2026-04-01 00:55:12 +03:00
Lauri Ojansivu ccc9301ed4 v8.49 2026-03-31 19:21:00 +03:00
Lauri Ojansivu f2e46d660f Updated ChangeLog. 2026-03-31 18:55:50 +03:00
Lauri Ojansivu 8b4676175c Try to fix Snap.
Thanks to xet7 !
2026-03-31 18:54:49 +03:00
Lauri Ojansivu e9a547c89b Updated ChangeLog. 2026-03-31 18:47:01 +03:00
Lauri Ojansivu d07fcceb6a Fix docker-compose.yml.
Thanks to xet7 !
2026-03-31 18:45:54 +03:00
Lauri Ojansivu 6b5002f314 Updated ChangeLog. 2026-03-31 18:21:50 +03:00
Lauri Ojansivu 5418ffd558 Merge pull request #6237 from kovacs-andras/main
fix: missing module '@meteorjs/reify/lib/runtime'
2026-03-31 18:17:38 +03:00
Andras Kovacs ea98afeb68 fix: missing module '@meteorjs/reify/lib/runtime' 2026-03-31 15:09:17 +02:00
Lauri Ojansivu be7d7e26fb v8.48 2026-03-31 05:56:09 +03:00
Lauri Ojansivu 56377aaf4c Updated release scripts. 2026-03-31 05:51:38 +03:00
Lauri Ojansivu 43601c5e3e Updated release scripts. 2026-03-31 05:31:29 +03:00
Lauri Ojansivu 1db7f3a57d v8.47 2026-03-31 05:04:33 +03:00
Lauri Ojansivu e58e99c285 Updated attachments and avatar path to be at WRITABLE_PATH/files/attachments etc.
Thanks to xet7 !
2026-03-31 05:00:17 +03:00
Lauri Ojansivu b38e25c9f1 v8.46 2026-03-31 04:32:57 +03:00
Lauri Ojansivu ce52b8a0ab Updated release scripts. 2026-03-31 04:29:23 +03:00
Lauri Ojansivu f7800846a5 Updated ChangeLog. 2026-03-31 04:00:52 +03:00
Lauri Ojansivu 02b75824e5 Updated docker-compose.yml to change from FerretDB/PostgreSQL to MongoDB.
Thanks to xet7 !
2026-03-31 03:59:07 +03:00
Lauri Ojansivu cf52c2f6e4 Updated ChangeLog. 2026-03-31 03:44:02 +03:00
Lauri Ojansivu 6bc68ba57b Updated release scripts.
Thanks to xet7 !
2026-03-31 03:42:19 +03:00
Lauri Ojansivu 5ec9cfa537 v8.45 2026-03-31 03:23:36 +03:00
Lauri Ojansivu 3457cab0b1 Updated ChangeLog. 2026-03-31 03:20:21 +03:00
Lauri Ojansivu 8349fb6b63 Updated release scripts. Added missing dependencies.
Thanks to xet7 !
2026-03-31 02:56:06 +03:00
Lauri Ojansivu 33267fa2e4 Try to fix Snap 2026-03-31 01:54:28 +03:00
Lauri Ojansivu 5c4130311a Try to fix Snap 2026-03-31 01:45:46 +03:00
Lauri Ojansivu ed718f083a Try to fix Snap 2026-03-31 01:40:40 +03:00
Lauri Ojansivu 2aa8ecfc39 Try to fix Snap 2026-03-31 01:35:24 +03:00
Lauri Ojansivu cdf1d3305b Try to fix Snap 2026-03-31 01:30:47 +03:00
Lauri Ojansivu fd58bb9e4d Try to fix Snap 2026-03-31 01:25:11 +03:00
Lauri Ojansivu 55ec0b5ff5 Try to fix Snap.
Thanks to xet7 !
2026-03-31 01:17:58 +03:00
Lauri Ojansivu e5c551f009 Docker registry Quay.io is currently read only, it's not possible to push newest WeKan Docker image to Quay.io
Thanks to xet7 !
2026-03-31 01:08:09 +03:00
Lauri Ojansivu 7666c6c0e1 Merge pull request #6235 from wekan/dependabot/github_actions/docker/login-action-4
Bump docker/login-action from 3 to 4
2026-03-31 00:58:41 +03:00
Lauri Ojansivu e6ccbfd3b8 Merge pull request #6234 from wekan/dependabot/github_actions/docker/setup-qemu-action-4
Bump docker/setup-qemu-action from 3 to 4
2026-03-31 00:58:23 +03:00
Lauri Ojansivu d8a2e06a95 Merge pull request #6233 from wekan/dependabot/github_actions/docker/build-push-action-7
Bump docker/build-push-action from 6 to 7
2026-03-31 00:58:06 +03:00
Lauri Ojansivu c38fa1c272 Merge pull request #6232 from wekan/dependabot/github_actions/actions/checkout-6
Bump actions/checkout from 4 to 6
2026-03-31 00:57:48 +03:00
Lauri Ojansivu 1a5e23617d Merge pull request #6231 from wekan/dependabot/github_actions/actions/setup-node-6
Bump actions/setup-node from 4 to 6
2026-03-31 00:57:22 +03:00
Lauri Ojansivu 87916c20bf Try to fix Snap. Part 2.
Thanks to xet7 !
2026-03-31 00:49:33 +03:00
Lauri Ojansivu 12c223d226 Try to fix Snap.
Thanks to xet7 !
2026-03-31 00:37:53 +03:00
dependabot[bot] 9ae2842fa4 Bump docker/login-action from 3 to 4
Bumps [docker/login-action](https://github.com/docker/login-action) from 3 to 4.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3...v4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 21:33:06 +00:00
dependabot[bot] 79853a2fb7 Bump docker/setup-qemu-action from 3 to 4
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3 to 4.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 21:33:02 +00:00
dependabot[bot] ec1e383558 Bump docker/build-push-action from 6 to 7
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6 to 7.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6...v7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 21:32:59 +00:00
dependabot[bot] df067f01e8 Bump actions/checkout from 4 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 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/v4...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>
2026-03-30 21:32:55 +00:00
dependabot[bot] 4b8cc0683e Bump actions/setup-node from 4 to 6
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4...v6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 21:32:47 +00:00
Lauri Ojansivu f43b76c323 Updated release scripts.
Thanks to xet7 !
2026-03-31 00:07:19 +03:00
Lauri Ojansivu e90b2e384c Add release-all workflow for multi-platform builds
Add a GitHub Actions workflow for releasing all platforms, including version extraction, building bundles for multiple architectures, and creating a GitHub release.
2026-03-30 23:54:05 +03:00
Lauri Ojansivu 6125822cf7 Updated release scripts. 2026-03-30 23:53:23 +03:00
Lauri Ojansivu 4223c2a79f Updated release scripts.
Thanks to xet7 !
2026-03-30 23:39:29 +03:00
Lauri Ojansivu 7059cc244a v8.44 2026-03-30 22:36:22 +03:00
Lauri Ojansivu 5fc608e310 Updated ChangeLog. 2026-03-30 21:43:33 +03:00
Lauri Ojansivu 29550cbd93 Updated release scripts. Part 2.
Thanks to xet7 !
2026-03-30 21:42:24 +03:00
Lauri Ojansivu 35674a205b Updated ChangeLog. 2026-03-30 20:57:13 +03:00
Lauri Ojansivu a702596bee Updated release scripts.
Thanks to xet7 !
2026-03-30 20:53:20 +03:00
Lauri Ojansivu 02fef72e7e Updated ChangeLog. 2026-03-30 20:38:18 +03:00
Lauri Ojansivu d5c9e402ce Fix myCards crash.
Thanks to cristomoya !

Fixes #6202
2026-03-30 20:35:17 +03:00
Lauri Ojansivu b788d398b9 Fix globalSearch.
Thanks to snowsky !

Fixes #6230
2026-03-30 20:29:40 +03:00
Lauri Ojansivu 5c3b73e3b0 Updated ChangeLog. 2026-03-30 19:32:26 +03:00
Lauri Ojansivu 82f1de078f Updated dependencies.
Thanks to developers of dependencies !
2026-03-30 19:30:26 +03:00
Lauri Ojansivu ddc353023c Updated ChangeLog. 2026-03-30 18:54:44 +03:00
Lauri Ojansivu ef75b9ecac Always show all data, like Shared Lists that were before Per-Swimlanes lists, Orphaned Swimlanes/Lists/Cards etc.
Thanks to xet7 !
2026-03-30 18:46:27 +03:00
Lauri Ojansivu cf2b3e4fc9 From FerretDB/PostgreSQL back to MongoDB 7.x.
Thanks to xet7 !
2026-03-30 18:21:05 +03:00
Lauri Ojansivu cf21991a3f Updated translations. 2026-03-30 18:00:08 +03:00
Lauri Ojansivu 9e4f72e2a4 Updated Node.js at Windows docs 2026-03-28 01:04:30 +02:00
Lauri Ojansivu f7da243c9e Updated release scripts. Part 5.
Thanks to xet7 !
2026-03-28 00:49:32 +02:00
Lauri Ojansivu a71efd2668 v8.43 2026-03-28 00:37:33 +02:00
Lauri Ojansivu 6a23290584 Updated ChangeLog. 2026-03-27 23:25:02 +02:00
Lauri Ojansivu 0e59033ea6 Updated release scripts. Part 4.
Thanks to xet7 !
2026-03-27 23:24:02 +02:00
Lauri Ojansivu 186f7a6b82 Updated ChangeLog. 2026-03-27 23:21:41 +02:00
Lauri Ojansivu 5cf3bba68f Updated release scripts. Part 3.
Thanks to xet7 !
2026-03-27 23:19:55 +02:00
Lauri Ojansivu 4647ddc2b7 Updated ChangeLog. 2026-03-27 23:10:51 +02:00
Lauri Ojansivu 1ac708b0d9 Updated ChangeLog. 2026-03-27 22:49:43 +02:00
Lauri Ojansivu 07eb062474 Updated release scripts. Part 2.
Thanks to xet7 !
2026-03-27 22:45:52 +02:00
Lauri Ojansivu f660817627 Updated ChangeLog. 2026-03-27 20:55:15 +02:00
Lauri Ojansivu 0fdf45e847 Updated release scripts.
Thanks to xet7 !
2026-03-27 20:48:39 +02:00
Lauri Ojansivu 0c3cebd30a Merge pull request #6223 from YosserDerbali/fix/list-actions-popup-overflow-scroll
Fix/list actions popup overflow scroll
2026-03-27 20:47:09 +02:00
Yosser Derbali c589918989 Merge upstream/main and resolve popup.css conflict
- Keep upstream's cardDetailsActionsPopup styling
- Combine date picker container styling from both versions
- Ensure both list actions and card details popups work correctly
2026-03-27 19:42:25 +01:00
Yosser Derbali 256aa0a053 fix(popup): keep list actions popup inside viewport and scrollable 2026-03-27 19:17:00 +01:00
Lauri Ojansivu 67c56e41b0 Merge pull request #6222 from KhaoulaMaleh/fix/global-search
fix search: guard async methods in ReactiveCache and search publications
2026-03-27 20:16:52 +02:00
KhaoulaMaleh ade9e0f589 fix search: guard async methods in ReactiveCache and search publications 2026-03-27 18:36:32 -11:00
Lauri Ojansivu e14dfb9e7e Merge pull request #6220 from aminsaidane/fix/card-actions-popup-overflow
Fix card actions popup overflow
2026-03-27 20:15:36 +02:00
Lauri Ojansivu c26cc313fc Updated ChangeLog. 2026-03-27 19:22:45 +02:00
Lauri Ojansivu 55660e0e69 Updated dependencies. Part 2.
Thanks to developers of dependencies !
2026-03-27 19:20:27 +02:00
Lauri Ojansivu 3701544f6a Updated ChangeLog. 2026-03-27 19:04:28 +02:00
Lauri Ojansivu acfb081b90 Updated dependencies.
Thanks to developers of dependencies !
2026-03-27 19:02:42 +02:00
Lauri Ojansivu a30d7cabe3 Updated ChangeLog. 2026-03-27 18:59:12 +02:00
Lauri Ojansivu 517d148387 Moved Meteor 3 migration docs to docs/Upgrade/
Thanks to xet7 !
2026-03-27 18:56:43 +02:00
aminsaidane f8aec4de73 Fix card actions popup overflow
- Added max-height constraint to card details actions popup
- Ensured popup fits within viewport on all screen sizes
- Implemented scrollable content when menu exceeds viewport
- Used min(380px, 85vw) for responsive width sizing
- Prevents menu items from being cut off or hidden

Resolves card pop-up menu content overflow issue
2026-03-27 17:47:00 +01:00
Lauri Ojansivu e1b6cf6124 Updated translations 2026-03-27 18:37:27 +02:00
Lauri Ojansivu 1eac00e7fa Updated ChangeLog. 2026-03-27 18:18:24 +02:00
Lauri Ojansivu 278ed23440 Updated ChangeLog. 2026-03-27 18:15:31 +02:00
Lauri Ojansivu b42689a54d Fix attachment rename fails for clipboard uploads and keep extension metadata in sync.
Thanks to snowsky, YosserDerbali and xet7 !

Fixes #6203,
related https://github.com/wekan/wekan/pull/6217
2026-03-27 18:14:10 +02:00
Lauri Ojansivu 10bfa30458 Merge pull request #6218 from aminsaidane/fix/workspace-settings-modal-overflow
Fix workspace settings modal overflow
2026-03-27 18:09:37 +02:00
aminsaidane a0a1cda0a7 Fix workspace settings modal overflow
- Added top padding to popup content to prevent crowding
- Added max-height constraints for workspace actions popup
- Ensured modal fits within viewport on all screen sizes
- Implemented scrollable content when exceeding viewport
- Fixes issue with partially hidden controls in Workspace Settings modal

Closes #6200
2026-03-27 16:27:14 +01:00
Lauri Ojansivu 001af3fabd Updated translations. 2026-03-26 16:52:06 +02:00
Lauri Ojansivu c0316e9446 Merge pull request #6215 from KhaoulaMaleh/fix/csv-export
Fix: Clarify CSV export separator labels for i18n
2026-03-26 15:53:33 +02:00
KhaoulaMaleh d80a6e7e38 Fix: Clarify CSV export separator labels for i18n 2026-03-26 04:20:21 -11:00
Lauri Ojansivu 8d8d8f9cee Updated ChangeLog. 2026-03-26 01:45:40 +02:00
Lauri Ojansivu 0630d62ad0 Fix Set Color of Swimlane, List and Card.
Thanks to SVANNER, brlin-tw and xet7 !

Fixes #6207,
fixes #6212,
fixes #5660
2026-03-26 01:36:06 +02:00
Lauri Ojansivu c07b282dc9 Updated translations 2026-03-25 23:54:41 +02:00
Lauri Ojansivu 5aa39463b0 Updated ChangeLog. 2026-03-25 23:45:08 +02:00
Lauri Ojansivu f1e71247c7 Updated rebuild-wekan.sh for macOS 26.4.
Thanks to xet7 !
2026-03-25 23:40:47 +02:00
Lauri Ojansivu 419fb2535b Updated ChangeLog. 2026-03-25 21:57:25 +02:00
Lauri Ojansivu 23697d2a33 Fixed typo.
Thanks to YosserDerbali and xet7 !

Related https://github.com/wekan/wekan/pull/6210
2026-03-25 21:54:52 +02:00
Lauri Ojansivu 55edc7ef63 Updated ChangeLog. 2026-03-25 02:28:16 +02:00
Lauri Ojansivu c1e3ce64a7 Removed temporary debug logs.
Thanks to xet7 !
2026-03-25 02:26:52 +02:00
Lauri Ojansivu d34163402c Updated ChangeLog. 2026-03-25 01:40:03 +02:00
Lauri Ojansivu 5c5ed102ca Export Card to Excel. Part 3. Also, fixed uploading attachments.
Thanks to xet7 !

Fixes #5324
2026-03-25 01:37:42 +02:00
Lauri Ojansivu 0bbadfe9a6 Updated ChangeLog. 2026-03-24 21:53:49 +02:00
Lauri Ojansivu 21ab640686 Updated @meteorjs/rspack.
Thanks to Meteor developers!
2026-03-24 21:52:20 +02:00
Lauri Ojansivu a3f6d200ac Updated ChangeLog. 2026-03-24 21:41:58 +02:00
Lauri Ojansivu 701c10d618 Updated @meteorjs/rspack.
Thanks to Meteor developers!
2026-03-24 21:40:00 +02:00
Lauri Ojansivu 7f8de1f988 Updated ChangeLog. 2026-03-24 21:28:01 +02:00
Lauri Ojansivu 95ff8ef1f2 Updated dependencies of npm package @wekanteam/exceljs.
Thanks to xet7 !
2026-03-24 21:26:14 +02:00
Lauri Ojansivu 4eb885f88a Updated translations 2026-03-24 20:59:27 +02:00
Lauri Ojansivu 9a7a0b7271 Updated ChangeLog. 2026-03-24 20:57:26 +02:00
Lauri Ojansivu 4a8293f881 Fixed rebuild-wekan.sh to build WeKan correctly.
Thanks to xet7 !
2026-03-24 20:55:54 +02:00
Lauri Ojansivu 56ed341510 Updated ChangeLog. 2026-03-24 20:44:53 +02:00
Lauri Ojansivu 7584afda6d Upgraded to Meteor 3.4, Node.js 22.22, MongoDB 7.x, here related fixes.
Thanks to harryadel and xet7 !
2026-03-24 20:39:21 +02:00
Harry Adel bbb8f8e51d Merge branch 'main' into meteor-3-migration
# Conflicts:
#	.meteor/packages
#	.meteor/versions
#	models/attachments.js
#	models/boards.js
#	models/server/ExporterExcelCard.js
#	package-lock.json
2026-03-24 02:22:57 +02:00
Harry Adel ddd873c7a0 Fix client runtime regressions after Meteor 3 migration 2026-03-24 01:51:20 +02:00
Harry Adel 63ce45c538 Split shared models from Meteor 3 server runtime 2026-03-24 01:50:39 +02:00
Harry Adel 8ffc0cb5ff Centralize client boot and rspack package loading 2026-03-24 01:50:25 +02:00
Lauri Ojansivu ce95907778 v8.42 2026-03-23 20:07:35 +02:00
Lauri Ojansivu 4ed3250352 Updated ChangeLog. 2026-03-23 20:04:28 +02:00
Lauri Ojansivu 2dce7a84cd Fix SSO login is broken.
Thanks to sbaecker, vladimirdulov and xet7 !

Fixes #6204
2026-03-23 20:00:26 +02:00
Lauri Ojansivu b9631659f7 Updated version. 2026-03-23 18:36:04 +02:00
Harry Adel 8486bb1e87 Fix load order: two-stage entry points with require() bootstrap
ES module import statements are hoisted and evaluated in dependency-graph
order, not textual order. This means `import '/imports/collectionHelpers'`
being first in the file does NOT guarantee it runs first.

Fix: Split entry points into two stages:
- main.js: uses require() only (sequential, not hoisted)
  1. require('/imports/collectionHelpers') — loads collection2, SimpleSchema
  2. require('./imports') — loads all app code
- imports.js: contains all the import statements (safe because
  collectionHelpers has already fully loaded via require())

This pattern guarantees attachSchema/SimpleSchema/helpers are available
before ANY model file evaluates, regardless of circular dependency chains.
2026-03-22 19:03:11 +02:00
Harry Adel 655f44919a Fix attachSchema: convert collectionHelpers to pure require()
The root cause: import statements are hoisted and trigger evaluation
chains that reach model files (via reactiveCache circular deps)
BEFORE collection2 has patched Mongo.Collection.prototype.attachSchema.

Fix: Convert collectionHelpers.js to use ONLY require() calls.
require() is NOT hoisted — each call completes synchronously before
the next, guaranteeing collection2 fully loads before any model
file can run.
2026-03-22 18:59:09 +02:00
Harry Adel 17ea7978f0 Fix attachSchema: require collection2 directly in settings.js
settings.js is loaded early via circular dependency chain before
the entry point's hoisted imports finish. Add explicit require()
for aldeed:collection2 to ensure attachSchema is available.
2026-03-22 18:56:37 +02:00
Harry Adel b518946c41 Centralize collection2/SimpleSchema setup in collectionHelpers shim
Move all collection infrastructure into imports/collectionHelpers.js:
- Import aldeed:collection2 (provides attachSchema on Mongo.Collection)
- Import and set SimpleSchema as global
- Register denyUpdate/denyInsert as allowed schema extensions
- Provide Collection.helpers() shim

This ensures collection2 is loaded before any model file uses
attachSchema, regardless of circular dependency import ordering.
2026-03-22 18:54:01 +02:00
Harry Adel ca8a4af354 Fix SimpleSchema v2: add missing parent object declarations
SimpleSchema v2.0 requires explicit parent object declarations for
dot-notation sub-keys:
- settings.js: Add mailServer as Object parent for mailServer.* sub-keys
- watchable.js: Add watchers as [Object] parent for watchers.$.* sub-keys
2026-03-22 18:51:30 +02:00
Harry Adel 1682f8834d Fix denyUpdate/denyInsert: register as allowed SimpleSchema extensions
aldeed:simple-schema v2.0 no longer includes denyUpdate/denyInsert as
built-in properties. Register them via SimpleSchema.extendOptions() in:
- settings.js (first model loaded via circular dep chain)
- client/main.js and server/main.js (for all other models)
2026-03-22 18:42:53 +02:00
Harry Adel eac472c54b Fix SimpleSchema: use default import (not named export)
aldeed:simple-schema v2.0 uses `export default SimpleSchema`, not
a named export. Fix all import/require sites:
- settings.js: require().default
- attachmentStorageSettings.js: import default
- client/main.js, server/main.js: import default for global
2026-03-22 18:41:31 +02:00
Harry Adel 3a873559f8 Fix settings.js: use require() for ALL deps to prevent import hoisting
ES module import statements are hoisted above all code, so placing
Settings = new Mongo.Collection() between imports has no effect.
Convert ALL imports to require() so the collection definition
truly executes first, breaking the circular dependency.
2026-03-22 18:36:32 +02:00
Harry Adel aec6779601 Fix SimpleSchema not defined: import explicitly + set global
- settings.js: Import SimpleSchema explicitly from aldeed:simple-schema
  before attachSchema call (breaks circular dep ordering)
- client/main.js, server/main.js: Set SimpleSchema as global for the
  29 other model files that use it without importing
2026-03-22 18:30:38 +02:00
Harry Adel 973d9eccae Fix runtime errors: circular dependency in settings.js, fibers in users.js
- settings.js: Break circular import (settings → ReactiveCache → i18n →
  translation → ReactiveCache → settings) by defining Settings collection
  before require() calls instead of after import statements
- users.js: Make Users.after.insert hook async for template container
  creation (already committed fibers/future removal)
- fileStoreStrategy.js: Remove duplicate sanitizeFilename (already committed)
- wekanCreator.js: Fix forEach→for...of for await (already committed)
2026-03-22 18:27:41 +02:00
Harry Adel 7468b30001 Fix build errors: fibers/future, duplicate sanitizeFilename, forEach await
- users.js: Replace fibers/future pattern with async/await using
  insertAsync/updateAsync for template container creation
- fileStoreStrategy.js: Remove duplicate sanitizeFilename declaration
- wekanCreator.js: Convert attachments.forEach to for...of loop
  so await works inside the iteration body
2026-03-22 18:24:07 +02:00
Harry Adel e68cb5f32f Meteor 3.4 migration: Flip release to METEOR@3.4
Update .meteor/release from METEOR@2.16 to METEOR@3.4.
This activates Node 22, Express 5, async collection APIs,
MongoDB driver 6.x, and the Rspack bundler integration.
2026-03-22 17:58:34 +02:00
Harry Adel a157ae5d1c Meteor 3.4 migration: Module restructuring + Rspack setup
Create explicit entry points required by Meteor 3.4's Rspack bundler:

- client/main.js: imports all client code (models, components, jade
  templates, libraries, config, startup)
- server/main.js: imports all server code (models, methods, publications,
  routes, migrations, notifications, cron, auth)
- rspack.config.js: configures meteor-jade-loader for .jade files
- package.json: adds meteor.mainModule pointing to entry files
- .gitignore: adds Rspack build artifact directories
2026-03-22 17:58:04 +02:00
Harry Adel 620e5ce9dc Meteor 3.4 migration: Convert remaining sync methods to async
Convert all remaining Meteor.methods with sync collection calls:
- users.js: deleteWorkspace → async with findOneAsync/updateAsync
- userPositionHistory.js: 3 methods → async with insertAsync/fetchAsync
- attachmentStorageSettings.js: 4 methods → findOneAsync/insertAsync/upsertAsync
- org.js, team.js, translation.js: insert → insertAsync
- boards.js: acceptInvite + collection hooks → updateAsync
- settings.js: sendInvitation → updateAsync/insertAsync
- import.js: importBoard/cloneBoard → async with await creator.create()
- attachments.js: onAfterUpload → async with findOneAsync/updateAsync
2026-03-22 17:50:31 +02:00
Harry Adel 2e3fbf25db Meteor 3.4 migration: Convert allow/deny callbacks to async
Meteor 3.x supports async allow/deny callbacks. Convert all 25 sync
allow/deny callbacks across 23 model files to use async/await with
findOneAsync/fetchAsync instead of sync findOne/find().fetch().

Also converts helper functions in server/lib/utils.js:
- allowIsBoardMemberWithWriteAccessByCard -> async
- allowIsBoardMemberByCard -> async
- canUpdateCard (cards.js) -> async
- permissionHelper.allow (integrations.js) -> async
2026-03-22 17:44:29 +02:00
Harry Adel e8fee653c3 Meteor 3.4 migration: Phases 0-3
Phase 0 - Pre-migration cleanup:
- Remove msavin:usercache (unused), es5-shim, http@2.0.0, fibers
- Replace Meteor.wrapAsync in counters.js, exporter.js, attachmentUrlValidation.js
- Replace meteorhacks:subs-manager with Meteor.subscribe in 6 client files

Phase 1 - Create meteor-jade-loader:
- Custom Rspack/webpack loader for .jade templates
- Extracts mquandalle:jade-compiler pipeline (lexer, parser, transpiler)
- Bundles Meteor vendor packages (htmljs, html-tools, blaze-tools, spacebars-compiler)

Phase 2 - Package version bumps & replacements:
- Remove mquandalle:jade, dburles:collection-helpers, aldeed:simple-schema
- Remove communitypackages:picker (Meteor 3 bundles Express)
- Replace simple:json-routes -> communitypackages:json-routes
- Replace simple:rest-accounts-password -> communitypackages:rest-accounts-password
- Add collection-helpers shim at imports/collectionHelpers.js
- Convert Picker.route() to WebApp.handlers.get() in export files + sandstorm.js
- Add api.versionsFrom(['2.16', '3.0']) to all 9 custom packages
- Remove version pins from .meteor/packages

Phase 3 - Express 5 migration:
- Rename WebApp.connectHandlers -> WebApp.handlers (20 occurrences)
- Rename WebApp.rawConnectHandlers -> WebApp.rawHandlers (5 occurrences)
- Convert regexp route patterns to Express 5 named params in attachmentApi.js, avatarServer.js
2026-03-22 17:17:48 +02:00
Harry Adel 0d3f3474d1 Merge remote-tracking branch 'fork/use-email-sendAsync' into meteor-3-migration 2026-03-22 15:07:38 +02:00
Harry Adel 6ecb6bdaa7 Merge remote-tracking branch 'fork/remove-meteorhacks-aggregate' into meteor-3-migration 2026-03-22 15:07:33 +02:00
Harry Adel 073c297490 Remove unused meteorhacks:aggregate package for Meteor 3.0 migration
The only aggregation usage (metrics.js) already calls rawCollection().aggregate()
directly — the native MongoDB driver method — not the monkey-patched method from
this package. Convert to async/await (replacing Promise.await) for Meteor 3.0
Fibers removal compatibility.
2026-03-22 03:02:09 +02:00
909 changed files with 75063 additions and 39985 deletions

No files matched your search

+1 -1
View File
@@ -251,7 +251,7 @@ gosu wekan:wekan /home/wekan/.meteor/meteor -- help
# Build app (Development)
cd /home/wekan/app
gosu wekan:wekan /home/wekan/.meteor/meteor add standard-minifier-js
gosu wekan:wekan /home/wekan/.meteor/meteor npm install
gosu wekan:wekan /home/wekan/.meteor/meteor npm install --no-audit
# Put back the original tar
mv $(which tar)~ $(which tar)
+1 -4
View File
@@ -93,7 +93,6 @@
"Template": false,
"$": false,
"_": false,
"autosize": false,
"Avatar": true,
"Avatars": true,
@@ -111,8 +110,7 @@
"Ps": true,
"Restivus": false,
"SimpleSchema": false,
"SubsManager": false,
"T9n": false,
"T9n": false,
"TAPi18n": false,
"AccountsTemplates": true,
"Activities": true,
@@ -146,7 +144,6 @@
"Checklists": true,
"Settings": true,
"InvitationCodes": true,
"JsonRoutes": true,
"Authentication": true,
"Integrations": true,
"HTTP": true,
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
+481
View File
@@ -0,0 +1,481 @@
name: Release All Platforms
# Triggered when maintainer pushes a version tag, e.g.: git tag v8.43 && git push --tags
# Does not work:
# Required GitHub Secrets:
# QUAY_USERNAME - Quay.io username
# QUAY_PASSWORD - Quay.io password or token
# DOCKERHUB_USERNAME - Docker Hub username
# DOCKERHUB_TOKEN - Docker Hub access token
# SNAP_STORE_LOGIN - Snapcraft store credentials: run 'snapcraft export-login --snaps wekan --channels stable -' and base64-encode the output
on:
push:
tags:
- 'v*.*'
permissions:
contents: write
packages: write
jobs:
# ─── 1. Extract version and release notes ───────────────────────────────────
prepare:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
changelog: ${{ steps.changelog.outputs.content }}
steps:
- uses: actions/checkout@v6
- name: Extract version from tag
id: version
run: echo "version=${GITHUB_REF_NAME#v}" >> $GITHUB_OUTPUT
- name: Extract release notes from CHANGELOG.md
id: changelog
run: |
VERSION="${{ steps.version.outputs.version }}"
# Try to find the version-specific section first, then fall back to Upcoming
NOTES=$(python3 - <<PYEOF
import re, sys
version = "$VERSION"
with open("CHANGELOG.md", encoding="utf-8") as f:
content = f.read()
# Pattern: section heading through (but not including) the next heading
patterns = [
rf"(# v{re.escape(version)} .*?)(?=\n# v[0-9]|\Z)",
r"(# Upcoming WeKan.*?)(?=\n# v[0-9]|\Z)",
]
for pat in patterns:
m = re.search(pat, content, re.DOTALL)
if m:
print(m.group(1).strip())
sys.exit(0)
print(f"WeKan v{version}")
PYEOF
)
echo "content<<GHEOF" >> $GITHUB_OUTPUT
echo "$NOTES" >> $GITHUB_OUTPUT
echo "GHEOF" >> $GITHUB_OUTPUT
# ─── 2. Build amd64 bundle (full Meteor 3 build) ────────────────────────────
# Meteor 3.5-beta.7 installer supports only: Linux x86_64, Linux aarch64, macOS, Windows.
# It explicitly rejects armhf, s390x, and ppc64le at install time.
# Therefore the full Meteor build always runs here on x86_64.
# All other Linux architectures then rebuild only the native npm modules
# from this bundle (much faster than a full Meteor build).
# Node.js version must match what Meteor 3.5-beta.7 bundles: 24.14.1 (ABI 143).
build-amd64:
needs: prepare
runs-on: ubuntu-24.04
timeout-minutes: 180
env:
VERSION: ${{ needs.prepare.outputs.version }}
steps:
- uses: actions/checkout@v6
- name: Cache Meteor installation
uses: actions/cache@v5
with:
path: ~/.meteor
key: meteor-${{ hashFiles('.meteor/release') }}-${{ hashFiles('.meteor/packages') }}
restore-keys: meteor-
- name: Cache npm packages
uses: actions/cache@v5
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential g++ make python3 curl wget zip unzip
# Pin to Node 24.14.1 — the exact version bundled by Meteor 3.5-beta.7.
# Using the same version for build and npm rebuild ensures consistent native module ABI.
- uses: actions/setup-node@v6
with:
node-version: '24.14.1'
- name: Install Meteor
run: |
if [ ! -f ~/.meteor/meteor ]; then
curl https://install.meteor.com/ | sh
fi
echo "$HOME/.meteor" >> $GITHUB_PATH
- name: Build WeKan bundle
run: |
rm -rf .build node_modules .meteor/local/bundler-cache
meteor npm install
meteor build .build --directory
- name: Install dependencies in bundle
run: |
cd .build/bundle/programs/server
meteor npm install
- name: Create amd64 zip
run: |
cd .build
zip -r wekan-${VERSION}-amd64.zip bundle
- uses: actions/upload-artifact@v7
with:
name: bundle-amd64
path: .build/wekan-${{ needs.prepare.outputs.version }}-amd64.zip
retention-days: 1
# ─── 3. Build arm64 bundle (rebuild native modules natively) ─────────────────
# Meteor 3.5-beta.7 does support Linux aarch64, but running a full Meteor build a
# second time would double the build time for the same result. npm rebuild on
# the native arm64 GitHub Actions runner is much faster and produces the same
# platform-specific native modules.
build-arm64:
needs: [prepare, build-amd64]
runs-on: ubuntu-24.04-arm
timeout-minutes: 60
env:
VERSION: ${{ needs.prepare.outputs.version }}
steps:
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential g++ make python3 curl unzip zip
- uses: actions/setup-node@v6
with:
node-version: '24.14.1'
- name: Download amd64 bundle
uses: actions/download-artifact@v8
with:
name: bundle-amd64
- name: Rebuild native modules for arm64
run: |
unzip wekan-${VERSION}-amd64.zip
cd bundle/programs/server
rm -rf node_modules
npm install
cd ../..
- name: Clean up and create arm64 zip
run: |
zip -r wekan-${VERSION}-arm64.zip bundle
- uses: actions/upload-artifact@v7
with:
name: bundle-arm64
path: wekan-${{ needs.prepare.outputs.version }}-arm64.zip
retention-days: 1
# ─── 4. Build s390x bundle (rebuild native modules via QEMU container) ───────
# Meteor 3.5-beta.7 does NOT support s390x (installer exits with error on that arch).
# GitHub Actions has no native s390x runners.
# Solution: QEMU emulation + node:24.14.1-slim (s390x variant) for npm rebuild.
# node:24.14.1-slim is pinned to match Meteor 3.5-beta.7's bundled Node.js version exactly.
build-s390x:
needs: [prepare, build-amd64]
runs-on: ubuntu-latest
timeout-minutes: 90
env:
VERSION: ${{ needs.prepare.outputs.version }}
steps:
- name: Set up QEMU for s390x
uses: docker/setup-qemu-action@v4
with:
platforms: s390x
- name: Install unzip/zip
run: sudo apt-get update && sudo apt-get install -y unzip zip
- name: Download amd64 bundle
uses: actions/download-artifact@v8
with:
name: bundle-amd64
- name: Rebuild native modules for s390x in container
run: |
unzip wekan-${VERSION}-amd64.zip
rm -rf bundle/programs/server/node_modules
# node:24.14.1-slim s390x — same Node.js version as Meteor 3.5-beta.7 bundled
docker run --rm --platform linux/s390x \
-v "$(pwd)/bundle:/bundle" \
-w /bundle/programs/server \
node:24.14.1-slim \
sh -c "apt-get update -qq && apt-get install -y -q build-essential python3 && npm install"
- name: Clean up and create s390x zip
run: |
zip -r wekan-${VERSION}-s390x.zip bundle
- uses: actions/upload-artifact@v7
with:
name: bundle-s390x
path: wekan-${{ needs.prepare.outputs.version }}-s390x.zip
retention-days: 1
# ─── 4b. Build ppc64le bundle (rebuild native modules via QEMU container) ────
# Meteor 3.5-beta.7 does NOT support ppc64le (installer exits with error on that arch).
# GitHub Actions has no native ppc64le runners.
# Solution: QEMU emulation + node:24.14.1-slim (ppc64le variant) for npm rebuild.
build-ppc64le:
needs: [prepare, build-amd64]
runs-on: ubuntu-latest
timeout-minutes: 90
env:
VERSION: ${{ needs.prepare.outputs.version }}
steps:
- name: Set up QEMU for ppc64le
uses: docker/setup-qemu-action@v4
with:
platforms: ppc64le
- name: Install unzip/zip
run: sudo apt-get update && sudo apt-get install -y unzip zip
- name: Download amd64 bundle
uses: actions/download-artifact@v8
with:
name: bundle-amd64
- name: Rebuild native modules for ppc64le in container
run: |
unzip wekan-${VERSION}-amd64.zip
rm -rf bundle/programs/server/node_modules
# node:24.14.1-slim ppc64le — same Node.js version as Meteor 3.5-beta.7 bundled
docker run --rm --platform linux/ppc64le \
-v "$(pwd)/bundle:/bundle" \
-w /bundle/programs/server \
node:24.14.1-slim \
sh -c "apt-get update -qq && apt-get install -y -q build-essential python3 && npm install"
- name: Clean up and create ppc64le zip
run: |
zip -r wekan-${VERSION}-ppc64le.zip bundle
- uses: actions/upload-artifact@v7
with:
name: bundle-ppc64le
path: wekan-${{ needs.prepare.outputs.version }}-ppc64le.zip
retention-days: 1
# ─── 4c. Build armhf bundle (rebuild native modules via QEMU container) ──────
# Meteor 3.5-beta.7 does NOT support arm/v7 (armhf). Installer explicitly rejects it.
# GitHub Actions has no native arm/v7 runners.
# Solution: QEMU emulation + node:24.14.1-slim (arm/v7 variant) for npm rebuild.
# arm/v7 under QEMU is slowest — timeout set to 120 min.
build-armhf:
needs: [prepare, build-amd64]
runs-on: ubuntu-latest
timeout-minutes: 120
env:
VERSION: ${{ needs.prepare.outputs.version }}
steps:
- name: Set up QEMU for arm/v7
uses: docker/setup-qemu-action@v4
with:
platforms: arm
- name: Install unzip/zip
run: sudo apt-get update && sudo apt-get install -y unzip zip
- name: Download amd64 bundle
uses: actions/download-artifact@v8
with:
name: bundle-amd64
- name: Rebuild native modules for armhf in container
run: |
unzip wekan-${VERSION}-amd64.zip
rm -rf bundle/programs/server/node_modules
# node:24.14.1-slim arm/v7 — same Node.js version as Meteor 3.5-beta.7 bundled
docker run --rm --platform linux/arm/v7 \
-v "$(pwd)/bundle:/bundle" \
-w /bundle/programs/server \
node:24.14.1-slim \
sh -c "apt-get update -qq && apt-get install -y -q build-essential python3 && npm install"
- name: Clean up and create armhf zip
run: |
zip -r wekan-${VERSION}-armhf.zip bundle
- uses: actions/upload-artifact@v7
with:
name: bundle-armhf
path: wekan-${{ needs.prepare.outputs.version }}-armhf.zip
retention-days: 1
# ─── 5. Build win64 bundle (rebuild native modules on Windows) ───────────────
build-win64:
needs: [prepare, build-amd64]
runs-on: windows-latest
timeout-minutes: 60
env:
VERSION: ${{ needs.prepare.outputs.version }}
steps:
- uses: actions/setup-node@v6
with:
node-version: '24.14.1'
## With Node 24 these are already preinstalled at GitHub Windows runners
#- name: Install node-gyp and build tools
# run: |
# npm install -g windows-build-tools --vs2019 2>$null; exit 0
- name: Install 7-zip
run: choco install -y 7zip
- name: Download amd64 bundle
uses: actions/download-artifact@v8
with:
name: bundle-amd64
- name: Rebuild native modules for Windows
shell: cmd
run: |
7z x wekan-%VERSION%-amd64.zip
cd bundle\programs\server
rmdir /s /q node_modules
npm install
cd ..\..\..
- name: Create win64 zip
shell: cmd
run: 7z a wekan-%VERSION%-win64.zip bundle
- uses: actions/upload-artifact@v7
with:
name: bundle-win64
path: wekan-${{ needs.prepare.outputs.version }}-win64.zip
retention-days: 1
# ─── 6. Create GitHub Release and upload all bundles ────────────────────────
release:
needs: [prepare, build-amd64, build-arm64, build-s390x, build-ppc64le, build-armhf, build-win64]
runs-on: ubuntu-latest
env:
VERSION: ${{ needs.prepare.outputs.version }}
steps:
- name: Download all bundles
uses: actions/download-artifact@v8
with:
pattern: bundle-*
merge-multiple: true
- name: Create GitHub Release with all bundles
uses: softprops/action-gh-release@v2
with:
name: v${{ needs.prepare.outputs.version }}
body: ${{ needs.prepare.outputs.changelog }}
files: |
wekan-${{ needs.prepare.outputs.version }}-amd64.zip
wekan-${{ needs.prepare.outputs.version }}-arm64.zip
wekan-${{ needs.prepare.outputs.version }}-s390x.zip
wekan-${{ needs.prepare.outputs.version }}-ppc64le.zip
wekan-${{ needs.prepare.outputs.version }}-armhf.zip
wekan-${{ needs.prepare.outputs.version }}-win64.zip
# ─── 7. Build and push multi-platform Docker image ──────────────────────────
#docker:
# needs: [prepare, release]
# runs-on: ubuntu-latest
# env:
# VERSION: ${{ needs.prepare.outputs.version }}
# steps:
# - uses: actions/checkout@v6
#
# - name: Set up QEMU
# uses: docker/setup-qemu-action@v4
#
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v3
#
# - name: Log in to Docker Hub
# uses: docker/login-action@v4
# with:
# username: ${{ secrets.DOCKERHUB_USERNAME }}
# password: ${{ secrets.DOCKERHUB_TOKEN }}
#
# - name: Log in to Quay.io
# uses: docker/login-action@v4
# with:
# registry: quay.io
# username: ${{ secrets.QUAY_USERNAME }}
# password: ${{ secrets.QUAY_PASSWORD }}
#
# - name: Log in to GitHub Container Registry
# uses: docker/login-action@v4
# with:
# registry: ghcr.io
# username: ${{ github.actor }}
# password: ${{ secrets.GITHUB_TOKEN }}
#
# - name: Build and push Docker image to all registries
# uses: docker/build-push-action@v7
# with:
# context: .
# platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/ppc64le,linux/s390x
# push: true
# build-args: VERSION=${{ needs.prepare.outputs.version }}
# tags: |
# #wekanteam/wekan:v${{ needs.prepare.outputs.version }}
# #wekanteam/wekan:latest
# quay.io/wekan/wekan:v${{ needs.prepare.outputs.version }}
# quay.io/wekan/wekan:latest
# ghcr.io/wekan/wekan:v${{ needs.prepare.outputs.version }}
# ghcr.io/wekan/wekan:latest
# cache-from: type=gha
# cache-to: type=gha,mode=max
# ─── 8. Build and publish snap packages (all architectures via Launchpad) ────
# Uses snapcraft remote-build on Launchpad's native builders (more reliable
# than QEMU for snap builds). Each architecture bundles its own database:
# amd64, arm64 — MongoDB 7.x prebuilt native binary
# armhf, s390x, ppc64el — MongoDB 7.x amd64 binary run via qemu-x86_64-static
# (no prebuilt MongoDB binaries exist for these arches)
#snap:
# needs: [prepare, release]
# runs-on: ubuntu-latest
# env:
# VERSION: ${{ needs.prepare.outputs.version }}
# steps:
# - uses: actions/checkout@v6
#
# - name: Install snapcraft
# run: sudo snap install snapcraft --classic
#
# - name: Remote-build snap for all architectures via Launchpad
# env:
# SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_STORE_LOGIN }}
# run: |
# snapcraft remote-build \
# --build-for=amd64,arm64,armhf,s390x,ppc64el \
# --launchpad-accept-public-upload
#
# - name: Upload all snap files as artifacts
# uses: actions/upload-artifact@v7
# with:
# name: snaps-all-arches
# path: "*.snap"
# retention-days: 30
#
# # Publish to edge — NOT stable.
# # Stable channel holds the old WeKan version until database migrations are ready.
# # Promote to stable manually via the Snap Store dashboard when migrations exist.
# - name: Publish all snaps to Snap Store (candidate, beta, edge)
# env:
# SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAP_STORE_LOGIN }}
# run: |
# for snap_file in *.snap; do
# echo "Publishing $snap_file ..."
# snapcraft upload --release=edge "$snap_file"
# done
#
# - name: Upload all snaps to GitHub Release
# uses: softprops/action-gh-release@v2
# with:
# files: "*.snap"
+7 -2
View File
@@ -124,13 +124,18 @@ jobs:
restore-keys: |
v1-meteor_build_cache-
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '24.14.1'
- name: Setup meteor
uses: meteorengineer/setup-meteor@v3
with:
meteor-release: '2.2'
meteor-release: '3.5-beta.7'
- name: Install NPM Dependencies
run: meteor npm ci
run: meteor npm install
- name: Run Tests
run: sh ./test-wekan.sh -cv
+14
View File
@@ -35,7 +35,21 @@ ehthumbs.db
.devcontainer/volumes*/
.coverage
# Rspack build artifacts
_build/
public/build-assets/
public/build-chunks/
private/build-assets/
private/build-chunks/
public/client-rspack.js.map
# Helm chart
# Chart dependencies
/helm/wekan/**/*.tgz
/helm/wekan/charts
# Meteor Modern-Tools build context directories
_build
*/build-assets
*/build-chunks
.rsdoctor
+35 -44
View File
@@ -3,32 +3,36 @@
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
meteor-base@1.5.1
meteor-base@1.5.2
# Build system
ecmascript
standard-minifier-js
mquandalle:jade
ecmascript@0.17.0
standard-minifier-js@3.2.0
rspack@1.0.0
# Polyfills
es5-shim@4.8.0
blaze
blaze@3.0.2
# Collections
aldeed:collection2
aldeed:schema-index@4.0.0-beta.5
reywood:publish-composite
dburles:collection-helpers
mongo@1.16.8
mongo@2.3.0-beta350.7
# The holy trifecta - this is a replacement for subs-manager
# for IndexedDB persistence and auto-sync
jam:offline
# for optimistic methods with offline queuing
jam:method
# global subscription caching
jam:pub-sub
# Account system
accounts-password@2.4.0
communitypackages:core@2.0.3
communitypackages:unstyled@2.0.1
accounts-password@3.2.3-beta350.7
communitypackages:core
communitypackages:unstyled
communitypackages:flow-routing
# Routing
ostrio:flow-router-extra
simple:rest-accounts-password
wekan-ldap
wekan-accounts-cas
wekan-accounts-sandstorm
@@ -37,42 +41,29 @@ wekan-oidc
wekan-accounts-oidc
# Utilities
check@1.3.2
jquery@3.0.0!
random@1.2.1
reactive-dict@1.3.1
session@1.2.1
tracker@1.3.3
audit-argument-checks@1.0.7
http@2.0.0! # force new http package
# Datepicker (disabled - using native HTML inputs)
# wekan-bootstrap-datepicker
check@1.5.0
random@1.2.2
reactive-dict@1.3.2
session@1.2.2
tracker@1.3.4
audit-argument-checks@1.0.8
# UI components
ostrio:i18n
reactive-var@1.0.12
meteor-autosize
shell-server@0.5.0
email@2.2.5
dynamic-import@0.7.3
msavin:usercache
# Keep stylus in 1.1.0, because building v2 takes extra 52 minutes.
meteorhacks:subs-manager
reactive-var@1.0.13
shell-server@0.7.0
email@3.2.0-beta350.7
dynamic-import@0.7.4
wekan-markdown
quave:synced-cron
ostrio:cookies
ostrio:files@2.3.0
lmieulet:meteor-coverage
meteortesting:mocha@2.0.3
aldeed:simple-schema
ostrio:files
# lmieulet:meteor-coverage
meteortesting:mocha
matb33:collection-hooks
simple:json-routes
service-configuration@1.3.2
communitypackages:picker
minifier-css@1.6.4
ejson@1.1.3
logging@1.3.3
service-configuration@1.3.5
minifier-css@2.0.1
ejson@1.2.0-beta350.7
logging@1.3.6
wekan-fullcalendar
wekan-fontawesome
+1 -1
View File
@@ -1 +1 @@
METEOR@2.16
METEOR@3.5-beta.7
+102 -114
View File
@@ -1,126 +1,114 @@
accounts-base@2.2.11
accounts-oauth@1.4.4
accounts-password@2.4.0
aldeed:collection2@2.10.0
aldeed:collection2-core@1.2.0
aldeed:schema-deny@1.1.0
aldeed:schema-index@1.1.1
aldeed:simple-schema@1.5.4
allow-deny@1.1.1
audit-argument-checks@1.0.7
autoupdate@1.8.0
babel-compiler@7.10.5
babel-runtime@1.5.1
base64@1.0.12
binary-heap@1.0.11
blaze@2.7.1
blaze-tools@1.1.3
boilerplate-generator@1.7.2
caching-compiler@1.2.2
caching-html-compiler@1.2.1
callback-hook@1.5.1
check@1.4.1
accounts-base@3.3.0-beta350.7
accounts-oauth@1.4.6
accounts-password@3.2.3-beta350.7
aldeed:collection2@4.1.5
aldeed:schema-index@4.0.0
aldeed:simple-schema@1.13.1
allow-deny@2.1.0
audit-argument-checks@1.0.8
autoupdate@2.0.1
babel-compiler@7.13.0
babel-runtime@1.5.2
base64@1.0.13
binary-heap@1.0.12
blaze@3.0.2
blaze-tools@2.0.0
boilerplate-generator@2.1.0
caching-compiler@2.0.1
caching-html-compiler@2.0.0
callback-hook@1.6.1
check@1.5.0
communitypackages:core@2.0.3
communitypackages:flow-routing@2.1.0
communitypackages:picker@1.1.1
communitypackages:unstyled@2.0.1
dburles:collection-helpers@1.1.0
ddp@1.4.1
ddp-client@2.6.2
ddp-common@1.4.1
ddp-rate-limiter@1.2.1
ddp-server@2.7.1
deps@1.0.12
diff-sequence@1.1.2
dynamic-import@0.7.3
ecmascript@0.16.8
ecmascript-runtime@0.8.1
ecmascript-runtime-client@0.12.1
ecmascript-runtime-server@0.11.0
ejson@1.1.3
email@2.2.6
es5-shim@4.8.0
fetch@0.1.4
geojson-utils@1.0.11
hot-code-push@1.0.4
html-tools@1.1.3
htmljs@1.1.1
http@2.0.0
id-map@1.1.1
inter-process-messaging@0.1.1
jquery@3.0.0
lmieulet:meteor-coverage@1.1.4
localstorage@1.2.0
logging@1.3.4
matb33:collection-hooks@1.3.0
mdg:validation-error@0.5.1
meteor@1.11.5
meteor-autosize@5.0.1
meteor-base@1.5.1
meteorhacks:picker@1.0.3
meteorhacks:subs-manager@1.6.4
core-runtime@1.0.0
ddp@1.4.2
ddp-client@3.2.0-beta350.7
ddp-common@1.4.4
ddp-rate-limiter@1.3.0-beta350.7
ddp-server@3.2.0-beta350.7
diff-sequence@1.1.3
dynamic-import@0.7.4
ecmascript@0.17.0
ecmascript-runtime@0.8.3
ecmascript-runtime-client@0.12.3
ecmascript-runtime-server@0.11.1
ejson@1.2.0-beta350.7
email@3.2.0-beta350.7
es5-shim@4.8.1
facts-base@1.0.2
fetch@0.1.6
geojson-utils@1.0.12
hot-code-push@1.0.5
html-tools@2.0.0
htmljs@2.0.1
http@1.4.4
id-map@1.2.0
inter-process-messaging@0.1.2
jam:method@1.9.1
jam:offline@0.4.1
jam:pub-sub@0.5.1
jquery@3.0.2
lai:collection-extensions@1.0.0
localstorage@1.2.1
logging@1.3.6
matb33:collection-hooks@2.1.0
meteor@2.2.0
meteor-base@1.5.2
meteortesting:browser-tests@1.4.2
meteortesting:mocha@2.1.0
meteortesting:mocha-core@8.0.1
minifier-css@1.6.4
minifier-js@2.8.0
minifiers@1.1.8-faster-rebuild.0
minimongo@1.9.4
modern-browsers@0.1.10
modules@0.20.0
modules-runtime@0.13.1
mongo@1.16.10
mongo-decimal@0.1.3
mongo-dev-server@1.1.0
mongo-id@1.0.8
mquandalle:jade@0.4.9
mquandalle:jade-compiler@0.4.5
msavin:usercache@1.8.0
npm-mongo@4.17.2
oauth@2.2.1
oauth2@1.3.2
observe-sequence@1.0.21
ordered-dict@1.1.0
ostrio:cookies@2.7.2
minifier-css@2.0.1
minifier-js@3.1.0
minimongo@2.1.0-beta350.7
modern-browsers@0.2.3
modules@0.20.3
modules-runtime@0.13.2
mongo@2.3.0-beta350.7
mongo-decimal@0.2.0
mongo-dev-server@1.1.1
mongo-id@1.0.9
npm-mongo@6.16.1
oauth@3.0.2
oauth2@1.3.3
observe-sequence@2.0.0
ordered-dict@1.2.0
ostrio:cookies@2.9.1
ostrio:cstorage@4.0.1
ostrio:files@2.3.3
ostrio:flow-router-extra@3.10.1
ostrio:files@3.0.1
ostrio:flow-router-extra@3.12.1
ostrio:i18n@3.2.1
promise@0.12.2
quave:synced-cron@2.2.1
raix:eventemitter@0.1.3
random@1.2.1
rate-limit@1.1.1
react-fast-refresh@0.2.8
reactive-dict@1.3.1
reactive-var@1.0.12
reload@1.3.1
retry@1.1.0
promise@1.0.0
quave:synced-cron@2.4.0
raix:eventemitter@1.0.0
random@1.2.2
rate-limit@1.2.0-beta350.7
react-fast-refresh@0.3.0
reactive-dict@1.3.2
reactive-var@1.0.13
reload@1.3.2
retry@1.1.1
reywood:publish-composite@1.9.0
routepolicy@1.1.1
service-configuration@1.3.4
session@1.2.1
sha@1.0.9
shell-server@0.5.0
simple:authenticate-user-by-token@1.2.1
simple:json-routes@2.3.1
simple:rest-accounts-password@1.2.2
simple:rest-bearer-token-parser@1.1.1
simple:rest-json-error-handler@1.1.1
socket-stream-client@0.5.2
spacebars@1.4.1
spacebars-compiler@1.3.1
standard-minifier-js@2.8.1
templating@1.4.1
templating-compiler@1.4.1
templating-runtime@1.5.0
templating-tools@1.2.2
tracker@1.3.3
typescript@4.9.5
underscore@1.6.1
url@1.3.2
webapp@1.13.8
webapp-hashing@1.1.1
routepolicy@1.1.2
rspack@1.0.0
service-configuration@1.3.5
session@1.2.2
sha@1.0.10
shell-server@0.7.0
socket-stream-client@0.7.0-beta350.7
spacebars@2.0.0
spacebars-compiler@2.0.0
standard-minifier-js@3.2.0
templating@1.4.4
templating-compiler@2.0.0
templating-runtime@2.0.1
templating-tools@2.0.0
tools-core@1.0.0
tracker@1.3.4
typescript@5.9.3
url@1.3.5
webapp@2.2.0-beta350.7
webapp-hashing@1.1.2
wekan-accounts-cas@0.2.0
wekan-accounts-lockout@1.1.0
wekan-accounts-oidc@1.0.10
+501 -10
View File
@@ -2,6 +2,7 @@
Newest WeKan at these platforms:
- [Upgrade WeKan](https://wekan.fi/upgrade/)
- [bundle zip at releases](https://github.com/wekan/wekan/releases), docs at
- [Linux amd64, arm64, s390x](https://github.com/wekan/wekan/blob/main/docs/Platforms/FOSS/RaspberryPi/Raspberry-Pi.md)
- [Windows amd64, works also at Windows arm64](https://github.com/wekan/wekan/blob/main/docs/Platforms/Propietary/Windows/Offline.md)
@@ -11,23 +12,513 @@ Newest WeKan at these platforms:
- Docker amd64/arm64/s390x
- Kubernetes Docker amd64
- Bitnami MongoDB Docker images do not exist anymore. [MongoDump/MongoRestore to groundhog2k MongoDB images](https://github.com/wekan/charts/issues/45)
- Fixing other platforms In Progress.
Fixing other platforms In Progress.
Versions:
- [Node.js 14.21.4](https://github.com/wekan/node-v14-esm/releases/tag/v14.21.4) or [Node.js 14.21.3](https://nodejs.org/dist/latest-v14.x/)
- MongoDB 6.x and 7.x, or [FerretDB2/PostgreSQL](https://github.com/wekan/wekan/blob/main/docs/Databases/FerretDB2-PostgreSQL.md)
- WeKan 8.75 and newer uses Meteor 3.5-beta.7, [Node.js 24.x](https://nodejs.org/dist/latest-v24.x/),
MongoDB 7.x (or 6.x), or [FerretDB2/PostgreSQL](https://github.com/wekan/wekan/blob/main/docs/Databases/FerretDB2-PostgreSQL.md)
- WeKan 8.43 upgraded to Meteor 3.x, huge thanks to harryadel:
- https://harryadel.com/dev-diary-24/
- https://harryadel.com/dev-diary-25/
- https://harryadel.com/dev-diary-26/
- WeKan 8.00-8.24 used Colorful Unicode Emoji Icons, versions before and after use mostly Font Awesome 4.7 icons.
- WeKan 8.00-8.06 had wrong raw database directory setting /var/snap/wekan/common/wekan and some cards were not visible,
it was fixed at WeKan 8.07 where database directory is back to /var/snap/wekan/common and all cards are visible.
[Upgrade WeKan](https://wekan.fi/upgrade/)
# v8.78 2026-04-09 WeKan ® release
WeKan 8.00-8.06 had wrong raw database directory setting /var/snap/wekan/common/wekan and some cards were not visible.
Those are fixed at WeKan 8.07 where database directory is back to /var/snap/wekan/common and all cards are visible.
This release adds the the following updates:
WeKan 8.00-8.24 used Colorful Unicode Emoji Icons, versions before and after use mostly Font Awesome 4.7 icons.
- Upgraded to Meteor 3.5-beta.7 and Node.js 24.14.1. Now prefers MongoDB Change Streams at Snap Candidate and docker-compose.yml.
[Part 1](https://github.com/wekan/wekan/commit/14f9ac39ad93db4f7c06721f1efd4c18e53cc5f8),
[Part 2](https://github.com/wekan/wekan/commit/b1707b8691d0ba5f355e78e250765c8a7657c3d7).
Thanks to Meteor developers.
- [Enhance translations with new key:value pairs](https://github.com/wekan/wekan/pull/6259).
Thanks to KhaoulaMaleh.
- [Refine translations](https://github.com/wekan/wekan/pull/6261).
Thanks to KhaoulaMaleh.
- [Updated to Node.js 24.x at release scripts](https://github.com/wekan/wekan/commit/bdb92744854cc211866631746c63d748d4a93cfa).
Thanks to xet7.
- [Fix GitHub Actions builds](https://github.com/wekan/wekan/commit/bc88c0dfc2742154e94861201d239bc4b3c51cef).
Thanks to xet7.
Upgrading to Meteor 3 progress:
and fixes the following bugs:
- https://harryadel.com/dev-diary-24/
- https://harryadel.com/dev-diary-25/
- [Fixed Card settings checkboxes](https://github.com/wekan/wekan/pull/6260).
Thanks to KhaoulaMaleh.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.74 2026-04-08 WeKan ® release
This release fixes the following bugs:
- [Fix can not save Card Data. Part 2](https://github.com/wekan/wekan/commit/0328e70d5f4a277722635e3fd9ea78312d1359cf).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.73 2026-04-07 WeKan ® release
This release fixes the following bugs:
- [Fix Link to Windows release doesn't work](https://github.com/wekan/wekan/commit/9c695302565073b971a3521c0a94a380641cc436).
Thanks to hmeunier95 and xet7.
- [Fix 8.72 Bug: Can not save Card Date](https://github.com/wekan/wekan/commit/8da0f34ee6ca66884ab8a2dcdf1a724a0c9fcc81).
Thanks to xet7.
- [Removed exclude-archs from rebuild-wekan.sh, it is not necessary anymore](https://github.com/wekan/wekan/commit/028b404377651d34e334b048ecc960d3ff5b2eab).
Thanks to xet7.
- [Fix 8.72 Bug: LDAP login does not work. ReactiveCache is not defined](https://github.com/wekan/wekan/commit/bcbf4771ea88fb617ddc44650c8ac139b0cfc998).
Thanks to poc-sm and xet7.
- [Fix Error in choosing member or assignee in a card](https://github.com/wekan/wekan/commit/7f0c42cca555dd2dca04d3b6413c0080ea75e96e).
Thanks to hmeunier95 and xet7.
- [Fix Search All Boards result opened card missing Copy Card Link etc that can be used to open correct card](https://github.com/wekan/wekan/commit/304a007d078c04dac7de5074a856c4f0cc2c0aa7).
Thanks bcook-konza and xet7.
- [Fix Checklists add and Subtasks reorder. Fix Board Activity part 1](https://github.com/wekan/wekan/commit/08ebad0fe4a2b8a7b93d75a9a9c2317ce9374fea).
Thanks bcook-konza and xet7.
- [Fix Change list inside or card](https://github.com/wekan/wekan/commit/aea8c285c246b490ac259ceafe7d9f733681c20f).
Thanks to mimZD and xet7.
- [Fix board and card activities. Part 2](https://github.com/wekan/wekan/commit/c7408ad8d147f36cfdbb71089af688bef91c3ecf).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.72 2026-04-07 WeKan ® release
This release adds the following new features and fixes:
- [Fix Impersonate User. Show/Edit Avatar at Admin Panel/People/People](https://github.com/wekan/wekan/commit/3ac7c9057e1e53da9f285149ce832ad430b3bc30).
Thanks to ahlgrimma, DnT1mmer, robbagithub, norrig and xet7.
and adds the following updates:
- [Bump actions/download-artifact from 4 to 8](https://github.com/wekan/wekan/pull/6247).
Thanks to dependabot.
- [Bump actions/upload-artifact from 4 to 7](https://github.com/wekan/wekan/pull/6248).
Thanks to dependabot.
- [Bump actions/cache from 4 to 5](https://github.com/wekan/wekan/pull/6249).
Thanks to dependabot.
- [Bump docker/login-action from 4.0.0 to 4.1.0](https://github.com/wekan/wekan/pull/6250).
Thanks to dependabot.
- [Updated release scripts](https://github.com/wekan/wekan/commit/87f592c67ffb1962ec8acb0d1676b8df6ac42a5d).
Thanks to xet7.
and fixes the following bugs:
- [Fix Admin Panel / Version not showing name of MongoDB storage engine and status of MongoDB Oplog enabled](https://github.com/wekan/wekan/commit/3b98f862bcda3e8a9b0d0131b91cdfa59986c2d8).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.71 2026-04-06 WeKan ® release
This release fixes the following bugs:
- [Fix: Added missing null check to notification](https://github.com/wekan/wekan/commit/5df924f416083a50795443687e1c28f402dc8132).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.70 2026-04-06 WeKan ® release
This release fixes the following bugs:
- [Convert upsert calls to async in authentication.js](https://github.com/wekan/wekan/commit/dfcaaf0ae719e8d8b918ac632100293a6e401371).
Thanks to snowsky and xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.69 2026-04-06 WeKan ® release
This release fixes the following bugs:
- [Try to fix Snap to start MongoDB OpLog correctly](https://github.com/wekan/wekan/commit/fbdfcead50fb599d4fafecc2f5847c46c73adf72).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.68 2026-04-06 WeKan ® release
This release fixes the following bugs:
- [Try to fix Snap to start MongoDB OpLog correctly](https://github.com/wekan/wekan/commit/9e316662800fc3b5e38b1176e70a50b18d73d2a6).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.67 2026-04-05 WeKan ® release
This release fixes the following bugs:
- [Try to fix GitHub Actions release bundle builds](https://github.com/wekan/wekan/commit/7707a5b7fc21f981f26c512cd3fea6171a57bd46).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.65 2026-04-05 WeKan ® release
This release fixes the following bugs:
- [Fix Docker Actions Test suite](https://github.com/wekan/wekan/commit/c0661506c42ac412ae9e8fc5392d160efbc446ea).
Thanks to xet7.
- [Try to fix GitHub Actions release bundle builds](https://github.com/wekan/wekan/commit/d3155d24c64fe1104afe454b756d2a6324fab5c2).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.64 2026-04-05 WeKan ® release
This release fixes the following bugs:
- [Try to fix GitHub Actions release bundle builds](https://github.com/wekan/wekan/commit/7a7909b49feaaa938b5dbfc2083b0c719187859d).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.63 2026-04-05 WeKan ® release
This release fixes the following bugs:
- [Try to fix GitHub Actions release bundle builds](https://github.com/wekan/wekan/commit/6a78a178e2fcff9ff280ba44c123403e2792bb7b).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.62 2026-04-05 WeKan ® release
This release fixes the following bugs:
- [Try to fix GitHub Actions release bundle builds](https://github.com/wekan/wekan/commit/cd55c0e12ed80ddcb07895f2d787be95c440f991).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.61 2026-04-05 WeKan ® release
This release adds the following updates:
- [Updated Docker build script to check directories and repos exist](https://github.com/wekan/wekan/commit/81319fc48bbd2a91553090428455bc11aff24fbc).
Thanks to xet7.
- [Updated release scripts: Delete all GitHub Actions Caches, so that old stuff does not break builds](https://github.com/wekan/wekan/commit/f3a97b993842425e4f13bb78e5939a069d64887e).
Thanks to xet7.
and fixes the following bugs:
- [Fix: Server crash during comment deletion](https://github.com/wekan/wekan/pull/6246).
Thanks to KhaoulaMaleh.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.60 2026-04-05 WeKan ® release
This release adds the following updates:
- [Updated Docker build script](https://github.com/wekan/wekan/commit/0b59f87887c215dc34093409ccd12da9acd3ef02).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.59 2026-04-05 WeKan ® release
This release fixes the following bugs:
- [Fix docker-compose.yml to use OpLog](https://github.com/wekan/wekan/commit/30a1792c92de9d485c0b9ed3970a1187ca974576).
Thanks to xet7.
- [Fix Snap to use OpLog](https://github.com/wekan/wekan/commit/1fa72fd34ab4547e97e7e0c201bc3d50d478ef5e).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.58 2026-04-05 WeKan ® release
This release adds the following updates:
- [Updated build docs](https://github.com/wekan/wekan/commit/b3d14fb5e87545635006958686f0340028711820).
Thanks to xet7.
and fixes the following bugs:
- [Try to fix .zip Bundle, Docker and Snap](https://github.com/wekan/wekan/commit/59956ca720d2103baaa9ecf2befff2ec76430feb).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.57 2026-04-05 WeKan ® release
This release fixes the following bugs:
- [Try to fix Snap](https://github.com/wekan/wekan/commit/9f549dfcd2efa241a2ad3bdf5b0d2d942064d4e3).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.56 2026-04-05 WeKan ® release
This release fixes the following bugs:
- [Try to fix Snap](https://github.com/wekan/wekan/commit/56f26418ea9d1321fc5109c4b39104ff98d65429).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.55 2026-04-05 WeKan ® release
This release adds the following updates:
- Docs: IMPORTANT: Do NOT `npm audit fix --force`, it breaks builds.
[Part 1](https://github.com/wekan/wekan/commit/2bad17010cd445c004a228b732b17c68e18eb316),
[Part 2](https://github.com/wekan/wekan/commit/9fa6574c80589218569dc3c49782a1a8036aa542).
Thanks to xet7.
- [Updated dependencies](https://github.com/wekan/wekan/commit/73d69f4631216dfe504d9f05230adadc2850c4f3).
Thanks to developers of dependencies.
and fixes the following bugs:
- [Fix Cards Reports is not working](https://github.com/wekan/wekan/commit/2b02e7712ee6a8ae8af5d416ce2d288a25386fdf).
Thanks to titver968 and xet7.
- [Fix Search All Boards](https://github.com/wekan/wekan/commit/faf8453c6d10613edf32a734e549961860fcdd32).
Thanks to YosserDerbali and xet7.
- [Try to fix Snap](https://github.com/wekan/wekan/commit/8443e3511d61d8c1bb419ed5bca2e28483c5d133).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.54 2026-04-04 WeKan ® release
This release fixes the following bugs:
- [Revert v8.53 Refactor upsert calls to use async method](https://github.com/wekan/wekan/commit/811b79b641c765461f3d696ed00b5459a4747867).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.53 2026-04-04 WeKan ® release
This release fixes the following bugs:
- [Refactor upsert calls to use async method](https://github.com/wekan/wekan/pull/6243).
Thanks to snowsky.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.52 2026-04-03 WeKan ® release
This release fixes the following bugs:
- [Try to fix Docker and Snap](https://github.com/wekan/wekan/commit/03e0cfdc15c56ac0219f5e435034a1c0ad5006ae).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.51 2026-04-03 WeKan ® release
This release adds the following updates:
- [Updated release scripts](https://github.com/wekan/wekan/commit/4594711be41d1e9cf15b1f8bb070fcb36bf736ca).
Thanks to xet7.
and fixes the following bugs:
- [Fix Bug: WeKan v8.50 WRITABLE_PATH](https://github.com/wekan/wekan/commit/edc6b18b5f77320e966e56c888575bcb764096dc).
Thanks to snowsky and xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.50 2026-04-03 WeKan ® release
This release adds the following updates:
- [Uncommented build steps. Not yet tested are required dependencies included](https://github.com/wekan/wekan/commit/145a17fee5dc9430de7a60100657566a4a69bc7b).
Thanks to xet7.
- [Updated npm package @wekanteam/exceljs](https://github.com/wekan/wekan/commit/6e8589b1f96b5f0c2ddec0831b238096ce610b1e).
Thanks to xet7.
- [Updated dependencies](https://github.com/wekan/wekan/commit/bf02c9a453e27edba1c08830eb4783b9dfc7bbc2).
Thanks to developers of dependencies.
NOTE: Do not `npm audit fix --force`, it downgrades @meteorjs/rspack and breaks builds.
`npm audit` shows false info, it does not recognize that elliptic is
already updated to 6.6.1 with override at package.json like you can see with
`npm list elliptic`.
and fixes the following bugs:
- [Fix indentation for release-all.yml workflow](https://github.com/wekan/wekan/pull/6240).
Thanks to snowsky.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.49 2026-03-31 WeKan ® release
This release fixes the following bugs:
- [Fix: Missing module @meteorjs/reify/lib/runtime](https://github.com/wekan/wekan/pull/6237).
Thanks to mzch and kovacs-andras.
- [Fix docker-compose.yml](https://github.com/wekan/wekan/commit/d07fcceb6a884cf1ff0c9a2d36691af0304f36e7).
Thanks to xet7.
- [Try to fix Snap](https://github.com/wekan/wekan/commit/8b4676175caa4af36bf11567676f3c3f522f9512).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.48 2026-03-31 WeKan ® release
This release adds the following updates:
- Updated release scripts.
[Part 1](https://github.com/wekan/wekan/commit/43601c5e3e95d6026f2e558f261c02a9578dcecd),
[Part 2](https://github.com/wekan/wekan/commit/56377aaf4c5e92c1a03dc996b6c649ce17553280).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.47 2026-03-31 WeKan ® release
This release adds the following updates:
- [Updated attachments and avatar path to be at WRITABLE_PATH/files/attachments etc](https://github.com/wekan/wekan/commit/e58e99c2852dd81a040dcffc0f1f2798c4b8cf15).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.46 2026-03-31 WeKan ® release
This release adds the following updates:
- Updated release scripts.
[Part 1](https://github.com/wekan/wekan/commit/6bc68ba57bf15339b80011d131a7da65124e071d),
[Part 2](https://github.com/wekan/wekan/commit/ce52b8a0ab29805c562a2af0b79e30d970594e17).
Thanks to xet7.
- [Updated docker-compose.yml to change from FerretDB/PostgreSQL to MongoDB](https://github.com/wekan/wekan/commit/02b75824e5dc24c968959d5a64f846efe33dee4f).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.45 2026-03-31 WeKan ® release
This release adds the following updates:
- Updated release scripts.
[Part 1](https://github.com/wekan/wekan/commit/4223c2a79f4c276ab1c9734a94314f1d1d1ce033),
[Part 2](https://github.com/wekan/wekan/commit/6125822cf7244242832d1fe02f99150abb7350d0),
[Part 3](https://github.com/wekan/wekan/commit/e90b2e384c168fbb4cee389d8af9b54a5bc57734),
[Part 4](https://github.com/wekan/wekan/commit/29550cbd9335fc95e4f4ac399fe754ece8573450),
[Part 5](https://github.com/wekan/wekan/commit/8349fb6b63097970d06af73af388b030761967bd),
[Part 6](https://github.com/wekan/wekan/commit/f43b76c323c1f09cf1885251792b29229ef77d2b),
[Part 7](https://github.com/wekan/wekan/commit/e90b2e384c168fbb4cee389d8af9b54a5bc57734).
Thanks to xet7.
- Updated GitHub Actions.
[Part 1](https://github.com/wekan/wekan/commit/4b8cc0683e9dfdffb7ff7896f622afe8f908a7f5),
[Part 2](https://github.com/wekan/wekan/commit/df067f01e894f43305ad36cebdf48a1bf55ff33a),
[Part 3](https://github.com/wekan/wekan/commit/ec1e3835586bdc09d93d5049794e8657b460acef),
[Part 4](https://github.com/wekan/wekan/commit/79853a2fb7efb738f2f10b6c2e9fef69def88c48),
[Part 5](https://github.com/wekan/wekan/commit/9ae2842fa4bedbd4c132943703ef462cc353ab1a),
[Part 6](https://github.com/wekan/wekan/commit/1a5e23617de98e6b65f78dfb25c233440f818d11),
[Part 7](https://github.com/wekan/wekan/commit/c38fa1c272c32323d8a164dfcf34b898127e6b1d),
[Part 8](https://github.com/wekan/wekan/commit/d8a2e06a95713e33e5676df38951dfd3f991ecb3),
[Part 9](https://github.com/wekan/wekan/commit/e6ccbfd3b88299c52fbaad9f85fc3b438e3cdc89),
[Part 10](https://github.com/wekan/wekan/commit/7666c6c0e1660b5aba300f4e465877bcf73d8066).
Thanks to dependabot.
and fixes the following bugs:
- Try to fix Snap.
[Part 1](https://github.com/wekan/wekan/commit/e5c551f0092ccb71fafa3433bfd99911f9a2e9c0),
[Part 2](https://github.com/wekan/wekan/commit/55ec0b5ff5594c10ebbc8cba74339de2ad0557a6),
[Part 3](https://github.com/wekan/wekan/commit/fd58bb9e4dd0f825237a50043a171fc223d44101),
[Part 4](https://github.com/wekan/wekan/commit/cdf1d3305bd738fe5357a5f3a9df2f7e6858d1d0),
[Part 5](https://github.com/wekan/wekan/commit/2aa8ecfc39a6f971134784817b46ba9867bb402d),
[Part 6](https://github.com/wekan/wekan/commit/ed718f083aa0c9f82e368a5badfe73393753a617),
[Part 7](https://github.com/wekan/wekan/commit/5c4130311a063340bd89dff571fbf685cda7a3ba),
[Part 8](https://github.com/wekan/wekan/commit/33267fa2e462fcc6cd2243ae1b5b39d59b03988f),
[Part 9](https://github.com/wekan/wekan/commit/87916c20bf579e16e21fa3bd9ac7687019b277eb),
[Part 10](https://github.com/wekan/wekan/commit/12c223d2260c9bd9547ae35a394869ef90e8bc83).
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.44 2026-03-30 WeKan ® release
This release adds the following updates:
- [From WeKan v8.43 FerretDB/PostgreSQL back to MongoDB 7.x](https://github.com/wekan/wekan/commit/cf2b3e4fc95ce4619c1ba13b63c3ad9d90bf59f6).
Thanks to xet7.
- [Updated dependencies](https://github.com/wekan/wekan/commit/82f1de078f16e87028ffd101e3f80550a4b18afa).
Thanks to developers of dependencies.
- Updated release scripts.
[Part 1](https://github.com/wekan/wekan/commit/a702596bee6aa812cb69a6673ad34f9b2875c729),
[Part 2](https://github.com/wekan/wekan/commit/29550cbd9335fc95e4f4ac399fe754ece8573450).
Thanks to xet7.
and fixes the following bugs:
- [Always show all data without migrations, like Shared Lists that were before Per-Swimlanes lists, Orphaned Swimlanes/Lists/Cards etc](https://github.com/wekan/wekan/commit/ef75b9ecac8210ff000439aca92765b53a1551a4).
Thanks to xet7.
- [Fix globalSearch](https://github.com/wekan/wekan/commit/b788d398b9b2cb0caad9c28f79acdceb5b577f25).
Thanks to snowsky.
- [Fix myCards crash](https://github.com/wekan/wekan/commit/d5c9e402ce4ebe0318bc29d5b83784771cbe8ac3).
Thanks to cristomoya.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.43 2026-03-27 WeKan ® release
This release adds the following updates:
- Updated to Meteor 3.4, Node.js 22.22, MongoDB 7.x.
[Part 1](https://github.com/wekan/wekan/pull/6205),
[Part 2](https://github.com/wekan/wekan/commit/7584afda6d28b75ae7bfa5388f71e8bcb5ab50a7).
Thanks to harryadel and xet7.
- [Updated dependencies of npm package @wekanteam/exceljs](https://github.com/wekan/wekan/commit/95ff8ef1f2f119c713ffba72122d51b6569b9d42).
Thanks to xet7.
- [Updated @meteorjs/rspack](https://github.com/wekan/wekan/commit/21ab640686d868ee46e16e812b36a1c3820952a5).
Thanks to Meteor developers.
- [Updated rebuild-wekan.sh for macOS 26.4](https://github.com/wekan/wekan/commit/f1e71247c70043634e040bd12f7d065b30ed6fc2).
Thanks to xet7.
- [Moved Meteor 3 migration docs to docs/Upgrade/](https://github.com/wekan/wekan/commit/517d1483876926cdbf6ce4774c8c52540eaec593).
Thanks to xet7.
- Updated dependencies.
[Part 1](https://github.com/wekan/wekan/commit/acfb081b907703c6846b8ccf71a9e2912b41713f),
[Part 2](https://github.com/wekan/wekan/commit/55660e0e69b7a283ab04ad408674d33eb71bb279).
Thanks to developers of dependencies.
- Updated release scripts.
[Part 1](https://github.com/wekan/wekan/commit/0fdf45e8476c0a619cd36891568c67d9092bd65b),
[Part 2](https://github.com/wekan/wekan/commit/07eb062474a5593683c1ed32f2637339fab72d48),
[Part 3](https://github.com/wekan/wekan/commit/5cf3bba68fdfc57a7a9358bd914c11af7bdd37d4),
[Part 4](https://github.com/wekan/wekan/commit/0e59033ea60f504d09d7eddb22a33ed113ea1326).
Thanks to xet7.
and fixes the following bugs:
- [Fixed rebuild-wekan.sh to build WeKan correctly](https://github.com/wekan/wekan/commit/4a8293f8812276f08d8a77a2c8be56441d7e87c1).
Thanks to xet7.
- [Export Card to Excel. Part 3. Also, fixed uploading attachments](https://github.com/wekan/wekan/commit/5c5ed102cabd07e1be00d3fdb01ea599da0a5517).
Thanks to xet7.
- [Removed temporary debug logs](https://github.com/wekan/wekan/commit/c1e3ce64a796fc8b921d1a30705b4aaf66c9c259).
Thanks to xet7.
- [Fixed typo](https://github.com/wekan/wekan/commit/23697d2a33ca527b29bcdadfe432fd9d1a180f9c).
Thanks to YosserDerbali and xet7.
- [Fix Set Color of Swimlane, List and Card](https://github.com/wekan/wekan/commit/0630d62ad0a02d80ab77436ce9797e4390c5cae2).
Thanks to SVANNER, brlin-tw and xet7.
- [Fix workspace settings modal overflow](https://github.com/wekan/wekan/pull/6218).
Thanks to AymenHassini19 and aminsaidane.
- [Fix attachment rename fails for clipboard uploads and keep extension metadata in sync](https://github.com/wekan/wekan/commit/38c670e08700f7b0ff185aadda40985bc71b1ac4).
Thanks to snowsky, YosserDerbali and xet7.
- [Fix card actions popup overflow](https://github.com/wekan/wekan/pull/6220).
Thanks to aminsaidane.
- [Fix Search: Guard Async methods in ReactiveCache and search publications](https://github.com/wekan/wekan/pull/6222).
Thanks to KhaoulaMaleh.
- [Fix/list actions popup overflow scroll](https://github.com/wekan/wekan/pull/6223).
Thanks to YosserDerbali.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.42 2026-03-23 WeKan ® release
This release fixes the following bugs:
- [Fix SSO login is broken](https://github.com/wekan/wekan/commit/2dce7a84cd0ce011972d377a05f5b26ff12daba7).
Thanks to sbaecker, vladimirdulov and xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v8.41 2026-03-23 WeKan ® release
+1 -1
View File
@@ -9,7 +9,7 @@ Not paid:
- This is because full-featured WeKan FLOSS kanban with MIT license is free to use
also for Commercial Use without any payment, with full source code at
https://github.com/wekan/wekan for all platforms at https://wekan.fi/install/ .
There is nothing propietary. WeKan is NOT Big Tech.
There is nothing proprietary. WeKan is NOT Big Tech.
WeKan is maintained by https://github.com/xet7 at Finland https://wekan.fi .
- Public FLOSS Developer Community Support:
- https://github.com/wekan/wekan/issues
+22 -18
View File
@@ -4,20 +4,20 @@ LABEL org.opencontainers.image.ref.name="ubuntu"
LABEL org.opencontainers.image.version="24.04"
LABEL org.opencontainers.image.source="https://github.com/wekan/wekan"
# TARGETARCH is automatically provided by Docker Buildx
# TARGETARCH and TARGETVARIANT are automatically provided by Docker Buildx
ARG TARGETARCH
ARG TARGETVARIANT
ARG VERSION=8.78
ARG DEBIAN_FRONTEND=noninteractive
ENV BUILD_DEPS="apt-utils gnupg wget bzip2 g++ curl libarchive-tools build-essential git ca-certificates python3 unzip"
ENV \
DEBUG=false \
NODE_VERSION=v14.21.4 \
METEOR_RELEASE=METEOR@2.16 \
NODE_VERSION=v24.14.1 \
METEOR_RELEASE=METEOR@3.5-beta.7 \
USE_EDGE=false \
METEOR_EDGE=1.5-beta.17 \
NPM_VERSION=6.14.17 \
FIBERS_VERSION=4.0.1 \
NPM_VERSION=11.11.0 \
SRC_PATH=./ \
WITH_API=true \
MONGO_OPLOG_URL="" \
@@ -169,21 +169,24 @@ apt-get update --assume-yes
apt-get upgrade --assume-yes
apt-get install --assume-yes --no-install-recommends ${BUILD_DEPS}
# Multi-arch mapping logic
# Multi-arch mapping: Docker TARGETARCH -> Node.js arch name + WeKan bundle name
# arm/v7 uses TARGETARCH=arm; armhf has no MongoDB Community -> uses FerretDB
case "${TARGETARCH}" in
"amd64") NODE_ARCH="x64" WEKAN_ARCH="amd64" ;;
"arm64") NODE_ARCH="arm64" WEKAN_ARCH="arm64" ;;
"s390x") NODE_ARCH="s390x" WEKAN_ARCH="s390x" ;;
"amd64") NODE_ARCH="x64" WEKAN_ARCH="amd64" ;;
"arm64") NODE_ARCH="arm64" WEKAN_ARCH="arm64" ;;
"arm") NODE_ARCH="armv7l" WEKAN_ARCH="armhf" ;;
"ppc64le") NODE_ARCH="ppc64le" WEKAN_ARCH="ppc64le" ;;
"s390x") NODE_ARCH="s390x" WEKAN_ARCH="s390x" ;;
*) echo "Unsupported architecture: ${TARGETARCH}"; exit 1 ;;
esac
# Node.js Installation
# Node.js Installation (official nodejs.org builds for Node 22)
cd /tmp
wget "https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz"
wget "https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/SHASUMS256.txt"
grep "node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz" SHASUMS256.txt | shasum -a 256 -c -
wget "https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz"
wget "https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc"
grep "node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz" SHASUMS256.txt.asc | shasum -a 256 -c -
tar xzf "node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz" -C /usr/local --strip-components=1 --no-same-owner
rm -f "node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz" SHASUMS256.txt
rm -f "node-${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz" SHASUMS256.txt.asc
ln -s "/usr/local/bin/node" "/usr/local/bin/nodejs"
# NPM configuration
@@ -197,9 +200,10 @@ ln -sf $(which bsdtar) $(which tar)
# WeKan Bundle Installation
mkdir -p /home/wekan/app
cd /home/wekan/app
wget "https://github.com/wekan/wekan/releases/download/v8.41/wekan-8.41-${WEKAN_ARCH}.zip"
unzip "wekan-8.41-${WEKAN_ARCH}.zip"
rm "wekan-8.41-${WEKAN_ARCH}.zip"
wget "https://github.com/wekan/wekan/releases/download/v${VERSION}/wekan-${VERSION}-${WEKAN_ARCH}.zip"
unzip "wekan-${VERSION}-${WEKAN_ARCH}.zip"
rm "wekan-${VERSION}-${WEKAN_ARCH}.zip"
npm install --production --no-audit --prefix ./bundle/programs/server
mv /home/wekan/app/bundle /build
# Restore original tar
-98
View File
@@ -1,98 +0,0 @@
FROM arm64v8/ubuntu:23.04 AS builder
#FROM amd64/alpine:latest AS builder
# Set the environment variables for builder
ENV QEMU_VERSION=v7.2.0-1 \
QEMU_ARCHITECTURE=aarch64 \
NODE_ARCHITECTURE=linux-arm64 \
NODE_VERSION=v14.21.4 \
WEKAN_VERSION=latest \
WEKAN_ARCHITECTURE=arm64
# Install dependencies
#RUN apk update && apk add ca-certificates outils-sha1 && \
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
RUN apt update && apt install ca-certificates wget unzip -y && \
\
# Download qemu static for our architecture
wget https://github.com/multiarch/qemu-user-static/releases/download/${QEMU_VERSION}/qemu-${QEMU_ARCHITECTURE}-static.tar.gz -O - | tar -xz && \
\
# Download wekan and shasum
wget https://releases.wekan.team/raspi3/wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip && \
wget https://releases.wekan.team/raspi3/SHA256SUMS.txt && \
# Verify wekan
grep wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip SHA256SUMS.txt | sha256sum -c - && \
\
# Unzip wekan
unzip wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip && \
\
# Download node and shasums
wget https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz && \
wget https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/SHASUMS256.txt && \
#wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz && \
#wget https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc && \
#wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz && \
#wget https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc && \
\
# Verify nodejs authenticity
grep node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz SHASUMS256.txt | sha256sum -c - && \
\
# Extract node and remove tar.gz
tar xvzf node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz
# Build wekan dockerfile
FROM --platform=linux/arm64 arm64v8/ubuntu:23.04
LABEL maintainer="wekan"
# Set the environment variables (defaults where required)
ENV QEMU_ARCHITECTURE=aarch64 \
NODE_ARCHITECTURE=linux-arm64 \
NODE_VERSION=v14.21.4 \
NODE_ENV=production \
NPM_VERSION=latest \
WITH_API=true \
PORT=8080 \
ROOT_URL=http://localhost \
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
# Copy the app to the image
COPY --from=builder bundle /home/wekan/bundle
# Copy
COPY --from=builder node-${NODE_VERSION}-${NODE_ARCHITECTURE} /opt/nodejs
RUN \
set -o xtrace && \
# Add non-root user wekan
useradd --user-group --system --home-dir /home/wekan wekan && \
\
# Install Node
ln -s /opt/nodejs/bin/node /usr/bin/node && \
ln -s /opt/nodejs/bin/npm /usr/bin/npm && \
mkdir -p /opt/nodejs/lib/node_modules/fibers/.node-gyp /root/.node-gyp/8.16.1 /home/wekan/.config && \
chown wekan --recursive /home/wekan/.config
# \
# # Install Node dependencies
# #npm install -g npm@${NPM_VERSION} && \
# \
# # Install Health Check dependencies
# #apk add curl
#
#HEALTHCHECK --start-period=30s --interval=30s --timeout=10s --retries=3 \
# CMD curl --fail "http://localhost:$PORT" || exit 1
EXPOSE $PORT
USER wekan
# CMD ["bash", "-c", "ulimit -s 65500; exec node --stack-size=65500 --max-old-space-size=8192 /home/wekan/bundle/main.js"]
CMD ["bash", "-c", "ulimit -s 65500; exec node /home/wekan/bundle/main.js"]
-99
View File
@@ -1,99 +0,0 @@
FROM arm64v8/ubuntu:23.04 AS builder
#FROM --platform=linux/amd64 amd64/ubuntu:23.04 AS builder
#FROM --platform=linux/amd64 ghcr.io/wekan/wekan:main AS builder
#FROM arm64v8/ubuntu:23.04 AS builder
#FROM amd64/alpine:latest AS builder
# Set the environment variables for builder
ENV QEMU_VERSION=v7.2.0-1 \
QEMU_ARCHITECTURE=s390x \
NODE_ARCHITECTURE=linux-s390x \
NODE_VERSION=v14.21.4 \
WEKAN_VERSION=latest \
WEKAN_ARCHITECTURE=s390x
# Install dependencies
#RUN apk update && apk add ca-certificates outils-sha1 && \
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
RUN apt update && apt install ca-certificates wget unzip -y && \
\
# Download qemu static for our architecture
wget https://github.com/multiarch/qemu-user-static/releases/download/${QEMU_VERSION}/qemu-${QEMU_ARCHITECTURE}-static.tar.gz -O - | tar -xz && \
\
# Download wekan and shasum
wget https://releases.wekan.team/${WEKAN_ARCHITECTURE}/wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip && \
wget https://releases.wekan.team/${WEKAN_ARCHITECTURE}/SHA256SUMS.txt && \
# Verify wekan
grep wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip SHA256SUMS.txt | sha256sum -c - && \
\
# Unzip wekan
unzip wekan-${WEKAN_VERSION}-${WEKAN_ARCHITECTURE}.zip && \
\
# Download node and shasums
wget https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz && \
wget https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/SHASUMS256.txt && \
#wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz && \
#wget https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc && \
#wget https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz && \
#wget https://nodejs.org/dist/${NODE_VERSION}/SHASUMS256.txt.asc && \
\
# Verify nodejs authenticity
grep node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz SHASUMS256.txt | sha256sum -c - && \
\
# Extract node and remove tar.gz
tar xvzf node-${NODE_VERSION}-${NODE_ARCHITECTURE}.tar.gz
# Build wekan dockerfile
FROM --platform=linux/s390x s390x/ubuntu:23.04
LABEL maintainer="wekan"
# Set the environment variables (defaults where required)
ENV QEMU_ARCHITECTURE=s390x \
NODE_ARCHITECTURE=linux-s390x \
NODE_VERSION=v14.21.4 \
NODE_ENV=production \
NPM_VERSION=latest \
WITH_API=true \
PORT=8080 \
ROOT_URL=http://localhost \
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
# Copy the app to the image
COPY --from=builder bundle /home/wekan/bundle
# Copy
COPY --from=builder node-${NODE_VERSION}-${NODE_ARCHITECTURE} /opt/nodejs
RUN \
set -o xtrace && \
# Add non-root user wekan
useradd --user-group --system --home-dir /home/wekan wekan && \
\
# Install Node
ln -s /opt/nodejs/bin/node /usr/bin/node && \
ln -s /opt/nodejs/bin/npm /usr/bin/npm && \
mkdir -p /opt/nodejs/lib/node_modules/fibers/.node-gyp /root/.node-gyp/8.16.1 /home/wekan/.config && \
chown wekan --recursive /home/wekan/.config
# \
# # Install Node dependencies
# #npm install -g npm@${NPM_VERSION} && \
# \
# # Install Health Check dependencies
# #apk add curl
#
#HEALTHCHECK --start-period=30s --interval=30s --timeout=10s --retries=3 \
# CMD curl --fail "http://localhost:$PORT" || exit 1
EXPOSE $PORT
USER wekan
CMD ["bash", "-c", "ulimit -s 65500; exec node /home/wekan/bundle/main.js"]
+1 -1
View File
@@ -1,5 +1,5 @@
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
appVersion: "v8.41.0"
appVersion: "v8.78.0"
files:
userUploads:
- README.md
+2 -1
View File
@@ -10,7 +10,8 @@ import '/client/config/blazeHelpers';
// Import board converter for on-demand conversion
import '/client/lib/boardConverter';
import '/client/components/boardConversionProgress';
import '/client/features/boardConversion';
import { Utils } from '/client/lib/utils';
// Import migration manager and progress UI - COMMENTED OUT
// import '/client/lib/attachmentMigrationManager';
+2 -2
View File
@@ -9,11 +9,11 @@ template(name="activities")
+cardActivities
template(name="boardActivities")
each activityData in currentBoard.activities
each activityData in boardActivitiesList
+activity(activity=activityData card=card mode=mode)
template(name="cardActivities")
each activityData in activities
each activityData in cardActivitiesList
+activity(activity=activityData card=card mode=mode)
template(name="activity")
+33 -3
View File
@@ -1,12 +1,17 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { ReactiveVar } from 'meteor/reactive-var';
import { Template } from 'meteor/templating';
import DOMPurify from 'dompurify';
import { sanitizeHTML, sanitizeText } from '/imports/lib/secureDOMPurify';
import { TAPi18n } from '/imports/i18n';
import { Utils } from '/client/lib/utils';
import { getSidebarInstance } from '/client/features/sidebar/service';
const activitiesPerPage = 500;
Template.activities.onCreated(function () {
// Register with sidebar so it can call loadNextPage on us
const Sidebar = getSidebarInstance();
if (Sidebar) {
Sidebar.activitiesInstance = this;
}
@@ -22,7 +27,7 @@ Template.activities.onCreated(function () {
};
// TODO is sidebar always available? E.g. on small screens/mobile devices
const sidebar = Sidebar;
const sidebar = getSidebarInstance();
if (sidebar && sidebar.infiniteScrolling) {
sidebar.infiniteScrolling.resetNextPeak();
}
@@ -71,7 +76,9 @@ function _showActivities(data) {
const currentCard = Utils.getCurrentCard();
ret = currentCard.showActivities ?? false;
} else if (mode === 'card') {
ret = data?.card?.showActivities ?? false;
// For card mode, get showActivities from data context or fallback to current card
const card = data?.card || Utils.getCurrentCard();
ret = card?.showActivities ?? false;
} else {
ret = Utils.getCurrentBoard().showActivities ?? false;
}
@@ -80,8 +87,31 @@ function _showActivities(data) {
}
Template.activities.helpers({
showActivities() {
const data = Template.currentData();
return _showActivities(data);
},
activities() {
return this.card.activities();
const activities = this.card?.activities?.();
return activities || [];
},
});
Template.boardActivities.helpers({
boardActivitiesList() {
const board = Utils.getCurrentBoard();
const activities = board?.activities?.();
return activities || [];
},
});
Template.cardActivities.helpers({
cardActivitiesList() {
const data = Template.currentData();
const card = data?.card;
const activities = card?.activities?.();
return activities || [];
},
});
+5
View File
@@ -1,4 +1,9 @@
import { ReactiveCache } from '/imports/reactiveCache';
import CardComments from '/models/cardComments';
import { UnsavedEdits } from '/client/lib/unsavedEdits';
import { EscapeActions } from '/client/lib/escapeActions';
import { Utils } from '/client/lib/utils';
import autosize from 'autosize';
const commentFormIsOpen = new ReactiveVar(false);
+2
View File
@@ -1,5 +1,7 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import Boards from '/models/boards';
import { Utils } from '/client/lib/utils';
Template.archivedBoards.onCreated(function () {
this.subscribe('archivedBoards');
+38 -24
View File
@@ -1,5 +1,4 @@
import { ReactiveCache } from '/imports/reactiveCache';
import '../gantt/gantt.js';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import { TAPi18n } from '/imports/i18n';
import dragscroll from '@wekanteam/dragscroll';
@@ -7,13 +6,16 @@ import { boardConverter } from '/client/lib/boardConverter';
import { formatDateByUserPreference } from '/imports/lib/dateUtils';
import Swimlanes from '/models/swimlanes';
import Lists from '/models/lists';
import TableVisibilityModeSettings from '/models/tableVisibilityModeSettings';
import { EscapeActions } from '/client/lib/escapeActions';
import { Utils } from '/client/lib/utils';
const subManager = new SubsManager();
// SubsManager removed for Meteor 3 migration
const { calculateIndex } = Utils;
const swimlaneWhileSortingHeight = 150;
// Global reference so child components (swimlanes, cards) can access boardBody instance
BoardBody = null;
let BoardBody = null;
Template.board.onCreated(function () {
this.isBoardReady = new ReactiveVar(false);
@@ -22,7 +24,7 @@ Template.board.onCreated(function () {
this._boardProcessed = false; // Track if board has been processed
this._lastProcessedBoardId = null; // Track last processed board ID
this.ensureDefaultSwimlane = (boardId) => {
this.ensureDefaultSwimlane = async (boardId) => {
// Only create swimlane once per board
if (this._swimlaneCreated.has(boardId)) {
return;
@@ -33,25 +35,15 @@ Template.board.onCreated(function () {
if (!board) return;
const swimlanes = board.swimlanes();
if (swimlanes.length === 0) {
// Check if any swimlane exists in the database to avoid race conditions
const existingSwimlanes = ReactiveCache.getSwimlanes({ boardId });
if (existingSwimlanes.length === 0) {
const swimlaneId = Swimlanes.insert({
title: 'Default',
boardId: boardId,
});
if (process.env.DEBUG === 'true') {
console.log(
`Created default swimlane ${swimlaneId} for board ${boardId}`,
);
}
const swimlaneId = await Meteor.callAsync('ensureDefaultSwimlane', boardId);
if (process.env.DEBUG === 'true' && swimlaneId) {
console.log(
`Ensured default swimlane ${swimlaneId} for board ${boardId}`,
);
}
this._swimlaneCreated.add(boardId);
} else {
this._swimlaneCreated.add(boardId);
}
this._swimlaneCreated.add(boardId);
} catch (error) {
console.error('Error creating default swimlane:', error);
}
@@ -81,7 +73,7 @@ Template.board.onCreated(function () {
const currentBoardId = Session.get('currentBoard');
if (!currentBoardId) return;
const handle = subManager.subscribe('board', currentBoardId, false);
const handle = Meteor.subscribe('board', currentBoardId, false);
// Use a separate autorun for subscription ready state to avoid reactive loops
this.subscriptionReadyAutorun = Tracker.autorun(() => {
@@ -1091,11 +1083,33 @@ Template.calendarView.helpers({
const createCardButton = modalElement.querySelector(
'#create-card-button',
);
createCardButton.addEventListener('click', function () {
createCardButton.addEventListener('click', async function () {
const myTitle = modalElement.querySelector('#card-title-input').value;
if (myTitle) {
const firstList = currentBoard.draggableLists()[0];
const firstSwimlane = currentBoard.swimlanes()[0];
let firstSwimlane = currentBoard.swimlanes()[0];
if (!firstSwimlane) {
const swimlaneId = await Swimlanes.insertAsync({
title: 'Default',
boardId: currentBoard._id,
});
firstSwimlane = ReactiveCache.getSwimlane(swimlaneId);
}
let firstList = currentBoard.draggableLists()[0];
if (!firstList && firstSwimlane) {
const defaultTitle = TAPi18n.__('default');
const listId = await Lists.insertAsync({
title: typeof defaultTitle === 'string' ? defaultTitle : 'Default',
boardId: currentBoard._id,
swimlaneId: firstSwimlane._id,
});
firstList = ReactiveCache.getList(listId);
}
if (!firstList || !firstSwimlane) {
return;
}
Meteor.call(
'createCardWithDueDate',
currentBoard._id,
+62 -78
View File
@@ -2,6 +2,14 @@ import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import dragscroll from '@wekanteam/dragscroll';
import getSlug from 'limax';
import Boards from '/models/boards';
import Swimlanes from '/models/swimlanes';
import TableVisibilityModeSettings from '/models/tableVisibilityModeSettings';
import { Filter } from '/client/lib/filter';
import { MultiSelection } from '/client/lib/multiSelection';
import { getSidebarInstance } from '/client/features/sidebar/service';
import { Utils } from '/client/lib/utils';
/*
const DOWNCLS = 'fa-sort-down';
@@ -11,6 +19,7 @@ const sortCardsBy = new ReactiveVar('');
Template.boardChangeTitlePopup.events({
async submit(event, templateInstance) {
event.preventDefault();
const newTitle = templateInstance
.$('.js-board-name')
.val()
@@ -27,7 +36,6 @@ Template.boardChangeTitlePopup.events({
}
Popup.back();
}
event.preventDefault();
},
});
@@ -94,30 +102,32 @@ Template.boardHeaderBar.events({
console.log('Hamburger menu clicked');
}
// Use the same approach as keyboard shortcuts
if (typeof Sidebar !== 'undefined' && Sidebar && typeof Sidebar.toggle === 'function') {
const sidebar = getSidebarInstance();
if (sidebar && typeof sidebar.toggle === 'function') {
if (process.env.DEBUG === 'true') {
console.log('Using Sidebar.toggle()');
}
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') {
const blazeSidebar = Blaze._globalHelpers.Sidebar();
if (blazeSidebar && typeof blazeSidebar.toggle === 'function') {
if (process.env.DEBUG === 'true') {
console.log('Using Blaze helper Sidebar.toggle()');
}
sidebar.toggle();
blazeSidebar.toggle();
}
}
}
},
'click .js-open-filter-view'() {
if (Sidebar) {
Sidebar.setView('filter');
const sidebar = getSidebarInstance();
if (sidebar) {
sidebar.setView('filter');
} else {
console.warn('Sidebar not available for setView');
}
@@ -137,8 +147,9 @@ Template.boardHeaderBar.events({
*/
'click .js-filter-reset'(event) {
event.stopPropagation();
if (Sidebar) {
Sidebar.setView();
const sidebar = getSidebarInstance();
if (sidebar) {
sidebar.setView();
} else {
console.warn('Sidebar not available for setView');
}
@@ -148,8 +159,9 @@ Template.boardHeaderBar.events({
Session.set('sortBy', '');
},
'click .js-open-search-view'() {
if (Sidebar) {
Sidebar.setView('search');
const sidebar = getSidebarInstance();
if (sidebar) {
sidebar.setView('search');
} else {
console.warn('Sidebar not available for setView');
}
@@ -214,55 +226,26 @@ function createBoardHelpers() {
};
}
function createBoardSubmit(tpl, event) {
async function createBoardSubmit(tpl, event) {
event.preventDefault();
const title = tpl.find('.js-new-board-title').value;
const slug = getSlug(title) || 'board';
const addTemplateContainer = $('#add-template-container.is-checked').length > 0;
if (addTemplateContainer) {
//const templateContainerId = Meteor.call('setCreateTemplateContainer');
//Utils.goBoardId(templateContainerId);
//alert('niinku template ' + Meteor.call('setCreateTemplateContainer'));
tpl.boardId.set(
Boards.insert({
// title: TAPi18n.__('templates'),
title: title,
permission: 'private',
type: 'template-container',
migrationVersion: 1, // Latest version - no migration needed
}),
);
// Insert the card templates swimlane
Swimlanes.insert({
// title: TAPi18n.__('card-templates-swimlane'),
title: 'Card Templates',
boardId: tpl.boardId.get(),
sort: 1,
await Meteor.callAsync('createBoardWithInitialSwimlanes', {
title,
slug,
permission: 'private',
type: 'template-container',
migrationVersion: 1,
swimlanes: [
{ title: 'Card Templates', sort: 1, type: 'template-container' },
{ title: 'List Templates', sort: 2, type: 'template-container' },
{ title: 'Board Templates', sort: 3, type: 'template-container' },
],
}),
// Insert the list templates swimlane
Swimlanes.insert(
{
// title: TAPi18n.__('list-templates-swimlane'),
title: 'List Templates',
boardId: tpl.boardId.get(),
sort: 2,
type: 'template-container',
},
);
// Insert the board templates swimlane
Swimlanes.insert(
{
//title: TAPi18n.__('board-templates-swimlane'),
title: 'Board Templates',
boardId: tpl.boardId.get(),
sort: 3,
type: 'template-container',
},
);
// Assign to space if one was selected
@@ -274,24 +257,21 @@ function createBoardSubmit(tpl, event) {
Session.set('createBoardInWorkspace', null); // Clear after use
}
Utils.goBoardId(tpl.boardId.get());
FlowRouter.go('board', { id: tpl.boardId.get(), slug });
} else {
const visibility = tpl.visibility.get();
tpl.boardId.set(
Boards.insert({
await Meteor.callAsync('createBoardWithInitialSwimlanes', {
title,
slug,
permission: visibility,
migrationVersion: 1, // Latest version - no migration needed
migrationVersion: 1,
swimlanes: [{ title: 'Default' }],
}),
);
Swimlanes.insert({
title: 'Default',
boardId: tpl.boardId.get(),
});
// Assign to space if one was selected
const spaceId = Session.get('createBoardInWorkspace');
if (spaceId) {
@@ -301,7 +281,7 @@ function createBoardSubmit(tpl, event) {
Session.set('createBoardInWorkspace', null); // Clear after use
}
Utils.goBoardId(tpl.boardId.get());
FlowRouter.go('board', { id: tpl.boardId.get(), slug });
}
}
@@ -315,8 +295,8 @@ function createBoardEvents() {
tpl.visibilityMenuIsOpen.set(!tpl.visibilityMenuIsOpen.get());
},
'click .js-import': Popup.open('boardImportBoard'),
'submit'(event, tpl) {
createBoardSubmit(tpl, event);
async 'submit'(event, tpl) {
await createBoardSubmit(tpl, event);
},
'click .js-import-board': Popup.open('chooseBoardSource'),
'click .js-board-template': Popup.open('searchElement'),
@@ -375,7 +355,7 @@ Template.headerBarCreateBoardPopup.events({
},
'click .js-import': Popup.open('boardImportBoard'),
async submit(event, tpl) {
createBoardSubmit(tpl, event);
await createBoardSubmit(tpl, event);
// Immediately star boards created with the headerbar popup.
await ReactiveCache.getCurrentUser().toggleBoardStar(tpl.boardId.get());
},
@@ -403,9 +383,11 @@ Template.boardChangeVisibilityPopup.helpers({
Template.boardChangeVisibilityPopup.events({
'click .js-select-visibility'() {
const currentBoard = Utils.getCurrentBoard();
const visibility = String(this);
currentBoard.setVisibility(visibility);
Popup.back();
const visibility = Template.currentData();
if (typeof visibility === 'string') {
currentBoard.setVisibility(visibility);
Popup.back();
}
},
});
@@ -423,16 +405,18 @@ Template.boardChangeWatchPopup.helpers({
Template.boardChangeWatchPopup.events({
'click .js-select-watch'() {
const level = String(this);
Meteor.call(
'watch',
'board',
Session.get('currentBoard'),
level,
(err, ret) => {
if (!err && ret) Popup.back();
},
);
const level = Template.currentData();
if (typeof level === 'string') {
Meteor.call(
'watch',
'board',
Session.get('currentBoard'),
level,
(err, ret) => {
if (!err && ret) Popup.back();
},
);
}
},
});
+1 -1
View File
@@ -1,4 +1,4 @@
@import url("../../../css/reset.css") print, screen;
@import url("../../../public/css/reset.css") print, screen;
/* Board List Header with Zoom Controls */
.board-list-header {
+6 -2
View File
@@ -2,8 +2,12 @@ import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import getSlug from 'limax';
import TableVisibilityModeSettings from '/models/tableVisibilityModeSettings';
import { BoardMultiSelection } from '/client/lib/boardMultiSelection';
import { EscapeActions } from '/client/lib/escapeActions';
import { Utils } from '/client/lib/utils';
const subManager = new SubsManager();
// SubsManager removed for Meteor 3 migration
const DEFAULT_WORKSPACE_ICON = '📁';
@@ -657,7 +661,7 @@ Template.boardList.events({
console.error(err);
} else {
Session.set('fromBoard', null);
subManager.subscribe('board', res, false);
Meteor.subscribe('board', res, false);
FlowRouter.go('board', {
id: res,
slug: title,
+4
View File
@@ -86,6 +86,10 @@ template(name="attachmentGallery")
b
= name
span.file-size ({{fileSize size}})
if uploaderName
div.attachment-uploader {{uploaderName}}
if uploadedAt
div.attachment-uploaded-at {{uploadedAt}}
.attachment-actions
a.js-download(href="{{link}}?download=true", download="{{name}}", title="{{_ 'download'}}")
i.fa.fa-arrow-down
+37 -18
View File
@@ -1,12 +1,15 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { ObjectID } from 'bson';
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';
import Attachments from '/models/attachments';
import { Utils } from '/client/lib/utils';
import { formatDateTime } from '/imports/lib/dateUtils';
const filesize = require('filesize');
const prettyMilliseconds = require('pretty-ms');
const { filesize } = require('filesize');
import prettyMilliseconds from 'pretty-ms';
// We store current card ID and the ID of currently opened attachment in a
// global var. This is used so that we know what's the next attachment to open
@@ -19,7 +22,7 @@ let touchStartCoords = null;
let touchEndCoords = null;
// Stores link to the attachment for which attachment actions popup was opened
attachmentActionsLink = null;
let attachmentActionsLink = null;
Template.attachmentGallery.events({
'click .open-preview'(event) {
@@ -40,8 +43,8 @@ Template.attachmentGallery.events({
attachmentActionsLink = event.currentTarget.getAttribute("data-attachment-link");
},
'click .js-rename': Popup.open('attachmentRename'),
'click .js-confirm-delete': Popup.afterConfirm('attachmentDelete', function() {
Attachments.remove(this._id);
'click .js-confirm-delete': Popup.afterConfirm('attachmentDelete', async function() {
await Attachments.removeAsync(this._id);
Popup.back();
}),
});
@@ -196,8 +199,6 @@ function processTouch(){
xDist = touchEndCoords.x - touchStartCoords.x;
yDist = touchEndCoords.y - touchStartCoords.y;
console.log("xDist: " + xDist);
// Left swipe
if (Math.abs(xDist) > Math.abs(yDist) && xDist < 0) {
openNextAttachment();
@@ -217,14 +218,12 @@ function processTouch(){
Template.attachmentViewer.events({
'touchstart #viewer-container'(event) {
console.log("touchstart")
touchStartCoords = {
x: event.changedTouches[0].screenX,
y: event.changedTouches[0].screenY
}
},
'touchend #viewer-container'(event) {
console.log("touchend")
touchEndCoords = {
x: event.changedTouches[0].screenX,
y: event.changedTouches[0].screenY
@@ -273,6 +272,26 @@ Template.attachmentGallery.helpers({
sanitize(value) {
return sanitizeHTML(value);
},
uploaderName() {
const uploaderId = this.userId;
if (!uploaderId) return '';
const uploader = ReactiveCache.getUser(uploaderId);
if (!uploader) return '';
return uploader.profile && uploader.profile.fullname
? uploader.profile.fullname
: uploader.username || '';
},
uploadedAt() {
if (this.uploadedAtOstrio) {
return formatDateTime(this.uploadedAtOstrio);
}
// Fall back to ObjectId timestamp (first 4 bytes = Unix seconds)
try {
const ts = parseInt(this._id.substring(0, 8), 16) * 1000;
if (!isNaN(ts)) return formatDateTime(new Date(ts));
} catch (_) {}
return '';
},
});
Template.cardAttachmentsPopup.onCreated(function() {
@@ -294,12 +313,12 @@ Template.cardAttachmentsPopup.helpers({
});
Template.cardAttachmentsPopup.events({
'change .js-attach-file'(event, templateInstance) {
async 'change .js-attach-file'(event, templateInstance) {
const card = this;
const files = event.currentTarget.files;
if (files) {
let uploads = [];
const uploaders = handleFileUpload(card, files);
const uploaders = await handleFileUpload(card, files);
uploaders.forEach(uploader => {
uploader.on('start', function() {
@@ -328,7 +347,7 @@ const COMPRESS_RATIO = Utils.IMAGE_COMPRESS_RATIO;
let pastedResults = null;
// Shared upload logic for drag-and-drop functionality
export function handleFileUpload(card, files) {
export async function handleFileUpload(card, files) {
if (!files || files.length === 0) {
return [];
}
@@ -361,7 +380,7 @@ export function handleFileUpload(card, files) {
continue;
}
const fileId = new ObjectID().toString();
const fileId = new ObjectId().toString();
let fileName = sanitizeText(file.name);
// If sanitized filename is not same as original filename,
@@ -385,7 +404,7 @@ export function handleFileUpload(card, files) {
config.meta.fileId = fileId;
try {
const uploader = Attachments.insert(
const uploader = await Attachments.insertAsync(
config,
false,
);
@@ -460,12 +479,12 @@ Template.previewClipboardImagePopup.onRendered(() => {
});
Template.previewClipboardImagePopup.events({
'click .js-upload-pasted-image'() {
async 'click .js-upload-pasted-image'() {
const card = this;
if (pastedResults && pastedResults.file) {
const file = pastedResults.file;
window.oPasted = pastedResults;
const fileId = new ObjectID().toString();
const fileId = new ObjectId().toString();
const config = {
file,
fileId: fileId,
@@ -474,7 +493,7 @@ Template.previewClipboardImagePopup.events({
chunkSize: 'dynamic',
};
config.meta.fileId = fileId;
const uploader = Attachments.insert(
const uploader = await Attachments.insertAsync(
config,
false,
);
+6 -1
View File
@@ -29,6 +29,8 @@ import {
} from '/imports/lib/dateUtils';
import { CustomFieldStringTemplate } from '/client/lib/customFields'
import { getCurrentCardFromContext } from '/client/lib/currentCard';
import { EscapeActions } from '/client/lib/escapeActions';
import { getSidebarInstance } from '/client/features/sidebar/service';
Template.cardCustomFieldsPopup.helpers({
hasCustomField() {
@@ -49,7 +51,10 @@ Template.cardCustomFieldsPopup.events({
},
'click .js-settings'(event) {
EscapeActions.executeUpTo('detailsPane');
Sidebar.setView('customFields');
const sidebar = getSidebarInstance();
if (sidebar) {
sidebar.setView('customFields');
}
event.preventDefault();
},
});
+4 -4
View File
@@ -48,7 +48,7 @@ Template.editCardReceivedDatePopup.helpers(datePickerHelpers());
Template.editCardReceivedDatePopup.events(datePickerEvents({
storeDate(date) {
this.datePicker.card.setReceived(formatDateTime(date));
this.datePicker.card.setReceived(date);
},
deleteDate() {
this.datePicker.card.unsetReceived();
@@ -72,7 +72,7 @@ Template.editCardStartDatePopup.helpers(datePickerHelpers());
Template.editCardStartDatePopup.events(datePickerEvents({
storeDate(date) {
this.datePicker.card.setStart(formatDateTime(date));
this.datePicker.card.setStart(date);
},
deleteDate() {
this.datePicker.card.unsetStart();
@@ -96,7 +96,7 @@ Template.editCardDueDatePopup.helpers(datePickerHelpers());
Template.editCardDueDatePopup.events(datePickerEvents({
storeDate(date) {
this.datePicker.card.setDue(formatDateTime(date));
this.datePicker.card.setDue(date);
},
deleteDate() {
this.datePicker.card.unsetDue();
@@ -120,7 +120,7 @@ Template.editCardEndDatePopup.helpers(datePickerHelpers());
Template.editCardEndDatePopup.events(datePickerEvents({
storeDate(date) {
this.datePicker.card.setEnd(formatDateTime(date));
this.datePicker.card.setEnd(date);
},
deleteDate() {
this.datePicker.card.unsetEnd();
+23 -24
View File
@@ -11,31 +11,30 @@ template(name="cardDetails")
+editCardTitleForm
else
unless isMiniScreen
unless isPopup
span.card-collapse-toggle.js-card-collapse-toggle(title="{{_ 'collapse-card'}}")
if cardCollapsed
i.fa.fa-caret-right
else
i.fa.fa-caret-down
a.close-card-details.js-close-card-details(title="{{_ 'close-card'}}")
i.fa.fa-times-thin
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
i.fa.fa-caret-right
else
a.fa.fa-window-maximize.maximize-card-details.js-maximize-card-details(title="{{_ 'maximize-card'}}")
a.card-details-menu.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
i.fa.fa-bars
a.card-copy-button.js-copy-link(
id="cardURL_copy"
title="{{_ 'copy-card-link-to-clipboard'}}"
href="{{ originRelativeUrl }}"
)
span.emoji-icon
i.fa.fa-link
if canModifyCard
span.card-drag-handle.js-card-drag-handle(title="Drag card")
i.fa.fa-arrows
span.copied-tooltip {{_ 'copied'}}
i.fa.fa-caret-down
a.close-card-details.js-close-card-details(title="{{_ 'close-card'}}")
i.fa.fa-times-thin
if cardMaximized
a.fa.fa-window-minimize.minimize-card-details.js-minimize-card-details(title="{{_ 'minimize-card'}}")
else
a.fa.fa-window-maximize.maximize-card-details.js-maximize-card-details(title="{{_ 'maximize-card'}}")
a.card-details-menu.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
i.fa.fa-bars
a.card-copy-button.js-copy-link(
id="cardURL_copy"
title="{{_ 'copy-card-link-to-clipboard'}}"
href="{{ originRelativeUrl }}"
)
span.emoji-icon
i.fa.fa-link
if canModifyCard
span.card-drag-handle.js-card-drag-handle(title="Drag card")
i.fa.fa-arrows
span.copied-tooltip {{_ 'copied'}}
else
a.close-card-details.js-close-card-details(title="{{_ 'close-card'}}")
i.fa.fa-times-thin
+65 -30
View File
@@ -37,6 +37,7 @@ import CardComments from '/models/cardComments';
import { ALLOWED_COLORS } from '/config/const';
import { uniqBy } from '/imports/lib/collectionHelpers';
import { UserAvatar } from '../users/userAvatar';
import { Filter } from '/client/lib/filter';
import { BoardSwimlaneListCardDialog } from '/client/lib/dialogWithBoardSwimlaneListCard';
import { handleFileUpload } from './attachments';
import { InfiniteScrolling } from '/client/lib/infiniteScrolling';
@@ -45,8 +46,14 @@ import {
getCurrentCardFromContext,
} from '/client/lib/currentCard';
import uploadProgressManager from '../../lib/uploadProgressManager';
import { CSSEvents } from '/client/lib/cssEvents';
import { UnsavedEdits } from '/client/lib/unsavedEdits';
import { EscapeActions } from '/client/lib/escapeActions';
import { MultiSelection } from '/client/lib/multiSelection';
import { Utils } from '/client/lib/utils';
import autosize from 'autosize';
const subManager = new SubsManager();
// SubsManager removed for Meteor 3 migration
const { calculateIndexData } = Utils;
function getCardId() {
@@ -200,22 +207,22 @@ Template.cardDetails.onRendered(function () {
EscapeActions.executeUpTo('popup-close');
},
stop(evt, ui) {
let prevChecklist = ui.item.prev('.js-subtasks').get(0);
if (prevChecklist) {
prevChecklist = Blaze.getData(prevChecklist).subtask;
let prevSubtask = ui.item.prev('.js-subtasks').get(0);
if (prevSubtask) {
prevSubtask = Blaze.getData(prevSubtask).subtask;
}
let nextChecklist = ui.item.next('.js-subtasks').get(0);
if (nextChecklist) {
nextChecklist = Blaze.getData(nextChecklist).subtask;
let nextSubtask = ui.item.next('.js-subtasks').get(0);
if (nextSubtask) {
nextSubtask = Blaze.getData(nextSubtask).subtask;
}
const sortIndex = calculateIndexData(prevChecklist, nextChecklist, 1);
const sortIndex = calculateIndexData(prevSubtask, nextSubtask, 1);
$subtasksDom.sortable('cancel');
const subtask = Blaze.getData(ui.item.get(0)).subtask;
Subtasks.update(subtask._id, {
Cards.updateAsync(subtask._id, {
$set: {
subtaskSort: sortIndex.base,
sort: sortIndex.base,
},
});
},
@@ -265,8 +272,8 @@ Template.cardDetails.helpers({
},
showActivities() {
const user = ReactiveCache.getCurrentUser();
return user && user.hasShowActivities();
const card = Template.currentData();
return card && card.showActivities;
},
cardCollapsed() {
@@ -330,7 +337,12 @@ Template.cardDetails.helpers({
},
isCurrentListId(listId) {
const data = Template.currentData();
// When called from within 'each currentBoard.lists' block, we need the card from parent context
let data = Template.currentData();
if (!data || typeof data.listId === 'undefined') {
// Try parent context (for use within each loops)
data = Template.parentData(1);
}
if (!data || typeof data.listId === 'undefined') return false;
return data.listId == listId;
},
@@ -549,9 +561,8 @@ Template.cardDetails.events({
}
},
async 'change .js-select-card-details-lists'(event, tpl) {
const listId = event.target.value;
let card = Template.currentData();
const listSelect = tpl.$('.js-select-card-details-lists')[0];
const listId = listSelect.options[listSelect.selectedIndex].value;
const minOrder = await card.getMinSort(listId, card.swimlaneId);
await card.move(card.boardId, card.swimlaneId, listId, minOrder - 1);
@@ -595,6 +606,10 @@ Template.cardDetails.events({
const card = Template.currentData();
await card.toggleHideCheckedChecklistItems();
},
async 'change #toggleShowActivitiesCard'() {
const card = Template.currentData();
await card.toggleShowActivities();
},
'click #toggleCustomFieldsGridButton'() {
Meteor.call('toggleCustomFieldsGrid');
},
@@ -1089,14 +1104,21 @@ const filterMembers = (filterTerm) => {
let members = currBoard.activeMembers();
if (filterTerm) {
const searchTerm = filterTerm.toLowerCase();
members = members
.map(member => ({
member,
user: ReactiveCache.getUser(member.userId)
}))
.filter(({ user }) =>
(user.profile.fullname !== undefined && user.profile.fullname.toLowerCase().indexOf(filterTerm.toLowerCase()) !== -1)
|| user.profile.fullname === undefined && user.profile.username !== undefined && user.profile.username.toLowerCase().indexOf(filterTerm.toLowerCase()) !== -1)
.filter(({ user }) => {
// Check if user data is available
if (!user || !user.profile) {
return false;
}
const fullname = (user.profile.fullname || '').toLowerCase();
const username = (user.username || '').toLowerCase();
return fullname.indexOf(searchTerm) !== -1 || username.indexOf(searchTerm) !== -1;
})
.map(({ member }) => member);
}
return members;
@@ -1427,7 +1449,8 @@ Template.setCardColorPopup.helpers({
Template.setCardColorPopup.events({
'click .js-palette-color'(event, tpl) {
tpl.currentColor.set(Template.currentData().color);
const paletteData = Blaze.getData(event.currentTarget);
tpl.currentColor.set(paletteData?.color);
},
async 'click .js-submit'(event, tpl) {
event.preventDefault();
@@ -1446,7 +1469,9 @@ Template.setCardColorPopup.events({
});
Template.setSelectionColorPopup.onCreated(function () {
this.currentColor = new ReactiveVar(null);
const selectedCards = ReactiveCache.getCards(MultiSelection.getMongoSelector());
const uniqueColors = [...new Set(selectedCards.map(card => card.color || null))];
this.currentColor = new ReactiveVar(uniqueColors.length === 1 ? uniqueColors[0] : null);
});
Template.setSelectionColorPopup.helpers({
@@ -1467,22 +1492,32 @@ Template.setSelectionColorPopup.events({
const color = colorClass ? colorClass.replace('card-details-', '') : null;
tpl.currentColor.set(color);
},
async 'click .js-submit'(event, tpl) {
async 'submit form.edit-label'(event, tpl) {
event.preventDefault();
const color = tpl.currentColor.get();
// Use MultiSelection to get selected cards and set color on each
for (const card of ReactiveCache.getCards(MultiSelection.getMongoSelector())) {
await card.setColor(color);
try {
for (const card of ReactiveCache.getCards(MultiSelection.getMongoSelector())) {
await card.setColor(color);
}
Popup.back();
} catch (error) {
alert(error?.reason || error?.message || 'Failed to save selection color');
}
Popup.back();
},
async 'click .js-submit'(event, tpl) {
event.preventDefault();
await tpl.$('form.edit-label').trigger('submit');
},
async 'click .js-remove-color'(event, tpl) {
event.preventDefault();
// Use MultiSelection to get selected cards and remove color from each
for (const card of ReactiveCache.getCards(MultiSelection.getMongoSelector())) {
await card.setColor(null);
try {
for (const card of ReactiveCache.getCards(MultiSelection.getMongoSelector())) {
await card.setColor(null);
}
Popup.back();
} catch (error) {
alert(error?.reason || error?.message || 'Failed to unset selection color');
}
Popup.back();
},
});
@@ -1593,7 +1628,7 @@ Template.cardMorePopup.events({
if (selection === 'none') {
tpl.parentBoard.set(null);
} else {
subManager.subscribe('board', $(event.currentTarget).val(), false);
Meteor.subscribe('board', $(event.currentTarget).val(), false);
tpl.parentBoard.set(selection);
list.prop('disabled', false);
}
+7 -2
View File
@@ -2,9 +2,14 @@ import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import Cards from '/models/cards';
import Boards from '/models/boards';
import ChecklistItems from '/models/checklistItems';
import Checklists from '/models/checklists';
import { BoardSwimlaneListCardDialog } from '/client/lib/dialogWithBoardSwimlaneListCard';
import { EscapeActions } from '/client/lib/escapeActions';
import { Utils } from '/client/lib/utils';
import autosize from 'autosize';
const subManager = new SubsManager();
// SubsManager removed for Meteor 3 migration
const { calculateIndexData } = Utils;
function initSorting(items) {
@@ -218,7 +223,7 @@ Template.checklists.events({
event.preventDefault();
Template.currentData().card.toggleHideFinishedChecklist();
},
keydown(event) {
'keydown textarea.js-add-checklist-item'(event) {
//If user press enter key inside a form, submit it
//Unless the user is also holding down the 'shift' key
if (event.keyCode === 13 && !event.shiftKey) {
+4 -1
View File
@@ -1,8 +1,11 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { LABEL_COLORS } from '/models/metadata/colors';
import { EscapeActions } from '/client/lib/escapeActions';
import { Utils } from '/client/lib/utils';
let labelColors;
Meteor.startup(() => {
labelColors = Boards.simpleSchema()._schema['labels.$.color'].allowedValues;
labelColors = LABEL_COLORS;
});
const getFallbackLabelColor = () => {
+33 -27
View File
@@ -11,17 +11,21 @@ template(name="minicard")
a.minicard-details-menu-with-handle.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
i.fa.fa-bars
.dates
if getReceived
.date
+minicardReceivedDate
if getStart
.date
+minicardStartDate
if getDue
.date
+minicardDueDate
if getEnd
+minicardEndDate
if showReceived
if getReceived
.date
+minicardReceivedDate
if showStart
if getStart
.date
+minicardStartDate
if showDue
if getDue
.date
+minicardDueDate
if showEnd
if getEnd
+minicardEndDate
if getSpentTime
.date
+cardSpentTime
@@ -68,19 +72,20 @@ template(name="minicard")
span.linked-icon.linked-archived
i.fa.fa-archive
+viewer
if currentBoard.allowsCardNumber
if showCardNumber
span.card-number
| ##{getCardNumber}
= getTitle
if labels
.minicard-labels(class="{{#if hiddenMinicardLabelText}}minicard-labels-no-text{{/if}}")
each labels
unless hiddenMinicardLabelText
span.js-card-label.card-label(class="card-label-{{color}}" title=name)
+viewer
= name
if hiddenMinicardLabelText
.minicard-label(class="card-label-{{color}}" title="{{name}}")
if showLabels
if labels
.minicard-labels(class="{{#if hiddenMinicardLabelText}}minicard-labels-no-text{{/if}}")
each labels
unless hiddenMinicardLabelText
span.js-card-label.card-label(class="card-label-{{color}}" title=name)
+viewer
= name
if hiddenMinicardLabelText
.minicard-label(class="card-label-{{color}}" title="{{name}}")
.minicard-custom-fields
each customFieldsWD
@@ -179,12 +184,13 @@ template(name="minicard")
span.badge-icon
i.fa.fa-paperclip
span.badge-text= attachments.length
if allSubtasks.count
.badge
span.badge-icon
i.fa.fa-globe
span.badge-text.check-list-text {{subtasksFinishedCount}}/{{allSubtasksCount}}
//{{subtasksFinishedCount}}/{{subtasksCount}} does not work because when a subtaks is archived, the count goes down
if showSubtasks
if allSubtasks.count
.badge
span.badge-icon
i.fa.fa-globe
span.badge-text.check-list-text {{subtasksFinishedCount}}/{{allSubtasksCount}}
//{{subtasksFinishedCount}}/{{subtasksCount}} does not work because when a subtaks is archived, the count goes down
if currentBoard.allowsCardSortingByNumber
if currentBoard.allowsCardSortingByNumberOnMinicard
.badge
+31 -12
View File
@@ -3,6 +3,7 @@ import { TAPi18n } from '/imports/i18n';
import { CustomFieldStringTemplate } from '/client/lib/customFields';
import { handleFileUpload } from './attachments';
import uploadProgressManager from '../../lib/uploadProgressManager';
import { Utils } from '/client/lib/utils';
// Template.cards.events({
// 'click .member': Popup.open('cardMember')
@@ -49,32 +50,50 @@ Template.minicard.helpers({
},
showMembers() {
// cache "board" to reduce the mini-mongodb access
const board = this.board();
let ret = false;
if (board) {
ret =
board.allowsMembers === null ||
board.allowsMembers === undefined ||
board.allowsMembers
;
ret = board.allowsMembersOnMinicard ?? false;
}
return ret;
},
showAssignee() {
// cache "board" to reduce the mini-mongodb access
const board = this.board();
let ret = false;
if (board) {
ret =
board.allowsAssignee === null ||
board.allowsAssignee === undefined ||
board.allowsAssignee
;
ret = board.allowsAssigneeOnMinicard ?? false;
}
return ret;
},
showReceived() {
const board = this.board();
return board ? board.allowsReceivedDateOnMinicard : false;
},
showStart() {
const board = this.board();
return board ? board.allowsStartDateOnMinicard : false;
},
showDue() {
const board = this.board();
return board ? board.allowsDueDateOnMinicard : false;
},
showEnd() {
const board = this.board();
return board ? board.allowsEndDateOnMinicard : false;
},
showLabels() {
const board = this.board();
return board ? board.allowsLabelsOnMinicard : false;
},
showCardNumber() {
const board = this.board();
return board ? board.allowsCardNumberOnMinicard : false;
},
showSubtasks() {
const board = this.board();
return board ? board.allowsSubtasksOnMinicard : false;
},
hiddenMinicardLabelText() {
const currentUser = ReactiveCache.getCurrentUser();
+45
View File
@@ -1,7 +1,52 @@
import { ReactiveCache } from '/imports/reactiveCache';
Template.resultCard.helpers({
userId() {
return Meteor.userId();
},
getBoard() {
const card = Template.currentData();
try {
if (card && card.boardId) {
const board = ReactiveCache.getBoard(card.boardId);
return board || null;
}
} catch (err) {
// Silently handle errors
}
return null;
},
getSwimlane() {
const card = Template.currentData();
try {
if (card && card.swimlaneId) {
const swimlane = ReactiveCache.getSwimlane(card.swimlaneId);
return swimlane || null;
}
} catch (err) {
// Silently handle errors
}
return null;
},
getList() {
const card = Template.currentData();
try {
if (card && card.listId) {
const list = ReactiveCache.getList(card.listId);
return list || null;
}
} catch (err) {
// Silently handle errors
}
return null;
},
originRelativeUrl() {
const card = Template.currentData();
if (card && card.boardId && card._id) {
return `/board/${card.boardId}?cardId=${card._id}`;
}
return '#';
},
});
Template.resultCard.events({
+15 -4
View File
@@ -15,7 +15,7 @@ textarea.js-edit-subtask-item {
color: #8c8c8c;
text-decoration: underline;
word-wrap: break-word;
float: right;
flex-shrink: 0;
padding-top: 6px;
}
.delete-text:hover,
@@ -24,8 +24,13 @@ textarea.js-edit-subtask-item {
.js-delete-subtask-item:hover {
color: inherit;
}
.subtask-title {
display: flex;
align-items: center;
width: 100%;
}
.subtask-title .checkbox {
float: left;
flex-shrink: 0;
width: 30px;
height: 30px;
font-size: 18px;
@@ -34,10 +39,12 @@ textarea.js-edit-subtask-item {
.subtask-title .title {
font-size: 18px;
line-height: 25px;
flex: 1;
order: 1;
}
.subtask-title .subtasks-stat {
margin: 0 0.5em;
float: right;
flex-shrink: 0;
padding-top: 6px;
}
.subtask-title .subtasks-stat.is-finished {
@@ -46,6 +53,9 @@ textarea.js-edit-subtask-item {
.subtask-title .js-delete-subtask {
margin: 0 0.5em;
}
.subtask-title .js-view-subtask {
margin: 0 0.5em;
}
.js-confirm-subtask-delete {
background-color: #f7f7f7;
position: absolute;
@@ -175,6 +185,7 @@ body.grey-icons-enabled .subtasks-item .check-box-unicode {
display: inline-block;
}
.subtask-details-menu {
float: right;
flex-shrink: 0;
padding: 6px 10px 6px 10px;
order: 2;
}
+28 -9
View File
@@ -1,8 +1,17 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import Cards from '/models/cards';
import { Filter } from '/client/lib/filter';
Template.subtasks.events({
'click .js-open-subtask-details-menu': Popup.open('subtaskActions'),
'click .js-open-subtask-details-menu'(event) {
// Close any existing popup first to avoid accumulating content
if (Popup.isOpen()) {
Popup.close();
}
// Now open the popup for this specific subtask
Popup.open('subtaskActions').call(this, event);
},
async 'submit .js-add-subtask'(event, tpl) {
event.preventDefault();
const textarea = tpl.find('textarea.js-add-subtask-item');
@@ -11,8 +20,16 @@ Template.subtasks.events({
const card = ReactiveCache.getCard(cardId);
const sortIndex = -1;
const crtBoard = ReactiveCache.getBoard(card.boardId);
const targetBoard = crtBoard.getDefaultSubtasksBoard();
const listId = targetBoard.getDefaultSubtasksListId();
const targetBoard = await crtBoard.getDefaultSubtasksBoardAsync();
if (!targetBoard) {
return;
}
const targetList = await targetBoard.getDefaultSubtasksListAsync();
if (!targetList) {
return;
}
const listId = targetList._id;
//Get the full swimlane data for the parent task.
const parentSwimlane = ReactiveCache.getSwimlane({
@@ -20,10 +37,12 @@ Template.subtasks.events({
_id: card.swimlaneId,
});
//find the swimlane of the same name in the target board.
const targetSwimlane = ReactiveCache.getSwimlane({
boardId: targetBoard._id,
title: parentSwimlane.title,
});
const targetSwimlane = parentSwimlane
? ReactiveCache.getSwimlane({
boardId: targetBoard._id,
title: parentSwimlane.title,
})
: undefined;
//If no swimlane with a matching title exists in the target board, fall back to the default swimlane.
const swimlaneId =
targetSwimlane === undefined
@@ -33,7 +52,7 @@ Template.subtasks.events({
const nextCardNumber = await targetBoard.getNextCardNumber();
if (title) {
const _id = Cards.insert({
const _id = await Cards.insertAsync({
title,
parentId: cardId,
members: [],
@@ -44,7 +63,7 @@ Template.subtasks.events({
sort: sortIndex,
swimlaneId,
type: 'cardType-card',
cardNumber: nextCardNumber
cardNumber: nextCardNumber,
});
// In case the filter is active we need to add the newly inserted card in
+2
View File
@@ -1,4 +1,5 @@
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import Cards from '/models/cards';
// Add click handler to ganttView for card titles
Template.ganttView.events({
@@ -14,6 +15,7 @@ Template.ganttView.events({
},
});
import { Template } from 'meteor/templating';
import { Utils } from '/client/lib/utils';
// Blaze template helpers for ganttView
function getISOWeekInfo(d) {
+2
View File
@@ -4,6 +4,8 @@ import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import { wekanGetMembersToMap } from './wekanMembersMapper';
import { csvGetMembersToMap } from './csvMembersMapper';
import getSlug from 'limax';
import { UserSearchIndex } from '/models/users';
import { Utils } from '/client/lib/utils';
const Papa = require('papaparse');
+1 -1
View File
@@ -1,4 +1,4 @@
const { ReactiveVar } = require('meteor/reactive-var');
import { ReactiveVar } from 'meteor/reactive-var';
// Creation-time stack tracking which basicTabs is currently being rendered.
// Blaze creates parent views before children, so when tabContent.onCreated fires,
+25 -25
View File
@@ -1226,52 +1226,52 @@ form.inlined-form .list-composer {
margin-top: unset !important;
}
.list-header-white {
border-bottom: 6px solid #fff;
border-bottom: 6px solid #fff !important;
}
.list-header-green {
border-bottom: 6px solid #3cb500;
border-bottom: 6px solid #3cb500 !important;
}
.list-header-yellow {
border-bottom: 6px solid #fad900;
border-bottom: 6px solid #fad900 !important;
}
.list-header-orange {
border-bottom: 6px solid #ff9f19;
border-bottom: 6px solid #ff9f19 !important;
}
.list-header-red {
border-bottom: 6px solid #eb4646;
border-bottom: 6px solid #eb4646 !important;
}
.list-header-purple {
border-bottom: 6px solid #a632db;
border-bottom: 6px solid #a632db !important;
}
.list-header-blue {
border-bottom: 6px solid #0079bf;
border-bottom: 6px solid #0079bf !important;
}
.list-header-pink {
border-bottom: 6px solid #ff78cb;
border-bottom: 6px solid #ff78cb !important;
}
.list-header-sky {
border-bottom: 6px solid #00c2e0;
border-bottom: 6px solid #00c2e0 !important;
}
.list-header-black {
border-bottom: 6px solid #4d4d4d;
border-bottom: 6px solid #4d4d4d !important;
}
.list-header-lime {
border-bottom: 6px solid #51e898;
border-bottom: 6px solid #51e898 !important;
}
.list-header-silver {
border-bottom: 6px solid #e4e4e4;
border-bottom: 6px solid #e4e4e4 !important;
}
.list-header-peachpuff {
border-bottom: 6px solid #ffdab9;
border-bottom: 6px solid #ffdab9 !important;
}
.list-header-crimson {
border-bottom: 6px solid #dc143c;
border-bottom: 6px solid #dc143c !important;
}
.list-header-plum {
border-bottom: 6px solid #dda0dd;
border-bottom: 6px solid #dda0dd !important;
}
.list-header-darkgreen {
border-bottom: 6px solid #006400;
border-bottom: 6px solid #006400 !important;
}
/* Final collapsed desktop overrides:
@@ -1379,31 +1379,31 @@ form.inlined-form .list-composer {
opacity: 1 !important;
}
.list-header-slateblue {
border-bottom: 6px solid #6a5acd;
border-bottom: 6px solid #6a5acd !important;
}
.list-header-magenta {
border-bottom: 6px solid #f0f;
border-bottom: 6px solid #f0f !important;
}
.list-header-gold {
border-bottom: 6px solid #ffd700;
border-bottom: 6px solid #ffd700 !important;
}
.list-header-navy {
border-bottom: 6px solid #000080;
border-bottom: 6px solid #000080 !important;
}
.list-header-gray {
border-bottom: 6px solid #808080;
border-bottom: 6px solid #808080 !important;
}
.list-header-saddlebrown {
border-bottom: 6px solid #8b4513;
border-bottom: 6px solid #8b4513 !important;
}
.list-header-paleturquoise {
border-bottom: 6px solid #afeeee;
border-bottom: 6px solid #afeeee !important;
}
.list-header-mistyrose {
border-bottom: 6px solid #ffe4e1;
border-bottom: 6px solid #ffe4e1 !important;
}
.list-header-indigo {
border-bottom: 6px solid #4b0082;
border-bottom: 6px solid #4b0082 !important;
}
.list.list-collapsed .collapsed-list-drag-area {
+3
View File
@@ -1,5 +1,8 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import { EscapeActions } from '/client/lib/escapeActions';
import { MultiSelection } from '/client/lib/multiSelection';
import { Utils } from '/client/lib/utils';
require('/client/lib/jquery-ui.js')
const { calculateIndex } = Utils;
+37 -14
View File
@@ -3,8 +3,15 @@ import { TAPi18n } from '/imports/i18n';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import { getSpinnerName, getSpinnerTemplate } from '/client/lib/spinner';
import getSlug from 'limax';
import Boards from '/models/boards';
import Cards from '/models/cards';
import Swimlanes from '/models/swimlanes';
import { Filter } from '/client/lib/filter';
import { MultiSelection } from '/client/lib/multiSelection';
import { Utils } from '/client/lib/utils';
import autosize from 'autosize';
const subManager = new SubsManager();
// SubsManager removed for Meteor 3 migration
const InfiniteScrollIter = 10;
Template.listBody.onCreated(function () {
@@ -59,7 +66,10 @@ Template.listBody.onCreated(function () {
};
this.idOrNull = (swimlaneId) => {
const data = Template.currentData();
const data = this.data;
if (!data) {
return undefined;
}
if (
Utils.boardView() === 'board-view-swimlanes' ||
data.board().isTemplatesBoard()
@@ -88,7 +98,10 @@ Template.listBody.onCreated(function () {
const labelIds = formComponent.labels.get();
const customFields = formComponent.customFields.get();
const data = Template.currentData();
const data = this.data;
if (!data) {
return;
}
const board = data.board();
let linkedId = '';
let swimlaneId = '';
@@ -107,13 +120,15 @@ Template.listBody.onCreated(function () {
if (swimlane.isCardTemplatesSwimlane()) cardType = 'template-card';
// If this is the board templates swimlane, insert a board template and a linked card
else if (swimlane.isBoardTemplatesSwimlane()) {
linkedId = Boards.insert({
linkedId = await Boards.insertAsync({
title,
slug: getSlug(title) || 'board',
permission: 'private',
type: 'template-board',
});
Swimlanes.insert({
title: TAPi18n.__('default'),
const defaultTitle = TAPi18n.__('default');
await Swimlanes.insertAsync({
title: typeof defaultTitle === 'string' ? defaultTitle : 'Default',
boardId: linkedId,
});
cardType = 'cardType-linkedBoard';
@@ -294,7 +309,15 @@ Template.listBody.helpers({
listId: Template.currentData()._id,
archived: false,
};
if (swimlaneId) selector.swimlaneId = swimlaneId;
if (swimlaneId) {
// Fallback: also show cards with no swimlaneId (null/empty) so that
// pre-migration / orphaned cards are always visible without a DB migration.
selector.$or = [
{ swimlaneId },
{ swimlaneId: null }, // null covers null AND missing field
{ swimlaneId: '' }, // empty string from shared-lists era
];
}
const ret = ReactiveCache.getCards(Filter.mongoSelector(selector), {
// sort: ['sort'],
sort: sortBy,
@@ -554,7 +577,7 @@ Template.linkCardPopup.onCreated(function () {
this.boardId = Session.get('currentBoard');
// In order to get current board info
subManager.subscribe('board', this.boardId, false);
Meteor.subscribe('board', this.boardId, false);
this.board = ReactiveCache.getBoard(this.boardId);
// List where to insert card
this.list = $(Popup._getTopStack().openerElement).closest('.js-list');
@@ -685,7 +708,7 @@ Template.linkCardPopup.helpers({
Template.linkCardPopup.events({
'change .js-select-boards'(evt, tpl) {
const val = $(evt.currentTarget).val();
subManager.subscribe('board', val, false);
Meteor.subscribe('board', val, false);
// Clear selections to allow linking only board or re-choose swimlane/list
tpl.selectedSwimlaneId.set('');
tpl.selectedListId.set('');
@@ -773,7 +796,7 @@ Template.searchElementPopup.onCreated(function () {
if (this.isTemplateSearch) {
const boardId = (ReactiveCache.getCurrentUser().profile || {}).templatesBoardId;
if (boardId) {
subManager.subscribe('board', boardId, false);
Meteor.subscribe('board', boardId, false);
this.board = ReactiveCache.getBoard(boardId);
}
} else {
@@ -785,7 +808,7 @@ Template.searchElementPopup.onCreated(function () {
}
this.boardId = this.board._id;
// Subscribe to this board
subManager.subscribe('board', this.boardId, false);
Meteor.subscribe('board', this.boardId, false);
this.selectedBoardId = new ReactiveVar(this.boardId);
this.list = $(Popup._getTopStack().openerElement).closest('.js-list');
@@ -849,7 +872,7 @@ Template.searchElementPopup.helpers({
} else if (tpl.isBoardTemplateSearch) {
const boards = board.searchBoards(tpl.term.get());
boards.forEach(board => {
subManager.subscribe('board', board.linkedId, false);
Meteor.subscribe('board', board.linkedId, false);
});
return boards;
} else {
@@ -860,7 +883,7 @@ Template.searchElementPopup.helpers({
Template.searchElementPopup.events({
'change .js-select-boards'(evt, tpl) {
subManager.subscribe('board', $(evt.currentTarget).val(), false);
Meteor.subscribe('board', $(evt.currentTarget).val(), false);
tpl.selectedBoardId.set($(evt.currentTarget).val());
},
'submit .js-search-term-form'(evt, tpl) {
@@ -915,7 +938,7 @@ Template.searchElementPopup.events({
},
(err, data) => {
_id = data;
subManager.subscribe('board', _id, false);
Meteor.subscribe('board', _id, false);
FlowRouter.go('board', {
id: _id,
slug: getSlug(element.title),
+33 -4
View File
@@ -3,10 +3,14 @@ import Lists from '../../../models/lists';
import { TAPi18n } from '/imports/i18n';
import dragscroll from '@wekanteam/dragscroll';
import { BoardSwimlaneListDialog } from '/client/lib/dialogWithBoardSwimlaneList';
import Cards from '/models/cards';
import { LIST_COLORS } from '/models/metadata/colors';
import { MultiSelection } from '/client/lib/multiSelection';
import { Utils } from '/client/lib/utils';
let listsColors;
Meteor.startup(() => {
listsColors = Lists.simpleSchema()._schema.color.allowedValues;
listsColors = LIST_COLORS;
});
Template.listHeader.helpers({
@@ -439,14 +443,39 @@ Template.setListColorPopup.helpers({
Template.setListColorPopup.events({
'click .js-palette-color'(event, tpl) {
tpl.currentColor.set(Template.currentData().color);
const paletteData = Blaze.getData(event.currentTarget);
tpl.currentColor.set(paletteData?.color);
},
async 'submit form'(event, tpl) {
event.preventDefault();
console.log('[ListColor] submit form, color:', tpl.currentColor.get(), 'listId:', tpl.currentList?._id, 'setColor type:', typeof tpl.currentList?.setColor);
try {
const result = await tpl.currentList.setColor(tpl.currentColor.get());
console.log('[ListColor] submit form setColor result:', result);
} catch (err) {
console.error('[ListColor] submit form setColor error:', err);
}
Popup.close();
},
async 'click .js-submit'(event, tpl) {
await tpl.currentList.setColor(tpl.currentColor.get());
event.preventDefault();
console.log('[ListColor] click submit, color:', tpl.currentColor.get(), 'listId:', tpl.currentList?._id, 'setColor type:', typeof tpl.currentList?.setColor);
try {
const result = await tpl.currentList.setColor(tpl.currentColor.get());
console.log('[ListColor] click submit setColor result:', result);
} catch (err) {
console.error('[ListColor] click submit setColor error:', err);
}
Popup.close();
},
async 'click .js-remove-color'(event, tpl) {
await tpl.currentList.setColor(null);
event.preventDefault();
console.log('[ListColor] remove color, listId:', tpl.currentList?._id);
try {
await tpl.currentList.setColor(null);
} catch (err) {
console.error('[ListColor] remove color error:', err);
}
Popup.close();
},
});
+1
View File
@@ -1,5 +1,6 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import AccessibilitySettings from '/models/accessibilitySettings';
// Shared helpers for both accessibility templates
const accessibilityHelpers = {
+2
View File
@@ -1,3 +1,5 @@
import Boards from '/models/boards';
Template.bookmarks.helpers({
hasStarredBoards() {
const user = ReactiveCache.getCurrentUser();
+1
View File
@@ -1,5 +1,6 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import { Utils } from '/client/lib/utils';
// const subManager = new SubsManager();
+6 -3
View File
@@ -1,6 +1,9 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { findWhere } from '/imports/lib/collectionHelpers';
import { TAPi18n } from '/imports/i18n';
import Attachments from '/models/attachments';
import { Utils } from '/client/lib/utils';
import autosize from 'autosize';
var converter = require('@wekanteam/html-to-markdown');
const specialHandles = [
@@ -207,11 +210,11 @@ Template.editor.onRendered(function () {
const currentCard = Utils.getCurrentCard();
const MAX_IMAGE_PIXEL = Utils.MAX_IMAGE_PIXEL;
const COMPRESS_RATIO = Utils.IMAGE_COMPRESS_RATIO;
const processUpload = function(file) {
const uploader = Attachments.insert(
const processUpload = async function(file) {
const uploader = await Attachments.insertAsync(
{
file,
meta: Utils.getCommonAttachmentMetaFrom(card),
meta: Utils.getCommonAttachmentMetaFrom(currentCard),
chunkSize: 'dynamic',
},
false,
+15
View File
@@ -84,6 +84,21 @@
margin-right: auto;
margin-left: auto;
}
.js-new-search {
cursor: pointer;
transition: background-color 0.2s;
}
.board-color-belize.js-new-search {
background-color: #216694 !important;
color: #fff !important;
border: none !important;
}
.board-color-belize.js-new-search:hover {
background-color: #1a4d6a !important;
}
.board-color-belize.js-new-search:active {
background-color: #0d334d !important;
}
.global-search-operator {
font-family: Courier;
}
+19 -40
View File
@@ -11,68 +11,47 @@ template(name="globalSearchModalTitle")
| {{_ 'globalSearch-title'}}
template(name="resultsPaged")
if resultsHeading.get
h1
= resultsHeading.get
a(title="{{_ 'link-to-search' }}" href="{{ getSearchHref }}")
| 🔗
each card in results.get
if resultsHeading
h1= resultsHeading
each card in results
+resultCard(card)
table.global-search-footer
tr
td.global-search-previous-page
if hasPreviousPage.get
if hasPreviousPage
button.js-previous-page
| {{_ 'previous-page' }}
td.global-search-next-page(align="right")
if hasNextPage.get
if hasNextPage
button.js-next-page
| {{_ 'next-page' }}
template(name="globalSearch")
if currentUser
.wrapper
form.global-search-page.js-search-query-form
.wrapper(class=boardColorClass)
form.global-search-page.js-search-query-form(onsubmit="return false;")
input.global-search-query-input(
style="{# if hasResults.get #}display: inline-block;{#/if#}"
style="display: inline-block;"
id="global-search-input"
type="text"
name="searchQuery"
placeholder="{{_ 'search-example'}}"
value="{{ query.get }}"
value="{{ queryValue }}"
autofocus dir="auto"
)
a.js-new-search
button.js-new-search.board-color-belize(type="button" style="background: #216694 !important; background-color: #216694 !important; color: #fff !important; border: none !important;")
| 🧹
if debug.get.show
h1 Debug
if debug.get.showSelector
h2 Selector
button.js-copy-debug-selector
= 'Copy'
pre(
id="debug-selector"
)
= sessionData.selector
if debug.get.showProjection
h2 Projection
button.js-copy-debug-projection
= 'Copy'
pre(
id="debug-projection"
)
= sessionData.projection
if searching.get
if searching
+spinner
else if hasResults.get
else if hasResults
.global-search-results-list-wrapper
if hasQueryErrors.get
if hasQueryErrors
ul
each msg in errorMessages
li.global-search-error-messages
= msg
+resultsPaged(this)
else if serverError.get
+resultsPaged getSearchData
else if serverError
.global-search-page
.global-search-help
h1 {{_ 'server-error' }}
@@ -83,22 +62,22 @@ template(name="globalSearch")
.global-search-help
h2 {{_ 'boards' }}
.lists-wrapper
each title in myBoardNames.get
each title in myBoardNames
span.card-label.list-title.js-board-title
= title
h2 {{_ 'lists' }}
.lists-wrapper
each title in myLists.get
each title in myLists
span.card-label.list-title.js-list-title
= title
h2 {{_ 'label-colors' }}
.palette-colors: each label in labelColors
span.card-label.palette-color.js-label-color(class="card-label-{{label.color}}")
= label.name
if myLabelNames.get.length
if myLabelNames
h2 {{_ 'label-names' }}
.lists-wrapper
each name in myLabelNames.get
each name in myLabelNames
span.card-label.list-title.js-label-name
= name
.global-search-instructions
+191 -160
View File
@@ -1,7 +1,8 @@
import { TAPi18n } from '/imports/i18n';
import { CardSearchPaged } from '../../lib/cardSearch';
import Boards from '../../../models/boards';
import { LABEL_COLORS } from '/models/metadata/colors';
import { Query, QueryErrors } from '../../../config/query-classes';
import { ReactiveCache } from '/imports/reactiveCache';
// const subManager = new SubsManager();
@@ -98,36 +99,45 @@ Template.globalSearch.helpers({
return Meteor.userId();
},
// Return ReactiveVars so jade can use .get pattern
// Return ReactiveVar values so Blaze can use them
searching() {
return Template.instance().search.searching;
const val = Template.instance().search.searching.get();
return val;
},
hasResults() {
return Template.instance().search.hasResults;
const val = Template.instance().search.hasResults.get();
return val;
},
hasQueryErrors() {
return Template.instance().search.hasQueryErrors;
const val = Template.instance().search.hasQueryErrors.get();
return val;
},
serverError() {
return Template.instance().search.serverError;
const val = Template.instance().search.serverError.get();
return val;
},
query() {
return Template.instance().search.query;
},
debug() {
return Template.instance().search.debug;
const val = Template.instance().search.debug.get();
return val;
},
resultsHeading() {
return Template.instance().search.resultsHeading;
const rv = Template.instance().search.resultsHeading;
return rv ? rv.get() : '';
},
results() {
return Template.instance().search.results;
const rv = Template.instance().search.results;
return rv ? rv.get() : [];
},
hasNextPage() {
return Template.instance().search.hasNextPage;
const rv = Template.instance().search.hasNextPage;
return rv ? rv.get() : false;
},
hasPreviousPage() {
return Template.instance().search.hasPreviousPage;
const rv = Template.instance().search.hasPreviousPage;
return rv ? rv.get() : false;
},
sessionData() {
return Template.instance().search.sessionData;
@@ -137,124 +147,170 @@ Template.globalSearch.helpers({
},
myLists() {
return Template.instance().myLists;
const rv = Template.instance().myLists;
return rv ? rv.get() : [];
},
myLabelNames() {
return Template.instance().myLabelNames;
const rv = Template.instance().myLabelNames;
return rv ? rv.get() : [];
},
myBoardNames() {
return Template.instance().myBoardNames;
const rv = Template.instance().myBoardNames;
return rv ? rv.get() : [];
},
errorMessages() {
return errorMessages(Template.instance());
},
searchInstructions() {
const tags = {
operator_board: TAPi18n.__('operator-board'),
operator_list: TAPi18n.__('operator-list'),
operator_swimlane: TAPi18n.__('operator-swimlane'),
operator_comment: TAPi18n.__('operator-comment'),
operator_label: TAPi18n.__('operator-label'),
operator_label_abbrev: TAPi18n.__('operator-label-abbrev'),
operator_user: TAPi18n.__('operator-user'),
operator_user_abbrev: TAPi18n.__('operator-user-abbrev'),
operator_member: TAPi18n.__('operator-member'),
operator_member_abbrev: TAPi18n.__('operator-member-abbrev'),
operator_assignee: TAPi18n.__('operator-assignee'),
operator_assignee_abbrev: TAPi18n.__('operator-assignee-abbrev'),
operator_creator: TAPi18n.__('operator-creator'),
operator_due: TAPi18n.__('operator-due'),
operator_created: TAPi18n.__('operator-created'),
operator_modified: TAPi18n.__('operator-modified'),
operator_status: TAPi18n.__('operator-status'),
operator_has: TAPi18n.__('operator-has'),
operator_sort: TAPi18n.__('operator-sort'),
operator_limit: TAPi18n.__('operator-limit'),
operator_debug: TAPi18n.__('operator-debug'),
operator_org: TAPi18n.__('operator-org'),
operator_team: TAPi18n.__('operator-team'),
predicate_overdue: TAPi18n.__('predicate-overdue'),
predicate_archived: TAPi18n.__('predicate-archived'),
predicate_all: TAPi18n.__('predicate-all'),
predicate_ended: TAPi18n.__('predicate-ended'),
predicate_week: TAPi18n.__('predicate-week'),
predicate_month: TAPi18n.__('predicate-month'),
predicate_quarter: TAPi18n.__('predicate-quarter'),
predicate_year: TAPi18n.__('predicate-year'),
predicate_attachment: TAPi18n.__('predicate-attachment'),
predicate_description: TAPi18n.__('predicate-description'),
predicate_checklist: TAPi18n.__('predicate-checklist'),
predicate_public: TAPi18n.__('predicate-public'),
predicate_private: TAPi18n.__('predicate-private'),
predicate_due: TAPi18n.__('predicate-due'),
predicate_created: TAPi18n.__('predicate-created'),
predicate_modified: TAPi18n.__('predicate-modified'),
predicate_start: TAPi18n.__('predicate-start'),
predicate_end: TAPi18n.__('predicate-end'),
predicate_assignee: TAPi18n.__('predicate-assignee'),
predicate_member: TAPi18n.__('predicate-member'),
predicate_selector: TAPi18n.__('predicate-selector'),
predicate_projection: TAPi18n.__('predicate-projection'),
};
getSearchData() {
return Template.instance().search;
},
let text = '';
[
['# ', 'globalSearch-instructions-heading'],
['\n', 'globalSearch-instructions-description'],
['\n\n', 'globalSearch-instructions-operators'],
['\n- ', 'globalSearch-instructions-operator-board'],
['\n- ', 'globalSearch-instructions-operator-list'],
['\n- ', 'globalSearch-instructions-operator-swimlane'],
['\n- ', 'globalSearch-instructions-operator-comment'],
['\n- ', 'globalSearch-instructions-operator-label'],
['\n- ', 'globalSearch-instructions-operator-hash'],
['\n- ', 'globalSearch-instructions-operator-user'],
['\n- ', 'globalSearch-instructions-operator-at'],
['\n- ', 'globalSearch-instructions-operator-member'],
['\n- ', 'globalSearch-instructions-operator-assignee'],
['\n- ', 'globalSearch-instructions-operator-creator'],
['\n- ', 'globalSearch-instructions-operator-org'],
['\n- ', 'globalSearch-instructions-operator-team'],
['\n- ', 'globalSearch-instructions-operator-due'],
['\n- ', 'globalSearch-instructions-operator-created'],
['\n- ', 'globalSearch-instructions-operator-modified'],
['\n- ', 'globalSearch-instructions-operator-status'],
['\n - ', 'globalSearch-instructions-status-archived'],
['\n - ', 'globalSearch-instructions-status-public'],
['\n - ', 'globalSearch-instructions-status-private'],
['\n - ', 'globalSearch-instructions-status-all'],
['\n - ', 'globalSearch-instructions-status-ended'],
['\n- ', 'globalSearch-instructions-operator-has'],
['\n- ', 'globalSearch-instructions-operator-sort'],
['\n- ', 'globalSearch-instructions-operator-limit'],
['\n## ', 'heading-notes'],
['\n- ', 'globalSearch-instructions-notes-1'],
['\n- ', 'globalSearch-instructions-notes-2'],
['\n- ', 'globalSearch-instructions-notes-3'],
['\n- ', 'globalSearch-instructions-notes-3-2'],
['\n- ', 'globalSearch-instructions-notes-4'],
['\n- ', 'globalSearch-instructions-notes-5'],
].forEach(([prefix, instruction]) => {
text += `${prefix}${TAPi18n.__(instruction, tags)}`
// Replace *<text>* with `<text>` so markdown shows correctly
.replace(/\*\</, '`<')
.replace(/\>\*/, '\>\`')
});
return text;
boardColorClass() {
const boardId = Session.get('currentBoard');
const board = boardId ? ReactiveCache.getBoard(boardId) : null;
return board ? board.colorClass() : 'board-color-belize';
},
searchInstructions() {
try {
const tags = {
operator_board: TAPi18n.__('operator-board'),
operator_list: TAPi18n.__('operator-list'),
operator_swimlane: TAPi18n.__('operator-swimlane'),
operator_comment: TAPi18n.__('operator-comment'),
operator_label: TAPi18n.__('operator-label'),
operator_label_abbrev: TAPi18n.__('operator-label-abbrev'),
operator_user: TAPi18n.__('operator-user'),
operator_user_abbrev: TAPi18n.__('operator-user-abbrev'),
operator_member: TAPi18n.__('operator-member'),
operator_member_abbrev: TAPi18n.__('operator-member-abbrev'),
operator_assignee: TAPi18n.__('operator-assignee'),
operator_assignee_abbrev: TAPi18n.__('operator-assignee-abbrev'),
operator_creator: TAPi18n.__('operator-creator'),
operator_due: TAPi18n.__('operator-due'),
operator_created: TAPi18n.__('operator-created'),
operator_modified: TAPi18n.__('operator-modified'),
operator_status: TAPi18n.__('operator-status'),
operator_has: TAPi18n.__('operator-has'),
operator_sort: TAPi18n.__('operator-sort'),
operator_limit: TAPi18n.__('operator-limit'),
operator_debug: TAPi18n.__('operator-debug'),
operator_org: TAPi18n.__('operator-org'),
operator_team: TAPi18n.__('operator-team'),
predicate_overdue: TAPi18n.__('predicate-overdue'),
predicate_archived: TAPi18n.__('predicate-archived'),
predicate_all: TAPi18n.__('predicate-all'),
predicate_ended: TAPi18n.__('predicate-ended'),
predicate_week: TAPi18n.__('predicate-week'),
predicate_month: TAPi18n.__('predicate-month'),
predicate_quarter: TAPi18n.__('predicate-quarter'),
predicate_year: TAPi18n.__('predicate-year'),
predicate_attachment: TAPi18n.__('predicate-attachment'),
predicate_description: TAPi18n.__('predicate-description'),
predicate_checklist: TAPi18n.__('predicate-checklist'),
predicate_public: TAPi18n.__('predicate-public'),
predicate_private: TAPi18n.__('predicate-private'),
predicate_due: TAPi18n.__('predicate-due'),
predicate_created: TAPi18n.__('predicate-created'),
predicate_modified: TAPi18n.__('predicate-modified'),
predicate_start: TAPi18n.__('predicate-start'),
predicate_end: TAPi18n.__('predicate-end'),
predicate_assignee: TAPi18n.__('predicate-assignee'),
predicate_member: TAPi18n.__('predicate-member'),
predicate_selector: TAPi18n.__('predicate-selector'),
predicate_projection: TAPi18n.__('predicate-projection'),
};
let text = '';
[
['# ', 'globalSearch-instructions-heading'],
['\n', 'globalSearch-instructions-description'],
['\n\n', 'globalSearch-instructions-operators'],
['\n- ', 'globalSearch-instructions-operator-board'],
['\n- ', 'globalSearch-instructions-operator-list'],
['\n- ', 'globalSearch-instructions-operator-swimlane'],
['\n- ', 'globalSearch-instructions-operator-comment'],
['\n- ', 'globalSearch-instructions-operator-label'],
['\n- ', 'globalSearch-instructions-operator-hash'],
['\n- ', 'globalSearch-instructions-operator-user'],
['\n- ', 'globalSearch-instructions-operator-at'],
['\n- ', 'globalSearch-instructions-operator-member'],
['\n- ', 'globalSearch-instructions-operator-assignee'],
['\n- ', 'globalSearch-instructions-operator-creator'],
['\n- ', 'globalSearch-instructions-operator-org'],
['\n- ', 'globalSearch-instructions-operator-team'],
['\n- ', 'globalSearch-instructions-operator-due'],
['\n- ', 'globalSearch-instructions-operator-created'],
['\n- ', 'globalSearch-instructions-operator-modified'],
['\n- ', 'globalSearch-instructions-operator-status'],
['\n - ', 'globalSearch-instructions-status-archived'],
['\n - ', 'globalSearch-instructions-status-public'],
['\n - ', 'globalSearch-instructions-status-private'],
['\n - ', 'globalSearch-instructions-status-all'],
['\n - ', 'globalSearch-instructions-status-ended'],
['\n- ', 'globalSearch-instructions-operator-has'],
['\n- ', 'globalSearch-instructions-operator-sort'],
['\n- ', 'globalSearch-instructions-operator-limit'],
['\n## ', 'heading-notes'],
['\n- ', 'globalSearch-instructions-notes-1'],
['\n- ', 'globalSearch-instructions-notes-2'],
['\n- ', 'globalSearch-instructions-notes-3'],
['\n- ', 'globalSearch-instructions-notes-3-2'],
['\n- ', 'globalSearch-instructions-notes-4'],
['\n- ', 'globalSearch-instructions-notes-5'],
].forEach(([prefix, instruction]) => {
text += `${prefix}${TAPi18n.__(instruction, tags)}`
// Replace *<text>* with `<text>` so markdown shows correctly
.replace(/\*\</, '`<')
.replace(/\>\*/, '\>\`');
});
return text;
} catch (e) {
console.error('Error building searchInstructions:', e);
return '';
}
},
labelColors() {
return Boards.simpleSchema()._schema['labels.$.color'].allowedValues.map(
color => {
return { color, name: TAPi18n.__(`color-${color}`) };
},
);
try {
if (!LABEL_COLORS) return [];
return LABEL_COLORS.map(
color => {
return { color, name: TAPi18n.__(`color-${color}`) };
},
);
} catch (e) {
console.error('Error building labelColors:', e);
return [];
}
},
});
Template.globalSearch.events({
'click input.global-search-query-input'(evt) {
evt.preventDefault();
evt.stopPropagation();
},
'focus input.global-search-query-input'(evt) {
evt.preventDefault();
evt.stopPropagation();
},
'keyup input.global-search-query-input'(evt, tpl) {
evt.preventDefault();
evt.stopPropagation();
// Store the query value
tpl.search.query.set(evt.target.value);
},
'keydown input.global-search-query-input'(evt, tpl) {
// Allow normal key behavior but capture Enter key
if (evt.key === 'Enter') {
evt.preventDefault();
evt.stopPropagation();
searchAllBoards(tpl, evt.target.value);
}
},
'submit .js-search-query-form'(evt, tpl) {
evt.preventDefault();
searchAllBoards(tpl, evt.target.searchQuery.value);
@@ -269,30 +325,6 @@ Template.globalSearch.events({
);
document.getElementById('global-search-input').focus();
},
'click .js-copy-debug-selector'(evt) {
/* Get the text field */
const selector = document.getElementById("debug-selector");
try {
navigator.clipboard.writeText(selector.textContent);
alert("Selector copied to clipboard");
} catch(err) {
alert("Error copying text: " + err);
}
},
'click .js-copy-debug-projection'(evt) {
/* Get the text field */
const projection = document.getElementById("debug-projection");
try {
navigator.clipboard.writeText(projection.textContent);
alert("Projection copied to clipboard");
} catch(err) {
alert("Error copying text: " + err);
}
},
'click .js-board-title'(evt, tpl) {
evt.preventDefault();
const input = document.getElementById('global-search-input');
@@ -340,47 +372,46 @@ Template.globalSearch.events({
},
});
// resultsPaged template helpers - this template is used by globalSearch, brokenCards, and brokenCardsReport.
// It receives the parent's data context (which includes reactive vars) via +resultsPaged(this).
// The jade accesses resultsHeading.get, results.get, getSearchHref, hasPreviousPage.get, hasNextPage.get
// directly on the data context, so we don't need helpers for those when the parent passes
// the right data context. However, we need to ensure the helpers exist for the template.
// resultsPaged template helpers - receives search data context from parent template.
Template.resultsPaged.helpers({
resultsHeading() {
const data = Template.currentData();
if (data && data.resultsHeading) return data.resultsHeading;
// fallback: check parent template
const parentTpl = Template.instance().view.parentView.templateInstance && Template.instance().view.parentView.templateInstance();
if (parentTpl && parentTpl.search) return parentTpl.search.resultsHeading;
return new ReactiveVar('');
if (data && data.resultsHeading) {
const rv = data.resultsHeading;
return rv && typeof rv.get === 'function' ? rv.get() : rv;
}
return '';
},
results() {
const data = Template.currentData();
if (data && data.results) return data.results;
const parentTpl = Template.instance().view.parentView.templateInstance && Template.instance().view.parentView.templateInstance();
if (parentTpl && parentTpl.search) return parentTpl.search.results;
return new ReactiveVar([]);
if (data && data.results) {
const rv = data.results;
return rv && typeof rv.get === 'function' ? rv.get() : rv;
}
return [];
},
getSearchHref() {
const data = Template.currentData();
if (data && data.getSearchHref) return data.getSearchHref();
const parentTpl = Template.instance().view.parentView.templateInstance && Template.instance().view.parentView.templateInstance();
if (parentTpl && parentTpl.search) return parentTpl.search.getSearchHref();
if (data && typeof data.getSearchHref === 'function') {
return data.getSearchHref();
}
return '';
},
hasPreviousPage() {
const data = Template.currentData();
if (data && data.hasPreviousPage) return data.hasPreviousPage;
const parentTpl = Template.instance().view.parentView.templateInstance && Template.instance().view.parentView.templateInstance();
if (parentTpl && parentTpl.search) return parentTpl.search.hasPreviousPage;
return new ReactiveVar(false);
if (data && data.hasPreviousPage) {
const rv = data.hasPreviousPage;
return rv && typeof rv.get === 'function' ? rv.get() : rv;
}
return false;
},
hasNextPage() {
const data = Template.currentData();
if (data && data.hasNextPage) return data.hasNextPage;
const parentTpl = Template.instance().view.parentView.templateInstance && Template.instance().view.parentView.templateInstance();
if (parentTpl && parentTpl.search) return parentTpl.search.hasNextPage;
return new ReactiveVar(false);
if (data && data.hasNextPage) {
const rv = data.hasNextPage;
return rv && typeof rv.get === 'function' ? rv.get() : rv;
}
return false;
},
});
+6 -2
View File
@@ -126,5 +126,9 @@ template(name="offlineWarning")
p
i.fa.fa-warning
| {{_ 'app-is-offline'}}
a.app-try-reconnect {{_ 'app-try-reconnect'}}
if appOfflineMessage
br
span= appOfflineMessage
if canReconnectNow
br
a.app-try-reconnect {{_ 'app-try-reconnect'}}
+26 -2
View File
@@ -1,5 +1,9 @@
import { Meteor } from 'meteor/meteor';
import { Session } from 'meteor/session';
import { ReactiveCache } from '/imports/reactiveCache';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import Announcements from '/models/announcements';
import { Utils } from '/client/lib/utils';
Meteor.subscribe('user-admin');
Meteor.subscribe('boards');
@@ -43,7 +47,27 @@ Template.header.helpers({
},
appIsOffline() {
return !Meteor.status().connected;
const status = Meteor.status();
return ['waiting', 'failed', 'offline'].includes(status.status);
},
appOfflineMessage() {
const status = Meteor.status();
if (status.status === 'failed' && status.reason) {
return status.reason;
}
if (status.status === 'offline') {
return 'Connection is paused.';
}
return null;
},
canReconnectNow() {
const status = Meteor.status();
return ['waiting', 'failed', 'offline'].includes(status.status);
},
hasAnnouncement() {
@@ -132,7 +156,7 @@ Template.header.events({
Session.set('currentCard', null);
},
'click .js-toggle-desktop-drag-handles'() {
currentUser = Meteor.user();
const currentUser = Meteor.user();
if (currentUser) {
Meteor.call('toggleDesktopDragHandles');
} else if (window.localStorage.getItem('showDesktopDragHandles')) {
+2
View File
@@ -1,6 +1,8 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import Users from '/models/users';
import { EscapeActions } from '/client/lib/escapeActions';
let alreadyCheck = 1;
let isCheckDone = false;
+1
View File
@@ -1,4 +1,5 @@
import { CardSearchPaged } from '../../lib/cardSearch';
import { Utils } from '/client/lib/utils';
Template.myCardsHeaderBar.helpers({
myCardsSort() {
+147 -97
View File
@@ -3,7 +3,7 @@
border-radius: 0.4vw;
border: 1px solid #dbdbdb;
border-bottom-color: #c2c2c2;
box-shadow: 0 0.2vh 0.8vh rgba(0,0,0,0.3);
box-shadow: 0 0.2vh 0.8vh rgba(0, 0, 0, 0.3);
position: absolute;
/* Wider default to fit full color palette */
width: min(380px, 55vw);
@@ -15,10 +15,10 @@
}
.pop-over p,
.pop-over textarea,
.pop-over input[type="text"],
.pop-over input[type="email"],
.pop-over input[type="password"],
.pop-over input[type="file"] {
.pop-over input[type='text'],
.pop-over input[type='email'],
.pop-over input[type='password'],
.pop-over input[type='file'] {
width: 100%;
}
.pop-over select {
@@ -79,8 +79,22 @@
}
/* Allow dynamic max-height to override default constraint */
.pop-over[style*="max-height"] .content-wrapper {
max-height: inherit;
.pop-over[style*='max-height'] {
overflow: hidden;
display: flex;
flex-direction: column;
}
.pop-over[style*='max-height'] .header {
flex: 0 0 auto;
}
.pop-over[style*='max-height'] .content-wrapper {
flex: 1 1 auto;
min-height: 0;
max-height: none;
overflow-y: auto;
overflow-x: hidden;
}
.pop-over .content-container {
width: 100%;
@@ -89,17 +103,17 @@
}
/* Allow dynamic max-height to override default constraint for content-container */
.pop-over[style*="max-height"] .content-container {
max-height: inherit;
.pop-over[style*='max-height'] .content-container {
max-height: none;
}
/* Fix overflow in the Member Settings (member menu) popup:
the popup itself gets a max-height inline style, but the header consumes space.
Make the header overlay the scrollable area so the list can't spill out. */
.pop-over[data-popup="memberMenuPopup"] {
.pop-over[data-popup='memberMenuPopup'] {
overflow: hidden;
}
.pop-over[data-popup="memberMenuPopup"] > .header {
.pop-over[data-popup='memberMenuPopup'] > .header {
position: absolute;
top: 0;
left: 0;
@@ -107,30 +121,30 @@
margin-bottom: 0;
z-index: 1;
}
.pop-over[data-popup="memberMenuPopup"] > .content-wrapper {
.pop-over[data-popup='memberMenuPopup'] > .content-wrapper {
padding-top: calc(4.5vh + 1vh);
box-sizing: border-box;
}
/* Admin edit popups: use full height */
.pop-over[data-popup="editUserPopup"],
.pop-over[data-popup="editOrgPopup"],
.pop-over[data-popup="editTeamPopup"] {
.pop-over[data-popup='editUserPopup'],
.pop-over[data-popup='editOrgPopup'],
.pop-over[data-popup='editTeamPopup'] {
height: calc(100vh - 20px) !important;
max-height: calc(100vh - 20px) !important;
}
.pop-over[data-popup="editUserPopup"] .content-wrapper,
.pop-over[data-popup="editOrgPopup"] .content-wrapper,
.pop-over[data-popup="editTeamPopup"] .content-wrapper {
.pop-over[data-popup='editUserPopup'] .content-wrapper,
.pop-over[data-popup='editOrgPopup'] .content-wrapper,
.pop-over[data-popup='editTeamPopup'] .content-wrapper {
max-height: calc(100vh - 80px) !important; /* Subtract header height */
height: calc(100vh - 80px) !important;
overflow-y: auto !important;
}
.pop-over[data-popup="editUserPopup"] .content-container,
.pop-over[data-popup="editOrgPopup"] .content-container,
.pop-over[data-popup="editTeamPopup"] .content-container {
.pop-over[data-popup='editUserPopup'] .content-container,
.pop-over[data-popup='editOrgPopup'] .content-container,
.pop-over[data-popup='editTeamPopup'] .content-container {
max-height: calc(100vh - 80px) !important; /* Subtract header height */
height: calc(100vh - 80px) !important;
}
@@ -142,7 +156,7 @@
}
/* Specific styling for language popup list */
.pop-over[data-popup="changeLanguagePopup"] .pop-over-list {
.pop-over[data-popup='changeLanguagePopup'] .pop-over-list {
max-height: none;
overflow: visible;
height: auto;
@@ -150,7 +164,7 @@
}
/* Ensure content div in language popup contains all items */
.pop-over[data-popup="changeLanguagePopup"] .content {
.pop-over[data-popup='changeLanguagePopup'] .content {
height: auto;
/* Remove forced min-height to avoid top gap */
display: flex;
@@ -158,7 +172,7 @@
}
/* Ensure hidden stack pages truly take no space */
.pop-over[data-popup="changeLanguagePopup"] .content.no-height {
.pop-over[data-popup='changeLanguagePopup'] .content.no-height {
min-height: 0 !important;
height: 0 !important;
padding: 0 !important;
@@ -167,7 +181,7 @@
}
/* Make language popup extend to bottom of browser window */
.pop-over[data-popup="changeLanguagePopup"] {
.pop-over[data-popup='changeLanguagePopup'] {
position: fixed !important;
bottom: 0 !important;
top: auto !important;
@@ -184,12 +198,12 @@
}
/* Allow dynamic height for Change Language popup */
.pop-over[data-popup="changeLanguagePopup"] .header {
.pop-over[data-popup='changeLanguagePopup'] .header {
flex-shrink: 0 !important;
height: auto !important;
}
.pop-over[data-popup="changeLanguagePopup"] .content-wrapper {
.pop-over[data-popup='changeLanguagePopup'] .content-wrapper {
flex: 1 !important;
overflow-y: auto !important;
overflow-x: hidden !important;
@@ -199,7 +213,7 @@
width: 100% !important;
}
.pop-over[data-popup="changeLanguagePopup"] .content-container {
.pop-over[data-popup='changeLanguagePopup'] .content-container {
height: auto !important;
max-height: none !important;
flex: 1 !important;
@@ -208,7 +222,7 @@
width: 100% !important;
}
.pop-over[data-popup="changeLanguagePopup"] .content {
.pop-over[data-popup='changeLanguagePopup'] .content {
height: auto !important;
max-height: none !important;
padding-bottom: 50px !important;
@@ -216,59 +230,59 @@
}
/* Date popup sizing for native HTML inputs */
.pop-over[data-popup="editCardReceivedDatePopup"],
.pop-over[data-popup="editCardStartDatePopup"],
.pop-over[data-popup="editCardDueDatePopup"],
.pop-over[data-popup="editCardEndDatePopup"],
.pop-over[data-popup*="Date"] {
.pop-over[data-popup='editCardReceivedDatePopup'],
.pop-over[data-popup='editCardStartDatePopup'],
.pop-over[data-popup='editCardDueDatePopup'],
.pop-over[data-popup='editCardEndDatePopup'],
.pop-over[data-popup*='Date'] {
width: min(400px, 90vw) !important; /* Smaller width for native inputs */
min-width: 350px !important;
max-height: 80vh !important;
}
.pop-over[data-popup="editCardReceivedDatePopup"] .content-wrapper,
.pop-over[data-popup="editCardStartDatePopup"] .content-wrapper,
.pop-over[data-popup="editCardDueDatePopup"] .content-wrapper,
.pop-over[data-popup="editCardEndDatePopup"] .content-wrapper,
.pop-over[data-popup*="Date"] .content-wrapper {
.pop-over[data-popup='editCardReceivedDatePopup'] .content-wrapper,
.pop-over[data-popup='editCardStartDatePopup'] .content-wrapper,
.pop-over[data-popup='editCardDueDatePopup'] .content-wrapper,
.pop-over[data-popup='editCardEndDatePopup'] .content-wrapper,
.pop-over[data-popup*='Date'] .content-wrapper {
max-height: 60vh !important;
overflow-y: auto !important;
}
.pop-over[data-popup="editCardReceivedDatePopup"] .content-container,
.pop-over[data-popup="editCardStartDatePopup"] .content-container,
.pop-over[data-popup="editCardDueDatePopup"] .content-container,
.pop-over[data-popup="editCardEndDatePopup"] .content-container,
.pop-over[data-popup*="Date"] .content-container {
.pop-over[data-popup='editCardReceivedDatePopup'] .content-container,
.pop-over[data-popup='editCardStartDatePopup'] .content-container,
.pop-over[data-popup='editCardDueDatePopup'] .content-container,
.pop-over[data-popup='editCardEndDatePopup'] .content-container,
.pop-over[data-popup*='Date'] .content-container {
max-height: 60vh !important;
}
/* Native HTML input styling */
.pop-over[data-popup*="Date"] .datepicker-container {
.pop-over[data-popup*='Date'] .datepicker-container {
width: 100% !important;
padding: 15px !important;
}
.pop-over[data-popup*="Date"] .datepicker-container .fields {
.pop-over[data-popup*='Date'] .datepicker-container .fields {
display: flex !important;
gap: 15px !important;
margin-bottom: 15px !important;
}
.pop-over[data-popup*="Date"] .datepicker-container .fields .left,
.pop-over[data-popup*="Date"] .datepicker-container .fields .right {
.pop-over[data-popup*='Date'] .datepicker-container .fields .left,
.pop-over[data-popup*='Date'] .datepicker-container .fields .right {
flex: 1 !important;
width: auto !important;
}
.pop-over[data-popup*="Date"] .datepicker-container label {
.pop-over[data-popup*='Date'] .datepicker-container label {
display: block !important;
margin-bottom: 5px !important;
font-weight: bold !important;
}
.pop-over[data-popup*="Date"] .datepicker-container input[type="date"],
.pop-over[data-popup*="Date"] .datepicker-container input[type="time"] {
.pop-over[data-popup*='Date'] .datepicker-container input[type='date'],
.pop-over[data-popup*='Date'] .datepicker-container input[type='time'] {
width: 100% !important;
padding: 8px !important;
border: 1px solid #ccc !important;
@@ -277,71 +291,109 @@
box-sizing: border-box !important;
}
.pop-over[data-popup*="Date"] .datepicker-container input[type="date"]:focus,
.pop-over[data-popup*="Date"] .datepicker-container input[type="time"]:focus {
.pop-over[data-popup*='Date'] .datepicker-container input[type='date']:focus,
.pop-over[data-popup*='Date'] .datepicker-container input[type='time']:focus {
outline: none !important;
border-color: #007cba !important;
box-shadow: 0 0 0 2px rgba(0, 124, 186, 0.2) !important;
}
/* Ensure date popup buttons stay within popup boundaries */
.pop-over[data-popup="editCardReceivedDatePopup"] .content,
.pop-over[data-popup="editCardStartDatePopup"] .content,
.pop-over[data-popup="editCardDueDatePopup"] .content,
.pop-over[data-popup="editCardEndDatePopup"] .content,
.pop-over[data-popup*="Date"] .content {
.pop-over[data-popup='editCardReceivedDatePopup'] .content,
.pop-over[data-popup='editCardStartDatePopup'] .content,
.pop-over[data-popup='editCardDueDatePopup'] .content,
.pop-over[data-popup='editCardEndDatePopup'] .content,
.pop-over[data-popup*='Date'] .content {
max-height: 60vh !important; /* Leave space for buttons */
overflow-y: auto !important;
padding-bottom: 100px !important; /* More space for buttons */
margin-bottom: 0 !important;
}
.pop-over[data-popup="editCardReceivedDatePopup"] .datepicker-container,
.pop-over[data-popup="editCardStartDatePopup"] .datepicker-container,
.pop-over[data-popup="editCardDueDatePopup"] .datepicker-container,
.pop-over[data-popup="editCardEndDatePopup"] .datepicker-container,
.pop-over[data-popup*="Date"] .datepicker-container {
/* Workspace Settings popup: ensure modal doesn't overflow viewport */
.pop-over[data-popup='workspaceActionsPopup'] {
max-height: 80vh !important;
width: min(420px, 85vw) !important;
}
.pop-over[data-popup='workspaceActionsPopup'] .content-wrapper {
max-height: calc(80vh - 70px) !important; /* Subtract header height */
overflow-y: auto !important;
}
.pop-over[data-popup='workspaceActionsPopup'] .content-container {
max-height: calc(80vh - 70px) !important;
}
.pop-over[data-popup='workspaceActionsPopup'] .content {
max-height: none !important; /* Allow content to scroll within container */
overflow: visible !important; /* Overflow handled by parent */
}
/* Card Details Actions popup: ensure menu doesn't overflow viewport */
.pop-over[data-popup="cardDetailsActionsPopup"] {
max-height: 80vh !important;
width: min(380px, 85vw) !important;
}
.pop-over[data-popup="cardDetailsActionsPopup"] .content-wrapper {
max-height: calc(80vh - 70px) !important; /* Subtract header height */
overflow-y: auto !important;
}
.pop-over[data-popup="cardDetailsActionsPopup"] .content-container {
max-height: calc(80vh - 70px) !important;
}
.pop-over[data-popup="cardDetailsActionsPopup"] .content {
max-height: none !important; /* Allow content to scroll within container */
overflow: visible !important; /* Overflow handled by parent */
}
.pop-over[data-popup='editCardReceivedDatePopup'] .datepicker-container,
.pop-over[data-popup='editCardStartDatePopup'] .datepicker-container,
.pop-over[data-popup='editCardDueDatePopup'] .datepicker-container,
.pop-over[data-popup='editCardEndDatePopup'] .datepicker-container,
.pop-over[data-popup*='Date'] .datepicker-container {
max-height: 50vh !important; /* Limit calendar height */
overflow-y: auto !important;
margin-bottom: 20px !important; /* Space before buttons */
}
/* Ensure buttons are properly positioned */
.pop-over[data-popup="editCardReceivedDatePopup"] .edit-date,
.pop-over[data-popup="editCardStartDatePopup"] .edit-date,
.pop-over[data-popup="editCardDueDatePopup"] .edit-date,
.pop-over[data-popup="editCardEndDatePopup"] .edit-date,
.pop-over[data-popup*="Date"] .edit-date {
.pop-over[data-popup='editCardReceivedDatePopup'] .edit-date,
.pop-over[data-popup='editCardStartDatePopup'] .edit-date,
.pop-over[data-popup='editCardDueDatePopup'] .edit-date,
.pop-over[data-popup='editCardEndDatePopup'] .edit-date,
.pop-over[data-popup*='Date'] .edit-date {
display: flex !important;
flex-direction: column !important;
height: 100% !important;
}
.pop-over[data-popup="editCardReceivedDatePopup"] .edit-date .fields,
.pop-over[data-popup="editCardStartDatePopup"] .edit-date .fields,
.pop-over[data-popup="editCardDueDatePopup"] .edit-date .fields,
.pop-over[data-popup="editCardEndDatePopup"] .edit-date .fields,
.pop-over[data-popup*="Date"] .edit-date .fields {
.pop-over[data-popup='editCardReceivedDatePopup'] .edit-date .fields,
.pop-over[data-popup='editCardStartDatePopup'] .edit-date .fields,
.pop-over[data-popup='editCardDueDatePopup'] .edit-date .fields,
.pop-over[data-popup='editCardEndDatePopup'] .edit-date .fields,
.pop-over[data-popup*='Date'] .edit-date .fields {
flex-shrink: 0 !important;
margin-bottom: 15px !important;
}
.pop-over[data-popup="editCardReceivedDatePopup"] .edit-date .js-datepicker,
.pop-over[data-popup="editCardStartDatePopup"] .edit-date .js-datepicker,
.pop-over[data-popup="editCardDueDatePopup"] .edit-date .js-datepicker,
.pop-over[data-popup="editCardEndDatePopup"] .edit-date .js-datepicker,
.pop-over[data-popup*="Date"] .edit-date .js-datepicker {
.pop-over[data-popup='editCardReceivedDatePopup'] .edit-date .js-datepicker,
.pop-over[data-popup='editCardStartDatePopup'] .edit-date .js-datepicker,
.pop-over[data-popup='editCardDueDatePopup'] .edit-date .js-datepicker,
.pop-over[data-popup='editCardEndDatePopup'] .edit-date .js-datepicker,
.pop-over[data-popup*='Date'] .edit-date .js-datepicker {
flex: 1 !important;
overflow-y: auto !important;
}
.pop-over[data-popup="editCardReceivedDatePopup"] .edit-date button,
.pop-over[data-popup="editCardStartDatePopup"] .edit-date button,
.pop-over[data-popup="editCardDueDatePopup"] .edit-date button,
.pop-over[data-popup="editCardEndDatePopup"] .edit-date button,
.pop-over[data-popup*="Date"] .edit-date button {
.pop-over[data-popup='editCardReceivedDatePopup'] .edit-date button,
.pop-over[data-popup='editCardStartDatePopup'] .edit-date button,
.pop-over[data-popup='editCardDueDatePopup'] .edit-date button,
.pop-over[data-popup='editCardEndDatePopup'] .edit-date button,
.pop-over[data-popup*='Date'] .edit-date button {
flex-shrink: 0 !important;
margin-top: 15px !important;
position: relative !important;
@@ -350,7 +402,7 @@
.pop-over .content-container .content {
/* Match wider popover, leave padding */
width: 100%;
padding: 0 1.3vw 1.3vh;
padding: 1.3vh 1.3vw 1.3vh;
box-sizing: border-box;
/* Ensure content is not shifted left */
margin-left: 0 !important;
@@ -387,8 +439,6 @@
margin-bottom: 2px;
}
/* Fix popup depth containers - don't block transforms which are needed for popup navigation */
.pop-over .popup-container-depth-1,
.pop-over .popup-container-depth-2,
@@ -477,15 +527,15 @@
text-decoration: none;
overflow: hidden;
line-height: 33px;
display:flex;
/* flex-wrap:wrap;*/
gap:5px;
display: flex;
/* flex-wrap:wrap;*/
gap: 5px;
align-items: center;
color: #000 !important;
}
.pop-over-list li > a > .member{
.pop-over-list li > a > .member {
align-self: flex-start;
flex:0 0 auto;
flex: 0 0 auto;
}
.pop-over-list li > a .item-name {
display: block;
@@ -657,10 +707,10 @@ body.grey-icons-enabled .pop-over-list .pop-over-list.checkable .fa-check {
}
.pop-over .content-container .content p,
.pop-over .content-container .content textarea,
.pop-over .content-container .content input[type="text"],
.pop-over .content-container .content input[type="email"],
.pop-over .content-container .content input[type="password"],
.pop-over .content-container .content input[type="file"] {
.pop-over .content-container .content input[type='text'],
.pop-over .content-container .content input[type='email'],
.pop-over .content-container .content input[type='password'],
.pop-over .content-container .content input[type='file'] {
width: 100%;
box-sizing: border-box;
}
+6 -1
View File
@@ -1,3 +1,5 @@
import { CSSEvents } from '/client/lib/cssEvents';
Popup.template.events({
'click .js-back-view'() {
Popup.back();
@@ -26,8 +28,11 @@ Popup.template.events({
// When a popup content is removed (ie, when the user press the "back" button),
// we need to wait for the container translation to end before removing the
// actual DOM element. For that purpose we use the undocumented `_uihooks` API.
Popup.template.onRendered(() => {
Popup.template.onRendered(function () {
const container = this.find('.content-container');
if (!container) {
return;
}
container._uihooks = {
removeElement(node) {
$(node).addClass('no-height');
@@ -1,13 +1,14 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { formatDateByUserPreference } from '/imports/lib/dateUtils';
import Users from '/models/users';
Template.notification.events({
'click .read-status .materialCheckBox'() {
async 'click .read-status .materialCheckBox'() {
const update = {};
const newReadValue = this.read ? null : Date.now();
const newReadValue = this.read ? null : new Date();
update[`profile.notifications.${this.index}.read`] = newReadValue;
Users.update(Meteor.userId(), { $set: update }, (error, result) => {
await Users.updateAsync(Meteor.userId(), { $set: update }).catch((error) => {
if (error) {
console.error('Error updating notification:', error);
}
@@ -1,5 +1,7 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import { toggleNotificationsDrawer } from './notifications.js';
import Users from '/models/users';
Template.notificationsDrawer.onCreated(function() {
Meteor.subscribe('notificationActivities');
@@ -34,7 +36,7 @@ Template.notificationsDrawer.events({
event.stopPropagation();
Session.set('showNotificationMenu', !Session.get('showNotificationMenu'));
},
'click .notification-menu .menu-item'(event) {
async 'click .notification-menu .menu-item'(event) {
const target = event.currentTarget;
if (target.classList.contains('mark-all-read')) {
@@ -42,8 +44,10 @@ Template.notificationsDrawer.events({
for (const index in notifications) {
if (notifications.hasOwnProperty(index) && !notifications[index].read) {
const update = {};
update[`profile.notifications.${index}.read`] = Date.now();
Users.update(Meteor.userId(), { $set: update });
update[`profile.notifications.${index}.read`] = new Date();
await Users.updateAsync(Meteor.userId(), { $set: update }).catch((error) => {
console.error('Error marking notification as read:', error);
});
}
}
Session.set('showNotificationMenu', false);
@@ -53,7 +57,9 @@ Template.notificationsDrawer.events({
if (notifications.hasOwnProperty(index) && notifications[index].read) {
const update = {};
update[`profile.notifications.${index}.read`] = null;
Users.update(Meteor.userId(), { $set: update });
await Users.updateAsync(Meteor.userId(), { $set: update }).catch((error) => {
console.error('Error marking notification as unread:', error);
});
}
}
Session.set('showNotificationMenu', false);
@@ -1,5 +1,9 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import Actions from '/models/actions';
import Rules from '/models/rules';
import Triggers from '/models/triggers';
import { Utils } from '/client/lib/utils';
Template.boardActions.onCreated(function () {
this.subscribe('boards');
@@ -1,6 +1,12 @@
import Actions from '/models/actions';
import { CARD_COLORS } from '/models/metadata/colors';
import Rules from '/models/rules';
import Triggers from '/models/triggers';
import { Utils } from '/client/lib/utils';
let cardColors;
Meteor.startup(() => {
cardColors = Cards.simpleSchema()._schema.color.allowedValues;
cardColors = CARD_COLORS;
});
// Module-level shared state so the color popup can read/write the
@@ -1,3 +1,8 @@
import Actions from '/models/actions';
import Rules from '/models/rules';
import Triggers from '/models/triggers';
import { Utils } from '/client/lib/utils';
Template.checklistActions.onCreated(function () {
this.subscribe('allRules');
});
@@ -1,3 +1,8 @@
import Actions from '/models/actions';
import Rules from '/models/rules';
import Triggers from '/models/triggers';
import { Utils } from '/client/lib/utils';
Template.mailActions.events({
'click .js-mail-action'(event, tpl) {
const emailTo = tpl.find('#email-to').value;
+3
View File
@@ -1,4 +1,7 @@
import { ReactiveCache } from '/imports/reactiveCache';
import Actions from '/models/actions';
import Rules from '/models/rules';
import Triggers from '/models/triggers';
Template.rulesMain.onCreated(function () {
this.rulesCurrentTab = new ReactiveVar('rulesList');
@@ -1,3 +1,5 @@
import { Utils } from '/client/lib/utils';
Template.boardTriggers.onCreated(function () {
this.provaVar = new ReactiveVar('');
this.currentPopupTriggerId = 'def';
@@ -1,4 +1,5 @@
import { TAPi18n } from '/imports/i18n';
import { Utils } from '/client/lib/utils';
Template.cardTriggers.onCreated(function () {
this.subscribe('allRules');
@@ -1,3 +1,5 @@
import { Utils } from '/client/lib/utils';
Template.checklistTriggers.onCreated(function () {
this.subscribe('allRules');
});
+5 -2
View File
@@ -1,11 +1,14 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import { AttachmentStorage } from '/models/attachments';
import Attachments, { AttachmentStorage } from '/models/attachments';
import { CardSearchPaged } from '/client/lib/cardSearch';
import SessionData from '/models/usersessiondata';
import { QueryParams } from '/config/query-classes';
import { OPERATOR_LIMIT } from '/config/search-const';
const filesize = require('filesize');
import Boards from '/models/boards';
import Cards from '/models/cards';
import Rules from '/models/rules';
const { filesize } = require('filesize');
// --- Shared helper functions (formerly AdminReport base class methods) ---
+6 -3
View File
@@ -1,7 +1,7 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { groupBy } from '/imports/lib/collectionHelpers';
import Attachments, { fileStoreStrategyFactory } from '/models/attachments';
const filesize = require('filesize');
import Attachments from '/models/attachments';
const { filesize } = require('filesize');
Template.attachments.onCreated(function () {
this.subscription = null;
@@ -62,7 +62,10 @@ Template.moveAttachments.helpers({
_attachment.flatVersion = Object.keys(_attachment.versions)
.map(_versionName => {
const _version = Object.assign(_attachment.versions[_versionName], {"versionName": _versionName});
_version.storageName = fileStoreStrategyFactory.getFileStrategy(_attachment, _versionName).getStorageName();
// Read storage directly from the document (set by onAfterUpload on server)
_version.storageName = _version.storage || (
(_attachment.meta?.source === 'import' || _version.meta?.gridFsFileId) ? 'gridfs' : 'fs'
);
return _version;
});
});
@@ -37,7 +37,7 @@ template(name='statistics')
td {{statistics.mongo.mongoStorageEngine}}
tr
th {{_ 'MongoDB_Oplog_enabled'}}
td {{statistics.mongo.mongoOplogEnabled}}
td {{formatBoolean statistics.mongo.mongoOplogEnabled}}
tr
th {{_ 'OS_Type'}}
td {{statistics.os.type}}
@@ -1,5 +1,5 @@
import { TAPi18n } from '/imports/i18n';
const filesize = require('filesize');
const { filesize } = require('filesize');
Template.statistics.onCreated(function () {
this.info = new ReactiveVar({});
@@ -47,4 +47,8 @@ Template.statistics.helpers({
}
return ret;
},
formatBoolean(value) {
return value ? TAPi18n.__('yes') : TAPi18n.__('no');
},
});
+43
View File
@@ -311,3 +311,46 @@ table td:first-child {
text-align: center;
color: #333;
}
/* Avatar styling - removed people-avatar column */
/* Username column styling - displays avatar inline with username */
.username a.js-edit-people-avatar {
display: inline-flex;
align-items: center;
cursor: pointer;
text-decoration: none;
margin-right: 8px;
vertical-align: middle;
}
.username a.js-edit-people-avatar:hover {
background-color: rgba(0, 0, 0, 0.05);
border-radius: 50%;
}
.username .avatar {
height: 30px;
width: 30px;
border-radius: 50%;
display: inline-block;
flex-shrink: 0;
overflow: hidden;
}
.username .avatar.avatar-initials {
background-color: #dbdbdb;
color: #444;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 500;
}
.username .avatar img.avatar-image {
object-fit: cover;
object-position: center;
height: 100%;
width: 100%;
}
+35 -14
View File
@@ -114,7 +114,7 @@ template(name="orgGeneral")
th {{_ 'shortName'}}
th {{_ 'website'}}
th {{_ 'createdAt'}}
th {{_ 'active'}}
th {{_ 'active-org'}}
tbody
tr
each org in orgList
@@ -137,7 +137,7 @@ template(name="teamGeneral")
th {{_ 'shortName'}}
th {{_ 'website'}}
th {{_ 'createdAt'}}
th {{_ 'active'}}
th {{_ 'active-team'}}
tbody
tr
each team in teamList
@@ -160,7 +160,7 @@ template(name="peopleGeneral")
th {{_ 'username'}}
th {{_ 'email'}}
th {{_ 'admin'}}
th {{_ 'admin-people-active-status'}}
th {{_ 'active-person'}}
th {{_ 'accounts-lockout-status'}}
th {{_ 'createdAt'}}
th
@@ -260,17 +260,38 @@ template(name="teamRow")
template(name="peopleRow")
tr
td
a.edit-user
a.edit-user(data-user-id=userData._id)
i.fa.fa-pencil-square-o
| {{_ 'edit'}}
a.more-settings-user
a.more-settings-user(data-user-id=userData._id)
| ⋯
if userData.loginDisabled
td.username <s>{{ userData.username }}</s>
td.username
a.js-edit-people-avatar(data-user-id=userData._id)
if hasAvatarUrl
.avatar
img.avatar-image(src="{{userData.profile.avatarUrl}}", alt="{{userData.username}}", title="{{_ 'change-avatar'}}")
else
+userAvatarInitials(userId=userData._id)
<s>{{ userData.username }}</s>
else if isUserLocked
td.username {{ userData.username }}
td.username
a.js-edit-people-avatar(data-user-id=userData._id)
if hasAvatarUrl
.avatar
img.avatar-image(src="{{userData.profile.avatarUrl}}", alt="{{userData.username}}", title="{{_ 'change-avatar'}}")
else
+userAvatarInitials(userId=userData._id)
| {{ userData.username }}
else
td.username {{ userData.username }}
td.username
a.js-edit-people-avatar(data-user-id=userData._id)
if hasAvatarUrl
.avatar
img.avatar-image(src="{{userData.profile.avatarUrl}}", alt="{{userData.username}}", title="{{_ 'change-avatar'}}")
else
+userAvatarInitials(userId=userData._id)
| {{ userData.username }}
if userData.loginDisabled
td <s>{{ userData.emails.[0].address }}</s>
else
@@ -333,7 +354,7 @@ template(name="editOrgPopup")
| {{_ 'website'}}
input.js-orgWebsite(type="text" value=org.orgWebsite)
label
| {{_ 'active'}}
| {{_ 'active-org'}}
select.select-active.js-org-isactive
option(value="false") {{_ 'no'}}
option(value="true" selected="{{org.orgIsActive}}") {{_ 'yes'}}
@@ -359,7 +380,7 @@ template(name="editTeamPopup")
| {{_ 'website'}}
input.js-teamWebsite(type="text" value=team.teamWebsite)
label
| {{_ 'active'}}
| {{_ 'active-team'}}
select.select-active.js-team-isactive
option(value="false") {{_ 'no'}}
option(value="true" selected="{{team.teamIsActive}}") {{_ 'yes'}}
@@ -406,7 +427,7 @@ template(name="editUserPopup")
option(value="false") {{_ 'no'}}
option(value="true" selected="{{userData.emails.[0].verified}}") {{_ 'yes'}}
label
| {{_ 'active'}}
| {{_ 'active-person'}}
select.select-active.js-profile-isactive
option(value="false") {{_ 'yes'}}
option(value="true" selected="{{user.loginDisabled}}") {{_ 'no'}}
@@ -469,7 +490,7 @@ template(name="newOrgPopup")
| {{_ 'website'}}
input.js-orgWebsite(type="text" value="" required)
label
| {{_ 'active'}}
| {{_ 'active-org'}}
select.select-active.js-org-isactive
option(value="false" selected="selected") {{_ 'no'}}
option(value="true") {{_ 'yes'}}
@@ -493,7 +514,7 @@ template(name="newTeamPopup")
| {{_ 'website'}}
input.js-teamWebsite(type="text" value="")
label
| {{_ 'active'}}
| {{_ 'active-team'}}
select.select-active.js-team-isactive
option(value="false" selected="selected") {{_ 'no'}}
option(value="true") {{_ 'yes'}}
@@ -555,7 +576,7 @@ template(name="newUserPopup")
option(value="false" selected="selected") {{_ 'no'}}
option(value="true") {{_ 'yes'}}
label
| {{_ 'active'}}
| {{_ 'active-person'}}
select.select-active.js-profile-isactive
option(value="false" selected="selected") {{_ 'yes'}}
option(value="true") {{_ 'no'}}
+81 -12
View File
@@ -1,7 +1,11 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { avatarUpdateCounter } from '/client/components/users/avatarUpdateCounter';
import { InfiniteScrolling } from '/client/lib/infiniteScrolling';
import LockoutSettings from '/models/lockoutSettings';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import Org from '/models/org';
import Team from '/models/team';
import Users from '/models/users';
const orgsPerPage = 25;
const teamsPerPage = 25;
@@ -533,7 +537,39 @@ Template.teamRow.helpers({
Template.peopleRow.helpers({
userData() {
return this.user || ReactiveCache.getUser(this.userId);
// Depend on global avatar update counter to reactively update when avatars change
avatarUpdateCounter.get();
// Get the user ID from either this._id or this.user._id
let userId;
if (this.user && this.user._id) {
userId = this.user._id;
} else if (this._id) {
userId = this._id;
}
// Always fetch from ReactiveCache to ensure latest data
if (userId) {
return ReactiveCache.getUser(userId);
}
return this.user || this;
},
hasAvatarUrl() {
// Depend on global avatar update counter to reactively update when avatars change
avatarUpdateCounter.get();
// Get the user ID from either this._id or this.user._id
let userId;
if (this.user && this.user._id) {
userId = this.user._id;
} else if (this._id) {
userId = this._id;
}
let user;
if (userId) {
user = ReactiveCache.getUser(userId);
} else {
user = this.user || this;
}
if (!user || !user.profile) return false;
return !!user.profile.avatarUrl;
},
isUserLocked() {
const user = this.user || ReactiveCache.getUser(this.userId);
@@ -723,8 +759,20 @@ Template.teamRow.events({
});
Template.peopleRow.events({
'click a.edit-user': Popup.open('editUser'),
'click a.more-settings-user': Popup.open('settingsUser'),
'click a.edit-user'(event) {
// Get the user ID from the data attribute
const userId = event.currentTarget.getAttribute('data-user-id');
if (userId) {
Popup.open('editUser').call({ userId: userId }, event);
}
},
'click a.more-settings-user'(event) {
// Get the user ID from the data attribute
const userId = event.currentTarget.getAttribute('data-user-id');
if (userId) {
Popup.open('settingsUser').call({ userId: userId }, event);
}
},
'click .selectUserChkBox': function(ev){
if(ev.currentTarget){
if(ev.currentTarget.checked){
@@ -788,6 +836,18 @@ Template.peopleRow.events({
// For now, we'll leave this as a no-op
}
},
'click a.js-edit-people-avatar'(event) {
// Extract the user ID from the data attribute
const userId = event.currentTarget.getAttribute('data-user-id');
if (userId) {
// Get the user from cache to pass correct context
const user = ReactiveCache.getUser(userId);
if (user) {
// Call Popup.open with the correct user data context
Popup.open('adminChangeAvatar').call({ _id: userId, user: user }, event);
}
}
},
});
Template.modifyTeamsUsers.helpers({
@@ -1180,7 +1240,7 @@ Template.editUserPopup.events({
},
});
UpdateUserOrgsOrTeamsElement = function(isNewUser = false){
const UpdateUserOrgsOrTeamsElement = function(isNewUser = false){
let selectedElt;
let selectedEltValue;
let selectedEltValueId;
@@ -1459,19 +1519,23 @@ Template.settingsTeamPopup.events({
Template.settingsUserPopup.events({
'click .impersonate-user'(event) {
event.preventDefault();
const userId = this.userId || this.user?._id;
Meteor.call('impersonate', this.userId, (err) => {
Meteor.call('impersonate', userId, (err) => {
if (!err) {
// Meteor.connection.setUserId() triggers automatic cache invalidation
// No need to manually invalidate - let Meteor handle the user data refresh
Meteor.connection.setUserId(userId);
FlowRouter.go('/');
Meteor.connection.setUserId(this.userId);
}
});
},
'click #deleteButton'(event) {
event.preventDefault();
const userId = this.userId || this.user?._id;
// Use secure server method instead of direct client-side removal
Meteor.call('removeUser', this.userId, (error, result) => {
Meteor.call('removeUser', userId, (error, result) => {
if (error) {
if (process.env.DEBUG === 'true') {
console.error('Error removing user:', error);
@@ -1498,19 +1562,24 @@ Template.settingsUserPopup.events({
Template.settingsUserPopup.helpers({
user() {
return ReactiveCache.getUser(this.userId);
const userId = this.userId || this.user?._id;
return ReactiveCache.getUser(userId);
},
authentications() {
return Template.instance().authenticationMethods.get();
},
isSelected(match) {
const userId = Template.instance().data.userId;
const selected = ReactiveCache.getUser(userId).authenticationMethod;
const userId = Template.instance().data.userId || Template.instance().data.user?._id;
const user = ReactiveCache.getUser(userId);
if (!user) return false;
const selected = user.authenticationMethod;
return selected === match;
},
isLdap() {
const userId = Template.instance().data.userId;
const selected = ReactiveCache.getUser(userId).authenticationMethod;
const userId = Template.instance().data.userId || Template.instance().data.user?._id;
const user = ReactiveCache.getUser(userId);
if (!user) return false;
const selected = user.authenticationMethod;
return selected === 'ldap';
},
errorMessage() {
+38 -14
View File
@@ -2,6 +2,11 @@ import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import { ALLOWED_WAIT_SPINNERS } from '/config/const';
import LockoutSettings from '/models/lockoutSettings';
import AccessibilitySettings from '/models/accessibilitySettings';
import AccountSettings from '/models/accountSettings';
import Announcements from '/models/announcements';
import Settings from '/models/settings';
import TableVisibilityModeSettings from '/models/tableVisibilityModeSettings';
// import {
// cronMigrationProgress,
// cronMigrationStatus,
@@ -492,7 +497,7 @@ Template.setting.events({
});
const validEmails = [];
emails.forEach((email) => {
if (email && SimpleSchema.RegEx.Email.test(email.trim())) {
if (email && /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(email.trim())) {
validEmails.push(email.trim());
}
});
@@ -1083,25 +1088,44 @@ Template.announcementSettings.helpers({
});
Template.announcementSettings.events({
'click a.js-toggle-activemessage'(event, tpl) {
async 'click a.js-toggle-activemessage'(event, tpl) {
event.preventDefault();
tpl.loading.set(true);
const announcements = Announcements.findOne();
if (!announcements) {
tpl.loading.set(false);
return;
}
const isActive = announcements.enabled;
Announcements.update(announcements._id, {
$set: { enabled: !isActive },
});
tpl.loading.set(false);
if (isActive) {
$('.admin-announcement').slideUp();
} else {
$('.admin-announcement').slideDown();
try {
await Announcements.updateAsync(announcements._id, {
$set: { enabled: !isActive },
});
if (isActive) {
$('.admin-announcement').slideUp();
} else {
$('.admin-announcement').slideDown();
}
} catch (error) {
alert(error?.reason || error?.message || 'Failed to update announcement setting');
} finally {
tpl.loading.set(false);
}
},
'click button.js-announcement-save'() {
async 'click button.js-announcement-save'(event) {
event.preventDefault();
const message = $('#admin-announcement').val().trim();
Announcements.update(Announcements.findOne()._id, {
$set: { body: message },
});
const announcement = Announcements.findOne();
if (!announcement) {
return;
}
try {
await Announcements.updateAsync(announcement._id, {
$set: { body: message },
});
} catch (error) {
alert(error?.reason || error?.message || 'Failed to save announcement');
}
},
});
+34 -34
View File
@@ -177,8 +177,8 @@ template(name="boardCardSettingsPopup")
a.flex.js-field-has-receiveddate(title="{{_ 'card-received'}}" class="{{#if allowsReceivedDate}}is-checked{{/if}}")
i.fa(class="{{#if allowsReceivedDate}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
a.flex.js-field-has-receiveddate(title="{{_ 'card-received'}}" class="{{#if allowsReceivedDate}}is-checked{{/if}}")
i.fa(class="{{#if allowsReceivedDate}}fa-check{{else}}fa-square-o{{/if}}")
a.flex.js-field-has-receiveddate-on-minicard(title="{{_ 'card-received'}}" class="{{#if allowsReceivedDateOnMinicard}}is-checked{{/if}}")
i.fa(class="{{#if allowsReceivedDateOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
i.fa.fa-sign-in
@@ -188,8 +188,8 @@ template(name="boardCardSettingsPopup")
a.flex.js-field-has-startdate(title="{{_ 'card-start'}}" class="{{#if allowsStartDate}}is-checked{{/if}}")
i.fa(class="{{#if allowsStartDate}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
a.flex.js-field-has-startdate(title="{{_ 'card-start'}}" class="{{#if allowsStartDate}}is-checked{{/if}}")
i.fa(class="{{#if allowsStartDate}}fa-check{{else}}fa-square-o{{/if}}")
a.flex.js-field-has-startdate-on-minicard(title="{{_ 'card-start'}}" class="{{#if allowsStartDateOnMinicard}}is-checked{{/if}}")
i.fa(class="{{#if allowsStartDateOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
i.fa.fa-hourglass
@@ -199,8 +199,8 @@ template(name="boardCardSettingsPopup")
a.flex.js-field-has-duedate(title="{{_ 'card-due'}}" class="{{#if allowsDueDate}}is-checked{{/if}}")
i.fa(class="{{#if allowsDueDate}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
a.flex.js-field-has-duedate(title="{{_ 'card-due'}}" class="{{#if allowsDueDate}}is-checked{{/if}}")
i.fa(class="{{#if allowsDueDate}}fa-check{{else}}fa-square-o{{/if}}")
a.flex.js-field-has-duedate-on-minicard(title="{{_ 'card-due'}}" class="{{#if allowsDueDateOnMinicard}}is-checked{{/if}}")
i.fa(class="{{#if allowsDueDateOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
i.fa.fa-sign-in
@@ -210,8 +210,8 @@ template(name="boardCardSettingsPopup")
a.flex.js-field-has-enddate(title="{{_ 'card-end'}}" class="{{#if allowsEndDate}}is-checked{{/if}}")
i.fa(class="{{#if allowsEndDate}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
a.flex.js-field-has-enddate(title="{{_ 'card-end'}}" class="{{#if allowsEndDate}}is-checked{{/if}}")
i.fa(class="{{#if allowsEndDate}}fa-check{{else}}fa-square-o{{/if}}")
a.flex.js-field-has-enddate-on-minicard(title="{{_ 'card-end'}}" class="{{#if allowsEndDateOnMinicard}}is-checked{{/if}}")
i.fa(class="{{#if allowsEndDateOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
i.fa.fa-clock-o
@@ -221,8 +221,8 @@ template(name="boardCardSettingsPopup")
a.flex.js-field-has-members(title="{{_ 'members'}}" class="{{#if allowsMembers}}is-checked{{/if}}")
i.fa(class="{{#if allowsMembers}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
a.flex.js-field-has-members(title="{{_ 'members'}}" class="{{#if allowsMembers}}is-checked{{/if}}")
i.fa(class="{{#if allowsMembers}}fa-check{{else}}fa-square-o{{/if}}")
a.flex.js-field-has-members-on-minicard(title="{{_ 'members'}}" class="{{#if allowsMembersOnMinicard}}is-checked{{/if}}")
i.fa(class="{{#if allowsMembersOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
i.fa.fa-users
@@ -252,8 +252,8 @@ template(name="boardCardSettingsPopup")
a.flex.js-field-has-assignee(title="{{_ 'assignee'}}" class="{{#if allowsAssignee}}is-checked{{/if}}")
i.fa(class="{{#if allowsAssignee}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
a.flex.js-field-has-assignee(title="{{_ 'assignee'}}" class="{{#if allowsAssignee}}is-checked{{/if}}")
i.fa(class="{{#if allowsAssignee}}fa-check{{else}}fa-square-o{{/if}}")
a.flex.js-field-has-assignee-on-minicard(title="{{_ 'assignee'}}" class="{{#if allowsAssigneeOnMinicard}}is-checked{{/if}}")
i.fa(class="{{#if allowsAssigneeOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
i.fa.fa-user
@@ -263,8 +263,8 @@ template(name="boardCardSettingsPopup")
a.flex.js-field-has-assigned-by(title="{{_ 'assigned-by'}}" class="{{#if allowsAssignedBy}}is-checked{{/if}}")
i.fa(class="{{#if allowsAssignedBy}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
a.flex.js-field-has-assigned-by(title="{{_ 'assigned-by'}}" class="{{#if allowsAssignedBy}}is-checked{{/if}}")
i.fa(class="{{#if allowsAssignedBy}}fa-check{{else}}fa-square-o{{/if}}")
a.flex.js-field-has-assigned-by-on-minicard(title="{{_ 'assigned-by'}}" class="{{#if allowsAssignedByOnMinicard}}is-checked{{/if}}")
i.fa(class="{{#if allowsAssignedByOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
i.fa.fa-shopping-cart
@@ -274,8 +274,8 @@ template(name="boardCardSettingsPopup")
a.flex.js-field-has-requested-by(title="{{_ 'requested-by'}}" class="{{#if allowsRequestedBy}}is-checked{{/if}}")
i.fa(class="{{#if allowsRequestedBy}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
a.flex.js-field-has-requested-by(title="{{_ 'requested-by'}}" class="{{#if allowsRequestedBy}}is-checked{{/if}}")
i.fa(class="{{#if allowsRequestedBy}}fa-check{{else}}fa-square-o{{/if}}")
a.flex.js-field-has-requested-by-on-minicard(title="{{_ 'requested-by'}}" class="{{#if allowsRequestedByOnMinicard}}is-checked{{/if}}")
i.fa(class="{{#if allowsRequestedByOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
i.fa.fa-user
@@ -316,8 +316,8 @@ template(name="boardCardSettingsPopup")
a.flex.js-field-has-labels(title="{{_ 'labels'}}" class="{{#if allowsLabels}}is-checked{{/if}}")
i.fa(class="{{#if allowsLabels}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
a.flex.js-field-has-labels(title="{{_ 'labels'}}" class="{{#if allowsLabels}}is-checked{{/if}}")
i.fa(class="{{#if allowsLabels}}fa-check{{else}}fa-square-o{{/if}}")
a.flex.js-field-has-labels-on-minicard(title="{{_ 'labels'}}" class="{{#if allowsLabelsOnMinicard}}is-checked{{/if}}")
i.fa(class="{{#if allowsLabelsOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
i.fa.fa-tag
@@ -337,8 +337,8 @@ template(name="boardCardSettingsPopup")
a.flex.js-field-has-card-number(title="{{_ 'card'}} {{_ 'number'}}" class="{{#if allowsCardNumber}}is-checked{{/if}}")
i.fa(class="{{#if allowsCardNumber}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
a.flex.js-field-has-card-number(title="{{_ 'card'}} {{_ 'number'}}" class="{{#if allowsCardNumber}}is-checked{{/if}}")
i.fa(class="{{#if allowsCardNumber}}fa-check{{else}}fa-square-o{{/if}}")
a.flex.js-field-has-card-number-on-minicard(title="{{_ 'card'}} {{_ 'number'}}" class="{{#if allowsCardNumberOnMinicard}}is-checked{{/if}}")
i.fa(class="{{#if allowsCardNumberOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
| #️⃣
@@ -349,8 +349,8 @@ template(name="boardCardSettingsPopup")
a.flex.js-field-has-description-title(title="{{_ 'description'}} {{_ 'title'}}" class="{{#if allowsDescriptionTitle}}is-checked{{/if}}")
i.fa(class="{{#if allowsDescriptionTitle}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
a.flex.js-field-has-description-title(title="{{_ 'description'}} {{_ 'title'}}" class="{{#if allowsDescriptionTitle}}is-checked{{/if}}")
i.fa(class="{{#if allowsDescriptionTitle}}fa-check{{else}}fa-square-o{{/if}}")
a.flex.js-field-has-description-title-on-minicard(title="{{_ 'description'}} {{_ 'title'}}" class="{{#if allowsDescriptionTitleOnMinicard}}is-checked{{/if}}")
i.fa(class="{{#if allowsDescriptionTitleOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
i.fa.fa-file-text-o
@@ -361,8 +361,8 @@ template(name="boardCardSettingsPopup")
a.flex.js-field-has-description-text(title="{{_ 'description'}} {{_ 'custom-field-text'}}" class="{{#if allowsDescriptionText}}is-checked{{/if}}")
i.fa(class="{{#if allowsDescriptionText}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
a.flex.js-field-has-description-text(title="{{_ 'description'}} {{_ 'custom-field-text'}}" class="{{#if allowsDescriptionText}}is-checked{{/if}}")
i.fa(class="{{#if allowsDescriptionText}}fa-check{{else}}fa-square-o{{/if}}")
a.flex.js-field-has-description-text-on-minicard(title="{{_ 'description'}} {{_ 'custom-field-text'}}" class="{{#if allowsDescriptionTextOnMinicard}}is-checked{{/if}}")
i.fa(class="{{#if allowsDescriptionTextOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
i.fa.fa-file-text-o
@@ -383,8 +383,8 @@ template(name="boardCardSettingsPopup")
a.flex.js-field-has-checklists(title="{{_ 'checklists'}}" class="{{#if allowsChecklists}}is-checked{{/if}}")
i.fa(class="{{#if allowsChecklists}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
a.flex.js-field-has-checklists(title="{{_ 'checklists'}}" class="{{#if allowsChecklists}}is-checked{{/if}}")
i.fa(class="{{#if allowsChecklists}}fa-check{{else}}fa-square-o{{/if}}")
a.flex.js-field-has-checklists-on-minicard(title="{{_ 'checklists'}}" class="{{#if allowsChecklistsOnMinicard}}is-checked{{/if}}")
i.fa(class="{{#if allowsChecklistsOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
i.fa.fa-check
@@ -394,8 +394,8 @@ template(name="boardCardSettingsPopup")
a.flex.js-field-has-subtasks(title="{{_ 'subtasks'}}" class="{{#if allowsSubtasks}}is-checked{{/if}}")
i.fa(class="{{#if allowsSubtasks}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
a.flex.js-field-has-subtasks(title="{{_ 'subtasks'}}" class="{{#if allowsSubtasks}}is-checked{{/if}}")
i.fa(class="{{#if allowsSubtasks}}fa-check{{else}}fa-square-o{{/if}}")
a.flex.js-field-has-subtasks-on-minicard(title="{{_ 'subtasks'}}" class="{{#if allowsSubtasksOnMinicard}}is-checked{{/if}}")
i.fa(class="{{#if allowsSubtasksOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
i.fa.fa-globe
@@ -405,8 +405,8 @@ template(name="boardCardSettingsPopup")
a.flex.js-field-has-attachments(title="{{_ 'attachments'}}" class="{{#if allowsAttachments}}is-checked{{/if}}")
i.fa(class="{{#if allowsAttachments}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
a.flex.js-field-has-attachments(title="{{_ 'attachments'}}" class="{{#if allowsAttachments}}is-checked{{/if}}")
i.fa(class="{{#if allowsAttachments}}fa-check{{else}}fa-square-o{{/if}}")
a.flex.js-field-has-attachments-on-minicard(title="{{_ 'attachments'}}" class="{{#if allowsAttachmentsOnMinicard}}is-checked{{/if}}")
i.fa(class="{{#if allowsAttachmentsOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
.card-settings-column
span
i.fa.fa-paperclip
@@ -528,8 +528,8 @@ template(name="deleteDuplicateListsPopup")
template(name="outgoingWebhooksPopup")
each integrations
form.integration-form
a.flex
i.fa(class="{{#unless enabled}}fa-check{{else}}fa-square-o{{/unless}}")
a.flex.js-toggle-webhook-enabled
.materialCheckBox(class="{{#unless enabled}}is-checked{{/unless}}")
span {{_ 'disable-webhook'}}
input.js-outgoing-webhooks-title(placeholder="{{_ 'webhook-title'}}" type="text" name="title" value=title)
input.js-outgoing-webhooks-url(type="text" name="url" value=url)
@@ -640,11 +640,11 @@ template(name="exportBoardPopup")
li
a(href="{{exportCsvUrl}}", download="{{exportCsvFilename}}")
i.fa.fa-upload
| {{_ 'export-board-csv'}} ,
| {{_ 'export-board-csv-comma'}}
li
a(href="{{exportScsvUrl}}", download="{{exportCsvFilename}}")
i.fa.fa-upload
| {{_ 'export-board-csv'}} ;
| {{_ 'export-board-csv-semicolon'}}
li
a(href="{{exportTsvUrl}}", download="{{exportTsvFilename}}")
i.fa.fa-upload
+210 -43
View File
@@ -1,9 +1,24 @@
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import { InfiniteScrolling } from '/client/lib/infiniteScrolling';
import AccessibilitySettings from '/models/accessibilitySettings';
import Boards from '/models/boards';
import Integrations from '/models/integrations';
import Lists from '/models/lists';
import { BOARD_COLORS } from '/models/metadata/colors';
import { Filter } from '/client/lib/filter';
import { EscapeActions } from '/client/lib/escapeActions';
import { Utils } from '/client/lib/utils';
import {
clearSidebarInstance,
setSidebarInstance,
} from '/client/features/sidebar/service';
Sidebar = null;
export let Sidebar = null;
const defaultView = 'home';
const MCB = '.materialCheckBox';
@@ -25,6 +40,7 @@ Template.sidebar.onCreated(function() {
this.infiniteScrolling = new InfiniteScrolling();
this.activitiesInstance = null;
Sidebar = this;
setSidebarInstance(this);
// Subscribe to accessibility settings
Meteor.subscribe('accessibilitySettings');
@@ -106,6 +122,7 @@ Template.sidebar.onCreated(function() {
});
Template.sidebar.onDestroyed(function() {
clearSidebarInstance(this);
Sidebar = null;
});
@@ -153,7 +170,7 @@ Template.sidebar.events({
tpl.setView();
},
'click .js-toggle-minicard-label-text'() {
currentUser = ReactiveCache.getCurrentUser();
const currentUser = ReactiveCache.getCurrentUser();
if (currentUser) {
Meteor.call('toggleMinicardLabelText');
} else if (window.localStorage.getItem('hiddenMinicardLabelText')) {
@@ -176,12 +193,12 @@ Template.sidebar.events({
'click .js-show-week-of-year-toggle'() {
ReactiveCache.getCurrentUser().toggleShowWeekOfYear();
},
'click .sidebar-accessibility'() {
'click .sidebar-accessibility'(event, tpl) {
FlowRouter.go('accessibility');
Sidebar.toggle();
tpl.toggle();
},
'click .js-close-sidebar'() {
Sidebar.toggle()
'click .js-close-sidebar'(event, tpl) {
tpl.toggle();
},
'scroll .js-board-sidebar-content'(event, tpl) {
tpl.infiniteScrolling.checkScrollPosition(event.currentTarget, () => {
@@ -194,7 +211,7 @@ Blaze.registerHelper('Sidebar', () => Sidebar);
Template.homeSidebar.helpers({
hiddenMinicardLabelText() {
currentUser = ReactiveCache.getCurrentUser();
const currentUser = ReactiveCache.getCurrentUser();
if (currentUser) {
return (currentUser.profile || {}).hiddenMinicardLabelText;
} else if (window.localStorage.getItem('hiddenMinicardLabelText')) {
@@ -218,8 +235,8 @@ Template.homeSidebar.helpers({
});
Template.homeSidebar.events({
'click .js-toggle-show-activities'() {
Utils.getCurrentBoard().toggleShowActivities();
async 'click .js-toggle-show-activities'() {
await Utils.getCurrentBoard().toggleShowActivities();
},
});
@@ -237,7 +254,9 @@ Template.boardInfoOnMyBoardsPopup.helpers({
EscapeActions.register(
'sidebarView',
() => {
Sidebar.setView(defaultView);
if (Sidebar) {
Sidebar.setView(defaultView);
}
},
() => {
return Sidebar && Sidebar.getView() !== defaultView;
@@ -296,11 +315,15 @@ Template.boardMenuPopup.events({
Popup.back();
},
'click .js-custom-fields'() {
Sidebar.setView('customFields');
if (Sidebar) {
Sidebar.setView('customFields');
}
Popup.back();
},
'click .js-open-archives'() {
Sidebar.setView('archives');
if (Sidebar) {
Sidebar.setView('archives');
}
Popup.back();
},
'click .js-change-board-color': Popup.open('boardChangeColor'),
@@ -544,10 +567,6 @@ Template.membersWidget.events({
},
});
Template.outgoingWebhooksPopup.onCreated(function() {
this.disabled = new ReactiveVar(false);
});
Template.outgoingWebhooksPopup.helpers({
boardId() {
return Session.get('currentBoard') || Integrations.Const.GLOBAL_WEBHOOK_ID;
@@ -571,20 +590,23 @@ Template.outgoingWebhooksPopup.helpers({
});
Template.outgoingWebhooksPopup.events({
'click a.flex'(evt, tpl) {
tpl.disabled.set(!tpl.disabled.get());
$(evt.target).toggleClass(CKCLS, tpl.disabled.get());
},
submit(evt, tpl) {
'click .js-toggle-webhook-enabled'(evt) {
evt.preventDefault();
const url = evt.target.url.value;
$(evt.currentTarget).find(MCB).toggleClass(CKCLS);
},
async submit(evt) {
evt.preventDefault();
const url = evt.target.url.value.trim();
const boardId = Session.get('currentBoard') || Integrations.Const.GLOBAL_WEBHOOK_ID;
let id = null;
let integration = null;
const title = evt.target.title.value;
const token = evt.target.token.value;
const type = evt.target.type.value;
const enabled = !tpl.disabled.get();
const title = evt.target.title.value.trim();
const token = evt.target.token.value.trim();
const type = evt.target.type.value.trim();
const enabled = !$(evt.target)
.find('.js-toggle-webhook-enabled')
.find(MCB)
.hasClass(CKCLS);
let remove = false;
const values = {
url,
@@ -609,22 +631,27 @@ Template.outgoingWebhooksPopup.events({
} else if (url) {
integration = findIntegration({ url, token });
}
if (remove) {
Integrations.remove(integration._id);
} else if (integration && integration._id) {
Integrations.update(integration._id, {
$set: values,
});
} else if (url) {
Integrations.insert({
...values,
userId: Meteor.userId(),
enabled: true,
boardId,
activities: ['all'],
});
try {
if (remove && integration && integration._id) {
await Integrations.removeAsync(integration._id);
} else if (integration && integration._id) {
await Integrations.updateAsync(integration._id, {
$set: values,
});
} else if (url) {
await Integrations.insertAsync({
...values,
userId: Meteor.userId(),
enabled,
boardId,
activities: ['all'],
});
}
Popup.back();
} catch (error) {
alert(error?.reason || error?.message || 'Failed to save webhook');
}
Popup.back();
},
});
@@ -778,7 +805,7 @@ Template.labelsWidget.onRendered(draggableMembersLabelsWidgets);
Template.boardChangeColorPopup.helpers({
backgroundColors() {
return Boards.simpleSchema()._schema.color.allowedValues;
return BOARD_COLORS;
},
isSelected() {
const currentBoard = Utils.getCurrentBoard();
@@ -1002,26 +1029,51 @@ Template.boardCardSettingsPopup.helpers({
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsReceivedDate : false;
},
allowsReceivedDateOnMinicard() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsReceivedDateOnMinicard : false;
},
allowsStartDate() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsStartDate : false;
},
allowsStartDateOnMinicard() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsStartDateOnMinicard : false;
},
allowsDueDate() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsDueDate : false;
},
allowsDueDateOnMinicard() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsDueDateOnMinicard : false;
},
allowsEndDate() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsEndDate : false;
},
allowsEndDateOnMinicard() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsEndDateOnMinicard : false;
},
allowsSubtasks() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsSubtasks : false;
},
allowsSubtasksOnMinicard() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsSubtasksOnMinicard : false;
},
allowsCreator() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
@@ -1037,21 +1089,41 @@ Template.boardCardSettingsPopup.helpers({
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsMembers : false;
},
allowsMembersOnMinicard() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsMembersOnMinicard : false;
},
allowsAssignee() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsAssignee : false;
},
allowsAssigneeOnMinicard() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsAssigneeOnMinicard : false;
},
allowsAssignedBy() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsAssignedBy : false;
},
allowsAssignedByOnMinicard() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsAssignedByOnMinicard : false;
},
allowsRequestedBy() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsRequestedBy : false;
},
allowsRequestedByOnMinicard() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsRequestedByOnMinicard : false;
},
allowsCardSortingByNumber() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
@@ -1067,6 +1139,11 @@ Template.boardCardSettingsPopup.helpers({
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsLabels : false;
},
allowsLabelsOnMinicard() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsLabelsOnMinicard : false;
},
allowsShowListsOnMinicard() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
@@ -1077,11 +1154,21 @@ Template.boardCardSettingsPopup.helpers({
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsChecklists : false;
},
allowsChecklistsOnMinicard() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsChecklistsOnMinicard : false;
},
allowsAttachments() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsAttachments : false;
},
allowsAttachmentsOnMinicard() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsAttachmentsOnMinicard : false;
},
allowsComments() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
@@ -1092,11 +1179,21 @@ Template.boardCardSettingsPopup.helpers({
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsCardNumber : false;
},
allowsCardNumberOnMinicard() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsCardNumberOnMinicard : false;
},
allowsDescriptionTitle() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsDescriptionTitle : false;
},
allowsDescriptionTitleOnMinicard() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
return currentBoard ? currentBoard.allowsDescriptionTitleOnMinicard : false;
},
allowsDescriptionText() {
const boardId = Session.get('currentBoard');
const currentBoard = ReactiveCache.getBoard(boardId);
@@ -1186,26 +1283,51 @@ Template.boardCardSettingsPopup.events({
const newValue = !tpl.currentBoard.allowsReceivedDate;
Boards.update(tpl.currentBoard._id, { $set: { allowsReceivedDate: newValue } });
},
'click .js-field-has-receiveddate-on-minicard'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsReceivedDateOnMinicard;
Boards.update(tpl.currentBoard._id, { $set: { allowsReceivedDateOnMinicard: newValue } });
},
'click .js-field-has-startdate'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsStartDate;
Boards.update(tpl.currentBoard._id, { $set: { allowsStartDate: newValue } });
},
'click .js-field-has-startdate-on-minicard'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsStartDateOnMinicard;
Boards.update(tpl.currentBoard._id, { $set: { allowsStartDateOnMinicard: newValue } });
},
'click .js-field-has-enddate'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsEndDate;
Boards.update(tpl.currentBoard._id, { $set: { allowsEndDate: newValue } });
},
'click .js-field-has-enddate-on-minicard'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsEndDateOnMinicard;
Boards.update(tpl.currentBoard._id, { $set: { allowsEndDateOnMinicard: newValue } });
},
'click .js-field-has-duedate'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsDueDate;
Boards.update(tpl.currentBoard._id, { $set: { allowsDueDate: newValue } });
},
'click .js-field-has-duedate-on-minicard'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsDueDateOnMinicard;
Boards.update(tpl.currentBoard._id, { $set: { allowsDueDateOnMinicard: newValue } });
},
'click .js-field-has-subtasks'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsSubtasks;
Boards.update(tpl.currentBoard._id, { $set: { allowsSubtasks: newValue } });
},
'click .js-field-has-subtasks-on-minicard'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsSubtasksOnMinicard;
Boards.update(tpl.currentBoard._id, { $set: { allowsSubtasksOnMinicard: newValue } });
},
'click .js-field-has-creator'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsCreator;
@@ -1221,21 +1343,41 @@ Template.boardCardSettingsPopup.events({
const newValue = !tpl.currentBoard.allowsMembers;
Boards.update(tpl.currentBoard._id, { $set: { allowsMembers: newValue } });
},
'click .js-field-has-members-on-minicard'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsMembersOnMinicard;
Boards.update(tpl.currentBoard._id, { $set: { allowsMembersOnMinicard: newValue } });
},
'click .js-field-has-assignee'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsAssignee;
Boards.update(tpl.currentBoard._id, { $set: { allowsAssignee: newValue } });
},
'click .js-field-has-assignee-on-minicard'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsAssigneeOnMinicard;
Boards.update(tpl.currentBoard._id, { $set: { allowsAssigneeOnMinicard: newValue } });
},
'click .js-field-has-assigned-by'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsAssignedBy;
Boards.update(tpl.currentBoard._id, { $set: { allowsAssignedBy: newValue } });
},
'click .js-field-has-assigned-by-on-minicard'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsAssignedByOnMinicard;
Boards.update(tpl.currentBoard._id, { $set: { allowsAssignedByOnMinicard: newValue } });
},
'click .js-field-has-requested-by'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsRequestedBy;
Boards.update(tpl.currentBoard._id, { $set: { allowsRequestedBy: newValue } });
},
'click .js-field-has-requested-by-on-minicard'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsRequestedByOnMinicard;
Boards.update(tpl.currentBoard._id, { $set: { allowsRequestedByOnMinicard: newValue } });
},
'click .js-field-has-card-sorting-by-number'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsCardSortingByNumber;
@@ -1251,6 +1393,11 @@ Template.boardCardSettingsPopup.events({
const newValue = !tpl.currentBoard.allowsLabels;
Boards.update(tpl.currentBoard._id, { $set: { allowsLabels: newValue } });
},
'click .js-field-has-labels-on-minicard'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsLabelsOnMinicard;
Boards.update(tpl.currentBoard._id, { $set: { allowsLabelsOnMinicard: newValue } });
},
'click .js-field-has-card-show-lists-on-minicard'(evt, tpl) {
evt.preventDefault();
tpl.currentBoard.allowsShowListsOnMinicard = !tpl.currentBoard
@@ -1283,6 +1430,11 @@ Template.boardCardSettingsPopup.events({
tpl.currentBoard.allowsDescriptionTitle,
);
},
'click .js-field-has-description-title-on-minicard'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsDescriptionTitleOnMinicard;
Boards.update(tpl.currentBoard._id, { $set: { allowsDescriptionTitleOnMinicard: newValue } });
},
'click .js-field-has-card-number'(evt, tpl) {
evt.preventDefault();
tpl.currentBoard.allowsCardNumber = !tpl.currentBoard
@@ -1299,6 +1451,11 @@ Template.boardCardSettingsPopup.events({
tpl.currentBoard.allowsCardNumber,
);
},
'click .js-field-has-card-number-on-minicard'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsCardNumberOnMinicard;
Boards.update(tpl.currentBoard._id, { $set: { allowsCardNumberOnMinicard: newValue } });
},
'click .js-field-has-description-text-on-minicard'(evt, tpl) {
evt.preventDefault();
tpl.currentBoard.allowsDescriptionTextOnMinicard = !tpl.currentBoard
@@ -1347,6 +1504,11 @@ Template.boardCardSettingsPopup.events({
tpl.currentBoard.allowsChecklists,
);
},
'click .js-field-has-checklists-on-minicard'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsChecklistsOnMinicard;
Boards.update(tpl.currentBoard._id, { $set: { allowsChecklistsOnMinicard: newValue } });
},
'click .js-field-has-attachments'(evt, tpl) {
evt.preventDefault();
tpl.currentBoard.allowsAttachments = !tpl.currentBoard
@@ -1363,6 +1525,11 @@ Template.boardCardSettingsPopup.events({
tpl.currentBoard.allowsAttachments,
);
},
'click .js-field-has-attachments-on-minicard'(evt, tpl) {
evt.preventDefault();
const newValue = !tpl.currentBoard.allowsAttachmentsOnMinicard;
Boards.update(tpl.currentBoard._id, { $set: { allowsAttachmentsOnMinicard: newValue } });
},
'click .js-field-has-comments'(evt, tpl) {
evt.preventDefault();
tpl.currentBoard.allowsComments = !tpl.currentBoard.allowsComments;
@@ -1472,7 +1639,7 @@ Template.addMemberPopup.onCreated(function() {
};
this.isValidEmail = function(email) {
return SimpleSchema.RegEx.Email.test(email);
return /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(email);
};
this.performSearch = function(query) {
@@ -2,10 +2,13 @@ import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import Lists from '/models/lists';
import Swimlanes from '/models/swimlanes';
import Cards from '/models/cards';
import { Utils } from '/client/lib/utils';
const ARCHIVE_PAGE_SIZE = 30;
const ARCHIVE_SCROLL_THRESHOLD_PX = 120;
function throttle(func, wait) {
let timeoutId = null;
let lastRan = 0;
@@ -1,5 +1,6 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import CustomFields from '/models/customFields';
Template.customFieldsSidebar.helpers({
customFields() {
+6 -2
View File
@@ -1,7 +1,11 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import { Filter } from '/client/lib/filter';
import { EscapeActions } from '/client/lib/escapeActions';
import { MultiSelection } from '/client/lib/multiSelection';
import { Utils } from '/client/lib/utils';
const subManager = new SubsManager();
// SubsManager removed for Meteor 3 migration
Template.filterSidebar.events({
'submit .js-list-filter'(evt, tpl) {
@@ -64,7 +68,7 @@ Template.filterSidebar.events({
Filter.resetExceptions();
const currentBoardId = Session.get('currentBoard');
if (!currentBoardId) return;
subManager.subscribe(
Meteor.subscribe(
'board',
currentBoardId,
Filter.archive.isSelected(),
@@ -1,3 +1,5 @@
import { Utils } from '/client/lib/utils';
Template.searchSidebar.onCreated(function () {
this.term = new ReactiveVar('');
});
+10 -4
View File
@@ -1,10 +1,13 @@
import { TAPi18n } from '/imports/i18n';
import { ReactiveCache } from '/imports/reactiveCache';
import { SWIMLANE_COLORS } from '/models/metadata/colors';
import Swimlanes from '/models/swimlanes';
import { Utils } from '/client/lib/utils';
const { calculateIndexData } = Utils;
let swimlaneColors;
Meteor.startup(() => {
swimlaneColors = Swimlanes.simpleSchema()._schema.color.allowedValues;
swimlaneColors = SWIMLANE_COLORS;
});
function swimlaneHeaderCollapsed(check = undefined) {
@@ -117,7 +120,7 @@ Template.swimlaneAddPopup.onCreated(function () {
});
Template.swimlaneAddPopup.events({
submit(event, tpl) {
async submit(event, tpl) {
event.preventDefault();
const currentBoard = Utils.getCurrentBoard();
const nextSwimlane = currentBoard.nextSwimlane(tpl.currentSwimlane);
@@ -133,7 +136,7 @@ Template.swimlaneAddPopup.events({
: 'swimlane';
if (title) {
Swimlanes.insert({
await Swimlanes.insertAsync({
title,
boardId: Session.get('currentBoard'),
sort: sortValue.base || 0,
@@ -172,13 +175,16 @@ Template.setSwimlaneColorPopup.events({
Popup.back();
},
'click .js-palette-color'(event, tpl) {
tpl.currentColor.set(Template.currentData().color);
const paletteData = Blaze.getData(event.currentTarget);
tpl.currentColor.set(paletteData?.color);
},
async 'click .js-submit'(event, tpl) {
event.preventDefault();
await tpl.currentSwimlane.setColor(tpl.currentColor.get());
Popup.back();
},
async 'click .js-remove-color'(event, tpl) {
event.preventDefault();
await tpl.currentSwimlane.setColor(null);
Popup.back();
},
+57 -6
View File
@@ -1,6 +1,11 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { once } from '/imports/lib/collectionHelpers';
import dragscroll from '@wekanteam/dragscroll';
import Lists from '/models/lists';
import { CSSEvents } from '/client/lib/cssEvents';
import { Filter } from '/client/lib/filter';
import { EscapeActions } from '/client/lib/escapeActions';
import { Utils } from '/client/lib/utils';
const { calculateIndex } = Utils;
function saveSorting(ui) {
@@ -137,10 +142,23 @@ function saveSorting(ui) {
function currentListIsInThisSwimlane(swimlaneId) {
const currentList = Utils.getCurrentList();
return (
currentList &&
(currentList.swimlaneId === swimlaneId || currentList.swimlaneId === '')
if (!currentList) return false;
// Match the list's own swimlane, or shared/orphaned lists (empty/null
// swimlaneId are visible in every swimlane as a fallback).
if (currentList.swimlaneId === swimlaneId || !currentList.swimlaneId) {
return true;
}
// Also match when the list has an orphaned swimlaneId (references a deleted
// swimlane) and THIS is the first swimlane — orphaned lists are shown there.
const currentBoard = Utils.getCurrentBoard();
if (!currentBoard) return false;
const allSwimlanes = ReactiveCache.getSwimlanes(
{ boardId: currentBoard._id, archived: false },
{ sort: ['sort'] },
);
if (!allSwimlanes.length || allSwimlanes[0]._id !== swimlaneId) return false;
const validIds = new Set(allSwimlanes.map(s => s._id));
return !validIds.has(currentList.swimlaneId);
}
function currentCardIsInThisList(listId, swimlaneId) {
@@ -154,7 +172,10 @@ function currentCardIsInThisList(listId, swimlaneId) {
return (
currentCard &&
currentCard.listId === listId &&
currentCard.swimlaneId === swimlaneId
// Match cards for this swimlane, AND orphaned/shared cards that have no
// swimlaneId assigned (null/empty) — they are shown in every swimlane as
// a fallback so no content is ever invisible.
(currentCard.swimlaneId === swimlaneId || !currentCard.swimlaneId)
);
else if (
//currentUser &&
@@ -577,8 +598,38 @@ Template.swimlane.helpers({
return ReactiveCache.getCurrentUser().isBoardAdmin();
},
lists() {
// Return per-swimlane lists for this swimlane
return this.myLists();
const swimlane = this;
// myLists() already covers:
// • lists owned by this swimlane (swimlaneId === this._id)
// • shared / pre-migration lists (swimlaneId empty or null)
const regularLists = swimlane.myLists();
// Additionally, detect lists whose swimlaneId references a swimlane that
// no longer exists ("orphaned-swimlane" lists). These would be invisible
// in every swimlane without this fallback. Show them in the FIRST swimlane
// on the board so they are always accessible without a DB migration.
const allSwimlanes = ReactiveCache.getSwimlanes(
{ boardId: swimlane.boardId, archived: false },
{ sort: ['sort'] },
);
// Only the first swimlane picks up orphaned-swimlane lists.
if (!allSwimlanes.length || allSwimlanes[0]._id !== swimlane._id) {
return regularLists;
}
const validIds = allSwimlanes.map(s => s._id);
const orphaned = swimlane.orphanedSwimlaneLists(validIds);
if (!orphaned.length) return regularLists;
// Merge, deduplicating by _id (regularLists may already contain some).
const seen = new Set(regularLists.map(l => l._id));
const combined = [...regularLists];
for (const l of orphaned) {
if (!seen.has(l._id)) {
seen.add(l._id);
combined.push(l);
}
}
return combined;
},
collapseSwimlane() {
return Utils.getSwimlaneCollapseState(this);
@@ -0,0 +1,4 @@
import { ReactiveVar } from 'meteor/reactive-var';
// Reactive source for invalidating people row when avatars change
export const avatarUpdateCounter = new ReactiveVar(0);
+37 -2
View File
@@ -70,11 +70,11 @@ template(name="changeAvatarPopup")
each uploadedAvatars
li: a.js-select-avatar
.member
img.avatar.avatar-image(src="{{link}}")
img.avatar.avatar-image(src="{{avatarLink}}")
if isSelected
i.fa.fa-check
p.sub-name
a.js-delete-avatar {{_ 'delete'}}
a.js-delete-avatar(data-avatar-id="{{_id}}") {{_ 'delete'}}
| -
= name
li: a.js-select-initials
@@ -100,6 +100,41 @@ template(name="deleteAvatarPopup")
p {{_ 'delete-avatar-confirm'}}
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
template(name="adminChangeAvatarPopup")
.admin-change-avatar-header
h3 {{_ 'change-avatar'}} - {{ username }}
if error.get
.warning {{_ error.get}}
ul.pop-over-list
each uploadedAvatars
li: a.js-select-avatar
.member
img.avatar.avatar-image(src="{{avatarLink}}")
if isSelected
i.fa.fa-check
p.sub-name
a.js-delete-avatar(data-avatar-id="{{_id}}") {{_ 'delete'}}
| -
= name
li: a.js-select-initials
.member
+userAvatarInitials(userData=userData)
| {{_ 'initials' }}
if noAvatarUrl
i.fa.fa-check
p.sub-name {{_ 'default-avatar'}}
input.hide.js-upload-avatar-input(accept="image/*;capture=camera" type="file")
if Meteor.settings.public.avatarsUploadMaxSize
| {{_ 'max-avatar-filesize'}} {{Meteor.settings.public.avatarsUploadMaxSize}}
br
if Meteor.settings.public.avatarsUploadMimeTypes
| {{_ 'allowed-avatar-filetypes'}} {{Meteor.settings.public.avatarsUploadMimeTypes}}
br
| {{_ 'invalid-file'}}
button.full.js-upload-avatar
i.fa.fa-upload
| {{_ 'upload-avatar' }}
template(name="cardMemberPopup")
.board-member-menu
.mini-profile-info
+191 -14
View File
@@ -1,9 +1,9 @@
import { ReactiveCache } from '/imports/reactiveCache';
import Cards from '/models/cards';
import { ReactiveVar } from 'meteor/reactive-var';
import { avatarUpdateCounter } from '/client/components/users/avatarUpdateCounter';
import Avatars from '/models/avatars';
import Users from '/models/users';
import Org from '/models/org';
import Team from '/models/team';
import Presences from '/models/presences';
import { Utils } from '/client/lib/utils';
Template.userAvatar.helpers({
userData() {
@@ -166,6 +166,7 @@ Template.boardTeamName.helpers({
Template.changeAvatarPopup.onCreated(function () {
this.error = new ReactiveVar('');
this.avatarUpdateCounter = new ReactiveVar(0); // Trigger to force helper re-evaluation
Meteor.subscribe('my-avatars');
});
@@ -174,16 +175,28 @@ Template.changeAvatarPopup.helpers({
return Template.instance().error;
},
uploadedAvatars() {
Template.instance().avatarUpdateCounter.get(); // Create dependency on update counter
const ret = ReactiveCache.getAvatars({ userId: Meteor.userId() }, {}, true);
return ret;
},
avatarLink() {
if (this && typeof this.link === 'function') {
return this.link();
}
return '';
},
isSelected() {
Template.instance().avatarUpdateCounter.get(); // Create dependency on update counter
const userProfile = ReactiveCache.getCurrentUser().profile;
const avatarUrl = userProfile && userProfile.avatarUrl;
const currentAvatarUrl = Template.currentData().link();
return avatarUrl === currentAvatarUrl;
const currentAvatarUrl = this.link && typeof this.link === 'function' ? this.link() : '';
// Normalize URLs by removing query parameters for comparison
// (they may be added for boardId but shouldn't affect selection comparison)
const normalizeUrl = (url) => url ? url.split('?')[0] : '';
return normalizeUrl(avatarUrl) === normalizeUrl(currentAvatarUrl);
},
noAvatarUrl() {
Template.instance().avatarUpdateCounter.get(); // Create dependency on update counter
const userProfile = ReactiveCache.getCurrentUser().profile;
const avatarUrl = userProfile && userProfile.avatarUrl;
return !avatarUrl;
@@ -194,6 +207,14 @@ function changeAvatarSetAvatar(tpl, avatarUrl) {
Meteor.call('setAvatarUrl', avatarUrl, (err) => {
if (err) {
tpl.error.set(err.reason || 'Error setting avatar');
} else {
// Trigger a re-evaluation of helpers to show updated avatar selection
const counter = tpl.avatarUpdateCounter.get();
tpl.avatarUpdateCounter.set(counter + 1);
// Also increment global counter for admin people list updates
avatarUpdateCounter.set(avatarUpdateCounter.get() + 1);
// Clear input for next upload
tpl.$('.js-upload-avatar-input').val('');
}
});
}
@@ -202,9 +223,9 @@ Template.changeAvatarPopup.events({
'click .js-upload-avatar'(event, tpl) {
tpl.$('.js-upload-avatar-input').click();
},
'change .js-upload-avatar-input'(event, tpl) {
async 'change .js-upload-avatar-input'(event, tpl) {
if (event.currentTarget.files && event.currentTarget.files[0]) {
const uploader = Avatars.insert(
const uploader = await Avatars.insertAsync(
{
file: event.currentTarget.files[0],
chunkSize: 'dynamic',
@@ -214,15 +235,25 @@ Template.changeAvatarPopup.events({
uploader.on('error', (error, fileData) => {
tpl.error.set(error.reason);
});
uploader.on('uploaded', (error, fileRef) => {
if (!error) {
// Trigger a re-evaluation of helpers to show new uploaded avatar
const counter = tpl.avatarUpdateCounter.get();
tpl.avatarUpdateCounter.set(counter + 1);
// Also increment global counter for admin people list updates
avatarUpdateCounter.set(avatarUpdateCounter.get() + 1);
} else {
tpl.error.set(error.reason);
}
});
uploader.start();
}
},
'click .js-select-avatar'(event, tpl) {
event.preventDefault();
event.stopPropagation();
const data = Blaze.getData(event.currentTarget);
if (data && typeof data.link === 'function') {
const avatarUrl = data.link();
if (this && typeof this.link === 'function') {
const avatarUrl = this.link();
changeAvatarSetAvatar(tpl, avatarUrl);
}
},
@@ -231,10 +262,13 @@ Template.changeAvatarPopup.events({
event.stopPropagation();
changeAvatarSetAvatar(tpl, '');
},
'click .js-delete-avatar': Popup.afterConfirm('deleteAvatar', function (event) {
Avatars.remove(this._id);
'click .js-delete-avatar': Popup.afterConfirm('deleteAvatar', async function() {
// Inside the each loop, 'this' is the avatar object
const avatarId = this._id;
if (avatarId) {
await Avatars.removeAsync(avatarId);
}
Popup.back();
event.stopPropagation();
}),
});
@@ -251,3 +285,146 @@ Template.cardMemberPopup.events({
},
'click .js-edit-profile': Popup.open('editProfile'),
});
Template.adminChangeAvatarPopup.onCreated(function () {
this.error = new ReactiveVar('');
this.avatarUpdateCounter = new ReactiveVar(0);
const userId = this.data._id || (this.data.user && this.data.user._id);
this.targetUserId = userId;
if (userId) {
Meteor.subscribe('avatars-for-user', userId);
}
});
Template.adminChangeAvatarPopup.helpers({
error() {
return Template.instance().error;
},
userId() {
const instance = Template.instance();
return instance.targetUserId || (this._id || (this.user && this.user._id));
},
userData() {
return this.user || this;
},
uploadedAvatars() {
Template.instance().avatarUpdateCounter.get();
const instance = Template.instance();
const userId = instance.targetUserId || (this._id || (this.user && this.user._id));
if (!userId) return [];
const ret = ReactiveCache.getAvatars({ userId: userId }, {}, true);
return ret;
},
avatarLink() {
if (this && typeof this.link === 'function') {
return this.link();
}
return '';
},
currentEditingUser() {
Template.instance().avatarUpdateCounter.get();
const instance = Template.instance();
const userId = instance.targetUserId;
if (!userId) return null;
return ReactiveCache.getUser(userId);
},
isSelected() {
Template.instance().avatarUpdateCounter.get();
const instance = Template.instance();
const userId = instance.targetUserId;
if (!userId) return false;
const user = ReactiveCache.getUser(userId);
if (!user) return false;
const userProfile = user.profile;
const avatarUrl = userProfile && userProfile.avatarUrl;
const currentAvatarUrl = this.link && typeof this.link === 'function' ? this.link() : '';
const normalizeUrl = (url) => url ? url.split('?')[0] : '';
return normalizeUrl(avatarUrl) === normalizeUrl(currentAvatarUrl);
},
noAvatarUrl() {
Template.instance().avatarUpdateCounter.get();
const instance = Template.instance();
const userId = instance.targetUserId;
if (!userId) return true;
const user = ReactiveCache.getUser(userId);
if (!user) return true;
const userProfile = user.profile;
const avatarUrl = userProfile && userProfile.avatarUrl;
return !avatarUrl;
},
});
function adminChangeAvatarSetAvatar(tpl, avatarUrl) {
const userId = tpl.targetUserId || (Template.currentData()._id || (Template.currentData().user && Template.currentData().user._id));
if (!userId) {
console.error('Cannot set avatar: no userId found');
return;
}
Meteor.call('adminSetAvatarUrl', userId, avatarUrl, (err) => {
if (err) {
tpl.error.set(err.reason || 'Error setting avatar');
} else {
const counter = tpl.avatarUpdateCounter.get();
tpl.avatarUpdateCounter.set(counter + 1);
// Also increment global counter to update admin people list
avatarUpdateCounter.set(avatarUpdateCounter.get() + 1);
tpl.$('.js-upload-avatar-input').val('');
}
});
}
Template.adminChangeAvatarPopup.events({
'click .js-upload-avatar'(event, tpl) {
tpl.$('.js-upload-avatar-input').click();
},
async 'change .js-upload-avatar-input'(event, tpl) {
if (event.currentTarget.files && event.currentTarget.files[0]) {
const userId = tpl.targetUserId || (this._id || (this.user && this.user._id));
const uploader = await Avatars.insertAsync(
{
file: event.currentTarget.files[0],
chunkSize: 'dynamic',
meta: {
adminUploadForUserId: userId,
},
},
false,
);
uploader.on('error', (error, fileData) => {
tpl.error.set(error.reason);
});
uploader.on('uploaded', (error, fileRef) => {
if (!error) {
const counter = tpl.avatarUpdateCounter.get();
tpl.avatarUpdateCounter.set(counter + 1);
// Also increment global counter to update admin people list
avatarUpdateCounter.set(avatarUpdateCounter.get() + 1);
} else {
tpl.error.set(error.reason);
}
});
uploader.start();
}
},
'click .js-select-avatar'(event, tpl) {
event.preventDefault();
event.stopPropagation();
if (this && typeof this.link === 'function') {
const avatarUrl = this.link();
adminChangeAvatarSetAvatar(tpl, avatarUrl);
}
},
'click .js-select-initials'(event, tpl) {
event.preventDefault();
event.stopPropagation();
adminChangeAvatarSetAvatar(tpl, '');
},
'click .js-delete-avatar': Popup.afterConfirm('deleteAvatar', async function() {
// Inside the each loop, 'this' is the avatar object
const avatarId = this._id;
if (avatarId) {
await Avatars.removeAsync(avatarId);
}
Popup.back();
}),
});
+8 -4
View File
@@ -1,6 +1,9 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import AccountSettings from '/models/accountSettings';
import Users from '/models/users';
import { Utils } from '/client/lib/utils';
Template.headerUserBar.events({
'click .js-open-header-member-menu': Popup.open('memberMenu'),
@@ -37,7 +40,7 @@ Template.memberMenuPopup.helpers({
isSameDomainNameSettingValue(){
const currSett = ReactiveCache.getCurrentSetting();
if(currSett && currSett != undefined && currSett.disableRegistration && currSett.mailDomainName !== undefined && currSett.mailDomainName != ""){
currentUser = ReactiveCache.getCurrentUser();
const currentUser = ReactiveCache.getCurrentUser();
if (currentUser) {
let found = false;
for(let i = 0; i < currentUser.emails.length; i++) {
@@ -57,7 +60,8 @@ Template.memberMenuPopup.helpers({
isNotOAuth2AuthenticationMethod(){
const currentUser = ReactiveCache.getCurrentUser();
if (currentUser) {
return currentUser.authenticationMethod.toLowerCase() != 'oauth2';
const method = (currentUser.authenticationMethod || '').toLowerCase();
return method !== 'oauth2';
} else {
return true;
}
@@ -136,7 +140,7 @@ Template.invitePeoplePopup.events({
});
const validEmails = [];
emails.forEach(email => {
if (email && SimpleSchema.RegEx.Email.test(email.trim())) {
if (email && /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(email.trim())) {
validEmails.push(email.trim());
}
});
@@ -361,7 +365,7 @@ Template.changeSettingsPopup.helpers({
});
},
startDayOfWeek() {
currentUser = ReactiveCache.getCurrentUser();
const currentUser = ReactiveCache.getCurrentUser();
if (currentUser) {
return currentUser.getStartDayOfWeek();
} else {
+19
View File
@@ -28,6 +28,7 @@ import {
fromNow,
calendar,
} from '/imports/lib/dateUtils';
import { Utils } from '/client/lib/utils';
Blaze.registerHelper('currentBoard', () => {
const ret = Utils.getCurrentBoard();
@@ -139,3 +140,21 @@ Blaze.registerHelper('$nin', (...args) => {
return args.every((arg) => value !== arg);
});
Blaze.registerHelper('$', () => ({ Session, Meteor }));
// Expose module-scoped objects to Blaze templates (jade references)
import { Filter } from '/client/lib/filter';
import { MultiSelection } from '/client/lib/multiSelection';
import { BoardMultiSelection } from '/client/lib/boardMultiSelection';
let _Sidebar;
function getSidebar() {
if (!_Sidebar) {
_Sidebar = require('/client/features/sidebar/service').getSidebarInstance;
}
return _Sidebar();
}
Blaze.registerHelper('Filter', () => Filter);
Blaze.registerHelper('MultiSelection', () => MultiSelection);
Blaze.registerHelper('Sidebar', () => getSidebar());
Blaze.registerHelper('BoardMultiSelection', () => BoardMultiSelection);
+9
View File
@@ -0,0 +1,9 @@
import '/client/components/activities/activities.jade';
import '/client/components/activities/comments.jade';
import '/client/components/activities/activities.js';
import '/client/components/activities/comments.js';
import '/client/components/activities/activities.css';
import '/client/components/activities/comments.css';
+4
View File
@@ -0,0 +1,4 @@
import '/client/components/boardConversionProgress.jade';
import '/client/components/boardConversionProgress.js';
import '/client/components/boardConversionProgress.css';
+20
View File
@@ -0,0 +1,20 @@
import '/client/components/boards/boardArchive.jade';
import '/client/components/boards/boardBody.jade';
import '/client/components/boards/boardHeader.jade';
import '/client/components/boards/boardsList.jade';
import '/client/components/boards/miniboard.jade';
import '/client/components/boards/originalPositionsView.jade';
import '/client/components/boards/boardArchive.js';
import '/client/components/boards/boardBody.js';
import '/client/components/boards/boardHeader.js';
import '/client/components/boards/boardsList.js';
import '/client/components/boards/originalPositionsView.js';
import '/client/components/boards/boardBody.css';
import '/client/components/boards/boardColors.css';
import '/client/components/boards/boardHeader.css';
import '/client/components/boards/boardsList.css';
import '/client/components/boards/calendarView.css';
import '/client/components/boards/originalPositionsView.css';
+36
View File
@@ -0,0 +1,36 @@
import '/client/components/cards/attachments.jade';
import '/client/components/cards/cardCustomFields.jade';
import '/client/components/cards/cardDate.jade';
import '/client/components/cards/cardDescription.jade';
import '/client/components/cards/cardDetails.jade';
import '/client/components/cards/cardTime.jade';
import '/client/components/cards/checklists.jade';
import '/client/components/cards/inlinedCardDescription.jade';
import '/client/components/cards/labels.jade';
import '/client/components/cards/minicard.jade';
import '/client/components/cards/resultCard.jade';
import '/client/components/cards/subtasks.jade';
import '/client/components/cards/attachments.js';
import '/client/components/cards/cardCustomFields.js';
import '/client/components/cards/cardDate.js';
import '/client/components/cards/cardDescription.js';
import '/client/components/cards/cardDetails.js';
import '/client/components/cards/cardTime.js';
import '/client/components/cards/checklists.js';
import '/client/components/cards/labels.js';
import '/client/components/cards/minicard.js';
import '/client/components/cards/resultCard.js';
import '/client/components/cards/subtasks.js';
import '/client/components/cards/attachments.css';
import '/client/components/cards/cardDate.css';
import '/client/components/cards/cardDescription.css';
import '/client/components/cards/cardDetails.css';
import '/client/components/cards/cardTime.css';
import '/client/components/cards/checklists.css';
import '/client/components/cards/labels.css';
import '/client/components/cards/minicard.css';
import '/client/components/cards/resultCard.css';
import '/client/components/cards/subtasks.css';
+4
View File
@@ -0,0 +1,4 @@
import '/client/components/common/originalPosition.jade';
import '/client/components/common/originalPosition.js';
import '/client/components/common/originalPosition.css';
+8
View File
@@ -0,0 +1,8 @@
import '/client/components/forms/datepicker.jade';
import '/client/components/forms/inlinedform.jade';
import '/client/lib/inlinedform';
import '/client/components/forms/datepicker.js';
import '/client/components/forms/datepicker.css';
import '/client/components/forms/forms.css';
+9
View File
@@ -0,0 +1,9 @@
import '/client/components/gantt/gantt.jade';
import '/client/components/gantt/ganttCard.jade';
import '/client/components/gantt/gantt.js';
import '/client/components/gantt/ganttCard.js';
import '/client/components/gantt/gantt.css';
import '/client/components/gantt/ganttCard.css';
+9
View File
@@ -0,0 +1,9 @@
import '/client/components/import/import.jade';
import '/client/components/import/import.js';
import '/client/components/import/csvMembersMapper.js';
import '/client/components/import/trelloMembersMapper.js';
import '/client/components/import/wekanMembersMapper.js';
import '/client/components/import/import.css';
+3
View File
@@ -0,0 +1,3 @@
import '/client/components/lib/basicTabs.jade';
import '/client/components/lib/basicTabs.js';
+11
View File
@@ -0,0 +1,11 @@
import '/client/components/lists/list.jade';
import '/client/components/lists/listBody.jade';
import '/client/components/lists/listHeader.jade';
import '/client/components/lists/minilist.jade';
import '/client/components/lists/list.js';
import '/client/components/lists/listBody.js';
import '/client/components/lists/listHeader.js';
import '/client/components/lists/list.css';
+56
View File
@@ -0,0 +1,56 @@
import '/client/components/main/accessibility.jade';
import '/client/components/main/bookmarks.jade';
import '/client/components/main/brokenCards.jade';
import '/client/components/main/dueCards.jade';
import '/client/components/main/editor.jade';
import '/client/components/main/globalSearch.jade';
import '/client/components/main/header.jade';
import '/client/components/main/keyboardShortcuts.jade';
import '/client/components/main/layouts.jade';
import '/client/components/main/myCards.jade';
import '/client/components/main/popup.tpl.jade';
import '/client/components/main/spinner.jade';
import '/client/components/main/spinner_bounce.jade';
import '/client/components/main/spinner_cube.jade';
import '/client/components/main/spinner_cube_grid.jade';
import '/client/components/main/spinner_dot.jade';
import '/client/components/main/spinner_double_bounce.jade';
import '/client/components/main/spinner_rotateplane.jade';
import '/client/components/main/spinner_scaleout.jade';
import '/client/components/main/spinner_wave.jade';
import '/client/components/main/support.jade';
import '/client/lib/keyboard';
import '/client/lib/popup';
import '/client/components/main/accessibility.js';
import '/client/components/main/bookmarks.js';
import '/client/components/main/brokenCards.js';
import '/client/components/main/dueCards.js';
import '/client/components/main/editor.js';
import '/client/components/main/globalSearch.js';
import '/client/components/main/header.js';
import '/client/components/main/layouts.js';
import '/client/components/main/myCards.js';
import '/client/components/main/popup.js';
import '/client/components/main/spinner.js';
import '/client/components/main/support.js';
import '/client/components/main/accessibility.css';
import '/client/components/main/brokenCards.css';
import '/client/components/main/dueCards.css';
import '/client/components/main/editor.css';
import '/client/components/main/fonts.css';
import '/client/components/main/globalSearch.css';
import '/client/components/main/header.css';
import '/client/components/main/keyboardShortcuts.css';
import '/client/components/main/layouts.css';
import '/client/components/main/myCards.css';
import '/client/components/main/popup.css';
import '/client/components/main/spinner_bounce.css';
import '/client/components/main/spinner_cube.css';
import '/client/components/main/spinner_cube_grid.css';
import '/client/components/main/spinner_dot.css';
import '/client/components/main/spinner_double_bounce.css';
import '/client/components/main/spinner_rotateplane.css';
import '/client/components/main/spinner_scaleout.css';
import '/client/components/main/spinner_wave.css';
+13
View File
@@ -0,0 +1,13 @@
import '/client/components/notifications/notification.jade';
import '/client/components/notifications/notificationIcon.jade';
import '/client/components/notifications/notifications.jade';
import '/client/components/notifications/notificationsDrawer.jade';
import '/client/components/notifications/notification.js';
import '/client/components/notifications/notifications.js';
import '/client/components/notifications/notificationsDrawer.js';
import '/client/components/notifications/notification.css';
import '/client/components/notifications/notifications.css';
import '/client/components/notifications/notificationsDrawer.css';
Loaded 100 of 909 files, more files were not shown because too many files have changed in this diff. Show more