Compare commits

...
1038 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
Lauri Ojansivu 405cf3efa3 v8.41 2026-03-23 18:34:52 +02:00
Lauri Ojansivu 462b3801ee Updated translations. 2026-03-23 18:31:51 +02:00
Lauri Ojansivu 7ec42880f0 Updated ChangeLog. 2026-03-23 18:29:17 +02:00
Lauri Ojansivu 3d6797a305 Fixed Board lost its member list or permissions.
Thanks to marylinereumont, AymenHassini19, muabila and xet7 !

Fixes #6198
2026-03-23 18:25:38 +02:00
Lauri Ojansivu 065fe35b6a Updated translations. 2026-03-23 11:39:51 +02:00
Lauri Ojansivu 4a4e4135ab Updated ChangeLog. 2026-03-23 11:28:22 +02:00
Lauri Ojansivu e388d18aad Merge pull request #6199 from KhaoulaMaleh/fix/cards
Fix: prevent cards from disappearing after dragging in List view
2026-03-23 11:25:00 +02:00
KhaoulaMaleh 60d9698343 Fix: prevent cards from disappearing after dragging in List view 2026-03-23 03:44:28 -11:00
Lauri Ojansivu a7c9f7aa17 Updated ChangeLog. 2026-03-22 22:17:51 +02:00
Lauri Ojansivu 7de1b04ebc Export Card to Excel. Part 2.
Thanks to xet7 !

Fixes #5324
2026-03-22 22:15:33 +02:00
Lauri Ojansivu 024ec23b7f Updated ChangeLog. 2026-03-22 19:47:08 +02:00
Lauri Ojansivu 7b2128b356 Fixed 2nd new card should not have text of 1st card, it should be empty.
Thanks to xet7 !
2026-03-22 19:41:44 +02:00
Lauri Ojansivu 6d54b826e8 Merge pull request #6197 from harryadel/use-email-sendAsync
Replace Email.send with Email.sendAsync
2026-03-22 19:12:47 +02:00
Lauri Ojansivu 30c4010512 Merge pull request #6196 from harryadel/remove-meteorhacks-aggregate
Remove unused meteorhacks:aggregate for Meteor 3.0 migration
2026-03-22 19:11:40 +02:00
Lauri Ojansivu c4c8446fc2 Merge pull request #6195 from harryadel/migrate-communitypackages
Replace useraccounts packages with communitypackages alternatives
2026-03-22 19:10:55 +02:00
Lauri Ojansivu c374c87c08 Updated translations 2026-03-22 19:09:21 +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 738655d380 Use Email.sendAsync in SMTP test and update docs
Replace Email.send with await Email.sendAsync in the SMTP test email
method and update the documentation example to match.
2026-03-22 03:21:11 +02:00
Harry Adel 3f0eb227ec Await email calls in notifications and rules
Add missing await to EmailLocalization.sendEmail() calls and replace
fallback Email.send with Email.sendAsync in the notification email
handler and rules helper. The notification handler runs inside an
async setTimeout callback where the missing await meant email errors
were unhandled promise rejections instead of being caught.
2026-03-22 03:20:51 +02:00
Harry Adel 409a624e33 Await email calls in invitation code
Add missing await to EmailLocalization.sendEmail() calls and replace
fallback Email.send with Email.sendAsync in board invitation and
registration invitation flows. Without await, promise rejections from
failed email sends were silently swallowed instead of being caught
by the surrounding try/catch blocks.
2026-03-22 03:20:26 +02:00
Harry Adel 1b02184482 Use Email.sendAsync in email localization wrapper
Email.sendAsync is available since Meteor 2.16 (email@2.2.5).
The synchronous Email.send is just a wrapper around it via
Promise.await. Switch to the async version directly since
sendEmail() is already an async function.
2026-03-22 03:19:54 +02:00
Harry AdelandClaude Opus 4.6 f88e6671a2 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.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 03:02:09 +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
Harry Adel e92578d41d Remove dead useraccounts-core reference from stacksmith build
The sed command targeting packages/meteor-useraccounts-core/package.js
referenced a local package directory that no longer exists.
2026-03-22 02:43:16 +02:00
Harry Adel 0644aeb70e Replace useraccounts packages with communitypackages alternatives
Migrate from the unmaintained useraccounts:* packages to the
community-maintained communitypackages:* drop-in replacements:

- useraccounts:core -> communitypackages:core@2.0.3
- useraccounts:unstyled -> communitypackages:unstyled@2.0.1
- useraccounts:flow-routing-extra -> communitypackages:flow-routing@2.1.0

Also clean up .meteor/packages:
- Remove unused coffeescript and spacebars packages
- Unpin ecmascript and standard-minifier-js versions
2026-03-22 02:43:10 +02:00
Lauri Ojansivu 456bda1169 v8.40 2026-03-21 21:28:34 +02:00
Lauri Ojansivu 4b83a3f861 Updated translations 2026-03-21 21:23:49 +02:00
Lauri Ojansivu 7e18526ed1 Updated translations 2026-03-21 21:14:28 +02:00
Lauri Ojansivu ffef908c8d Updated ChangeLog. 2026-03-21 21:02:03 +02:00
Lauri Ojansivu be5bdbe314 Export Card to Excel and related fixes. Part 1.
Thanks to xet7 !

Fixes #5324
2026-03-21 20:57:07 +02:00
Lauri Ojansivu 6d0f869309 v8.39 2026-03-21 15:35:20 +02:00
Lauri Ojansivu bfd0f9d0e4 Updated translations 2026-03-21 15:32:05 +02:00
Lauri Ojansivu ee9b5a0d25 Updated ChangeLog. 2026-03-21 15:25:48 +02:00
Lauri Ojansivu 445746fd7f Updated ChangeLog. 2026-03-21 15:23:28 +02:00
Lauri Ojansivu ac33d8012b Delete workspace. Part 2.
Thanks to xet7 !

Fixes #6157
2026-03-21 15:18:02 +02:00
Lauri Ojansivu 8513b2e456 Delete workspace etc. In Progress.
Thanks to xet7 !

Related #6157
2026-03-21 11:32:56 +02:00
Lauri Ojansivu 26d393f8ee Fix open lock from all button position at Admin Panel/People/People.
Thanks to xet7 !
2026-03-21 10:33:06 +02:00
Lauri Ojansivu bee322cb2e v8.38 2026-03-21 02:28:01 +02:00
Lauri Ojansivu bf209bfdd7 Updated ChangeLog. 2026-03-21 00:12:24 +02:00
Lauri Ojansivu 0e410b6d7a Fix List of users is empty, some translations not visible, count of orgs/teams/people, theme of some buttons, New org/team to be leftmost column.
Thanks to xet7 !

Fixes #6194
2026-03-21 00:11:08 +02:00
Lauri Ojansivu 2e4e3b1a06 Updated ChangeLog. 2026-03-20 18:15:56 +02:00
Lauri Ojansivu 7ed26f437d Updated CONTRIBUTING.md 2026-03-20 18:07:33 +02:00
Lauri Ojansivu e50b9f211d Updated CONTRIBUTING.md 2026-03-20 18:05:05 +02:00
Lauri Ojansivu e705df6634 Updated CONTRIBUTING.md 2026-03-20 18:01:38 +02:00
Lauri Ojansivu e669a9fe64 Updated CONTRIBUTING.md 2026-03-20 17:57:41 +02:00
Lauri Ojansivu 6249af041c Updated CONTRIBUTING.md 2026-03-20 17:53:23 +02:00
Lauri Ojansivu a4a4fc1cf2 Merge branch 'main' of github.com:wekan/wekan 2026-03-20 17:51:45 +02:00
Lauri Ojansivu 6acc7c95ff Updated CONTRIBUTING.md and SECURITY.md 2026-03-20 17:50:38 +02:00
Lauri Ojansivu 3d75e905d1 v8.37 2026-03-20 15:03:36 +02:00
Lauri Ojansivu 55bc0ed411 Updated ChangeLog. 2026-03-20 15:00:07 +02:00
Lauri Ojansivu 37142f3bb9 Fix card save does not work.
Thanks to comunidadplayasdetijuana, Janfy and xet7 !

Fixes #6192
2026-03-20 14:56:59 +02:00
Lauri Ojansivu c11aa2a194 Updated translations. 2026-03-20 07:47:40 +02:00
Lauri Ojansivu 9da7adad95 v8.36 2026-03-20 00:40:50 +02:00
Lauri Ojansivu 69277d4b24 Updates 2026-03-20 00:36:05 +02:00
Lauri Ojansivu 2e157501c3 Updated ChangeLog. 2026-03-20 00:12:40 +02:00
Lauri Ojansivu 610849ab0b Add remaining features of Copy and Move of Swimlanes, Lists and Cards.
Thanks to xet7 !
2026-03-20 00:10:13 +02:00
Lauri Ojansivu b966607070 Updated ChangeLog. 2026-03-19 22:51:46 +02:00
Lauri Ojansivu d0c44bded8 Fix Copy Card and Move Card.
Thanks to BlobbyBob and xet7 !

Fixes #6169
2026-03-19 22:49:22 +02:00
Lauri Ojansivu c79f5be07a Updated translations. 2026-03-19 22:48:10 +02:00
Lauri Ojansivu baab46a3fb Updated ChangeLog. 2026-03-19 22:08:18 +02:00
Lauri Ojansivu 92beaa3137 Fix RebindBleed of IntegrationBleed.
Thanks to Rodolphe GHIO and xet7 !
2026-03-19 22:07:07 +02:00
Lauri Ojansivu f03d0ddf30 Updated ChangeLog. 2026-03-19 21:32:35 +02:00
Lauri Ojansivu 4dea702ff2 Fixed adding label and select color causes color not checkmarked and wrong color choosed.
Thanks to xet7 !
2026-03-19 21:29:09 +02:00
Lauri Ojansivu 7e55b83a7b Updated translations. 2026-03-19 21:22:54 +02:00
Lauri Ojansivu 259f3776d2 Updated ChangeLog. 2026-03-19 21:18:41 +02:00
Lauri Ojansivu 2f07eb7097 Fixed add remove checklist and checklist item.
Thanks to xet7 !
2026-03-19 21:17:14 +02:00
Lauri Ojansivu 6d6247d449 Updated ChangeLog. 2026-03-19 20:24:02 +02:00
Lauri Ojansivu c3f66cce55 Updated flatted.
Thanks to developers of flatted !
2026-03-19 20:22:51 +02:00
Lauri Ojansivu 8ee178dd89 Updated ChangeLog. 2026-03-19 20:16:05 +02:00
Lauri Ojansivu d01d7fbcc9 Merge pull request #6191 from harryadel/remove-raix-handlebar-helpers
Remove raix:handlebar-helpers, replace with native Blaze helpers
2026-03-19 20:14:57 +02:00
Harry Adel 252b95f661 Remove raix:handlebar-helpers, replace with native Blaze helpers
Replace the raix:handlebar-helpers package with inline Blaze helper
registrations for operator helpers ($eq, $neq, $gt, $lt, $gte, $lte,
$and, $or, $not, $in, $nin, $). This removes an external dependency
while preserving the same template helper API.

Also removes the unused ui@1.0.13 package pin.
2026-03-19 18:08:02 +02:00
Lauri Ojansivu 89f262feae Updated translations 2026-03-19 17:19:23 +02:00
Lauri Ojansivu 7167f53270 Updated ChangeLog. 2026-03-19 17:16:52 +02:00
Lauri Ojansivu c989c21d4f Fixed view, restore and delete of Archived Cards, Lists and Swimlanes. If delete list, it does not delete card of that list that was archived before archive list.
Thanks to xet7 !
2026-03-19 17:12:44 +02:00
Lauri Ojansivu 5d83d4c239 Updated translations. 2026-03-19 09:52:35 +02:00
Lauri Ojansivu 94089f9505 Updated ChangeLog. 2026-03-19 09:42:59 +02:00
Lauri Ojansivu 872dd578c8 Fixed Add after list to add after selected list. Added Confirm to Archive List and Archive Swimlane.
Thanks to xet7 !
2026-03-19 09:40:47 +02:00
Lauri Ojansivu fba00535fa Updated ChangeLog. 2026-03-19 08:10:15 +02:00
Lauri Ojansivu ccd23f6cde Fixed crash at sign-up page, when registering new user, happened after some PR.
Thanks to xet7 !
2026-03-19 08:07:42 +02:00
Lauri Ojansivu 7a1db4e040 Updated ChangeLog. 2026-03-19 07:56:48 +02:00
Lauri Ojansivu 7d0a422714 Removed not needed input fields from GitHub Bug report issue template.
Thanks to xet7 !
2026-03-19 07:55:01 +02:00
Lauri Ojansivu e411a4fd8e Updated ChangeLog. 2026-03-19 07:49:19 +02:00
Lauri Ojansivu 77b57d8233 Merge pull request #6190 from harryadel/remove-underscore
Replace underscore with native JavaScript utilities
2026-03-19 07:48:09 +02:00
Lauri Ojansivu d14005a183 Updated ChangeLog. 2026-03-19 07:47:44 +02:00
Lauri Ojansivu 0c29fa3cbe Fixed crash when IFTTT Rules archived card, but cards etc are not visible at archive yet. Fixed "Add After List" to show lists only from current Swimlane.
Thanks to xet7 !
2026-03-19 07:44:34 +02:00
Harry Adel 60bc1d55f4 Remove underscore from .meteor/packages
All application code now uses native JavaScript equivalents.
underscore remains as a transitive dependency of
raix:handlebar-helpers in .meteor/versions.
2026-03-18 22:39:34 +02:00
Harry Adel bcadc517c3 Remove underscore dependency from wekan-ldap package
- Remove import _ from 'underscore'
- _.reduce -> .reduce()
- _.map on objects -> Object.entries().forEach() (argument order swap)
- _.isEmpty -> manual null/length check
- _.isObject -> typeof check
- _.find -> .includes()
- _.last -> .at(-1)
- _.size -> Object.keys().length
- _.debounce -> inline debounce function
- Remove api.use('underscore') from package.js
2026-03-18 22:39:29 +02:00
Harry Adel eb4cb341c6 Remove underscore dependency from wekan-oidc package
- _.pick -> Object.fromEntries/Object.entries filter
- _.extend -> Object.assign (including on Error objects)
- _.pluck -> .map()
- Remove api.use('underscore') from package.js
2026-03-18 22:39:20 +02:00
Harry Adel 331bc2d1b9 Replace underscore with native JS in server and shared files
- server/publications: _.findWhere -> findWhere() helper
- server/notifications: _.clone -> spread operator
- server/rulesHelper: _.min/_.max -> Math.min/Math.max
- server/lib/utils: _.some/_.contains -> .some()/.includes()
- imports/reactiveCache: _.groupBy/_.indexBy -> helpers
- config/router: _.each on object -> Object.entries().forEach()
- sandstorm: _.pluck/_.values -> .map()/Object.values()
2026-03-18 22:39:14 +02:00
Harry Adel b5782c7b86 Replace underscore with native JS in models
Largest changes in cards.js (~76 calls), boards.js (~38 calls),
and activities.js (~31 calls). Key patterns:

- _.contains -> .includes() with null guards where needed
- _.findWhere/_.where -> findWhere()/where() helpers
- _.pluck -> .map()
- _.union -> [...new Set()]
- _.extend -> Object.assign()
- _.each on objects -> Object.entries().forEach() (argument order swap)
- _.debounce -> debounce() helper
- _.groupBy/_.indexBy -> groupBy()/indexBy() helpers
- _.clone -> spread operator
- _.sortBy -> [...arr].sort()
- Type checks -> native typeof
2026-03-18 22:39:06 +02:00
Harry Adel 01bb5a930d Replace underscore with native JS in client components
- _.contains -> .includes()
- _.filter -> .filter()
- _.pluck -> .map()
- _.findWhere -> findWhere() helper
- _.union -> [...new Set()]
- _.every/.some -> .every()/.some()
- _.once -> once() helper
- _.uniq with property -> uniqBy() helper
- _.sortBy -> [...arr].sort()
- _.chain().groupBy() -> groupBy() helper
- _.each -> .forEach()
- _.isNumber -> typeof x === 'number'
2026-03-18 22:38:56 +02:00
Harry Adel 1bb657b073 Replace underscore with native JS in client utility files
- _.has -> Object.hasOwn
- _.isString -> typeof x === 'string'
- _.isUndefined -> x === undefined
- _.sortBy -> [...arr].sort()
- _.any -> .some()
- _.range -> Array.from()
- _.times -> for loop
2026-03-18 22:38:44 +02:00
Harry Adel 740f39e17c Add native JS collection helpers to replace underscore utilities
Add findWhere, where, uniqBy, groupBy, indexBy, debounce, and once
helper functions in imports/lib/collectionHelpers.js. These provide
null-safe replacements for underscore methods that have no clean
native one-liner equivalent. Expose them on the Utils global for
client-side usage.
2026-03-18 22:38:37 +02:00
Lauri Ojansivu e34e4b8404 Updated ChangeLog. 2026-03-18 22:07:29 +02:00
Lauri Ojansivu e6fbfa87f0 Fix more Rules buttons styling.
Thanks to xet7 !
2026-03-18 22:06:25 +02:00
Lauri Ojansivu a31de9e1a0 Updated ChangeLog. 2026-03-18 22:02:13 +02:00
Lauri Ojansivu a3ac219503 Changed colors of Add Rule and Back buttons from black to theme color.
Thanks to xet7 !
2026-03-18 22:00:10 +02:00
Lauri Ojansivu 5af4a5395b Updated ChangeLog. 2026-03-18 21:42:29 +02:00
Lauri Ojansivu d2168aca3d Removed empty space at top of list header.
Thanks to xet7 !
2026-03-18 21:41:08 +02:00
Lauri Ojansivu 6148d96b9a Updated ChangeLog. 2026-03-18 21:20:41 +02:00
Lauri Ojansivu b6a6180dc3 Fix can not close opened card.
Thanks to xet7 !
2026-03-18 21:18:58 +02:00
Lauri Ojansivu 6edd3c77ec Updated ChangeLog. 2026-03-18 20:57:38 +02:00
Lauri Ojansivu 36741d7ae5 Fix Error reading default file .well-known/assetlinks.json.default.
Thanks to gre3x and xet7 !

Fixes #6189
2026-03-18 20:55:44 +02:00
Lauri Ojansivu 507b196101 Updated ChangeLog. 2026-03-18 20:24:15 +02:00
Lauri Ojansivu 6fc1cb9127 Fix List drag handle missing.
Thanks to FK-PATZ3 and xet7 !

Fixes #6185
2026-03-18 20:22:25 +02:00
Lauri Ojansivu b3ae3871f2 Updated ChangeLog. 2026-03-18 18:38:00 +02:00
Lauri Ojansivu 1f6b5ed0e3 Fix version not visible.
Thanks to FK-PATZ3 and xet7 !

Related #6185
2026-03-18 18:36:12 +02:00
Lauri Ojansivu ab5fe6411a Updated ChangeLog. 2026-03-18 18:07:10 +02:00
Lauri Ojansivu 5dd3209ee8 Updated ChangeLog. 2026-03-18 18:04:50 +02:00
Lauri Ojansivu fccefcaa6c Updated to MongoDB 7.0.31 at Snap Candidate.
Thanks to MongoDB developers !
2026-03-18 18:03:11 +02:00
Lauri Ojansivu 98249dda8e Updated translations. 2026-03-18 18:02:47 +02:00
Lauri Ojansivu 26732d4f6e Updated ChangeLog. 2026-03-16 03:07:22 +02:00
Lauri Ojansivu 9a6319772e Merge pull request #6187 from KhaoulaMaleh/bugfix/6165-card-search-results
Fix #6165: Improve global search relevance and add exact-field operators
2026-03-16 02:58:27 +02:00
Lauri Ojansivu 2547967113 Merge pull request #6188 from harryadel/fix/list-add-card-and-sort-persistence
Fix list add-card actions and sort persistence
2026-03-16 02:56:04 +02:00
Harry Adel f436525f20 Fix list add-card actions and sort persistence 2026-03-16 01:49:53 +02:00
KhaoulaMaleh 7b3f7b8e6b fix search relevance 2026-03-15 04:56:25 -11:00
Lauri Ojansivu a3dece58fb Updated ChangeLog. 2026-03-14 18:58:41 +02:00
Lauri Ojansivu a159856628 Updated flatted.
Thanks to developers of flatted !
2026-03-14 18:57:43 +02:00
Lauri Ojansivu 7fb6e2a5d9 Added docs about If you get "Board not Found" after archieving Template Board.
Thanks to marcuskki and xet7 !

Related #4632
2026-03-13 20:53:17 +02:00
Lauri Ojansivu 0cdae9def1 Updated ChangeLog. 2026-03-13 20:52:55 +02:00
Lauri Ojansivu 63df6cb197 Updated dependencies.
Thanks to developers of dependencies !
2026-03-13 20:51:44 +02:00
Lauri Ojansivu 55d2d0843a Updated ChangeLog. 2026-03-12 04:49:38 +02:00
Lauri Ojansivu 7e378be1d8 Fix Add List popup to not open after adding new board or there being no lists at all.
Thanks to xet7 !
2026-03-12 04:39:51 +02:00
Lauri Ojansivu 89f86caf69 Replaced incompatible file-type with mime-type.
Thanks to xet7 !
2026-03-12 04:29:49 +02:00
Lauri Ojansivu 9604598a79 Updates 2026-03-12 04:12:21 +02:00
Lauri Ojansivu 90d9805abf Updated ChangeLog. 2026-03-12 04:12:21 +02:00
Lauri Ojansivu 0deb7ab641 Updated file-type.
Thanks to developers of file-type.
2026-03-12 04:12:21 +02:00
Lauri Ojansivu 230f954571 Updated ChangeLog. 2026-03-12 04:12:21 +02:00
dependabot[bot] e3f739e5d9 Bump docker/login-action from 3.7.0 to 4.0.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.7.0 to 4.0.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/c94ce9fb468520275223c153574b00df6fe4bcc9...b45d80f862d83dbcd57f89517bcf500b2ab88fb2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-12 04:12:21 +02:00
dependabot[bot] 6b75bfcb0c Bump docker/metadata-action from 5.10.0 to 6.0.0
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5.10.0 to 6.0.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/c299e40c65443455700f0fdfc63efafe5b349051...030e881283bb7a6894de51c315a6bfe6a94e05cf)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-12 04:12:21 +02:00
dependabot[bot] db47ed5430 Bump docker/build-push-action from 6.19.2 to 7.0.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.19.2 to 7.0.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/10e90e3645eae34f1e60eeb005ba3a3d33f178e8...d08e5c354a6adb9ed34480a06d141179aa583294)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-12 04:12:21 +02:00
KhaoulaMaleh 48361b2047 fixed linked card swimlane routing 2026-03-12 04:12:21 +02:00
Harry Adel 6953cc0670 Update PDF export models
Clean up exportPDF and ExporterCardPDF server-side code to align
with the BlazeComponent removal changes.
2026-03-08 11:13:17 +02:00
Harry Adel 477e1c89e5 Migrate rules, activities, and remaining components to Template
Convert all remaining BlazeComponent-based components to native Meteor
Template pattern: activities, comments, all rules actions/triggers,
swimlanes, users, gantt, import, and main utility components.
2026-03-08 11:13:17 +02:00
Harry Adel bae23f9ed8 Migrate sidebar and settings components from BlazeComponent to Template
Convert sidebar, sidebarArchives, sidebarCustomFields, sidebarFilters,
sidebarSearches, and all settings panel components to use native Meteor
Template.onCreated/helpers/events pattern.
2026-03-08 11:04:53 +02:00
Harry Adel d3625db755 Migrate card components from BlazeComponent to Template
Convert cardDetails, cardCustomFields, cardDate, cardTime,
cardDescription, attachments, checklists, labels, minicard,
resultCard, and subtasks to use native Meteor Template pattern.
2026-03-08 11:04:53 +02:00
Harry Adel d9e2e8f97e Migrate board and list components from BlazeComponent to Template
Convert boardBody, boardHeader, boardsList, boardArchive,
originalPositionsView, list, listBody, and listHeader to use
native Meteor Template.onCreated/helpers/events pattern.
2026-03-08 11:04:53 +02:00
Harry Adel f1625ad1f5 Migrate client library code from BlazeComponent to Template pattern
Convert popup, inlinedform, multiSelection, spinner, cardSearch,
datepicker, and dialog helper libraries to use native Meteor
Template.onCreated/helpers/events instead of BlazeComponent.
Update reactiveCache to remove BlazeComponent dependency.
2026-03-08 11:04:53 +02:00
Harry Adel 012947c076 Convert HTML templates to Jade and update template markup
Replace Spacebars .html templates with Jade equivalents for
originalPositionsView and originalPosition. Add new Jade template
for inlinedCardDescription. Update existing Jade templates to
reflect the BlazeComponent-to-Template migration changes.
2026-03-08 11:04:53 +02:00
Harry Adel 3b6f6fa80a Replace BlazeComponent mixins with standalone utility modules
Delete the old Mixins global and BlazeComponent-based InfiniteScrolling
mixin. Add standalone replacements:
- client/lib/infiniteScrolling.js: plain scroll-based pagination helper
- client/lib/currentCard.js: centralized current card resolution
- client/components/forms/datepicker.js: extracted date picker logic
2026-03-08 11:04:53 +02:00
Harry Adel 2848b1a38a Remove peerlibrary:blaze-components package and clean up config
Remove the blaze-components Meteor package and its peer dependencies
from .meteor/packages and .meteor/versions. Clean up related ESLint
globals (BlazeComponent, Mixins, InlinedForm, ReactiveTabs).
2026-03-08 11:04:53 +02:00
Lauri Ojansivu 53a0beffee Updates 2026-03-06 17:21:52 +02:00
Lauri Ojansivu 4cb99c6014 Updates 2026-03-06 17:16:50 +02:00
Lauri Ojansivu e485168169 Updates 2026-03-06 17:03:44 +02:00
Lauri Ojansivu 44a083e095 Updates 2026-03-06 17:00:40 +02:00
Lauri Ojansivu 1bd3363708 Updated ChangeLog. 2026-03-06 01:04:05 +02:00
Lauri Ojansivu ca87e5c3cf v8.35 2026-03-05 22:35:43 +02:00
Lauri Ojansivu 2cd702f48d Fix IntegrationBleed.
Thanks to Rodolphe GHIO and xet7 !
2026-03-05 21:26:22 +02:00
Lauri Ojansivu 97b0c392be Updated ChangeLog. 2026-03-05 17:30:35 +02:00
Lauri Ojansivu 274f1309c3 Fixed unable to delete Avatar, with Meteor 3 compatible avatar and attachments fixes.
Thanks to inDane and xet7 !

Fixes #6160
2026-03-05 17:27:33 +02:00
Lauri Ojansivu 326f9bed84 Updated ChangeLog. 2026-03-05 14:54:52 +02:00
Lauri Ojansivu 9f79a8b6ed Updated dompurify.
Thanks to xet7 !
2026-03-05 14:53:45 +02:00
Lauri Ojansivu 98eda7f49f Updated ChangeLog. 2026-03-05 14:47:17 +02:00
Lauri Ojansivu 5fe0b737c2 Updated translations 2026-03-05 14:40:25 +02:00
Lauri Ojansivu c29e92de1c Merge pull request #6175 from harryadel/remove-templates-tabs-package
Replace templates:tabs package with inline Blaze implementation
2026-03-05 14:38:19 +02:00
Lauri Ojansivu e9507d37cf Merge pull request #6174 from harryadel/replace-mongo-counter-package
Replace konecty:mongo-counter with inline implementation
2026-03-05 14:37:30 +02:00
Lauri Ojansivu 3454ab0278 Merge pull request #6173 from harryadel/fix-filescollection-findOneAsync
Fix FilesCollection findOneAsync errors for Avatars and Attachments
2026-03-05 14:37:02 +02:00
Lauri Ojansivu cfc9abea99 Merge pull request #6172 from wekan/dependabot/github_actions/actions/upload-artifact-7
Bump actions/upload-artifact from 6 to 7
2026-03-05 14:36:38 +02:00
Lauri Ojansivu 8e68bb6343 Merge pull request #6171 from wekan/dependabot/github_actions/meteorengineer/setup-meteor-3
Bump meteorengineer/setup-meteor from 2 to 3
2026-03-05 14:36:26 +02:00
Lauri Ojansivu 69ab456ed2 Merge pull request #6170 from wekan/dependabot/github_actions/actions/download-artifact-8
Bump actions/download-artifact from 7 to 8
2026-03-05 14:36:08 +02:00
Harry Adel 441b2b7c0d Merge branch 'main' into remove-templates-tabs-package 2026-03-05 05:32:28 +02:00
Harry Adel 81d3a08b9b Replace templates:tabs Atmosphere package with inline Blaze implementation
Drop the templates:tabs dependency as part of Meteor 3.0 migration prep.
The package is replaced by a lightweight inline basicTabs/tabContent
template pair with identical API, so no consumer template changes needed.
2026-03-05 05:31:35 +02:00
Harry Adel 3ebeac6b30 Replace konecty:mongo-counter with inline implementation
Remove the konecty:mongo-counter Meteor package dependency and replace
it with a minimal inline incrementCounter function using raw MongoDB
findOneAndUpdate. Also exports an async version for future Meteor 3.0
migration. No data migration needed — uses the same next_val field.
2026-03-05 05:16:40 +02:00
Harry Adel fa2f36b76b Merge branch 'main' into fix-filescollection-findOneAsync 2026-03-05 05:02:29 +02:00
Harry Adel 8b1e1dd92a Fix FilesCollection findOneAsync errors for Avatars and Attachments
FilesCollection from ostrio:files does not have findOneAsync. Use
the sync findOne method instead, matching the client-side cache.
2026-03-05 05:00:18 +02:00
dependabot[bot] 31614e58f2 Bump actions/upload-artifact from 6 to 7
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v6...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-03-02 22:16:39 +00:00
dependabot[bot] 9ff15562d9 Bump meteorengineer/setup-meteor from 2 to 3
Bumps [meteorengineer/setup-meteor](https://github.com/meteorengineer/setup-meteor) from 2 to 3.
- [Release notes](https://github.com/meteorengineer/setup-meteor/releases)
- [Commits](https://github.com/meteorengineer/setup-meteor/compare/v2...v3)

---
updated-dependencies:
- dependency-name: meteorengineer/setup-meteor
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-02 22:16:34 +00:00
dependabot[bot] 8c4f432e0c Bump actions/download-artifact from 7 to 8
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 7 to 8.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v7...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-03-02 22:16:30 +00:00
Lauri Ojansivu d34a886cac Back to package-lock.json format 1.
Thanks to xet7 !
2026-03-02 19:50:03 +02:00
Lauri Ojansivu 3ee4d1cace Merge pull request #6167 from wekan/dependabot/npm_and_yarn/minimatch-3.1.5
Bump minimatch from 3.1.3 to 3.1.5
2026-03-02 12:01:44 +02:00
Lauri Ojansivu ff7729fc35 Updated ChangeLog. 2026-03-01 00:12:15 +02:00
Lauri Ojansivu 7127862bea Optimized board loading.
Thanks to xet7 !

Fixes #6146
2026-03-01 00:10:09 +02:00
Lauri Ojansivu 4a8037047a Updated ChangeLog. 2026-02-28 23:50:27 +02:00
Lauri Ojansivu 9b3ecd795f Commented out Admin Panel/Settings/Migrations related menu option and code to speed up WeKan.
Thanks to xet7 !

Fixes #6146
2026-02-28 23:46:09 +02:00
dependabot[bot] cc06f515ce Bump minimatch from 3.1.3 to 3.1.5
Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.1.3 to 3.1.5.
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.1.3...v3.1.5)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-version: 3.1.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-28 21:31:56 +00:00
Lauri Ojansivu d57c5f736f Updated dependencies.
Thanks to developers of dependencies !
2026-02-28 23:30:33 +02:00
Lauri Ojansivu cf8c82b3fb Updated translations 2026-02-28 17:17:28 +02:00
Lauri Ojansivu f73bcfd134 Updated translations. 2026-02-25 12:45:03 +02:00
Lauri Ojansivu 3e400820e5 Merge pull request #6162 from harryadel/chore/fullcalendar-npm-moment-migration
fullcalendar npm moment migration
2026-02-25 12:34:44 +02:00
Harry Adel b6f9eac37e Add missing calendar translation keys for today day week and month 2026-02-24 23:44:04 +02:00
Harry Adel 7b443b7bfc Refine calendar toolbar labels and style create-card modal controls consistently 2026-02-24 23:43:58 +02:00
Harry Adel 8dafc774d8 Use template-bound autorun in wekan-fullcalendar to prevent Blaze current view errors 2026-02-24 23:43:53 +02:00
Harry Adel 2fe490ec3d Record FullCalendar post-Meteor-3.0 upgrade guidance in migration guide 2026-02-24 23:14:27 +02:00
Harry Adel d25da91e0b Document FullCalendar v5 wrapper usage and non-jQuery refetch flow 2026-02-24 23:07:07 +02:00
Harry Adel 1b74fd8d4e Migrate wekan-fullcalendar to npm FullCalendar v5 and remove Meteor moment package 2026-02-24 23:07:05 +02:00
Lauri Ojansivu d796c4d9bb Updates 2026-02-23 13:42:38 +02:00
Lauri Ojansivu 8787ce84ac Updates 2026-02-22 14:14:21 +02:00
Lauri Ojansivu 33e0c08adc v8.34 2026-02-20 00:35:29 +02:00
Lauri Ojansivu a34c2f35a6 Fix Notifications from not allowed Boards.
Thanks to FK-PATZ3 and xet7 !

Fixes #6103
2026-02-20 00:30:17 +02:00
Lauri Ojansivu 6903e12112 Updated ChangeLog. 2026-02-19 23:50:02 +02:00
Lauri Ojansivu 62216e36c1 Fix GHSL-2026-045_Wekan.
Thanks to GHSL and xet7 !
2026-02-19 23:47:56 +02:00
Lauri Ojansivu 73eb98c57a Fix GHSL-2026-044_Wekan.
Thanks to GHSL and xet7.
2026-02-19 23:40:56 +02:00
Lauri Ojansivu 1ee9b2e917 Fix GHSL-2026-037_Wekan.
Thanks to GHSL and xet7.
2026-02-19 23:36:44 +02:00
Lauri Ojansivu 8c00adc6b8 Fix GHSL-2026-036_Wekan.
Thanks to GHSL and xet7 !
2026-02-19 23:31:01 +02:00
Lauri Ojansivu 1c8667eae8 Fix GHSL-2026-035_Wekan.
Thanks to GHSL and xet7 !
2026-02-19 23:25:39 +02:00
Lauri Ojansivu f0deed448a Updated ChangeLog. 2026-02-19 23:19:36 +02:00
Lauri Ojansivu 06d418b12b Fix createWorkspace Meteor method fails with "Expected string, got undefined"
Thanks to TheBoysenBuilds and xet7 !

Fixes #6150
2026-02-19 23:17:53 +02:00
Lauri Ojansivu f6f16608dd Updated ChangeLog. 2026-02-19 23:08:54 +02:00
Lauri Ojansivu 8816c886cf Moved meteor-reactive-cache to npmjs.com @wekanteam/meteor-reactive-cache https://github.com/wekan/meteor-reactive-cache.
Thanks to xet7 !

Related https://github.com/wekan/wekan/pull/6139
2026-02-19 23:05:15 +02:00
Lauri Ojansivu 6192c19fde Updated ChangeLog. 2026-02-19 22:27:06 +02:00
Lauri Ojansivu f463198e40 Updated dependencies.
Thanks to developers of dependencies !
2026-02-19 22:24:45 +02:00
Lauri Ojansivu ed907f8c61 Removed duplicate code.
Thanks to xet7 !
2026-02-19 22:24:25 +02:00
Lauri Ojansivu 55d2a5d15c Updates 2026-02-19 22:13:44 +02:00
Lauri Ojansivu 6168356ad2 Updated ChangeLog. 2026-02-19 22:11:58 +02:00
Lauri Ojansivu 71b1a0381f Merge pull request #6156 from wekan/dependabot/npm_and_yarn/tar-7.5.9
Bump tar from 7.5.7 to 7.5.9
2026-02-19 22:03:19 +02:00
dependabot[bot] 640629f69c Bump tar from 7.5.7 to 7.5.9
Bumps [tar](https://github.com/isaacs/node-tar) from 7.5.7 to 7.5.9.
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v7.5.7...v7.5.9)

---
updated-dependencies:
- dependency-name: tar
  dependency-version: 7.5.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-19 20:02:55 +00:00
Lauri Ojansivu cde5af62cf Merge pull request #6151 from wekan/dependabot/npm_and_yarn/ajv-8.18.0
Bump ajv from 6.12.6 to 8.18.0
2026-02-19 22:01:37 +02:00
dependabot[bot] d016e66938 Bump ajv from 6.12.6 to 8.18.0
Bumps [ajv](https://github.com/ajv-validator/ajv) from 6.12.6 to 8.18.0.
- [Release notes](https://github.com/ajv-validator/ajv/releases)
- [Commits](https://github.com/ajv-validator/ajv/compare/v6.12.6...v8.18.0)

---
updated-dependencies:
- dependency-name: ajv
  dependency-version: 8.18.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-19 20:01:17 +00:00
Lauri Ojansivu 5be23f61d0 Merge pull request #6155 from KhaoulaMaleh/updates
Fix calendar
2026-02-19 22:01:09 +02:00
Lauri Ojansivu a0c918c646 Merge pull request #6153 from harryadel/fix/cron-migration-async-job
Fix unhandled Promise rejection in cron migration job callback
2026-02-19 21:59:38 +02:00
Lauri Ojansivu 1d910b8a89 Merge pull request #6139 from harryadel/feature/reactive-cache-async-migration
Migrate @wekanteam/meteor-reactive-cache
2026-02-19 21:58:37 +02:00
Lauri Ojansivu c042eeb07e Merge pull request #6149 from wekan/dependabot/github_actions/docker/build-push-action-6.19.2
Bump docker/build-push-action from 6.18.0 to 6.19.2
2026-02-19 21:57:40 +02:00
KhaoulaMaleh afd40b21f1 Merge branch 'updates' of https://github.com/KhaoulaMaleh/wekan into updates 2026-02-19 01:21:56 -11:00
Harry Adel f40a00ef0e Update lock files 2026-02-19 00:34:15 +02:00
Harry Adel e77be37450 Use sync code in allow/deny for 2.x 2026-02-19 00:26:47 +02:00
Harry Adel 15f979f08a Fix unhandled Promise rejection in cron migration job callback
The createCronJob method's job callback was not async and did not await
this.runMigrationStep(step), causing the returned Promise to float.
When runMigrationStep threw, the unhandled rejection triggered
quave:synced-cron's global handler which called process.exit(0),
crashing the app on startup.
2026-02-18 22:41:16 +02:00
Lauri Ojansivu c8beb6018f Updated ChangeLog. 2026-02-18 21:14:56 +02:00
Harry Adel f934aea2a5 Return data on client (sync) and Promise on server (async) naturally, without wrapping in an extra Promise 2026-02-18 18:24:55 +02:00
KhaoulaMaleh 3d69c33caf Fix calendar 2026-02-18 00:36:58 -11:00
Harry Adel 2b15a8ff09 Fix merge problem 2026-02-17 17:46:24 +02:00
Harry Adel 5212f3beb3 Merge branch 'main' into feature/reactive-cache-async-migration 2026-02-17 16:45:06 +02:00
dependabot[bot] 4f45ebef84 Bump docker/build-push-action from 6.18.0 to 6.19.2
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.18.0 to 6.19.2.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/263435318d21b8e681c14492fe198d362a7d2c83...10e90e3645eae34f1e60eeb005ba3a3d33f178e8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-16 20:37:22 +00:00
Lauri Ojansivu 8afdcd7a4c v8.33 2026-02-15 22:14:56 +02:00
Lauri Ojansivu 8ba18a971e Updated translations. 2026-02-15 22:13:54 +02:00
Lauri Ojansivu c47d443dcf Updated ChangeLog. 2026-02-15 21:55:32 +02:00
Lauri Ojansivu b5a13f0206 Admin Panel/Settings/Layout, for PWA: Custom head meta, link, icons, assetlinks.json, site.webmanifest.
Thanks to xet7 !
2026-02-15 21:49:55 +02:00
Lauri Ojansivu dace6b78c0 Fix typos 2026-02-15 18:29:38 +02:00
Lauri Ojansivu 29c5d6fbb3 Updated ChangeLog. 2026-02-15 17:21:43 +02:00
Lauri Ojansivu bebea9efea Updated dependencies.
Thanks to developers of dependencies !
2026-02-15 17:19:05 +02:00
Lauri Ojansivu 5a5eaec340 Merge pull request #6115 from harryadel/wekan-ldap-async-migration
Migrate wekan-ldap to async API for Meteor 3.0
2026-02-14 03:55:59 +02:00
Harry Adel 217cd7bcb6 Use ldapts instead ldapjs
We're using ldapts v4.2.6 because Wekan is on 2.16
2026-02-13 18:45:59 +02:00
Harry Adel 6bf60c496b Merge branch 'main' into wekan-ldap-async-migration 2026-02-13 17:11:16 +02:00
Lauri Ojansivu 68fb0a81e3 v8.32 2026-02-13 08:29:44 +02:00
Lauri Ojansivu 779f014fc5 Updated ChangeLog. 2026-02-13 08:26:21 +02:00
Lauri Ojansivu 4c5da0254a Updated ChangeLog. 2026-02-13 08:23:47 +02:00
Lauri Ojansivu fed2e9dd4e Updated to MongoDB 7.0.30 at Snap Candidate.
Thanks to MongoDB developers !
2026-02-13 08:22:29 +02:00
Lauri Ojansivu 3e8c2e98e7 Updated ChangeLog. 2026-02-13 07:42:18 +02:00
Lauri Ojansivu 0db7ab2eaa Merge pull request #6114 from harryadel/wekan-accounts-cas-async-migration
Migrate wekan-accounts-cas to async API for Meteor 3.0
2026-02-13 07:19:46 +02:00
Lauri Ojansivu 66ccba9267 Merge pull request #6112 from harryadel/wekan-accounts-sandstorm-async-migration
Migrate wekan-accounts-sandstorm to async API for Meteor 3.0
2026-02-13 07:18:57 +02:00
KhaoulaMaleh 0951c37536 fix the hour notation 2026-02-12 02:56:21 -11:00
Khaoula Maleh 1870c256b6 Merge branch 'main' into updates 2026-02-11 15:13:00 -11:00
Harry Adel 117a00c996 Migrate sandstorm.js to async 2026-02-12 03:35:40 +02:00
Harry Adel dd798b4c13 Update versions 2026-02-12 03:35:27 +02:00
Harry Adel b6ceef6775 Missing awaits 2026-02-12 03:29:31 +02:00
Harry Adel 89ce2f825d Merge branch 'main' into wekan-accounts-sandstorm-async-migration 2026-02-12 03:12:21 +02:00
Harry Adel eaf2d3fbb3 Final touches
- Removed underscore dependency and replaced it with a native solution
- Added ecmascript
- Update versions in .meteor
2026-02-12 02:50:29 +02:00
Harry Adel 91eb206ed5 Merge branch 'main' into wekan-accounts-cas-async-migration 2026-02-12 02:36:13 +02:00
Lauri Ojansivu eab8d7e2fc Updates 2026-02-11 21:19:12 +02:00
Lauri Ojansivu 4d65e214c3 Updated translations 2026-02-11 15:41:43 +02:00
Lauri Ojansivu 4d80dab633 Merge pull request #6111 from harryadel/wekan-oidc-async-migration
Migrate wekan-oidc to async API for Meteor 3.0
2026-02-11 15:17:51 +02:00
Lauri Ojansivu 8e1a800c31 Updated translations. 2026-02-10 08:19:28 +02:00
Harry Adel af30a00af2 Add missing imports 2026-02-09 20:16:57 +02:00
Harry Adel b52a337d36 Asyncif createObject and updateObject 2026-02-09 20:16:45 +02:00
Harry Adel 0f4ff6e30e Fix bugs 2026-02-09 19:52:10 +02:00
Lauri Ojansivu ffe7bb6ce5 v8.31 2026-02-08 01:40:14 +02:00
Lauri Ojansivu fc33f10e9b Updated ChangeLog. 2026-02-08 01:37:37 +02:00
Lauri Ojansivu f8aa487e91 Fix Copy Card and Move Card.
Thanks to xet7 !

Fixes #6119
2026-02-08 01:35:31 +02:00
Lauri Ojansivu 77041e0d74 v8.30 2026-02-08 00:56:35 +02:00
Lauri Ojansivu 1fd2468af6 Updated ChangeLog. 2026-02-08 00:51:03 +02:00
Lauri Ojansivu 1b8b8d2eef Reverted New UI Design of WeKan v8.29 and added more fixes and performance improvements.
Thanks to xet7 !
2026-02-08 00:48:39 +02:00
Lauri Ojansivu d152d8fc1b v8.29 2026-02-07 20:52:05 +02:00
Lauri Ojansivu 473f8640d0 Updated ChangeLog. 2026-02-07 20:46:02 +02:00
Lauri Ojansivu 6acdee51b6 Updated translations. 2026-02-07 20:42:56 +02:00
Lauri Ojansivu 537ea369bf Merge pull request #6133 from wekan/copilot/fix-merge-conflicts-pr-6131
Resolve merge conflicts for PR #6131 UI improvements
2026-02-07 18:36:31 +02:00
copilot-swe-agent[bot]andxet7 dba265aaef Fix merge conflict markers and logic bugs found in code review
Co-authored-by: xet7 <15545+xet7@users.noreply.github.com>
2026-02-07 16:33:00 +00:00
copilot-swe-agent[bot]andxet7 97dd5d2064 Resolve merge conflicts by accepting PR #6131 changes
Co-authored-by: xet7 <15545+xet7@users.noreply.github.com>
2026-02-07 16:30:08 +00:00
copilot-swe-agent[bot] dc0b68ee80 Initial plan 2026-02-07 16:26:45 +00:00
KhaoulaMaleh 93ebae78b8 Fix #6118: Raise minimum list width from 100 to 270 in existing configurable system 2026-02-06 22:23:28 -11:00
Lauri Ojansivu 9610d8f883 Updated ChangeLog. 2026-02-07 05:47:13 +02:00
Lauri Ojansivu 631c250f40 Fix move and copy popup duplicate view.
Thanks to mimZD and xet7 !

Fixes #6102
2026-02-07 05:44:34 +02:00
Lauri Ojansivu 5836e50e69 Updated ChangeLog. 2026-02-07 04:32:51 +02:00
Lauri Ojansivu 0a92e896f8 Fix Notifications from not allowed Boards.
Thanks to FK-PATZ3 and xet7 !

Fixes #6103
2026-02-07 04:31:24 +02:00
Lauri Ojansivu d6f9626a10 Updated ChangeLog. 2026-02-07 04:21:08 +02:00
Lauri Ojansivu 0ae9865fcb Added missing linefeeds.
Thanks to xet7 !
2026-02-07 04:19:05 +02:00
Lauri Ojansivu 0987154a7f Removed extra file. 2026-02-07 04:12:10 +02:00
Lauri Ojansivu cb6d487cbd Updated ChangeLog. 2026-02-07 03:54:07 +02:00
Lauri Ojansivu 55710835fe Fix No Add Card + etc.
Thanks to mimZD and xet7 !

Fixes #6108
2026-02-07 03:52:16 +02:00
Lauri Ojansivu c7b46c86c3 Updated ChangeLog. 2026-02-07 01:59:20 +02:00
Lauri Ojansivu 4456bc1360 Fixed extra space at RTL need margin.
Thanks to mimZD and xet7 !

Fixes #6099
2026-02-07 01:57:28 +02:00
Lauri Ojansivu 41c417fda0 Updated ChangeLog. 2026-02-07 01:09:24 +02:00
Lauri Ojansivu 345f9ec223 v8.28 2026-02-05 06:52:44 +02:00
Lauri Ojansivu 8bae45add3 Updated ChangeLog. 2026-02-05 06:31:37 +02:00
Lauri Ojansivu 6c2e2f271d Updated meteor-node-stubs.
Thanks to Meteor developers !
2026-02-05 06:29:48 +02:00
Lauri Ojansivu 90b0c47c41 Updated translations. 2026-02-05 06:02:08 +02:00
Lauri Ojansivu 79e9dadc38 Updated ChangeLog. 2026-02-05 06:00:10 +02:00
Lauri Ojansivu 404dd0735d Merge pull request #6126 from harryadel/fix/issue-6105-copy-move-remaining
Await async setDone before closing popup in copy/move dialogs
2026-02-05 05:45:00 +02:00
Harry Adel 30458c617e Guard against null newCard after cross-board copy
After a cross-board copy, ReactiveCache.getCard(newCardId) can return
null if the publication update hasn't reached the client yet. The card
is already created with a valid sort position server-side, so the
client-side repositioning is safely skippable.
2026-02-05 02:39:39 +02:00
Harry Adel e8b9a3a163 Fix Card.copy() mutating source card and normalize customFields
Card.copy() mutated `this` directly (boardId, labelIds, customFields,
etc.), corrupting the cached source card object and causing intermittent
failures on repeated copy operations. Now works on a shallow copy.

Also normalizes customFields to [] when it's not an array (e.g. legacy
{} data in the database), preventing "Custom fields must be an array"
schema validation errors on both copy and move operations.
2026-02-05 02:28:15 +02:00
Harry Adel d68ad47de6 Await async setDone before closing popup in copy/move dialogs
The click handler called setDone() without await then immediately
called Popup.back(2), destroying the popup template while the async
operation was still running. This caused unhandled promise rejections
and made errors invisible to the user.
2026-02-05 02:28:00 +02:00
Lauri Ojansivu 10cef2f254 Updated translations. 2026-02-05 00:26:07 +02:00
Lauri Ojansivu 8c252163c9 Updated ChangeLog. 2026-02-05 00:00:59 +02:00
Lauri Ojansivu c31758960f Fixed Jade syntax at header.
Thanks to xet7 !
2026-02-04 23:55:35 +02:00
Aymen Hassini ef32a72b4b improve cardDetails.css 2026-02-04 15:25:50 +01:00
Lauri Ojansivu 7dbebed6db Merge pull request #6122 from wekan/dependabot/github_actions/docker/login-action-3.7.0
Bump docker/login-action from 3.6.0 to 3.7.0
2026-02-04 03:49:03 +02:00
dependabot[bot] dda22ddb50 Bump docker/login-action from 3.6.0 to 3.7.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.6.0 to 3.7.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/5e57cd118135c172c3672efd75eb46360885c0ef...c94ce9fb468520275223c153574b00df6fe4bcc9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-03 02:16:13 +00:00
Lauri Ojansivu 42d1b1c725 Updated ChangeLog. 2026-02-02 20:47:11 +02:00
Lauri Ojansivu e16e3aac2d Merge pull request #6121 from KhaoulaMaleh/updates
fixed text truncation
2026-02-02 20:42:52 +02:00
KhaoulaMaleh 19237ca7ce fixed text truncation 2026-02-02 19:29:08 -11:00
Harry Adel 71eb01e233 Update ReactiveCache call sites to use async/await for Meteor 3.0
Part 3 of ReactiveCache async migration:
- Add await before all ReactiveCache.getX() calls
- Make functions containing ReactiveCache calls async
- Convert forEach/map/filter loops with async callbacks to for...of
- Update model helpers, Meteor methods, JsonRoutes handlers
- Update collection hooks (.before/.after insert/update/remove)
- Update .allow() callbacks to async

Files updated across models/ and server/ directories:
- Model files: cards, boards, lists, swimlanes, activities, users,
  checklists, checklistItems, customFields, attachments, integrations,
  cardComments, settings files, creators, exporters, and more
- Server files: publications, methods, notifications, routes, migrations
2026-02-01 00:54:38 +02:00
Lauri Ojansivu bb12311e2f v8.27 2026-01-31 20:51:19 +02:00
Harry Adel 2f6e34c5f5 Make ReactiveCache async for Meteor 3.0 compatibility
- Convert all ReactiveCacheServer methods to async
  - findOne() → findOneAsync()
  - find().fetch() → find().fetchAsync()
  - Meteor.user() → Meteor.userAsync()
- Convert ReactiveCache facade methods to async
  - Server path uses await for async operations
  - Client path returns sync Minimongo result (wrapped in Promise)

This prepares the codebase for Meteor 3.0 where MongoDB
operations are async.
2026-01-31 20:50:40 +02:00
Harry Adel e241e27ca1 Add local fork of meteor-reactive-cache package
- Clone @wekanteam/meteor-reactive-cache to npm-packages/
- Add .meteorignore to exclude npm-packages from Meteor
- Update package.json to use local file path

This allows direct modification of the package for Meteor 3.0
async migration while maintaining publishability to npm.
2026-01-31 20:46:05 +02:00
Lauri Ojansivu bbd9fd838b Merge pull request #6120 from harryadel/fix/issue-6105-async-copy-card
Fix async/await in copy/move card operations
2026-01-31 20:36:12 +02:00
Harry Adel 35715ef2a3 Propagate async/await to List.copy(), Swimlane.copy() and callers
Since Card.copy() is now async, all callers in the copy chain need to
be updated to properly await the async operations:

- Make List.copy() async and await card.copy() in loop
- Make Swimlane.copy() async and await list.copy() in loop
- Fix mutateSelectedCards() to support async callbacks and method calls
- Make template copy event handler async in listBody.js

This also fixes the copySelection feature which was passing a callback
to mutateSelectedCards() but the function only supported method names.
2026-01-31 19:46:22 +02:00
Harry Adel 14de981ac3 Fix async/await in copy/move card operations
The mapCustomFieldsToBoard() method is async but was being called without
await in copy() and move() methods. This caused a Promise to be assigned
to customFields instead of the actual array, failing MongoDB schema
validation on cross-board operations.

Changes:
- Make copy() method async and await mapCustomFieldsToBoard()
- Add await in move() for mapCustomFieldsToBoard()
- Make copyCard() server method async and await card.copy()
- Add null check in mapCustomFieldsToBoard() for cards without custom fields
- Update client to use Meteor.callAsync for server-only copyCard method

Fixes #6105
2026-01-31 19:45:43 +02:00
Lauri Ojansivu 88b35a6415 Updated ChangeLog. 2026-01-31 10:42:09 +02:00
Lauri Ojansivu b55e1bbd40 Updated MongoDB to 7.0.29 at Windows install docs.
Thanks to MongoDB developers.
2026-01-31 10:40:16 +02:00
Lauri Ojansivu 520304d560 v8.26 2026-01-31 09:44:57 +02:00
Lauri Ojansivu be1ad7d041 Updated ChangeLog. 2026-01-31 09:29:59 +02:00
Lauri Ojansivu f73eab23f9 Fix Bug: Rules view translation not is not shown correctly.
Thanks to cactus7as and xet7 !

Fixes #6117
2026-01-31 09:27:49 +02:00
Lauri Ojansivu 9c2f782f84 Updated ChangeLog. 2026-01-31 09:22:24 +02:00
Lauri Ojansivu 0b891464b9 Fix Card copy menu is not displayed.
Thanks to xet7 !

Fixes #6105
2026-01-31 09:20:24 +02:00
Lauri Ojansivu 03439d1bcc Updated dependencies.
Thanks to developers of dependencies !
2026-01-31 09:16:02 +02:00
Lauri Ojansivu 6e9c8a174f Merge branch 'harryadel-wekan-accounts-lockout-async-migration' 2026-01-30 19:07:41 +02:00
Lauri Ojansivu ef84d798b5 Updated translations 2026-01-30 19:05:58 +02:00
Lauri Ojansivu a76f40a4d0 Updated ChangeLog. 2026-01-30 19:05:58 +02:00
Lauri Ojansivu 537e25df93 Spreadsheet vs Kanban, version 2.
Thanks to xet7 !
2026-01-30 19:05:58 +02:00
Lauri Ojansivu 0764b46a02 Updated ChangeLog. 2026-01-30 19:05:58 +02:00
Lauri Ojansivu b0f983dfa9 Added Docs: Spreadsheet vs Kanban.
Thanks to xet7 !
2026-01-30 19:05:58 +02:00
Lauri Ojansivu a3c7269d9e Updated translations 2026-01-30 00:00:39 +02:00
Lauri Ojansivu dc94618c26 Updated ChangeLog. 2026-01-29 22:53:13 +02:00
Lauri Ojansivu 37d0daee59 Spreadsheet vs Kanban, version 2.
Thanks to xet7 !
2026-01-29 22:51:57 +02:00
Lauri Ojansivu 9225b61225 Updated ChangeLog. 2026-01-29 22:43:11 +02:00
Lauri Ojansivu a0a8d0186c Added Docs: Spreadsheet vs Kanban.
Thanks to xet7 !
2026-01-29 22:41:42 +02:00
Harry Adel 11dab6680d Update .meteor/versions 2026-01-29 21:31:19 +02:00
Harry Adel b431600d32 Migrate accounts-lockout server files to async for Meteor 3.0
- lockedUsers.js: async getLockedUsers, unlockUser, unlockAllUsers
- lockoutSettings.js: async reloadAccountsLockout with findOneAsync
- accounts-lockout-config.js: async startup with findOneAsync
- models/lockoutSettings.js: upsert → upsertAsync
2026-01-29 21:29:56 +02:00
Harry Adel fb79ccaa55 Replace slugify with limax and fix sync operations 2026-01-29 21:01:39 +02:00
Harry Adel fa3966e1f7 Replace Npm.require with ES imports 2026-01-29 20:59:19 +02:00
Harry Adel 81c81ff4aa Replace Npm.require(fs) with ES import 2026-01-29 20:57:49 +02:00
Harry Adel 788406c217 Migrate wekan-accounts-sandstorm from HTTP to fetch 2026-01-29 20:45:50 +02:00
Harry Adel 49a272d6c0 Migrate wekan-oidc from HTTP to fetch 2026-01-29 20:45:00 +02:00
Harry Adel 0fcdf47545 Migrate wekan-oidc to async API for Meteor 3.0 2026-01-29 20:16:12 +02:00
Harry Adel 65ce948bb0 Migrate wekan-accounts-lockout to async API for Meteor 3.0 2026-01-29 20:12:00 +02:00
Harry Adel 5d6014d496 Migrate wekan-accounts-sandstorm to async API for Meteor 3.0 2026-01-29 20:05:51 +02:00
Harry Adel 6e48988118 Migrate wekan-accounts-cas to async API for Meteor 3.0
- Remove Fiber dependency (Npm.require('fibers'))
- Use Meteor.bindEnvironment for WebApp handler
- Convert login handler to async with findOneAsync
- Bump version 0.1.0 → 0.2.0
2026-01-29 20:03:10 +02:00
Harry Adel 7d56dca80b Migrate wekan-ldap to async API for Meteor 3.0
- Replace Meteor.wrapAsync with native Promises
- Convert all sync methods to async
- Use async DB operations (findOneAsync, updateAsync)
- Bump version 0.0.2 → 0.1.0
2026-01-29 19:58:23 +02:00
Lauri Ojansivu eb0c9ac1e6 Updates 2026-01-29 14:27:27 +02:00
Lauri Ojansivu 752bd501bc Updated ChangeLog. 2026-01-29 13:27:37 +02:00
Lauri Ojansivu 542ec7b78b Updated ChangeLog. 2026-01-28 22:27:37 +02:00
Lauri Ojansivu 218855eebf Merge pull request #6106 from AymenHassini19/fix/member-settings-scroll-overflow
Reduce Member Settings Overflow for Better UI
2026-01-28 22:25:10 +02:00
Aymen Hassini f4a449b4ab Partially Fix Member Settings Scroll Overflow 2026-01-28 14:58:44 +01:00
Lauri Ojansivu 8ce77880bd v8.25 2026-01-28 14:02:55 +02:00
Lauri Ojansivu ac70fe2848 Updated to MongoDB 7.0.29 at Snap Candidate.
Thanks to developers of MongoDB !
2026-01-28 13:54:10 +02:00
Lauri Ojansivu 7ff174cf66 Revert multi-CPU Snap, will try it later.
Thanks to xet7 !
2026-01-28 13:27:08 +02:00
Lauri Ojansivu a419d831a4 Fix Filebleed of Floppybleed.
Thanks to Luke Hebenstreit Twitter lheben_ and xet7 !
2026-01-28 13:20:28 +02:00
Lauri Ojansivu 7ad04f4535 Most Unicode Icons back to Font Awesome 4.7 for better accessibility. Less always visible buttons, More at ☰ Men.
Thanks to xet7 !
2026-01-28 13:02:02 +02:00
Lauri Ojansivu 440f553de0 Fix autofocus.
Thanks to xet7 !
2026-01-28 12:55:22 +02:00
Lauri Ojansivu c29ff995b3 Updated translations. 2026-01-28 12:42:47 +02:00
Lauri Ojansivu dee71b652d Updated translations. 2026-01-27 19:07:26 +02:00
Lauri Ojansivu 7a76cac80c Updated ChangeLog. 2026-01-24 09:13:15 +02:00
Lauri Ojansivu 3e0ecea0c9 Updated ChangeLog. 2026-01-24 07:36:11 +02:00
Lauri Ojansivu 60846a4495 Deleted extra docker-compose.yml-arm64, because docker-compose.yml should work at amd64/arm64/s390x.
Thanks to xet7 !
2026-01-24 07:34:57 +02:00
Lauri Ojansivu 15eba12e91 Updated ChangeLog. 2026-01-24 07:28:03 +02:00
Lauri Ojansivu 98e5adfba8 File content moved to releases/docker-build.sh
Thanks to xet7 !
2026-01-24 07:27:02 +02:00
Lauri Ojansivu 422514e907 Updated ChangeLog. 2026-01-24 07:26:30 +02:00
Lauri Ojansivu b2c7c7f55b Docker and Snap for Linux amd64/arm64/s390x. Part 3.
Thanks to xet7 !
2026-01-24 07:25:02 +02:00
Lauri Ojansivu 82097d622b Updated ChangeLog. 2026-01-24 06:45:13 +02:00
Lauri Ojansivu e72019fa55 Docker image for Linux amd64/arm64/s390x. Part 2.
Thanks to xet7 !
2026-01-24 06:43:13 +02:00
Lauri Ojansivu 38711f0a29 Docker image for Linux amd64/arm64/s390x.
Thanks to xet7 !
2026-01-24 06:37:28 +02:00
Lauri Ojansivu be6635c2a3 Updated ChangeLog. 2026-01-24 06:03:19 +02:00
Lauri Ojansivu d298ab7486 Updated Dockerfile.
Thanks to xet7 !
2026-01-24 06:01:51 +02:00
Lauri Ojansivu c912542223 Updated ChangeLog. 2026-01-24 05:29:32 +02:00
Lauri Ojansivu bf5d50e8a9 Updated FerretDB 2 / PostgreSQL docs location. Part 3.
Thanks to xet7 !
2026-01-24 05:28:25 +02:00
Lauri Ojansivu 4a5cfaf929 Updated ChangeLog. 2026-01-24 05:27:39 +02:00
Lauri Ojansivu 0ede9d6d93 Updated FerretDB 2 / PostgreSQL docs location. Part 2.
Thanks to xet7 !
2026-01-24 05:26:23 +02:00
Lauri Ojansivu 1bf4bf1f30 Updated ChangeLog. 2026-01-24 05:24:53 +02:00
Lauri Ojansivu 710d522e06 Updated FerretDB 2 / PostgreSQL docs location.
Thanks to xet7 !
2026-01-24 05:23:29 +02:00
Lauri Ojansivu ba4f4af29b Updated ChangeLog. 2026-01-24 05:17:22 +02:00
Lauri Ojansivu 2f25f47d0b Updated code counts.
Thanks to xet7 !
2026-01-24 05:15:32 +02:00
Lauri Ojansivu 59c6003fe1 Updated ChangeLog. 2026-01-24 03:24:17 +02:00
Lauri Ojansivu 4b0cd22bf7 v8.24 2026-01-24 03:11:14 +02:00
Lauri Ojansivu 3aca142770 Updated ChangeLog. 2026-01-24 03:04:43 +02:00
Lauri Ojansivu 256990b95c Updated ChangeLog. 2026-01-24 02:55:59 +02:00
Lauri Ojansivu 537ad1eff3 Merge pull request #6096 from harryadel/fix-autofocus
List on body for global onRendered
2026-01-24 02:54:09 +02:00
Lauri Ojansivu 2217a27757 Merge pull request #6095 from harryadel/remove-idmontie-migrations
Remove idmontie:migrations
2026-01-24 02:53:52 +02:00
Harry Adel 55abe2ee75 Remove idmontie:migrations 2026-01-24 02:39:27 +02:00
Harry Adel 70825d3425 List on body for global onRendered 2026-01-24 02:38:25 +02:00
Lauri Ojansivu 7f1bb73b75 Updated translations 2026-01-24 02:08:46 +02:00
Lauri Ojansivu 8b362a4165 Updated ChangeLog. 2026-01-24 02:06:36 +02:00
Lauri Ojansivu 25eedd187e Merge pull request #6093 from harryadel/createIndex-migration
Migrate createIndex to createIndexAsync
2026-01-24 02:04:35 +02:00
Lauri Ojansivu ff9f29d12d Merge pull request #6088 from harryadel/autofocus-migration
Remove mquandalle:autofocus
2026-01-24 02:04:21 +02:00
Harry Adel 42968d4c15 Merge branch 'main' into autofocus-migration 2026-01-24 01:58:25 +02:00
Harry Adel ca2083c858 Migrate createIndex to createIndexAsync 2026-01-24 01:55:29 +02:00
Lauri Ojansivu de0ff4002e Updated ChangeLog. 2026-01-24 01:52:48 +02:00
Lauri Ojansivu d0dc206ffc Merge pull request #6087 from harryadel/speakingurl-to-limax
Replace ongoworks:speakingurl with limax
2026-01-24 01:51:52 +02:00
Lauri Ojansivu 02d2ba2b6b Updated ChangeLog. 2026-01-24 01:51:30 +02:00
Lauri Ojansivu 904211dba5 Merge branch 'harryadel-collection-mutations-migrations' 2026-01-24 01:49:15 +02:00
Lauri Ojansivu 1d374db0f3 Meteor 2.16 2026-01-24 01:40:30 +02:00
Harry Adel e0249493d0 Fix swimlanes 2026-01-23 22:28:59 +02:00
Harry Adel 47f067e38e Merge branch 'main' into collection-mutations-migrations 2026-01-23 22:14:33 +02:00
Harry Adel 3b36d61698 Merge branch 'main' into speakingurl-to-limax 2026-01-22 15:01:29 +02:00
Lauri Ojansivu 60ecddfce4 Updated ChangeLog. 2026-01-22 02:19:46 +02:00
Lauri Ojansivu cc8b771eb4 Secure Sandbox for VSCode at Debian 13 amd64. Part 2.
Thanks to xet7 !
2026-01-22 02:18:25 +02:00
Lauri Ojansivu f503b7f517 Updated ChangeLog. 2026-01-22 02:10:32 +02:00
Lauri Ojansivu 639ac9549f Secure Sandbox for VSCode at Debian 13 amd64.
Thanks to xet7 !
2026-01-22 02:06:29 +02:00
Lauri Ojansivu 839098c884 Updated translations. 2026-01-21 23:48:15 +02:00
Harry Adel d8495ab08f Remove mquandalle:autofocus 2026-01-21 20:03:40 +02:00
Harry Adel 53a314af65 Downgrade limax to 4.1.0 2026-01-21 19:45:23 +02:00
Harry Adel 2d0c4f5bd8 Replace ongoworks:speakingurl with limax 2026-01-21 19:39:01 +02:00
Harry Adel 94a3575e2c Replace mquandalle:collection-mutations with collection helpers 2026-01-21 19:22:54 +02:00
Lauri Ojansivu aca661583d v8.23 2026-01-21 15:09:38 +02:00
Lauri Ojansivu ca79fcb7e9 Updated ChangeLog. 2026-01-21 14:16:44 +02:00
Lauri Ojansivu 04bfa0e6ba Updated dependencies.
Thanks to developers of dependencies !
2026-01-21 14:15:01 +02:00
Lauri Ojansivu 3f8059baad Updated translations. 2026-01-21 14:11:40 +02:00
Lauri Ojansivu b803d5336e Updated ChangeLog. 2026-01-21 13:41:24 +02:00
Lauri Ojansivu 7b72fcaee1 Merge pull request #6085 from wekan/dependabot/npm_and_yarn/tar-7.5.6
Bump tar from 7.5.3 to 7.5.6
2026-01-21 13:36:05 +02:00
Lauri Ojansivu c56bdb84d2 Merge pull request #6084 from harryadel/migrate-to-publish-composite
Replace cottz:publish-relations with reywood:publish-composite
2026-01-21 13:35:07 +02:00
dependabot[bot] ccc0d16b52 Bump tar from 7.5.3 to 7.5.6
Bumps [tar](https://github.com/isaacs/node-tar) from 7.5.3 to 7.5.6.
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v7.5.3...v7.5.6)

---
updated-dependencies:
- dependency-name: tar
  dependency-version: 7.5.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-21 11:34:28 +00:00
Lauri Ojansivu f38e3624cf Merge pull request #6083 from harryadel/remove-kadira-dochead
Remove kadira:dochead
2026-01-21 13:33:50 +02:00
Lauri Ojansivu 8586fa8ce0 Merge pull request #6082 from harryadel/mousetrap-migration
Replace mousetrap
2026-01-21 13:33:09 +02:00
Harry Adel a5444e08ab Replace cottz:publish-relations with reywood:publish-composite 2026-01-21 07:52:04 +02:00
Harry Adel f9e584e13c Remove kadira:dochead 2026-01-21 07:36:17 +02:00
Harry Adel c795dfe96b Replace mousetrap 2026-01-21 07:03:46 +02:00
Lauri Ojansivu a528a411da Updated translations. 2026-01-21 01:02:14 +02:00
Lauri Ojansivu d96b8942e7 Updated ChangeLog. 2026-01-21 00:58:19 +02:00
Lauri Ojansivu a31a615da6 Fix DB migration from 8.19 to 8.21 stuck forever.
Thanks to MaccabeeY and xet7 !

Fixes #6078
2026-01-21 00:56:42 +02:00
Lauri Ojansivu e177bf54e2 Updated ChangeLog. 2026-01-20 20:49:28 +02:00
Lauri Ojansivu ce55f0d8f4 Fixed newly created "Default" swimlane are displayed as "key 'default (LOCALE)' returned an object instead of string".
Thanks to brlin-tw and xet7 !

Fixes #6064
2026-01-20 20:44:57 +02:00
Lauri Ojansivu 320b76aec4 Updated translations. 2026-01-20 20:03:24 +02:00
Lauri Ojansivu 5d86bcce6d Updated ChangeLog. 2026-01-20 19:59:53 +02:00
Lauri Ojansivu 0b9f23bd4a Merge branch 'harryadel-synced-cron-migration' 2026-01-20 19:57:34 +02:00
Harry Adel 526251397e Migrate from percolate:synced-cron to quave:synced-cron 2026-01-20 17:56:52 +02:00
Lauri Ojansivu 997b9a7c76 v8.22 2026-01-20 02:51:55 +02:00
Lauri Ojansivu bfebee5949 Updated translations. 2026-01-20 02:44:35 +02:00
Lauri Ojansivu bd899b2bf2 Updated ChangeLog. 2026-01-20 02:35:03 +02:00
Lauri Ojansivu ad511bd137 Fixed Add member and @mentions.
Thanks to xet7 !

Fixes #6076,
fixes #6077
2026-01-20 02:28:32 +02:00
Lauri Ojansivu 603a65ef17 v8.21 2026-01-18 21:18:03 +02:00
Lauri Ojansivu 2fa490d83d Fix DB migration from 8.19 to 8.20 is in a loop.
Thanks to MaccabeeY and xet7 !

Fixes #6072
2026-01-18 20:27:23 +02:00
Lauri Ojansivu a787bcddf3 Security Fix 14: RulesBleed.
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
2026-01-18 20:18:38 +02:00
Lauri Ojansivu 91a936e07d Security Fix 13: LinkedBoardActivitiesBleed.
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
2026-01-18 20:13:25 +02:00
Lauri Ojansivu 4ce181d172 Security Fix 12: FixDuplicateBleed.
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
2026-01-18 20:07:12 +02:00
Lauri Ojansivu 0f5a9c3877 Security Fix 11: CardPubSubBleed.
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
2026-01-18 20:00:37 +02:00
Lauri Ojansivu 545566f566 Security Fix 10: BoardTitleRESTBleed.
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
2026-01-18 19:55:48 +02:00
Lauri Ojansivu 8c0b4f79d8 Security Fix 9: ListWIPBleed.
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
2026-01-18 19:50:29 +02:00
Lauri Ojansivu c413a7e860 Security Fix 8: MoveStorageBleed.
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
2026-01-18 19:45:44 +02:00
Lauri Ojansivu 053bf1dfb7 Security Fix 7: AttachmentMigrationBleed.
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
2026-01-18 19:39:50 +02:00
Lauri Ojansivu 146905a459 Security Fix 6: SyncLDAPBleed.
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
2026-01-18 19:36:28 +02:00
Lauri Ojansivu 55576ec177 Security Fix 5: PositionHistoryBleed.
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
2026-01-18 19:29:58 +02:00
Lauri Ojansivu cc35dafef5 Security Fix 4: MigrationBleed.
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
2026-01-18 19:24:07 +02:00
Lauri Ojansivu 251d49eea9 Security Fix 3: Checklist REST Bleed.
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
2026-01-18 19:13:14 +02:00
Lauri Ojansivu cabfeed9a6 Security Fix 2: Orgs Teams permissions checks.
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
2026-01-18 19:08:28 +02:00
Lauri Ojansivu b4b90b1c41 Updated ChangeLog. 2026-01-18 15:24:30 +02:00
Lauri Ojansivu d337afd5d3 Fixed "Copy card link to clipboard" icon often not working.
Thanks to brlin-tw and xet7 !

Fixes #6068
2026-01-18 15:21:33 +02:00
Lauri Ojansivu 4473ca09f6 Updated ChangeLog. 2026-01-17 19:42:21 +02:00
Lauri Ojansivu d30192f7f9 Show password at Login and Register pages.
Thanks to xet7 !

Fixes #6070
2026-01-17 19:40:07 +02:00
Lauri Ojansivu 7291617a77 Updated ChangeLog. 2026-01-17 17:43:30 +02:00
Lauri Ojansivu 95da8966fe Updated dependencies.
Thanks to developers of dependencies !
2026-01-17 17:42:27 +02:00
Lauri Ojansivu db7ffc98fa Updated ChangeLog. 2026-01-17 17:33:39 +02:00
Lauri Ojansivu cb43a41b0c Updated translations. 2026-01-17 17:28:10 +02:00
Lauri Ojansivu a7e29b92bf Merge pull request #6072 from harryadel/meteor-2.16
Update 2.16
2026-01-17 13:10:20 +02:00
Lauri Ojansivu 4899330b6e Merge pull request #6071 from wekan/dependabot/npm_and_yarn/multi-5983a32967
Bump tar and @mapbox/node-pre-gyp
2026-01-17 13:09:43 +02:00
Harry Adel c56bc0be7f Update to 2.16 2026-01-16 23:18:29 +02:00
dependabot[bot] fd8ce39990 Bump tar and @mapbox/node-pre-gyp
Bumps [tar](https://github.com/isaacs/node-tar) to 7.5.3 and updates ancestor dependency [@mapbox/node-pre-gyp](https://github.com/mapbox/node-pre-gyp). These dependencies need to be updated together.


Updates `tar` from 6.2.1 to 7.5.3
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v6.2.1...v7.5.3)

Updates `@mapbox/node-pre-gyp` from 1.0.11 to 2.0.3
- [Release notes](https://github.com/mapbox/node-pre-gyp/releases)
- [Changelog](https://github.com/mapbox/node-pre-gyp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mapbox/node-pre-gyp/compare/v1.0.11...v2.0.3)

---
updated-dependencies:
- dependency-name: tar
  dependency-version: 7.5.3
  dependency-type: indirect
- dependency-name: "@mapbox/node-pre-gyp"
  dependency-version: 2.0.3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-16 21:17:58 +00:00
Harry Adel f9ed63ac76 Remove kadira:flow-router 2026-01-16 23:17:50 +02:00
Lauri Ojansivu 641d4c2f7f Updated ChangeLog. 2026-01-16 22:50:27 +02:00
Lauri Ojansivu 980510d71a Updated Linux s390x bundle build script.
Thanks to xet7 !
2026-01-16 22:48:43 +02:00
Lauri Ojansivu 07001a229d Updated ChangeLog. 2026-01-16 22:15:37 +02:00
Lauri Ojansivu e2ec50730f Update Linux arm64 bundle build script.
Thanks to xet7 !
2026-01-16 22:14:07 +02:00
Lauri Ojansivu 20884cf6c7 Updated ChangeLog. 2026-01-16 21:02:44 +02:00
Lauri Ojansivu f0118d52e9 Updated Windows Bundle build .bat script.
Thanks to xet7 !
2026-01-16 21:01:29 +02:00
Lauri Ojansivu d4e922eed4 Updated ChangeLog. 2026-01-16 19:53:36 +02:00
Lauri Ojansivu b88b27689a Updated Docker build command.
Thanks to xet7 !
2026-01-16 19:52:07 +02:00
Lauri Ojansivu a29733be9c v8.20 2026-01-16 19:43:46 +02:00
Lauri Ojansivu 33e0a82967 Updated translations. 2026-01-16 19:17:05 +02:00
Lauri Ojansivu 13b5b170b6 Updated translations. 2026-01-14 23:57:35 +02:00
Lauri Ojansivu e2f2090da1 Updated ChangeLog. 2026-01-14 23:45:34 +02:00
Lauri Ojansivu eabb6a239d Fix New Board Permissions: NormalAssignedOnly, CommentAssignedOnly, ReadOnly, ReadAssignedOnly. Part 1.
Thanks to nazim-oss and xet7 !

Related #6060
2026-01-14 23:43:11 +02:00
Lauri Ojansivu 2f59e42024 Updated ChangeLog. 2026-01-14 21:13:31 +02:00
Lauri Ojansivu 20b5e2ab8f Fix mentions and notifications drawer.
Thanks to xet7 !

Fixes #6062,
fixes #6003,
fixes #5996,
fixes #5720,
fixes #5911,
fixes #5792,
fixes #5163,
fixes #4431,
fixes #4126,
fixes #3363,
fixes #3150
2026-01-14 21:02:10 +02:00
Lauri Ojansivu 0d5dd3082c Updated ChangeLog. 2026-01-14 19:12:53 +02:00
Lauri Ojansivu 5cb712bee4 Added back feature: Toggle Drag Handles. Improved positions of Add List etc buttons.
Thanks to xet7 !
2026-01-14 19:10:51 +02:00
Lauri Ojansivu 00793c39f8 Updated ChangeLog. 2026-01-14 16:31:01 +02:00
Lauri Ojansivu e37220cde6 Updated translations. 2026-01-14 16:19:15 +02:00
Lauri Ojansivu dd20988b1c Updated translations. 2026-01-14 01:29:03 +02:00
Lauri Ojansivu 32785376d8 Updated ChangeLog. 2026-01-14 01:20:50 +02:00
Lauri Ojansivu 4a39bcc6ee Merge branch 'harryadel-routing-layer-migration' 2026-01-14 01:12:30 +02:00
Lauri Ojansivu 984a2dcec1 Some fixes to make WeKan working after Meteor 3 related router upgrades.
Thanks to xet7 !
2026-01-14 01:11:42 +02:00
Lauri Ojansivu c8bdb95b38 Updated ChangeLog. 2026-01-14 01:00:59 +02:00
Lauri Ojansivu e89f4d260c Fixed Change Avatar. Improved Admin Panel: People columns order, selected tab background color.
Thanks to xet7 !
2026-01-14 01:00:59 +02:00
Lauri Ojansivu eca42f32bb Updated ChangeLog. 2026-01-14 01:00:58 +02:00
Lauri Ojansivu 6ad9770bb6 Changed find.sh to not search from translations, because I'm trying to find code, not translations.
Thanks to xet7 !
2026-01-14 01:00:58 +02:00
Lauri Ojansivu 5b47340b76 Updated ChangeLog. 2026-01-14 01:00:58 +02:00
Lauri Ojansivu 69f54dddaf Removed extra pipe character from UI.
Thanks to xet7 !
2026-01-14 01:00:58 +02:00
Lauri Ojansivu b3cb47ac47 Updated ChangeLog. 2026-01-14 01:00:58 +02:00
Lauri Ojansivu 69e2ad1007 Add back button texts to Filter, Search, Board View and MultiSelection.
Thanks to audiocrush and xet7 !

Fixes #6066
2026-01-14 01:00:58 +02:00
Lauri Ojansivu 40784b2054 Updated ChangeLog. 2026-01-14 01:00:58 +02:00
Lauri Ojansivu ab49e09da6 Removed extra list borders.
Thanks to TDSCDMA and xet7 !

Related #6063
2026-01-14 01:00:58 +02:00
Lauri Ojansivu 6ed8e54dac Updated ChangeLog. 2026-01-14 01:00:58 +02:00
Lauri Ojansivu c80d1aae4c Swimlane drag button position improvements.
Thanks to TDSCDMA and xet7 !

Related #6063
2026-01-14 01:00:58 +02:00
Lauri Ojansivu a62b3c6ce0 Updated ChangeLog. 2026-01-14 01:00:58 +02:00
Lauri Ojansivu 8a0568ce01 Updated GitHub issue templates. Part 3.
Thanks to xet7 !
2026-01-14 01:00:58 +02:00
Lauri Ojansivu c740474145 Updated ChangeLog. 2026-01-14 01:00:58 +02:00
Lauri Ojansivu 4a373b45e1 Updated GitHub issue templates. Part 2.
Thanks to xet7 !
2026-01-14 01:00:58 +02:00
Lauri Ojansivu 32733023ca Updated ChangeLog. 2026-01-14 01:00:58 +02:00
Lauri Ojansivu 0ffb0fa24f Updated ChangeLog. 2026-01-14 01:00:58 +02:00
Lauri Ojansivu b5177e3870 Added FerretDB2/PostgreSQL Docs. Part 4.
Thanks to juri_ at WeKan Libera.Chat IRC and xet7 !
2026-01-14 01:00:58 +02:00
Lauri Ojansivu c326e58d5e Updated ChangeLog. 2026-01-14 01:00:58 +02:00
Lauri Ojansivu 4d4046986f Updated GitHub issue templates.
Thanks to xet7 !
2026-01-14 01:00:58 +02:00
Lauri Ojansivu 36cbd3a606 Updated ChangeLog. 2026-01-14 01:00:58 +02:00
Lauri Ojansivu 715d47dd2c Added FerretDB2/PostgreSQL Docs. Part 3.
Thanks to juri_ at WeKan Libera.Chat IRC and xet7 !
2026-01-14 01:00:58 +02:00
Lauri Ojansivu 71d84f58a4 Updated ChangeLog. 2026-01-14 01:00:58 +02:00
Lauri Ojansivu 306305a95c Docs: Added s390x firewall info.
Thanks to xet7 !
2026-01-14 01:00:58 +02:00
Lauri Ojansivu 2bc9fa1629 Updated ChangeLog. 2026-01-14 01:00:57 +02:00
Lauri Ojansivu fd45ae2a62 Added FerretDB2/PostgreSQL Docs. Part 2.
Thanks to juri_ at WeKan Libera.Chat IRC and xet7 !
2026-01-14 01:00:57 +02:00
Lauri Ojansivu 46100cfd1d Updated ChangeLog. 2026-01-14 01:00:57 +02:00
Lauri Ojansivu 1d62322b3c Added FerretDB2/PostgreSQL Docs.
Thanks to juri_ at WeKan Libera.Chat IRC and xet7 !
2026-01-14 01:00:57 +02:00
Lauri Ojansivu d784d2b633 Updated ChangeLog. 2026-01-14 00:42:51 +02:00
Lauri Ojansivu 07186e12a9 Fixed Change Avatar. Improved Admin Panel: People columns order, selected tab background color.
Thanks to xet7 !
2026-01-14 00:41:35 +02:00
Harry Adel 0635a663f0 Remove pwix:blaze-layout 2026-01-14 00:13:21 +02:00
Lauri Ojansivu dcd8d80b9d Updated ChangeLog. 2026-01-13 22:52:51 +02:00
Lauri Ojansivu 58ae2b6c68 Changed find.sh to not search from translations, because I'm trying to find code, not translations.
Thanks to xet7 !
2026-01-13 22:50:39 +02:00
Lauri Ojansivu c76a569fb0 Updated ChangeLog. 2026-01-13 22:38:52 +02:00
Lauri Ojansivu 66e79d2df7 Removed extra pipe character from UI.
Thanks to xet7 !
2026-01-13 22:35:36 +02:00
Lauri Ojansivu fc06ae1000 Updated ChangeLog. 2026-01-13 22:26:45 +02:00
Lauri Ojansivu dac7e17500 Add back button texts to Filter, Search, Board View and MultiSelection.
Thanks to audiocrush and xet7 !

Fixes #6066
2026-01-13 22:24:02 +02:00
Lauri Ojansivu 8f51fcd65a Updated ChangeLog. 2026-01-13 21:26:21 +02:00
Lauri Ojansivu a4f8faa48e Removed extra list borders.
Thanks to TDSCDMA and xet7 !

Related #6063
2026-01-13 21:22:39 +02:00
Lauri Ojansivu b5ab917dfb Updated ChangeLog. 2026-01-13 20:23:08 +02:00
Lauri Ojansivu 376a30f8a9 Swimlane drag button position improvements.
Thanks to TDSCDMA and xet7 !

Related #6063
2026-01-13 20:20:33 +02:00
Harry Adel 9ebf4d2426 Migrate routing layer from deprecated kadira packages to modern alternatives
- Remove deprecated kadira:flow-router, kadira:blaze-layout, arillo:flow-router-helpers
- Add ostrio:flow-router-extra (modern, actively maintained)
- Add pwix:blaze-layout (maintained fork of kadira:blaze-layout)
- Convert all 22 BlazeLayout.render() calls to this.render() in route actions
- Maintain full backward compatibility with existing FlowRouter API
- All route definitions remain functional without syntax changes
- Build compilation succeeds without errors

This migration prepares Wekan for Meteor 3.0 compatibility by replacing
9-year-old deprecated routing packages with modern alternatives.

Next phase: Schema and async collection methods migration
2026-01-13 19:46:32 +02:00
Lauri Ojansivu 42c14faf41 Updated ChangeLog. 2026-01-13 16:39:45 +02:00
Lauri Ojansivu 4a658dc02a Updated GitHub issue templates. Part 3.
Thanks to xet7 !
2026-01-13 16:38:12 +02:00
Lauri Ojansivu f250cba405 Updated ChangeLog. 2026-01-13 16:35:28 +02:00
Lauri Ojansivu cf6e691498 Updated GitHub issue templates. Part 2.
Thanks to xet7 !
2026-01-13 16:33:58 +02:00
Lauri Ojansivu 5b2e6511b0 Updated ChangeLog. 2026-01-13 16:30:33 +02:00
Lauri Ojansivu 52a9514536 Updated ChangeLog. 2026-01-13 16:23:12 +02:00
Lauri Ojansivu ffd37b9fd9 Added FerretDB2/PostgreSQL Docs. Part 4.
Thanks to juri_ at WeKan Libera.Chat IRC and xet7 !
2026-01-13 16:21:44 +02:00
Lauri Ojansivu e2b80652a5 Updated ChangeLog. 2026-01-13 16:13:44 +02:00
Lauri Ojansivu bd37b88e4d Updated GitHub issue templates.
Thanks to xet7 !
2026-01-13 16:12:01 +02:00
Lauri Ojansivu a0adc54a08 Updated ChangeLog. 2026-01-13 03:52:46 +02:00
Lauri Ojansivu 9431b2d530 Added FerretDB2/PostgreSQL Docs. Part 3.
Thanks to juri_ at WeKan Libera.Chat IRC and xet7 !
2026-01-13 03:50:52 +02:00
Lauri Ojansivu 594ef40f2e Updated ChangeLog. 2026-01-12 15:26:44 +02:00
Lauri Ojansivu aacdcea37b Docs: Added s390x firewall info.
Thanks to xet7 !
2026-01-12 15:25:11 +02:00
Lauri Ojansivu 309a456a0d Updated ChangeLog. 2026-01-12 03:55:35 +02:00
Lauri Ojansivu f198421f10 Added FerretDB2/PostgreSQL Docs. Part 2.
Thanks to juri_ at WeKan Libera.Chat IRC and xet7 !
2026-01-12 03:54:41 +02:00
Lauri Ojansivu fa0460b1f0 Updated ChangeLog. 2026-01-12 02:24:36 +02:00
Lauri Ojansivu 9fb1aeb827 Added FerretDB2/PostgreSQL Docs.
Thanks to juri_ at WeKan Libera.Chat IRC and xet7 !
2026-01-12 02:21:03 +02:00
Lauri Ojansivu 59ad67c08c Updated translations. 2026-01-09 18:49:54 +02:00
Lauri Ojansivu ec57618c98 Updated translations. 2026-01-06 00:20:33 +02:00
Lauri Ojansivu 3b378961b0 Updated ChangeLog. 2026-01-06 00:16:48 +02:00
Lauri Ojansivu cbb1cd78de Security Fix 1: There was not enough permission checks. Moved migrations to Admin Panel/Settings/Cron.
Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7.
2026-01-06 00:15:16 +02:00
Lauri Ojansivu d6834d0287 Updated ChangeLog. 2026-01-05 21:49:52 +02:00
Lauri Ojansivu a9a89b501a Published @wekanteam npm packages to npmjs.com .
Thanks to xet7 !
2026-01-05 21:31:54 +02:00
Lauri Ojansivu 7de4385160 Updated dependencies.
Thanks to developers of dependencies !
2026-01-05 19:19:56 +02:00
Lauri Ojansivu 7ac5a2bba5 Updated translations. 2026-01-05 18:23:26 +02:00
Lauri Ojansivu e0af5db1a9 Merge pull request #6056 from brlin-tw/patch-nonascii-attachment-download-error
WIP: AI: Fix attachment download error with non-ASCII filenames (#6055)
2026-01-04 20:06:06 +02:00
Lauri Ojansivu 87cd07dec7 Merge pull request #6059 from wekan/dependabot/npm_and_yarn/multi-8d39329a2d
Bump qs
2026-01-04 20:05:38 +02:00
dependabot[bot] e80f8e5121 Bump qs
Bumps [qs](https://github.com/ljharb/qs) to 6.14.0 and updates ancestor dependency . These dependencies need to be updated together.


Updates `qs` from 6.14.0 to 6.14.0
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.14.0...v6.14.0)

Updates `qs` from 6.13.0 to 6.14.1
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.14.0...v6.14.0)

---
updated-dependencies:
- dependency-name: qs
  dependency-version: 6.14.0
  dependency-type: indirect
- dependency-name: qs
  dependency-version: 6.14.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-01 13:35:17 +00:00
Lauri Ojansivu 2325a5c532 Updated translations 2025-12-30 12:19:55 +02:00
Lauri Ojansivu b1db262b37 Updated ChangeLog. 2025-12-30 12:01:28 +02:00
GitHub Copilot 2e564bd076 Fix attachment download error with non-ASCII filenames
Fixes #6055.

Signed-off-by: Buo-ren Lin (OSSII) <buoren.lin@ossii.com.tw>
2025-12-30 17:54:29 +08:00
Lauri Ojansivu e09e9114aa v8.19 2025-12-29 21:54:10 +02:00
Lauri Ojansivu efd91a8f72 Updated translations. 2025-12-29 21:49:37 +02:00
Lauri Ojansivu fbfde81bc8 Opened card Checklist menu: Hide finished tasks. Show Checklist at Minicard.
Thanks to C0rn3j and xet7 !

Fixes #6019,
fixes #5567,
fixes #2984
2025-12-29 21:42:19 +02:00
Lauri Ojansivu cf62807ad5 Fix Unable to delete Checklist. Added confirm delete to Checklist and Chekclist Item.
Thanks to C0rn3j and xet7 !

Fixes #6020
2025-12-29 19:42:20 +02:00
Lauri Ojansivu 2d87ba18b3 Fix move card last selection is gone.
Thanks to mimZD and xet7 !

Fixes #6046
2025-12-29 19:17:06 +02:00
Lauri Ojansivu 74f1dfde72 Fix copy move card at board and MultiSelect to have numbered target of board, card above or below. Added MultiSelect change color.
Thanks to mimZD and xet7 !

Fixes #6045
2025-12-29 19:09:45 +02:00
Lauri Ojansivu db4b04d837 Fix find.sh work with spaces, for example: ./find.sh "Some text"
Thanks to xet7 !
2025-12-29 18:46:05 +02:00
Lauri Ojansivu 0b0e16c3ea Security Fix 10: LDAP filter injection in LDAP auth.
Thanks to Joshua Rogers of joshua.hu, Twitter MegaManSec !
2025-12-29 17:13:32 +02:00
Lauri Ojansivu 1d16955b6d Security Fix 9: Attachment upload not scoped to card/board relationship.
Thanks to Joshua Rogers of joshua.hu, Twitter MegaManSec !
2025-12-29 17:06:22 +02:00
Lauri Ojansivu 6dfa3beb2b Security Fix 8: Attachments publication leaks metadata without auth.
Thanks to Joshua Rogers of joshua.hu, Twitter MegaManSec !
2025-12-29 17:03:02 +02:00
Lauri Ojansivu 5cd875813f Security Fix 7: Checklist create IDOR: cardId not verified against boardId.
Thanks to Joshua Rogers of joshua.hu, Twitter MegaManSec !
2025-12-29 16:58:26 +02:00
Lauri Ojansivu 08a6f084eb Security Fix 6: Checklist delete IDOR: checklist not verified against board/card.
Thanks to Joshua Rogers of joshua.hu, Twitter MegaManSec !
2025-12-29 16:54:04 +02:00
Lauri Ojansivu 181f837d8c Security Fix 5: Read-only roles can still update cards.
Thanks to Joshua Rogers of joshua.hu, Twitter MegaManSec !
2025-12-29 16:47:11 +02:00
Lauri Ojansivu 198509e760 Security Fix 4: Cross-board card move without destination authorization.
Thanks to Joshua Rogers of joshua.hu, Twitter MegaManSec !
2025-12-29 16:39:23 +02:00
Lauri Ojansivu 67cb47173c Security Fix 3: Card comment author spoofing (IDOR) via API.
Thanks to Joshua Rogers of joshua.hu, Twitter MegaManSec !
2025-12-29 16:34:00 +02:00
Lauri Ojansivu 7ed76c180e Security Fix 2: Private-only board setting can be bypassed.
Thanks to Joshua Rogers of joshua.hu, Twitter MegaManSec !
2025-12-29 16:29:01 +02:00
Lauri Ojansivu f244a43771 Security Fix 1: IDOR in setCreateTranslation. Non-admin could change Custom Translation.
Thanks to Joshua Rogers of joshua.hu, Twitter MegaManSec.
2025-12-29 16:20:17 +02:00
Lauri Ojansivu 48e856fca2 Updated translations. 2025-12-29 16:12:37 +02:00
Lauri Ojansivu 7d83cb3d0b Updated ChangeLog. 2025-12-29 15:31:26 +02:00
Lauri Ojansivu 3af3c9a89d Converted Gantt from js to Jade. Part 2.
Thanks to xet7 !
2025-12-29 15:26:16 +02:00
Lauri Ojansivu 2d3bef9033 Converted Gantt from js to Jade.
Thanks to xet7 !
2025-12-29 15:17:27 +02:00
Lauri Ojansivu ce9afbcaca Updated ChangeLog. 2025-12-29 10:07:47 +02:00
Lauri Ojansivu ba79d5389d Updated ChangeLog. 2025-12-29 07:27:48 +02:00
Lauri Ojansivu 2f4c40c1db Updated translations. 2025-12-29 07:25:26 +02:00
Lauri Ojansivu 6c9e8a4aa8 Merge pull request #6052 from Chostakovitch/6048-dnd-lists
Merge list component methods with same name
2025-12-29 07:19:35 +02:00
Lilou 223c38c50d Set sortable methods of lists only once 2025-12-29 02:45:48 +01:00
Lilou c1981dee9b Merge list component methods with same name
Probably results in inconsistant behavior (which one overwrites the other?)
2025-12-29 01:48:33 +01:00
Lauri Ojansivu 45b3373145 Updated ChangeLog. 2025-12-28 23:45:21 +02:00
Lauri Ojansivu 54a566cfb1 Merge pull request #6050 from Chostakovitch/6049-announcements
Re-add JS closing class to unicode close announcement symbol
2025-12-28 23:40:28 +02:00
Lilou 5a51c2940a Re-add JS closing class to unicode close announcement symbol 2025-12-28 21:54:39 +01:00
Lauri Ojansivu 7232070bd1 Updated ChangeLog. 2025-12-28 20:38:34 +02:00
Lauri Ojansivu 33ab8920ca v8.18 2025-12-28 20:32:44 +02:00
Lauri Ojansivu db4d47cc52 Updated ChangeLog. 2025-12-28 20:21:44 +02:00
Lauri Ojansivu e210c9973b Upgraded MongoDB to 7.0.28 to fix mongobleed at Snap Candidate.
Thanks to developers of MongoDB !
2025-12-28 19:59:36 +02:00
Lauri Ojansivu 98d05ce545 Updated translations 2025-12-25 08:47:55 +02:00
Lauri Ojansivu 9268d80974 Updated translations. 2025-12-25 08:46:43 +02:00
Lauri Ojansivu 1a221a6967 Updated translations. 2025-12-23 16:06:21 +02:00
Lauri Ojansivu a039bb1066 Per-User and Board-level data save fixes. Part 3.
Thanks to xet7 !
2025-12-23 09:03:41 +02:00
Lauri Ojansivu 90a7a61904 Updates 2025-12-23 08:03:04 +02:00
Lauri Ojansivu 58e970d685 Per-User and Board-level data save fixes. Part 2.
Thanks to xet7 !
2025-12-23 08:01:30 +02:00
Lauri Ojansivu edf0f3c05b Updated translations 2025-12-23 07:54:57 +02:00
Lauri Ojansivu 5bbf5e0dac Updated ChangeLog. 2025-12-23 07:52:46 +02:00
Lauri Ojansivu 414b8dbf41 Per-User and Board-level data save fixes. Per-User is collapse, width, height. Per-Board is Swimlanes, Lists, Cards etc.
Thanks to xet7 !

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Fixes #6008.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Fixes #5963
2025-10-21 14:47:57 +03:00
Lauri Ojansivu 7f31d7c812 v8.10 2025-10-21 14:15:16 +03:00
Lauri Ojansivu 4987a95d8e Prevent opened board re-migrating and reloading every 5 seconds.
Thanks to xet7 !
2025-10-21 14:12:12 +03:00
1189 changed files with 168206 additions and 68279 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)
+2 -11
View File
@@ -93,30 +93,24 @@
"Template": false,
"$": false,
"_": false,
"autosize": false,
"Avatar": true,
"Avatars": true,
"BlazeComponent": false,
"BlazeLayout": false,
"CollectionHooks": false,
"DocHead": false,
"ESSearchResults": false,
"FastRender": false,
"FlowRouter": false,
"FS": false,
"getSlug": false,
"Migrations": false,
"moment": false,
"Mousetrap": false,
"Picker": false,
"Presence": true,
"presences": true,
"Ps": true,
"ReactiveTabs": false,
"Restivus": false,
"SimpleSchema": false,
"SubsManager": false,
"T9n": false,
"T9n": false,
"TAPi18n": false,
"AccountsTemplates": true,
"Activities": true,
@@ -133,13 +127,11 @@
"CSSEvents": true,
"EscapeActions": true,
"Filter": true,
"Mixins": true,
"Modal": true,
"MultiSelection": true,
"Popup": true,
"Sidebar": true,
"Utils": true,
"InlinedForm": true,
"UnsavedEdits": true,
"Notifications": true,
"allowIsBoardAdmin": true,
@@ -152,7 +144,6 @@
"Checklists": true,
"Settings": true,
"InvitationCodes": true,
"JsonRoutes": true,
"Authentication": true,
"Integrations": true,
"HTTP": true,
+1
View File
@@ -1,3 +1,4 @@
# These are supported funding model platforms
github: wekan
custom: ['https://wekan.fi/commercial-support/']
-56
View File
@@ -1,56 +0,0 @@
## Issue
UPGRADE: https://wekan.fi/upgrade/
Pull requests welcome to fix any broken links at docs directory, and organizing docs/Features and their screenshots to subdirectories of each feature.
Please report these issues elsewhere:
- SECURITY ISSUES, PGP EMAIL: https://github.com/wekan/wekan/blob/main/SECURITY.md
- UCS: https://github.com/wekan/univention/issues
If WeKan Snap is slow, try this: https://github.com/wekan/wekan/wiki/Cron
Please search existing Open and Closed issues, most questions have already been answered.
If you can not login for any reason: https://github.com/wekan/wekan/wiki/Forgot-Password
Email settings, only SMTP MAIL_URL and MAIL_FROM are in use:
https://github.com/wekan/wekan/wiki/Troubleshooting-Mail
### Server Setup Information
Please anonymize info, and do not any of your Wekan board URLs, passwords,
API tokens etc to this public issue.
* Did you test in newest Wekan?:
* Did you configure root-url correctly so Wekan cards open correctly (see https://github.com/wekan/wekan/wiki/Settings)?
* Operating System:
* Deployment Method (Snap/Docker/Sandstorm/bundle/source):
* Http frontend if any (Caddy, Nginx, Apache, see config examples from Wekan GitHub wiki first):
* Node.js Version:
* MongoDB Version:
* What webbrowser version are you using (Wekan should work on all modern browsers that support Javascript)?
### Problem description
Add a recorded animated gif (e.g. with https://github.com/phw/peek) about
how it works currently, and screenshot mockups how it should work.
#### Reproduction Steps
#### Logs
Check Right Click / Inspect / Console in you browser - generally Chromium
based browsers show more detailed info than Firefox based browsers.
Please anonymize logs.
Snap: sudo snap logs wekan.wekan
Docker: sudo docker logs wekan-app
If logs are very long, attach them in .zip file
+42
View File
@@ -0,0 +1,42 @@
name: 🐛 Bug Report
description: Report a bug to help improve WeKan
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for reporting a bug! Please ensure you are using the [latest version of WeKan](https://wekan.fi/install) and [Upgrade](https://wekan.fi/upgrade) before submitting.
- type: textarea
id: description
attributes:
label: Bug Description
description: A clear and concise description of what the bug is. Mention versions of WeKan, Node.js, database name and version, frontend webserver version like Caddy etc.
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Steps to Reproduce
description: How can we recreate this issue?
placeholder: |
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant Logs
description: |
- Please paste any relevant anonymized server logs or browser console errors here.
- Snap: sudo snap logs wekan.wekan
- Docker: sudo docker logs wekan-app
- If logs are very long, attach them in .zip file
render: shell
- type: textarea
id: context
attributes:
label: Additional Context
description: Add any other context, anonymized screenshots or GIF video about the bug, and screenshot mockups about how it should work.
@@ -0,0 +1,31 @@
name: ✨ Feature Request
description: Suggest a new feature for WeKan
labels: ["Feature:Request"]
body:
- type: textarea
id: feature-description
attributes:
label: Problem Statement
description: Is your feature request related to a problem? Please describe.
placeholder: I'm always frustrated when [...]
validations:
required: true
- type: textarea
id: solution
attributes:
label: Proposed Solution
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives Considered
description: Any alternative solutions or features you've considered.
- type: textarea
id: context
attributes:
label: Additional Context
description: Add any other context, like anonymized screenshot mockups about how it should work.
@@ -0,0 +1,14 @@
name: 🛡️ Security Issue
description: Report a security vulnerability
labels: ["security", "critical"]
body:
- type: markdown
attributes:
value: |
## ⚠️ IMPORTANT: Please do not report security vulnerabilities via public issues.
To protect the WeKan community, we ask that you report security bugs privately. This allows us to fix the issue before it can be exploited by malicious actors.
### How to report:
Please read our **[Security Policy (SECURITY.md)](https://github.com/wekan/wekan/blob/main/SECURITY.md)** for the official reporting process and contact information.
+23
View File
@@ -0,0 +1,23 @@
name: 🗳️ Univention (UCS) Issue
description: Problems specifically related to the WeKan app on Univention Corporate Server
labels: ["UCS"]
body:
- type: markdown
attributes:
value: |
## 🛑 Is this a UCS-specific issue?
If your issue is related to the **Univention Corporate Server (UCS) integration**, packaging, or installation via the Univention App Center, it should be reported in the dedicated Univention repository.
### ➡️ [Report UCS Issues Here](https://github.com/wekan/univention/issues)
---
**Why?**
Reporting there ensures that the maintainers specifically focused on the UCS environment see your request.
If you are certain this is a **core WeKan bug** that affects all platforms (Docker, Snap, etc.), please go back and use the standard [Bug Report](https://github.com/wekan/wekan/issues/new?template=bug-report.yml) template.
- type: textarea
id: ucs-details
attributes:
label: Brief Description (Optional)
description: If you still wish to post here, please provide a brief summary of why this is a core Wekan issue and not a UCS-specific integration bug.
+1 -1
View File
@@ -9,6 +9,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: 'Dependency Review'
uses: actions/dependency-review-action@v4
+4 -4
View File
@@ -32,13 +32,13 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v5
uses: actions/checkout@v6
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -48,14 +48,14 @@ jobs:
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
+1 -1
View File
@@ -15,6 +15,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@v6
- name: Build the Docker image
run: docker build . --file Dockerfile --tag wekan:$(date +%s)
+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"
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
with:
fetch-depth: 0
+19 -14
View File
@@ -18,7 +18,7 @@ jobs:
# runs-on: ubuntu-latest
# steps:
# - name: checkout
# uses: actions/checkout@v5
# uses: actions/checkout@v6
#
# - name: setup node
# uses: actions/setup-node@v1
@@ -42,7 +42,7 @@ jobs:
# needs: [lintcode]
# steps:
# - name: checkout
# uses: actions/checkout@v5
# uses: actions/checkout@v6
#
# - name: setup node
# uses: actions/setup-node@v1
@@ -65,7 +65,7 @@ jobs:
# needs: [lintcode,lintstyle]
# steps:
# - name: checkout
# uses: actions/checkout@v5
# uses: actions/checkout@v6
#
# - name: setup node
# uses: actions/setup-node@v1
@@ -90,12 +90,12 @@ jobs:
# CHECKOUTS
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
# CACHING
- name: Install Meteor
id: cache-meteor-install
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ~/.meteor
key: v1-meteor-${{ hashFiles('.meteor/versions') }}
@@ -104,7 +104,7 @@ jobs:
- name: Cache NPM dependencies
id: cache-meteor-npm
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: ~/.npm
key: v1-npm-${{ hashFiles('package-lock.json') }}
@@ -113,7 +113,7 @@ jobs:
- name: Cache Meteor build
id: cache-meteor-build
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: |
.meteor/local/resolver-result-cache.json
@@ -124,19 +124,24 @@ jobs:
restore-keys: |
v1-meteor_build_cache-
- name: Setup meteor
uses: meteorengineer/setup-meteor@v2
- name: Setup Node.js
uses: actions/setup-node@v6
with:
meteor-release: '2.2'
node-version: '24.14.1'
- name: Setup meteor
uses: meteorengineer/setup-meteor@v3
with:
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
- name: Upload coverage
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: coverage-folder
path: .coverage/
@@ -147,10 +152,10 @@ jobs:
needs: [tests]
steps:
- name: Checkout
uses: actions/checkout@v5
uses: actions/checkout@v6
- name: Download coverage
uses: actions/download-artifact@v5
uses: actions/download-artifact@v8
with:
name: coverage-folder
path: .coverage/
+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
+41 -68
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@0.16.8
standard-minifier-js@2.8.1
mquandalle:jade
coffeescript@2.4.1!
ecmascript@0.17.0
standard-minifier-js@3.2.0
rspack@1.0.0
# Polyfills
es5-shim@4.8.0
blaze@3.0.2
# Collections
aldeed:collection2
cottz:publish-relations
dburles:collection-helpers
idmontie:migrations
easy:search
mongo@1.16.8
mquandalle:collection-mutations
aldeed:schema-index@4.0.0-beta.5
reywood:publish-composite
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
useraccounts:core
useraccounts:flow-routing
useraccounts:unstyled
simple:rest-accounts-password
accounts-password@3.2.3-beta350.7
communitypackages:core
communitypackages:unstyled
communitypackages:flow-routing
# Routing
ostrio:flow-router-extra
wekan-ldap
wekan-accounts-cas
wekan-accounts-sandstorm
@@ -37,60 +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
underscore@1.0.13
arillo:flow-router-helpers
audit-argument-checks@1.0.7
kadira:dochead
mquandalle:autofocus
ongoworks:speakingurl
raix:handlebar-helpers
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
mousetrap:mousetrap
mquandalle:jquery-textcomplete
mquandalle:mousetrap-bindglobal
templates:tabs
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
meteorhacks:aggregate@1.3.0
reactive-var@1.0.13
shell-server@0.7.0
email@3.2.0-beta350.7
dynamic-import@0.7.4
wekan-markdown
konecty:mongo-counter
percolate:synced-cron
quave:synced-cron
ostrio:cookies
ostrio:files@2.3.0
pascoual:pdfkit
lmieulet:meteor-coverage
meteortesting:mocha@2.0.3
aldeed:simple-schema
ostrio:files
# lmieulet:meteor-coverage
meteortesting:mocha
matb33:collection-hooks
simple:json-routes
kadira:flow-router
spacebars
service-configuration@1.3.2
communitypackages:picker
minifier-css@1.6.4
blaze
kadira:blaze-layout
peerlibrary:blaze-components
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
momentjs:moment@2.29.3
# wekan-fontawesome
wekan-fontawesome
+1 -1
View File
@@ -1 +1 @@
METEOR@2.14
METEOR@3.5-beta.7
+114 -157
View File
@@ -1,164 +1,121 @@
accounts-base@2.2.10
accounts-oauth@1.4.3
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
arillo:flow-router-helpers@0.5.2
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.3.2
coffeescript@2.7.0
coffeescript-compiler@2.4.1
communitypackages:picker@1.1.1
cottz:publish-relations@2.0.8
dburles:collection-helpers@1.1.0
ddp@1.4.1
ddp-client@2.6.1
ddp-common@1.4.0
ddp-rate-limiter@1.2.1
ddp-server@2.7.0
deps@1.0.12
diff-sequence@1.1.2
dynamic-import@0.7.3
easy:search@2.2.1
easysearch:components@2.2.2
easysearch:core@2.2.2
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.5
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
idmontie:migrations@1.0.3
inter-process-messaging@0.1.1
jquery@3.0.0
kadira:blaze-layout@2.3.0
kadira:dochead@1.5.0
kadira:flow-router@2.12.1
konecty:mongo-counter@0.0.5_3
lmieulet:meteor-coverage@1.1.4
localstorage@1.2.0
logging@1.3.3
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:aggregate@1.3.0
meteorhacks:collection-utils@1.2.0
meteorhacks:picker@1.0.3
meteorhacks:subs-manager@1.6.4
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:unstyled@2.0.1
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.7.5
minifiers@1.1.8-faster-rebuild.0
minimongo@1.9.3
modern-browsers@0.1.10
modules@0.20.0
modules-runtime@0.13.1
momentjs:moment@2.29.3
mongo@1.16.8
mongo-decimal@0.1.3
mongo-dev-server@1.1.0
mongo-id@1.0.8
mongo-livedata@1.0.12
mousetrap:mousetrap@1.4.6_1
mquandalle:autofocus@1.0.0
mquandalle:collection-mutations@0.1.0
mquandalle:jade@0.4.9
mquandalle:jade-compiler@0.4.5
mquandalle:jquery-textcomplete@0.8.0_1
mquandalle:mousetrap-bindglobal@0.0.1
msavin:usercache@1.8.0
npm-mongo@4.17.2
oauth@2.2.1
oauth2@1.3.2
observe-sequence@1.0.21
ongoworks:speakingurl@1.1.0
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:files@3.0.1
ostrio:flow-router-extra@3.12.1
ostrio:i18n@3.2.1
pascoual:pdfkit@1.0.7
peerlibrary:assert@0.3.0
peerlibrary:base-component@0.17.1
peerlibrary:blaze-components@0.23.0
peerlibrary:computed-field@0.10.0
peerlibrary:data-lookup@0.3.0
peerlibrary:reactive-field@0.6.0
percolate:synced-cron@1.5.2
promise@0.12.2
raix:eventemitter@0.1.3
raix:handlebar-helpers@0.2.5
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
routepolicy@1.1.1
service-configuration@1.3.3
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
templates:tabs@2.3.0
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
ui@1.0.13
underscore@1.0.13
url@1.3.2
useraccounts:core@1.16.2
useraccounts:flow-routing@1.15.0
useraccounts:unstyled@1.14.2
webapp@1.13.6
webapp-hashing@1.1.1
wekan-accounts-cas@0.1.0
wekan-accounts-lockout@1.0.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.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
wekan-accounts-sandstorm@0.8.0
wekan-fullcalendar@3.10.5
wekan-ldap@0.0.2
wekan-accounts-sandstorm@0.9.0
wekan-fontawesome@6.4.2
wekan-fullcalendar@5.11.5
wekan-ldap@0.1.0
wekan-markdown@1.0.9
wekan-oidc@1.0.12
yasaricli:slugify@0.0.7
zimme:active-route@2.3.2
zodern:types@1.0.10
wekan-oidc@1.1.0
zodern:types@1.0.13
+1
View File
@@ -0,0 +1 @@
npm-packages/
+1 -1
View File
@@ -1,6 +1,6 @@
[main]
host = https://www.transifex.com
lang_map = te_IN: te-IN, es_AR: es-AR, es_419: es-LA, es_TX: es-TX, he_IL: he-IL, zh_CN: zh-CN, ar_EG: ar-EG, cs_CZ: cs-CZ, fa_IR: fa-IR, ms_MY: ms-MY, nl_NL: nl-NL, de_CH: de-CH, en_IT: en-IT, uz_UZ: uz-UZ, fr_CH: fr-CH, hi_IN: hi-IN, et_EE: et-EE, es_PE: es-PE, es_MX: es-MX, gl_ES: gl-ES, mn_MN: mn, sl_SI: sl, zh_TW: zh-TW, ast_ES: ast-ES, es_CL: es-CL, ja_JP: ja, lv_LV: lv, ro_RO: ro-RO, az_AZ: az-AZ, cy_GB: cy-GB, gu_IN: gu-IN, pl_PL: pl-PL, vep: ve-PP, en_BR: en-BR, en@ysv: en-YS, hu_HU: hu, ko_KR: ko-KR, pt_BR: pt-BR, zh_HK: zh-HK, zu_ZA: zu-ZA, en_MY: en-MY, ja-Hira: ja-HI, fi_FI: fi, vec: ve-CC, vi_VN: vi-VN, fr_FR: fr-FR, id_ID: id, zh_Hans: zh-Hans, en_DE: en-DE, en_GB: en-GB, el_GR: el-GR, uk_UA: uk-UA, az@latin: az-LA, de_AT: de-AT, uz@Latn: uz-LA, vls: vl-SS, ar_DZ: ar-DZ, bg_BG: bg, es_PY: es-PY, fy_NL: fy-NL, uz@Arab: uz-AR, ru_UA: ru-UA, war: wa-RR, zh_CN.GB2312: zh-GB
lang_map = te_IN: te-IN, es_AR: es-AR, es_419: es-LA, es_TX: es-TX, he_IL: he-IL, zh_CN: zh-CN, ar_EG: ar-EG, cs_CZ: cs-CZ, fa_IR: fa-IR, ms_MY: ms-MY, nl_NL: nl-NL, de_CH: de-CH, en_IT: en-IT, uz_UZ: uz-UZ, fr_CH: fr-CH, hi_IN: hi-IN, et_EE: et-EE, es_PE: es-PE, es_MX: es-MX, gl_ES: gl-ES, mn_MN: mn, zh_TW: zh-TW, ast_ES: ast-ES, es_CL: es-CL, ja_JP: ja, lv_LV: lv, ro_RO: ro-RO, az_AZ: az-AZ, cy_GB: cy-GB, gu_IN: gu-IN, pl_PL: pl-PL, vep: ve-PP, en_BR: en-BR, en@ysv: en-YS, hu_HU: hu, ko_KR: ko-KR, pt_BR: pt-BR, zh_HK: zh-HK, zu_ZA: zu-ZA, en_MY: en-MY, ja-Hira: ja-HI, fi_FI: fi, vec: ve-CC, vi_VN: vi-VN, fr_FR: fr-FR, id_ID: id, zh_Hans: zh-Hans, en_DE: en-DE, en_GB: en-GB, el_GR: el-GR, uk_UA: uk-UA, az@latin: az-LA, de_AT: de-AT, uz@Latn: uz-LA, vls: vl-SS, ar_DZ: ar-DZ, bg_BG: bg, es_PY: es-PY, fy_NL: fy-NL, uz@Arab: uz-AR, ru_UA: ru-UA, war: wa-RR, zh_CN.GB2312: zh-GB
[o:wekan:p:wekan:r:application]
file_filter = imports/i18n/data/<lang>.i18n.json
+4 -1
View File
@@ -9,5 +9,8 @@
"TERM": "xterm-256color"
},
"terminal.integrated.shell.linux": "/bin/bash",
"terminal.integrated.shellArgs.linux": ["-l"]
"terminal.integrated.shellArgs.linux": [
"-l"
],
"files.simpleDialog.enable": true
}
+1380 -15
View File
File diff suppressed because it is too large. Load diff
+16 -15
View File
@@ -6,24 +6,25 @@ Not paid:
- Security reports, see [SECURITY.md](SECURITY.md)
- Pull requests
- xet7 checking pull requests
- Public Community Support
- 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 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
Paid by customers of WeKan Team:
Paid by customers of WeKan to WeKan:
- Commercial Support at https://wekan.fi/commercial-support/
- Support
- Private Chat
- Features
- Fixes
- Hosting
- Optional Private Paid Commercial Support at https://wekan.fi/commercial-support/
- Optional Paid Support/Features/Fixes/PrivateChat . All Paid Features and Fixes are added directly to FLOSS WeKan https://github.com/wekan/wekan
- Optional Paid Official SaaS Hosting https://wekan.fi/saas/ , if you need full-featured hosting, that is more than free app https://wekan.fi/app/
## Contributing Security related
For responsible security disclosure, please follow this process:
https://github.com/wekan/wekan/blob/main/SECURITY.md
For responsible security disclosure, please follow this process: [SECURITY.md](SECURITY.md)
CVE Hall of Fame is at https://wekan.github.io/hall-of-fame/
CVE Hall of Fame is at https://wekan.fi/hall-of-fame/
## Contributing to Documentation Wiki
@@ -33,7 +34,7 @@ and click send PR.
## Contributing code
[Building WeKan and sending PR](https://github.com/wekan/wekan/wiki/Emoji).
[Building WeKan and sending PR](https://github.com/wekan/wekan/blob/main/docs/DeveloperDocs/Build-and-Create-Pull-Request.md).
WeKan code contributors Hall of Fame is at ChangeLog, where
GitHub usernames are mentioned with changes added:
@@ -44,9 +45,9 @@ Changes can be like typo fixes, bugfixes, features, or anything else
like for example at open GitHub issues https://github.com/wekan/wekan/issues .
Closed issues are already fixed or implemented.
Also see other docs at wiki, for example:
Also see other docs:
https://github.com/wekan/wekan/wiki/Developer-Documentation
https://github.com/wekan/wekan/tree/main/docs/DeveloperDocs
Do not use code formatting or linting like eslist or prettier.
@@ -64,7 +65,7 @@ so that WeKan code will still work OK.
## Contributing translations
Non-English translations are contributed only at
https://transifex.com/wekan/wekan
https://app.transifex.com/wekan/wekan
When adding new features, in your PR to
https://github.com/wekan/wekan/pulls
+48 -114
View File
@@ -4,28 +4,23 @@ LABEL org.opencontainers.image.ref.name="ubuntu"
LABEL org.opencontainers.image.version="24.04"
LABEL org.opencontainers.image.source="https://github.com/wekan/wekan"
# 2022-04-25:
# - gyp does not yet work with Ubuntu 22.04 ubuntu:rolling,
# so changing to 21.10. https://github.com/wekan/wekan/issues/4488
# 2021-09-18:
# - Above Ubuntu base image copied from Docker Hub ubuntu:hirsute-20210825
# to Quay to avoid Docker Hub rate limits.
# 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 gosu wget bzip2 g++ curl libarchive-tools build-essential git ca-certificates python3 unzip"
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.14 \
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 \
ARCHITECTURE=linux-x64 \
NPM_VERSION=11.11.0 \
SRC_PATH=./ \
WITH_API=true \
MONGO_OPLOG_URL="" \
RESULTS_PER_PAGE="" \
DEFAULT_BOARD_ID="" \
ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3 \
@@ -163,134 +158,73 @@ ENV \
MONGO_PASSWORD_FILE="" \
S3_SECRET_FILE=""
# NODE_OPTIONS="--max_old_space_size=4096"
#---------------------------------------------
# == at docker-compose.yml: AUTOLOGIN WITH OIDC/OAUTH2 ====
# https://github.com/wekan/wekan/wiki/autologin
#- OIDC_REDIRECTION_ENABLED=true
#---------------------------------------------------------------------
# Copy the app to the image
#COPY ${SRC_PATH} /home/wekan/app
# Install OS
RUN <<EOR
set -o xtrace
# Add non-root user wekan
# Create Wekan user
useradd --user-group --system --home-dir /home/wekan wekan
# OS dependencies
# OS Updates
apt-get update --assume-yes
apt-get upgrade --assume-yes
apt-get install --assume-yes --no-install-recommends ${BUILD_DEPS}
# Meteor installer doesn't work with the default tar binary, so using bsdtar while installing.
# https://github.com/coreos/bugs/issues/1095#issuecomment-350574389
# 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" ;;
"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 (official nodejs.org builds for Node 22)
cd /tmp
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.asc
ln -s "/usr/local/bin/node" "/usr/local/bin/nodejs"
# NPM configuration
npm install -g npm@${NPM_VERSION} --production
chown --recursive wekan:wekan /home/wekan/
# Temporary Tar swap for Meteor bundle
cp $(which tar) $(which tar)~
ln -sf $(which bsdtar) $(which tar)
# Install NodeJS
cd /tmp
# Download nodejs
#wget "https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz"
wget "https://github.com/wekan/node-v14-esm/releases/download/v14.21.4/node-v14.21.4-linux-x64.tar.gz"
#wget "https://github.com/wekan/node-v14-esm/releases/download/${NODE_VERSION}/SHASUMS256.txt"
wget "https://github.com/wekan/node-v14-esm/releases/download/v14.21.4/SHASUMS256.txt"
# Verify nodejs authenticity
#grep "node-${NODE_VERSION}-${ARCHITECTURE}.tar.gz" "SHASUMS256.txt" | shasum -a 256 -c -
grep "node-v14.21.4-linux-x64.tar.gz" "SHASUMS256.txt" | shasum -a 256 -c -
rm -f "SHASUMS256.txt"
# Install Node
#tar xzf "node-$NODE_VERSION-$ARCHITECTURE.tar.gz" -C /usr/local --strip-components=1 --no-same-owner
tar xzf "node-v14.21.4-linux-x64.tar.gz" -C /usr/local --strip-components=1 --no-same-owner
#rm "node-$NODE_VERSION-$ARCHITECTURE.tar.gz" "SHASUMS256.txt"
rm "node-v14.21.4-linux-x64.tar.gz" "SHASUMS256.txt"
ln -s "/usr/local/bin/node" "/usr/local/bin/nodejs"
#mkdir -p "/opt/nodejs/lib/node_modules/fibers/.node-gyp" "/root/.node-gyp/${NODE_VERSION} /home/wekan/.config"
#mkdir -p "/opt/nodejs/lib/node_modules/fibers/.node-gyp" "/root/.node-gyp/v14.21.4 /home/wekan/.config"
# Install node dependencies
#npm install -g npm@${NPM_VERSION} --production
npm install -g npm@$6.14.17 --production
chown --recursive wekan:wekan /home/wekan/.config
# Install Meteor
cd /home/wekan
chown --recursive wekan:wekan /home/wekan
echo "Starting meteor ${METEOR_RELEASE} installation... \n"
#gosu wekan:wekan curl https://install.meteor.com/ | /bin/sh
# Specify Meteor version 2.14 to be compatible: https://github.com/wekan/wekan/pull/5816/files
#gosu wekan:wekan npm -g install meteor@2.14 --unsafe-perm
#mv /root/.meteor /home/wekan/
#chown --recursive wekan:wekan /home/wekan/.meteor
#sed -i 's/api\.versionsFrom/\/\/api.versionsFrom/' /home/wekan/app/packages/meteor-useraccounts-core/package.js
#cd /home/wekan/.meteor
#gosu wekan:wekan /home/wekan/.meteor/meteor -- help
# Build app (Production)
#cd /home/wekan/app
# WeKan Bundle Installation
mkdir -p /home/wekan/app
cd /home/wekan/app
#mkdir -p /home/wekan/.npm
#chown --recursive wekan:wekan /home/wekan/.npm
#chmod u+w *.json
#gosu wekan:wekan meteor npm install --production
#gosu wekan:wekan /home/wekan/.meteor/meteor build --directory /home/wekan/app_build
#cd /home/wekan/app_build/bundle/programs/server/
#chmod u+w *.json
#gosu wekan:wekan meteor npm install --production
#cd node_modules/fibers
#node build.js
#cd ../..
# Remove legacy webbroser bundle, so that Wekan works also at Android Firefox, iOS Safari, etc.
#rm -rf /home/wekan/app_build/bundle/programs/web.browser.legacy
#mv /home/wekan/app_build/bundle /build
wget "https://github.com/wekan/wekan/releases/download/v8.09/wekan-8.09-amd64.zip"
unzip wekan-8.09-amd64.zip
rm wekan-8.09-amd64.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
# Put back the original tar
# Restore original tar
mv $(which tar)~ $(which tar)
# Cleanup
apt-get remove --purge --assume-yes ${BUILD_DEPS}
#npm uninstall -g api2html
apt-get autoremove --assume-yes
apt-get clean --assume-yes
rm -Rf /tmp/*
rm -Rf /var/lib/apt/lists/*
rm -Rf /var/cache/apt
rm -Rf /var/lib/apt/lists
rm -Rf /home/wekan/app_build
rm -Rf /home/wekan/app
rm -Rf /home/wekan/.meteor
mkdir /data
chown wekan --recursive /data
mkdir -p /data
chown wekan:wekan --recursive /data
EOR
USER wekan
ENV PORT=8080
EXPOSE $PORT
STOPSIGNAL SIGKILL
WORKDIR /home/wekan/app
WORKDIR /build
#---------------------------------------------------------------------
# https://github.com/wekan/wekan/issues/3585#issuecomment-1021522132
# Add more Node heap:
# NODE_OPTIONS="--max_old_space_size=4096"
# Add more stack:
# bash -c "ulimit -s 65500; exec node --stack-size=65500 main.js"
#---------------------------------------------------------------------
#
# CMD ["node", "/build/main.js"]
# CMD ["bash", "-c", "ulimit -s 65500; exec node --stack-size=65500 /build/main.js"]
# CMD ["bash", "-c", "ulimit -s 65500; exec node --stack-size=65500 --max-old-space-size=8192 /build/main.js"]
CMD ["bash", "-c", "ulimit -s 65500; exec node /build/main.js"]
CMD ["bash", "-c", "ulimit -s 65500; exec node main.js"]
-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"]
+84 -19
View File
@@ -1,12 +1,36 @@
About money, see [CONTRIBUTING.md](CONTRIBUTING.md)
## About money
Security is very important to us. If you discover any issue regarding security, please disclose
the information responsibly by sending an email from Protonmail to security@wekan.fi
that is Protomail email address, or by using this PGP key
[security-at-wekan.fi.asc](security-at-wekan.fi.asc) to security@wekan.fi
and not by creating a GitHub issue. We will respond swiftly to fix verifiable security issues.
See [CONTRIBUTING](CONTRIBUTING.md)
We thank you with a place at our hall of fame page, that is at https://wekan.fi/hall-of-fame
## Responsible Security Disclosure
1. To send email, if possible, use PGP key [security-at-wekan.fi.asc](security-at-wekan.fi.asc)
2. Send info about security issue ONLY to security@wekan.fi . NOT TO ANYWHERE ELSE. NO CC, NO BCC.
3. Wait for new WeKan release that fixes security issue to appear to top of
https://github.com/wekan/wekan/blob/main/CHANGELOG.md
4. We will thank you by adding you to Hall of Fame: https://wekan.fi/hall-of-fame/
5. All vulnerability details will be private to security@wekan.fi ,
unless you help all WeKan platforms to have a way to upgrade, like sending
database migrations code to security@wekan.fi or PRs to https://github.com/wekan/wekan/pulls .
There is no benefit to Wordwide Security Community to have more details about vulnerabilities,
if Worldwide Security Community does not help to make upgrades possible.
6. If there some day becomes available a way to upgrade all WeKan platforms,
this page will be updated to add permission for security researchers
to request new GHSA or CVE ID and publish your vulnerability details at your blog, talks, etc,
and send that info also to security@wekan.fi to be added to
Hall of Fame: https://wekan.fi/hall-of-fame/ to get Upgrade Bonus Point Stars.
In that case, it will become possible for security@wekan.fi to publish all
remaining private security details, and publicly thank Worldwide Security Community.
## Bonus Points
- If you include code for fixing security issue
## Losing Points
- If you ask about [bounty](CONTRIBUTING.md). There is no bounty. WeKan is NOT Big Tech. WeKan is FLOSS.
- If you forget to include vulnerability details.
- If you send info about security issue to somewhere else than security@wekan.fi
## How should reports be formatted?
@@ -26,7 +50,7 @@ CWSS (optional): %cwss
Anyone who reports a unique security issue in scope and does not disclose it to
a third party before we have patched and updated may be upon their approval
added to the Wekan Hall of Fame.
added to the WeKan Hall of Fame https://wekan.fi/hall-of-fame/
## Which domains are in scope?
@@ -63,11 +87,6 @@ and by by companies that have 30k users.
- If you are thinking about TLS MITM, look at https://github.com/caddyserver/caddy/issues/2530
- Let's Encrypt TLS requires publicly accessible webserver, that Let's Encrypt TLS validation servers check.
- If firewall limits to only allowed IP addresses, you may need non-Let's Encrypt TLS cert.
- For On Premise:
- https://caddyserver.com/docs/automatic-https#local-https
- https://github.com/wekan/wekan/wiki/Caddy-Webserver-Config
- https://github.com/wekan/wekan/wiki/Azure
- https://github.com/wekan/wekan/wiki/Traefik-and-self-signed-SSL-certs
## XSS
@@ -172,6 +191,57 @@ Meteor.startup(() => {
- https://github.com/wekan/wekan/blob/main/client/components/cards/attachments.js#L303-L312
- https://wekan.github.io/hall-of-fame/filebleed/
### Attachments: Forced download to prevent stored XSS
- To prevent browser-side execution of uploaded content under the app origin, all attachment downloads are served with safe headers:
- `Content-Type: application/octet-stream`
- `Content-Disposition: attachment`
- `X-Content-Type-Options: nosniff`
- A restrictive `Content-Security-Policy` with `sandbox`
- This means attachments are downloaded instead of rendered inline by default. This mitigates HTML/JS/SVG based stored XSS vectors.
- Avatars and inline images remain supported but SVG uploads are blocked and never rendered inline.
## Users: Client update restrictions
- Client-side updates to user documents are limited to safe fields only:
- `username`
- `profile.*`
- Sensitive fields are blocked from any client updates and can only be modified by server methods with authorization:
- `orgs`, `teams`, `roles`, `isAdmin`, `createdThroughApi`, `loginDisabled`, `authenticationMethod`, `services.*`, `emails.*`, `sessionData.*`
- Attempts to update forbidden fields from the client are denied.
- Admin operations like managing org/team membership or toggling flags must use server methods that check permissions.
## Voting: integrity and authorization
- Client updates to card `vote` fields are blocked to prevent forged votes and inconsistent policy enforcement.
- Voting is performed via a server method that enforces:
- Authentication and board membership, or an explicit per-card flag allowing non-members to vote.
- Only the caller's own userId is added/removed from `vote.positive`/`vote.negative`.
- This prevents members from fabricating other users' votes and ensures non-members cannot vote unless explicitly allowed.
## Planning Poker: integrity and authorization
- Client updates to card `poker` fields are blocked. All poker actions go through server methods that enforce:
- Authentication and board membership for configuration and results.
- For casting a poker vote, either board membership or an explicit per-card flag allowing non-members to participate.
- Only the caller's own userId is added/removed from the selected estimation bucket (e.g., one, two, five, etc.).
- Methods cover setting/unsetting poker question/end, casting votes, replaying, and setting final estimation.
## Attachment API: authentication and DoS prevention
- The attachment API (`/api/attachment/*`) requires proper authentication using `X-User-Id` and `X-Auth-Token` headers.
- Authentication validates tokens by hashing with `Accounts._hashLoginToken` and matching against stored login tokens, preventing identity spoofing.
- Request handlers implement:
- 30-second timeout to prevent hanging connections.
- Request body size limits (50MB for uploads, 10MB for metadata operations).
- Proper error handling and guaranteed response completion.
- Request error event handlers to clean up failed connections.
- This prevents:
- DoS attacks via concurrent unauthenticated or malformed requests.
- Identity spoofing by using arbitrary bearer tokens or user IDs.
- Resource exhaustion from hanging connections or excessive payloads.
- Access control: all attachment operations verify board membership before allowing access.
## Brute force login protection
- https://github.com/wekan/wekan/commit/23e5e1e3bd081699ce39ce5887db7e612616014d
@@ -218,9 +288,4 @@ Typical already known or "no impact" bugs such as:
- Email spoofing, SPF, DMARC & DKIM. Wekan does not include email server.
Wekan is Open Source with MIT license, and free to use also for commercial use.
We welcome all fixes to improve security by email to security@wekan.team
## Bonus Points
If your Responsible Security Disclosure includes code for fixing security issue,
you get bonus points, as seen on [Hall of Fame](https://wekan.github.io/hall-of-fame).
We welcome all fixes to improve security by email to security@wekan.fi
+1 -1
View File
@@ -1,5 +1,5 @@
appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928
appVersion: "v8.09.0"
appVersion: "v8.78.0"
files:
userUploads:
- README.md
+77 -6
View File
@@ -5,13 +5,84 @@ if ('serviceWorker' in navigator) {
});
}
// Import Blaze helpers (replaces removed raix:handlebar-helpers package)
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
import '/client/lib/migrationManager';
import '/client/components/migrationProgress';
// Import migration manager and progress UI - COMMENTED OUT
// import '/client/lib/attachmentMigrationManager';
// import '/client/components/settings/migrationProgress';
// Import cron settings
import '/client/components/settings/cronSettings';
// Import cron settings - COMMENTED OUT
// import '/client/components/settings/cronSettings';
// Custom head tags
// Mirror Meteor login token into a cookie for server-side file route auth
// This enables cookie-based auth for /cdn/storage/* without leaking ROOT_URL
// Token already lives in localStorage; cookie adds same-origin send-on-request semantics
Meteor.startup(() => {
const COOKIE_NAME = 'meteor_login_token';
const cookieAttrs = () => {
const attrs = ['Path=/', 'SameSite=Lax'];
try {
if (window.location && window.location.protocol === 'https:') {
attrs.push('Secure');
}
} catch (_) {}
return attrs.join('; ');
};
const setCookie = (name, value) => {
if (!value) return;
document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; ${cookieAttrs()}`;
};
const clearCookie = (name) => {
document.cookie = `${encodeURIComponent(name)}=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; ${cookieAttrs()}`;
};
const syncCookie = () => {
try {
const token = Accounts && typeof Accounts._storedLoginToken === 'function' ? Accounts._storedLoginToken() : null;
if (token) setCookie(COOKIE_NAME, token); else clearCookie(COOKIE_NAME);
} catch (e) {
// ignore
}
};
// Initial sync on startup
syncCookie();
// Keep cookie in sync on login/logout
if (Accounts && typeof Accounts.onLogin === 'function') Accounts.onLogin(syncCookie);
if (Accounts && typeof Accounts.onLogout === 'function') Accounts.onLogout(syncCookie);
// Sync across tabs/windows when localStorage changes
window.addEventListener('storage', (ev) => {
if (ev && typeof ev.key === 'string' && ev.key.indexOf('Meteor.loginToken') !== -1) {
syncCookie();
}
});
});
// Subscribe to per-user small publications
Meteor.startup(() => {
Tracker.autorun(() => {
if (Meteor.userId()) {
Meteor.subscribe('userGreyIcons');
Meteor.subscribe('userDesktopDragHandles');
}
});
// Initialize mobile mode on startup for iOS devices
// This ensures mobile mode is applied correctly on page load
Tracker.afterFlush(() => {
if (typeof Utils !== 'undefined' && Utils.initializeUserSettings) {
Utils.initializeUserSettings();
}
});
});
+6 -5
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")
@@ -189,14 +189,15 @@ template(name="activity")
if(currentData.timeKey)
| {{_ activity.activityType }}
= ' '
i(title=currentData.timeValue).activity-meta {{ moment currentData.timeValue 'LLL' }}
i(title=currentData.timeValue).activity-meta {{ displayDate currentData.timeValue 'LLL' }}
if (currentData.timeOldValue)
= ' '
| {{{_ "previous_as" }}}
= ' '
i(title=currentData.timeOldValue).activity-meta {{ moment currentData.timeOldValue 'LLL' }}
i(title=currentData.timeOldValue).activity-meta {{ displayDate currentData.timeOldValue 'LLL' }}
= ' @'
else if(currentData.timeValue)
| {{_ activity.activityType currentData.timeValue}}
div(title=activity.createdAt).activity-meta {{ moment activity.createdAt }}
if($neq mode 'none')
div(title=activity.createdAt).activity-meta {{ displayDate activity.createdAt }}
+140 -118
View File
@@ -1,168 +1,192 @@
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;
BlazeComponent.extendComponent({
onCreated() {
// XXX Should we use ReactiveNumber?
this.page = new ReactiveVar(1);
this.loadNextPageLocked = false;
// TODO is sidebar always available? E.g. on small screens/mobile devices
const sidebar = Sidebar;
sidebar && sidebar.callFirstWith(null, 'resetNextPeak');
this.autorun(() => {
let mode = this.data()?.mode;
if (mode) {
const capitalizedMode = Utils.capitalize(mode);
let searchId;
const showActivities = this.showActivities();
if (mode === 'linkedcard' || mode === 'linkedboard') {
const currentCard = Utils.getCurrentCard();
searchId = currentCard.linkedId;
mode = mode.replace('linked', '');
} else if (mode === 'card') {
searchId = Utils.getCurrentCardId();
} else {
searchId = Session.get(`current${capitalizedMode}`);
}
const limit = this.page.get() * activitiesPerPage;
if (searchId === null) return;
Template.activities.onCreated(function () {
// Register with sidebar so it can call loadNextPage on us
const Sidebar = getSidebarInstance();
if (Sidebar) {
Sidebar.activitiesInstance = this;
}
this.subscribe('activities', mode, searchId, limit, showActivities, () => {
this.loadNextPageLocked = false;
// TODO the guard can be removed as soon as the TODO above is resolved
if (!sidebar) return;
// If the sibear peak hasn't increased, that mean that there are no more
// activities, and we can stop calling new subscriptions.
// XXX This is hacky! We need to know excatly and reactively how many
// activities there are, we probably want to denormalize this number
// dirrectly into card and board documents.
const nextPeakBefore = sidebar.callFirstWith(null, 'getNextPeak');
sidebar.calculateNextPeak();
const nextPeakAfter = sidebar.callFirstWith(null, 'getNextPeak');
if (nextPeakBefore === nextPeakAfter) {
sidebar.callFirstWith(null, 'resetNextPeak');
}
});
}
});
},
loadNextPage() {
// XXX Should we use ReactiveNumber?
this.page = new ReactiveVar(1);
this.loadNextPageLocked = false;
this.loadNextPage = () => {
if (this.loadNextPageLocked === false) {
this.page.set(this.page.get() + 1);
this.loadNextPageLocked = true;
}
},
showActivities() {
let ret = false;
let mode = this.data()?.mode;
};
// TODO is sidebar always available? E.g. on small screens/mobile devices
const sidebar = getSidebarInstance();
if (sidebar && sidebar.infiniteScrolling) {
sidebar.infiniteScrolling.resetNextPeak();
}
this.autorun(() => {
const data = Template.currentData();
let mode = data?.mode;
if (mode) {
const capitalizedMode = Utils.capitalize(mode);
let searchId;
const showActivities = _showActivities(data);
if (mode === 'linkedcard' || mode === 'linkedboard') {
const currentCard = Utils.getCurrentCard();
ret = currentCard.showActivities ?? false;
searchId = currentCard.linkedId;
mode = mode.replace('linked', '');
} else if (mode === 'card') {
ret = this.data()?.card?.showActivities ?? false;
searchId = Utils.getCurrentCardId();
} else {
ret = Utils.getCurrentBoard().showActivities ?? false;
searchId = Session.get(`current${capitalizedMode}`);
}
}
return ret;
},
activities() {
const ret = this.data().card.activities();
return ret;
},
}).register('activities');
const limit = this.page.get() * activitiesPerPage;
if (searchId === null) return;
BlazeComponent.extendComponent({
this.subscribe('activities', mode, searchId, limit, showActivities, () => {
this.loadNextPageLocked = false;
// TODO the guard can be removed as soon as the TODO above is resolved
if (!sidebar || !sidebar.infiniteScrolling) return;
// If the sidebar peak hasn't increased, that means that there are no more
// activities, and we can stop calling new subscriptions.
const nextPeakBefore = sidebar.infiniteScrolling.getNextPeak();
sidebar.calculateNextPeak();
const nextPeakAfter = sidebar.infiniteScrolling.getNextPeak();
if (nextPeakBefore === nextPeakAfter) {
sidebar.infiniteScrolling.resetNextPeak();
}
});
}
});
});
function _showActivities(data) {
let ret = false;
let mode = data?.mode;
if (mode) {
if (mode === 'linkedcard' || mode === 'linkedboard') {
const currentCard = Utils.getCurrentCard();
ret = currentCard.showActivities ?? false;
} else if (mode === 'card') {
// 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;
}
}
return ret;
}
Template.activities.helpers({
showActivities() {
const data = Template.currentData();
return _showActivities(data);
},
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 || [];
},
});
Template.activity.helpers({
checkItem() {
const checkItemId = this.currentData().activity.checklistItemId;
const checkItemId = this.activity.checklistItemId;
const checkItem = ReactiveCache.getChecklistItem(checkItemId);
return checkItem && checkItem.title;
},
boardLabelLink() {
const data = this.currentData();
const currentBoardId = Session.get('currentBoard');
if (data.mode !== 'board') {
// data.mode: card, linkedcard, linkedboard
return createBoardLink(data.activity.board(), data.activity.listName ? data.activity.listName : null);
if (this.mode !== 'board') {
return createBoardLink(this.activity.board(), this.activity.listName ? this.activity.listName : null);
}
else if (currentBoardId != data.activity.boardId) {
// data.mode: board
// current activitie is linked
return createBoardLink(data.activity.board(), data.activity.listName ? data.activity.listName : null);
else if (currentBoardId != this.activity.boardId) {
return createBoardLink(this.activity.board(), this.activity.listName ? this.activity.listName : null);
}
return TAPi18n.__('this-board');
},
cardLabelLink() {
const data = this.currentData();
const currentBoardId = Session.get('currentBoard');
if (data.mode == 'card') {
// data.mode: card
if (this.mode == 'card') {
return TAPi18n.__('this-card');
}
else if (data.mode !== 'board') {
// data.mode: linkedcard, linkedboard
return createCardLink(data.activity.card(), null);
else if (this.mode !== 'board') {
return createCardLink(this.activity.card(), null);
}
else if (currentBoardId != data.activity.boardId) {
// data.mode: board
// current activitie is linked
return createCardLink(data.activity.card(), data.activity.board().title);
else if (currentBoardId != this.activity.boardId) {
return createCardLink(this.activity.card(), this.activity.board().title);
}
return createCardLink(this.currentData().activity.card(), null);
return createCardLink(this.activity.card(), null);
},
cardLink() {
const data = this.currentData();
const currentBoardId = Session.get('currentBoard');
if (data.mode !== 'board') {
// data.mode: card, linkedcard, linkedboard
return createCardLink(data.activity.card(), null);
if (this.mode !== 'board') {
return createCardLink(this.activity.card(), null);
}
else if (currentBoardId != data.activity.boardId) {
// data.mode: board
// current activitie is linked
return createCardLink(data.activity.card(), data.activity.board().title);
else if (currentBoardId != this.activity.boardId) {
return createCardLink(this.activity.card(), this.activity.board().title);
}
return createCardLink(this.currentData().activity.card(), null);
return createCardLink(this.activity.card(), null);
},
receivedDate() {
const receivedDate = this.currentData().activity.card();
if (!receivedDate) return null;
return receivedDate.receivedAt;
const card = this.activity.card();
if (!card) return null;
return card.receivedAt;
},
startDate() {
const startDate = this.currentData().activity.card();
if (!startDate) return null;
return startDate.startAt;
const card = this.activity.card();
if (!card) return null;
return card.startAt;
},
dueDate() {
const dueDate = this.currentData().activity.card();
if (!dueDate) return null;
return dueDate.dueAt;
const card = this.activity.card();
if (!card) return null;
return card.dueAt;
},
endDate() {
const endDate = this.currentData().activity.card();
if (!endDate) return null;
return endDate.endAt;
const card = this.activity.card();
if (!card) return null;
return card.endAt;
},
lastLabel() {
const lastLabelId = this.currentData().activity.labelId;
const lastLabelId = this.activity.labelId;
if (!lastLabelId) return null;
const lastLabel = ReactiveCache.getBoard(
this.currentData().activity.boardId,
this.activity.boardId,
).getLabelById(lastLabelId);
if (lastLabel && (lastLabel.name === undefined || lastLabel.name === '')) {
return lastLabel.color;
@@ -175,7 +199,7 @@ BlazeComponent.extendComponent({
lastCustomField() {
const lastCustomField = ReactiveCache.getCustomField(
this.currentData().activity.customFieldId,
this.activity.customFieldId,
);
if (!lastCustomField) return null;
return lastCustomField.name;
@@ -183,16 +207,15 @@ BlazeComponent.extendComponent({
lastCustomFieldValue() {
const lastCustomField = ReactiveCache.getCustomField(
this.currentData().activity.customFieldId,
this.activity.customFieldId,
);
if (!lastCustomField) return null;
const value = this.currentData().activity.value;
const value = this.activity.value;
if (
lastCustomField.settings.dropdownItems &&
lastCustomField.settings.dropdownItems.length > 0
) {
const dropDownValue = _.find(
lastCustomField.settings.dropdownItems,
const dropDownValue = lastCustomField.settings.dropdownItems.find(
item => {
return item._id === value;
},
@@ -203,13 +226,13 @@ BlazeComponent.extendComponent({
},
listLabel() {
const activity = this.currentData().activity;
const activity = this.activity;
const list = activity.list();
return (list && list.title) || activity.title;
},
sourceLink() {
const source = this.currentData().activity.source;
const source = this.activity.source;
if (source) {
if (source.url) {
return Blaze.toHTML(
@@ -229,12 +252,12 @@ BlazeComponent.extendComponent({
memberLink() {
return Blaze.toHTMLWithData(Template.memberName, {
user: this.currentData().activity.member(),
user: this.activity.member(),
});
},
attachmentLink() {
const attachment = this.currentData().activity.attachment();
const attachment = this.activity.attachment();
// trying to display url before file is stored generates js errors
return (
(attachment &&
@@ -248,17 +271,16 @@ BlazeComponent.extendComponent({
sanitizeText(attachment.name),
),
)) ||
sanitizeText(this.currentData().activity.attachmentName)
sanitizeText(this.activity.attachmentName)
);
},
customField() {
const customField = this.currentData().activity.customField();
const customField = this.activity.customField();
if (!customField) return null;
return customField.name;
},
}).register('activity');
});
Template.activity.helpers({
sanitize(value) {
+5 -8
View File
@@ -108,15 +108,12 @@
text-decoration: none;
height: 24px;
}
.comments .comment .comment-desc .reactions .open-comment-reaction-popup i.fa.fa-smile-o {
font-size: 17px;
.comments .comment .comment-desc .reactions .open-comment-reaction-popup span {
display: inline-block;
font-size: clamp(14px, 2vw, 18px);
font-weight: 500;
margin-left: 2px;
}
.comments .comment .comment-desc .reactions .open-comment-reaction-popup i.fa.fa-plus {
font-size: 8px;
margin-top: -7px;
margin-left: 1px;
line-height: 1;
margin-left: 4px;
}
.comments .comment .comment-desc .reactions .reaction {
cursor: pointer;
+8 -5
View File
@@ -25,13 +25,14 @@ template(name="comment")
= text
.edit-controls
button.primary(type="submit") {{_ 'edit'}}
.fa.fa-times-thin.js-close-inlined-form
a.js-close-inlined-form(title="{{_ 'close' }}")
i.fa.fa-times-thin
else
.comment-text
+viewer
= text
+commentReactions(reactions=reactions commentId=_id)
span(title=createdAt).comment-meta {{ moment createdAt }}
span(title=createdAt).comment-meta {{ displayDate createdAt }}
if($eq currentUser._id userId)
+editOrDeleteComment
else if currentUser.isBoardAdmin
@@ -54,9 +55,11 @@ template(name="commentReactions")
span.reaction-codepoint !{reaction.reactionCodepoint}
span.reaction-count #{reaction.userIds.length}
if (currentUser.isBoardMember)
a.open-comment-reaction-popup(title="{{_ 'addReactionPopup-title'}}")
i.fa.fa-smile-o
i.fa.fa-plus
unless currentUser.isReadOnly
unless currentUser.isReadAssignedOnly
a.open-comment-reaction-popup(title="{{_ 'addReactionPopup-title'}}")
span(title="{{_ 'reaction' }}") 😀
span(title="{{_ 'add' }}") ➕
template(name="addReactionPopup")
.reactions-popup
+65 -74
View File
@@ -1,94 +1,85 @@
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);
BlazeComponent.extendComponent({
onDestroyed() {
commentFormIsOpen.set(false);
$('.note-popover').hide();
},
Template.commentForm.onDestroyed(function () {
commentFormIsOpen.set(false);
$('.note-popover').hide();
});
Template.commentForm.helpers({
commentFormIsOpen() {
return commentFormIsOpen.get();
},
});
getInput() {
return this.$('.js-new-comment-input');
Template.commentForm.events({
'submit .js-new-comment-form'(evt, tpl) {
const input = tpl.$('.js-new-comment-input');
const text = input.val().trim();
const card = Template.currentData();
let boardId = card.boardId;
let cardId = card._id;
if (card.isLinkedCard()) {
boardId = ReactiveCache.getCard(card.linkedId).boardId;
cardId = card.linkedId;
} else if (card.isLinkedBoard()) {
boardId = card.linkedId;
}
if (text) {
CardComments.insert({
text,
boardId,
cardId,
});
resetCommentInput(input);
Tracker.flush();
autosize.update(input);
input.trigger('submitted');
}
evt.preventDefault();
},
events() {
return [
{
'submit .js-new-comment-form'(evt) {
const input = this.getInput();
const text = input.val().trim();
const card = this.currentData();
let boardId = card.boardId;
let cardId = card._id;
if (card.isLinkedCard()) {
boardId = ReactiveCache.getCard(card.linkedId).boardId;
cardId = card.linkedId;
} else if (card.isLinkedBoard()) {
boardId = card.linkedId;
}
if (text) {
CardComments.insert({
text,
boardId,
cardId,
});
resetCommentInput(input);
Tracker.flush();
autosize.update(input);
input.trigger('submitted');
}
evt.preventDefault();
},
// Pressing Ctrl+Enter should submit the form
'keydown form textarea'(evt) {
if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) {
this.find('button[type=submit]').click();
}
},
},
];
// Pressing Ctrl+Enter should submit the form
'keydown form textarea'(evt, tpl) {
if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) {
tpl.find('button[type=submit]').click();
}
},
}).register('commentForm');
});
BlazeComponent.extendComponent({
Template.comments.helpers({
getComments() {
const ret = this.data().comments();
return ret;
const data = Template.currentData();
if (!data || typeof data.comments !== 'function') return [];
return data.comments();
},
}).register("comments");
});
BlazeComponent.extendComponent({
events() {
return [
{
'click .js-delete-comment': Popup.afterConfirm('deleteComment', () => {
const commentId = this.data()._id;
CardComments.remove(commentId);
Popup.back();
}),
'submit .js-edit-comment'(evt) {
evt.preventDefault();
const commentText = this.currentComponent()
.getValue()
.trim();
const commentId = this.data()._id;
if (commentText) {
CardComments.update(commentId, {
$set: {
text: commentText,
},
});
}
Template.comment.events({
'click .js-delete-comment': Popup.afterConfirm('deleteComment', function () {
const commentId = this._id;
CardComments.remove(commentId);
Popup.back();
}),
'submit .js-edit-comment'(evt, tpl) {
evt.preventDefault();
const textarea = tpl.find('.js-edit-comment textarea,input[type=text]');
const commentText = textarea && textarea.value ? textarea.value.trim() : '';
const commentId = this._id;
if (commentText) {
CardComments.update(commentId, {
$set: {
text: commentText,
},
},
];
});
}
},
}).register("comment");
});
// XXX This should be a static method of the `commentForm` component
function resetCommentInput(input) {
@@ -170,14 +170,14 @@
width: 95%;
margin: 20px;
}
.board-conversion-header,
.board-conversion-content,
.board-conversion-footer {
padding-left: 16px;
padding-right: 16px;
}
.board-conversion-header h3 {
font-size: 18px;
}
@@ -3,25 +3,25 @@ template(name="boardConversionProgress")
.board-conversion-modal
.board-conversion-header
h3
| ⚙️
i.fa.fa-cog
| {{_ 'converting-board'}}
p {{_ 'converting-board-description'}}
.board-conversion-content
.conversion-progress
.progress-bar
.progress-fill(style="width: {{conversionProgress}}%")
.progress-text {{conversionProgress}}%
.conversion-status
| ⚙️
i.fa.fa-cog
| {{conversionStatus}}
.conversion-time(style="{{#unless conversionEstimatedTime}}display: none;{{/unless}}")
| ⏰
i.fa.fa-clock-o
| {{_ 'estimated-time-remaining'}}: {{conversionEstimatedTime}}
.board-conversion-footer
.conversion-info
| ℹ️
i.fa.fa-info-circle
| {{_ 'conversion-info-text'}}
+4 -4
View File
@@ -1,6 +1,6 @@
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';
import {
import {
boardConverter,
isConverting,
conversionProgress,
@@ -12,15 +12,15 @@ Template.boardConversionProgress.helpers({
isConverting() {
return isConverting.get();
},
conversionProgress() {
return conversionProgress.get();
},
conversionStatus() {
return conversionStatus.get();
},
conversionEstimatedTime() {
return conversionEstimatedTime.get();
}
+4 -3
View File
@@ -1,6 +1,7 @@
template(name="archivedBoards")
h2
i.fa.fa-archive
span(title="{{_ 'archived-boards'}}")
i.fa.fa-archive
| {{_ 'archived-boards'}}
ul.archived-lists
@@ -8,13 +9,13 @@ template(name="archivedBoards")
li.archived-lists-item
div.board-header-btns
button.board-header-btn.js-delete-board
i.fa.fa-trash-o
i.fa.fa-trash
| {{_ 'delete-board'}}
button.board-header-btn.js-restore-board
i.fa.fa-undo
| {{_ 'restore-board'}}
= title
span {{ moment archivedAt 'LLL' }}
span {{ displayDate archivedAt 'LLL' }}
else
li.no-items-message {{_ 'no-archived-boards'}}
+36 -37
View File
@@ -1,10 +1,13 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import Boards from '/models/boards';
import { Utils } from '/client/lib/utils';
BlazeComponent.extendComponent({
onCreated() {
this.subscribe('archivedBoards');
},
Template.archivedBoards.onCreated(function () {
this.subscribe('archivedBoards');
});
Template.archivedBoards.helpers({
isBoardAdmin() {
return ReactiveCache.getCurrentUser().isBoardAdmin();
},
@@ -18,38 +21,34 @@ BlazeComponent.extendComponent({
);
return ret;
},
});
events() {
return [
{
'click .js-restore-board'() {
// TODO : Make isSandstorm variable global
const isSandstorm =
Meteor.settings &&
Meteor.settings.public &&
Meteor.settings.public.sandstorm;
if (isSandstorm && Utils.getCurrentBoardId()) {
const currentBoard = Utils.getCurrentBoard();
currentBoard.archive();
}
const board = this.currentData();
board.restore();
Utils.goBoardId(board._id);
},
'click .js-delete-board': Popup.afterConfirm('boardDelete', function() {
Popup.back();
const isSandstorm =
Meteor.settings &&
Meteor.settings.public &&
Meteor.settings.public.sandstorm;
if (isSandstorm && Utils.getCurrentBoardId()) {
const currentBoard = Utils.getCurrentBoard();
Boards.remove(currentBoard._id);
}
Boards.remove(this._id);
FlowRouter.go('home');
}),
},
];
Template.archivedBoards.events({
async 'click .js-restore-board'() {
// TODO : Make isSandstorm variable global
const isSandstorm =
Meteor.settings &&
Meteor.settings.public &&
Meteor.settings.public.sandstorm;
if (isSandstorm && Utils.getCurrentBoardId()) {
const currentBoard = Utils.getCurrentBoard();
await currentBoard.archive();
}
const board = this;
await board.restore();
Utils.goBoardId(board._id);
},
}).register('archivedBoards');
'click .js-delete-board': Popup.afterConfirm('boardDelete', async function() {
Popup.back();
const isSandstorm =
Meteor.settings &&
Meteor.settings.public &&
Meteor.settings.public.sandstorm;
if (isSandstorm && Utils.getCurrentBoardId()) {
const currentBoard = Utils.getCurrentBoard();
await Boards.removeAsync(currentBoard._id);
}
await Boards.removeAsync(this._id);
FlowRouter.go('home');
}),
});
+83 -16
View File
@@ -231,6 +231,30 @@
font-size: 1em !important; /* Keep original icon size */
}
/* Mobile iPhone: scale card details text and icons to 2x */
body.mobile-mode.iphone-device .card-details {
font-size: 2em !important;
}
body.mobile-mode.iphone-device .card-details .fa,
body.mobile-mode.iphone-device .card-details .icon,
body.mobile-mode.iphone-device .card-details i,
body.mobile-mode.iphone-device .card-details .emoji-icon,
body.mobile-mode.iphone-device .card-details a,
body.mobile-mode.iphone-device .card-details p,
body.mobile-mode.iphone-device .card-details span,
body.mobile-mode.iphone-device .card-details div,
body.mobile-mode.iphone-device .card-details button,
body.mobile-mode.iphone-device .card-details input,
body.mobile-mode.iphone-device .card-details select,
body.mobile-mode.iphone-device .card-details textarea {
font-size: inherit !important;
}
/* Section titles slightly larger than content but not as big as card title */
body.mobile-mode.iphone-device .card-details .card-details-item-title {
font-size: 1.1em !important;
font-weight: bold;
}
/* Ensure scrollbars are positioned correctly */
#content[style*="overflow-x: auto"]::-webkit-scrollbar:vertical {
width: 12px;
@@ -263,63 +287,106 @@
animation: fadeIn 0.2s;
z-index: 16;
}
/* Fix for mobile Safari: ensure overlay stays behind card details */
@media screen and (max-width: 800px) {
.board-wrapper .board-canvas .board-overlay {
z-index: 17 !important;
}
/* In desktop mode on small screens, still keep overlay behind card */
body.desktop-mode .board-wrapper .board-canvas .board-overlay {
z-index: 17 !important;
}
}
/* In mobile mode, lower the overlay z-index to stay behind card details */
body.mobile-mode .board-wrapper .board-canvas .board-overlay {
z-index: 17 !important;
}
/* iPhone in desktop mode: remove overlay to avoid blocking card */
body.desktop-mode.iphone-device .board-wrapper .board-canvas .board-overlay {
display: none !important;
pointer-events: none !important;
}
/* Desktop mode: hide overlay to allow multiple cards and board interaction */
body.desktop-mode .board-wrapper .board-canvas .board-overlay {
display: none !important;
pointer-events: none !important;
}
.board-wrapper .board-canvas.is-dragging-active .open-minicard-composer,
.board-wrapper .board-canvas.is-dragging-active .minicard-wrapper.is-checked {
display: none;
}
/* Mobile view styles - applied when isMiniScreen is true (iPhone, etc.) */
.board-wrapper.mobile-view {
width: 100% !important;
min-width: 100% !important;
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
left: 0 !important;
right: 0 !important;
overflow-x: hidden !important;
overflow-y: auto !important;
}
.board-wrapper.mobile-view .board-canvas {
width: 100% !important;
min-width: 100% !important;
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
left: 0 !important;
right: 0 !important;
overflow-x: hidden !important;
overflow-y: auto !important;
}
.board-wrapper.mobile-view .board-canvas.mobile-view .swimlane {
border-bottom: 1px solid #ccc;
display: flex;
display: block !important;
flex-direction: column;
margin: 0;
padding: 0;
overflow-x: hidden;
overflow-x: hidden !important;
overflow-y: auto;
width: 100%;
min-width: 100%;
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
}
@media screen and (max-width: 800px),
screen and (max-device-width: 932px) and (-webkit-min-device-pixel-ratio: 3) {
.board-wrapper {
width: 100% !important;
min-width: 100% !important;
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
left: 0 !important;
right: 0 !important;
overflow-x: hidden !important;
overflow-y: auto !important;
}
.board-wrapper .board-canvas {
width: 100% !important;
min-width: 100% !important;
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
left: 0 !important;
right: 0 !important;
overflow-x: hidden !important;
overflow-y: auto !important;
}
.board-wrapper .board-canvas .swimlane {
border-bottom: 1px solid #ccc;
display: flex;
display: block !important;
flex-direction: column;
margin: 0;
padding: 0;
overflow-x: hidden;
overflow-x: hidden !important;
overflow-y: auto;
width: 100%;
min-width: 100%;
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
}
}
.calendar-event-green {
+13 -9
View File
@@ -1,10 +1,8 @@
template(name="board")
if isMigrating.get
+migrationProgress
else if isConverting.get
if isConverting
+boardConversionProgress
else if isBoardReady.get
else if isBoardReady
if currentBoard
if onlyShowCurrentCard
+cardDetails(currentCard)
@@ -24,16 +22,16 @@ template(name="boardBody")
// Debug information (remove in production)
if debugBoardState
.debug-info(style="position: fixed; top: 0; left: 0; background: rgba(0,0,0,0.8); color: white; padding: 10px; z-index: 9999; font-size: 12px;")
| Board: {{currentBoard.title}} | View: {{boardView}} | HasSwimlanes: {{hasSwimlanes}} | Swimlanes: {{currentBoard.swimlanes.length}}
| {{_ 'board'}}: {{currentBoard.title}} | {{_ 'view'}}: {{boardView}} | {{_ 'has-swimlanes'}}: {{hasSwimlanes}} | {{_ 'swimlanes'}}: {{currentBoard.swimlanes.length}}
.board-wrapper(class=currentBoard.colorClass class="{{#if isMiniScreen}}mobile-view{{/if}}")
.board-canvas.js-swimlanes(
class="{{#if hasSwimlanes}}dragscroll{{/if}}"
class="{{#if Sidebar.isOpen}}is-sibling-sidebar-open{{/if}}"
class="{{#if MultiSelection.isActive}}is-multiselection-active{{/if}}"
class="{{#if draggingActive.get}}is-dragging-active{{/if}}"
class="{{#if draggingActive}}is-dragging-active{{/if}}"
class="{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}"
class="{{#if isMiniScreen}}mobile-view{{/if}}")
if showOverlay.get
if showOverlay
.board-overlay
if currentBoard.isTemplatesBoard
each currentBoard.swimlanes
@@ -49,6 +47,8 @@ template(name="boardBody")
+listsGroup(currentBoard)
else if isViewCalendar
+calendarView
else if isViewGantt
+ganttView
else
// Default view - show swimlanes if they exist, otherwise show lists
if hasSwimlanes
@@ -56,6 +56,10 @@ template(name="boardBody")
+swimlane(this)
else
+listsGroup(currentBoard)
//- Render multiple open cards in desktop mode
unless isMiniScreen
each openCards
+cardDetails(this cardIndex=@index)
+sidebar
template(name="calendarView")
@@ -63,4 +67,4 @@ template(name="calendarView")
.calendar-view.swimlane
if currentCard
+cardDetails(currentCard)
+fullcalendar(calendarOptions)
+fullcalendar(calendarOptions)
File diff suppressed because it is too large. Load diff
+117 -2
View File
@@ -49,6 +49,12 @@ THEME - NEPHRITIS
border-bottom: 2px solid #27ae60;
border-right: 2px solid #27ae60;
}
.board-color-nephritis .checklist-progress-bar {
background-color: #d4f1dd !important;
}
.board-color-nephritis .checklist-progress-bar .checklist-progress {
background-color: #27ae60 !important;
}
.board-color-nephritis .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard {
background: #e7faef;
}
@@ -150,6 +156,12 @@ THEME - Pomegranate
border-bottom: 2px solid #c0392b;
border-right: 2px solid #c0392b;
}
.board-color-pomegranate .checklist-progress-bar {
background-color: #f5d5d2 !important;
}
.board-color-pomegranate .checklist-progress-bar .checklist-progress {
background-color: #c0392b !important;
}
.board-color-pomegranate .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard {
background: #faeae9;
}
@@ -251,6 +263,12 @@ THEME - Belize
border-bottom: 2px solid #2980b9;
border-right: 2px solid #2980b9;
}
.board-color-belize .checklist-progress-bar {
background-color: #d1e7f5 !important;
}
.board-color-belize .checklist-progress-bar .checklist-progress {
background-color: #2980b9 !important;
}
.board-color-belize .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard {
background: #e8f3fa;
}
@@ -352,6 +370,12 @@ THEME - Wisteria
border-bottom: 2px solid #8e44ad;
border-right: 2px solid #8e44ad;
}
.board-color-wisteria .checklist-progress-bar {
background-color: #e8d9f0 !important;
}
.board-color-wisteria .checklist-progress-bar .checklist-progress {
background-color: #8e44ad !important;
}
.board-color-wisteria .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard {
background: #f4ecf7;
}
@@ -453,6 +477,12 @@ THEME - Midnight
border-bottom: 2px solid #2c3e50;
border-right: 2px solid #2c3e50;
}
.board-color-midnight .checklist-progress-bar {
background-color: #d2dae2 !important;
}
.board-color-midnight .checklist-progress-bar .checklist-progress {
background-color: #2c3e50 !important;
}
.board-color-midnight .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard {
background: #e6ecf1;
}
@@ -554,6 +584,12 @@ THEME - Pumpkin
border-bottom: 2px solid #e67e22;
border-right: 2px solid #e67e22;
}
.board-color-pumpkin .checklist-progress-bar {
background-color: #f9e5d1 !important;
}
.board-color-pumpkin .checklist-progress-bar .checklist-progress {
background-color: #e67e22 !important;
}
.board-color-pumpkin .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard {
background: #fdf2e9;
}
@@ -655,6 +691,12 @@ THEME - Moderate Pink
border-bottom: 2px solid #cd5a91;
border-right: 2px solid #cd5a91;
}
.board-color-moderatepink .checklist-progress-bar {
background-color: #f4dde8 !important;
}
.board-color-moderatepink .checklist-progress-bar .checklist-progress {
background-color: #cd5a91 !important;
}
.board-color-moderatepink .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard {
background: #faeef4;
}
@@ -756,6 +798,12 @@ THEME - Strong Cyan
border-bottom: 2px solid #00aecc;
border-right: 2px solid #00aecc;
}
.board-color-strongcyan .checklist-progress-bar {
background-color: #ccf2f9 !important;
}
.board-color-strongcyan .checklist-progress-bar .checklist-progress {
background-color: #00aecc !important;
}
.board-color-strongcyan .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard {
background: #e0fbff;
}
@@ -857,6 +905,12 @@ THEME - Lime Green
border-bottom: 2px solid #4bbf6b;
border-right: 2px solid #4bbf6b;
}
.board-color-limegreen .checklist-progress-bar {
background-color: #daf4de !important;
}
.board-color-limegreen .checklist-progress-bar .checklist-progress {
background-color: #4bbf6b !important;
}
.board-color-limegreen .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard {
background: #edf9f0;
}
@@ -959,6 +1013,12 @@ THEME - Dark
border-bottom: 2px solid #2c3e51;
border-right: 2px solid #2c3e51;
}
.board-color-dark .checklist-progress-bar {
background-color: #d2dae2 !important;
}
.board-color-dark .checklist-progress-bar .checklist-progress {
background-color: #2c3e51 !important;
}
.board-color-dark .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard {
background: #e6ecf1;
}
@@ -1162,6 +1222,12 @@ THEME - Relax
border-bottom: 2px solid #27ae61;
border-right: 2px solid #27ae61;
}
.board-color-relax .checklist-progress-bar {
background-color: #d4f1dd !important;
}
.board-color-relax .checklist-progress-bar .checklist-progress {
background-color: #27ae61 !important;
}
.board-color-relax .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard {
background: #e7faef;
}
@@ -1292,6 +1358,12 @@ THEME - Corteza
border-bottom: 2px solid #568ba2;
border-right: 2px solid #568ba2;
}
.board-color-corteza .checklist-progress-bar {
background-color: #dce6ec !important;
}
.board-color-corteza .checklist-progress-bar .checklist-progress {
background-color: #568ba2 !important;
}
.board-color-corteza .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard {
background: #eef3f6;
}
@@ -1397,6 +1469,12 @@ THEME - Clear Blue
border-bottom: 2px solid #499bea;
border-right: 2px solid #499bea;
}
.board-color-clearblue .checklist-progress-bar {
background-color: #daeefb !important;
}
.board-color-clearblue .checklist-progress-bar .checklist-progress {
background-color: #499bea !important;
}
.board-color-clearblue .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard {
background: #e0fbff;
}
@@ -1425,7 +1503,7 @@ THEME - Clear Blue
}
.board-color-clearblue .list {
background: rgba(255,255,255,0.35);
margin: 10px;
margin: 10px 0;
border: 0;
border-radius: 14px;
}
@@ -1660,6 +1738,12 @@ THEME - Natural
border-bottom: 2px solid #596557;
border-right: 2px solid #596557;
}
.board-color-natural .checklist-progress-bar {
background-color: #dee0dd !important;
}
.board-color-natural .checklist-progress-bar .checklist-progress {
background-color: #596557 !important;
}
.board-color-natural .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard {
background: #eef0ee;
}
@@ -1770,6 +1854,12 @@ THEME - Modern
border-bottom: 2px solid #2a80b8;
border-right: 2px solid #2a80b8;
}
.board-color-modern .checklist-progress-bar {
background-color: #d1e7f5 !important;
}
.board-color-modern .checklist-progress-bar .checklist-progress {
background-color: #2a80b8 !important;
}
.board-color-modern .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard {
background: #e8f3fa;
}
@@ -2062,6 +2152,12 @@ THEME - Modern Dark
border-bottom: 2px solid #2a2a2a;
border-right: 2px solid #2a2a2a;
}
.board-color-moderndark .checklist-progress-bar {
background-color: #d1d1d1 !important;
}
.board-color-moderndark .checklist-progress-bar .checklist-progress {
background-color: #2a2a2a !important;
}
.board-color-moderndark .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard {
background: #eaeaea;
}
@@ -2547,6 +2643,12 @@ THEME - Exodark
border-bottom: 2px solid #dbdbdb!important;/*Fix contrast of checkbox*/
border-right: 2px solid #dbdbdb!important;
}
.board-color-exodark .checklist-progress-bar {
background-color: #cccccc !important;
}
.board-color-exodark .checklist-progress-bar .checklist-progress {
background-color: #222 !important;
}
.board-color-exodark .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard {
background: #e9e9e9;
}
@@ -2588,7 +2690,7 @@ THEME - Exodark
background: #222;
}
.board-color-exodark .list {
margin: 10px;
margin: 10px 0;
color: #fff;
border-radius: 15px;
background-color: #1c1c1c;
@@ -3140,6 +3242,12 @@ THEME - Clean Dark
margin-left: 3px;
margin-top: 3px;
}
.board-color-cleandark .checklist-progress-bar {
background-color: #6b6b78 !important;
}
.board-color-cleandark .checklist-progress-bar .checklist-progress {
background-color: #23232B !important;
}
.board-color-cleandark .allBoards {
white-space: nowrap;
@@ -3892,6 +4000,13 @@ THEME - Clean Light
margin-left: 3px;
margin-top: 3px;
}
.board-color-cleanlight .checklist-progress-bar {
background-color: #f5f5f5 !important;
}
.board-color-cleanlight .checklist-progress-bar .checklist-progress {
background-color: #c0c0c0 !important;
color: #010101 !important;
}
.board-color-cleanlight .allBoards {
white-space: nowrap;
+63 -63
View File
@@ -505,73 +505,73 @@
flex-wrap: nowrap !important;
align-items: stretch !important;
justify-content: flex-start !important;
width: 100% !important;
max-width: 100% !important;
min-width: 100% !important;
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
overflow-x: hidden !important;
overflow-y: auto !important;
}
.mobile-mode .swimlane {
display: block !important;
width: 100% !important;
max-width: 100% !important;
min-width: 100% !important;
margin: 0 0 2rem 0 !important;
padding: 0 !important;
float: none !important;
clear: both !important;
}
.mobile-mode .swimlane {
display: block !important;
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
margin: 0 0 2rem 0 !important;
padding: 0 !important;
float: none !important;
clear: both !important;
}
.mobile-mode .swimlane .swimlane-header {
display: block !important;
width: 100% !important;
max-width: 100% !important;
min-width: 100% !important;
margin: 0 0 1rem 0 !important;
padding: 1rem !important;
font-size: clamp(18px, 2.5vw, 32px) !important;
font-weight: bold !important;
border-bottom: 2px solid #ccc !important;
}
.mobile-mode .swimlane .swimlane-header {
display: block !important;
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
margin: 0 0 1rem 0 !important;
padding: 1rem !important;
font-size: clamp(18px, 2.5vw, 32px) !important;
font-weight: bold !important;
border-bottom: 2px solid #ccc !important;
}
.mobile-mode .swimlane .lists {
display: block !important;
width: 100% !important;
max-width: 100% !important;
min-width: 100% !important;
margin: 0 !important;
padding: 0 !important;
flex-direction: column !important;
flex-wrap: nowrap !important;
align-items: stretch !important;
justify-content: flex-start !important;
}
.mobile-mode .swimlane .lists {
display: block !important;
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
margin: 0 !important;
padding: 0 !important;
flex-direction: column !important;
flex-wrap: nowrap !important;
align-items: stretch !important;
justify-content: flex-start !important;
}
.mobile-mode .list {
display: block !important;
width: 100% !important;
max-width: 100% !important;
min-width: 100% !important;
margin: 0 0 2rem 0 !important;
padding: 0 !important;
float: none !important;
clear: both !important;
border-left: none !important;
border-right: none !important;
border-top: none !important;
border-bottom: 2px solid #ccc !important;
flex: none !important;
flex-basis: auto !important;
flex-grow: 0 !important;
flex-shrink: 0 !important;
position: static !important;
left: auto !important;
right: auto !important;
top: auto !important;
bottom: auto !important;
transform: none !important;
}
.mobile-mode .list {
display: block !important;
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
margin: 0 0 2rem 0 !important;
padding: 0 !important;
float: none !important;
clear: both !important;
border-left: none !important;
border-right: none !important;
border-top: none !important;
border-bottom: 2px solid #ccc !important;
flex: none !important;
flex-basis: auto !important;
flex-grow: 0 !important;
flex-shrink: 0 !important;
position: static !important;
left: auto !important;
right: auto !important;
top: auto !important;
bottom: auto !important;
transform: none !important;
}
.mobile-mode .list:first-child {
margin-left: 0 !important;
@@ -667,9 +667,9 @@
flex-wrap: nowrap !important;
align-items: stretch !important;
justify-content: flex-start !important;
width: 100% !important;
max-width: 100% !important;
min-width: 100% !important;
width: 100vw !important;
max-width: 100vw !important;
min-width: 100vw !important;
overflow-x: hidden !important;
overflow-y: auto !important;
}
+179 -89
View File
@@ -14,41 +14,39 @@ template(name="boardHeaderBar")
with currentBoard
if currentUser.isBoardAdmin
a.board-header-btn(class="{{#if currentUser.isBoardAdmin}}js-edit-board-title{{else}}is-disabled{{/if}}" title="{{_ 'edit'}}" value=title)
| ✏️
a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}"
title="{{#if isStarred}}{{_ 'star-board-short-unstar'}}{{else}}{{_ 'star-board-short-star'}}{{/if}}" aria-label="{{#if isStarred}}{{_ 'star-board-short-unstar'}}{{else}}{{_ 'star-board-short-star'}}{{/if}}")
| {{#if isStarred}}⭐{{else}}☆{{/if}}
if showStarCounter
span
= currentBoard.stars
i.fa.fa-pencil-square-o
a.board-header-btn(
class="{{#if currentUser.isBoardAdmin}}js-change-visibility{{else}}is-disabled{{/if}}"
title="{{_ currentBoard.permission}}")
| {{#if currentBoard.isPublic}}🌐{{else}}🔒{{/if}}
i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
span {{_ currentBoard.permission}}
a.board-header-btn.js-watch-board(
title="{{_ watchLevel }}")
if $eq watchLevel "watching"
| 👁️
i.fa.fa-eye
if $eq watchLevel "tracking"
| 🔔
i.fa.fa-bell
if $eq watchLevel "muted"
| 🔕
i.fa.fa-bell-slash
span {{_ watchLevel}}
a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}"
title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}")
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
if showStarCounter
span.board-star-counter {{currentBoard.stars}}
a.board-header-btn(title="{{_ 'sort-cards'}}" class="{{#if isSortActive }}emphasis{{else}} js-sort-cards {{/if}}")
| {{sortCardsIcon}}
i.fa.fa-sort
span {{#if isSortActive }}{{_ 'sort-is-on'}}{{else}}{{_ 'sort-cards'}}{{/if}}
if isSortActive
a.board-header-btn-close.js-sort-reset(title="{{_ 'remove-sort'}}")
| ❌
i.fa.fa-times-thin
else
a.board-header-btn.js-log-in(
title="{{_ 'log-in'}}")
| 🚪
i.fa.fa-sign-in
span {{_ 'log-in'}}
.board-header-btns.center
@@ -59,99 +57,114 @@ template(name="boardHeaderBar")
if currentUser
with currentBoard
a.board-header-btn(class="{{#if currentUser.isBoardAdmin}}js-edit-board-title{{else}}is-disabled{{/if}}" title="{{_ 'edit'}}" value=title)
| ✏️
a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}"
title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}")
| {{#if isStarred}}⭐{{else}}☆{{/if}}
i.fa.fa-pencil-square-o
a.board-header-btn(
class="{{#if currentUser.isBoardAdmin}}js-change-visibility{{else}}is-disabled{{/if}}"
title="{{_ currentBoard.permission}}")
| {{#if currentBoard.isPublic}}🌐{{else}}🔒{{/if}}
i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
a.board-header-btn.js-watch-board(
title="{{_ watchLevel }}")
if $eq watchLevel "watching"
| 👁️
i.fa.fa-eye
if $eq watchLevel "tracking"
| 🔔
i.fa.fa-bell
if $eq watchLevel "muted"
| 🔕
i.fa.fa-bell-slash
a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}"
title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}")
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
a.board-header-btn(title="{{_ 'sort-cards'}}" class="{{#if isSortActive }}emphasis{{else}} js-sort-cards {{/if}}")
| {{sortCardsIcon}}
i.fa.fa-sort
span {{#if isSortActive }}{{_ 'sort-is-on'}}{{else}}{{_ 'sort-cards'}}{{/if}}
if isSortActive
a.board-header-btn-close.js-sort-reset(title="{{_ 'remove-sort'}}")
| ❌
i.fa.fa-times-thin
else
a.board-header-btn.js-log-in(
title="{{_ 'log-in'}}")
| 🚪
i.fa.fa-sign-in
if isSandstorm
if currentUser
a.board-header-btn.js-open-archived-board
| 📦
i.fa.fa-archive
//if showSort
// a.board-header-btn.js-open-sort-view(title="{{_ 'sort-desc'}}")
// i.fa(class="{{directionClass}}")
// span {{_ 'sort'}}{{_ listSortShortDesc}}
//
a.board-header-btn.js-open-sort-view(title="{{_ 'sort-desc'}}")
//
i.fa(class="{{directionClass}}")
//
span {{_ 'sort'}}{{_ listSortShortDesc}}
a.board-header-btn.js-open-filter-view(
title="{{#if Filter.isActive}}{{_ 'filter-on-desc'}}{{else}}{{_ 'filter'}}{{/if}}"
class="{{#if Filter.isActive}}emphasis{{/if}}")
| 🔽
class="{{#if Filter.isActive}}js-filter-active{{/if}}")
i.fa.fa-filter
span {{#if Filter.isActive}}{{_ 'filter-on-desc'}}{{else}}{{_ 'filter'}}{{/if}}
if Filter.isActive
a.board-header-btn-close.js-filter-reset(title="{{_ 'filter-clear'}}")
| ❌
i.fa.fa-times-thin
a.board-header-btn.js-open-search-view(title="{{_ 'search'}}")
| 🔍
i.fa.fa-search
span {{_ 'search'}}
unless currentBoard.isTemplatesBoard
a.board-header-btn.js-toggle-board-view(
title="{{_ 'board-view'}}")
| ▼
a.board-header-btn.js-toggle-board-view
i.fa.fa-caret-down
if $eq boardView 'board-view-swimlanes'
| 🏊
i.fa.fa-th-large
if $eq boardView 'board-view-lists'
| 📋
i.fa.fa-trello
if $eq boardView 'board-view-cal'
| 📅
i.fa.fa-calendar
if $eq boardView 'board-view-gantt'
i.fa.fa-bar-chart
span
if $eq boardView 'board-view-swimlanes'
| {{_ 'swimlanes'}}
if $eq boardView 'board-view-lists'
| {{_ 'lists'}}
if $eq boardView 'board-view-cal'
| {{_ 'calendar'}}
if $eq boardView 'board-view-gantt'
| {{_ 'gantt'}}
if canModifyBoard
a.board-header-btn.js-multiselection-activate(
title="{{#if MultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}}"
class="{{#if MultiSelection.isActive}}emphasis{{/if}}")
| ☑️
if MultiSelection.isActive
a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}")
| ❌
i.fa.fa-check-square-o
span {{#if MultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}}
if MultiSelection.isActive
a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}")
i.fa.fa-times-thin
.separator
a.board-header-btn.js-toggle-sidebar(title="{{_ 'sidebar-open'}} {{_ 'or'}} {{_ 'sidebar-close'}}")
| ☰
i.fa.fa-bars
template(name="boardVisibilityList")
ul.pop-over-list
li
with "private"
a.js-select-visibility
| 🔒
i.fa.fa-lock
| {{_ 'private'}}
if visibilityCheck
| ✅
i.fa.fa-check
span.sub-name {{_ 'private-desc'}}
if notAllowPrivateVisibilityOnly
li
with "public"
a.js-select-visibility
| 🌐
i.fa.fa-globe
| {{_ 'public'}}
if visibilityCheck
| ✅
i.fa.fa-check
span.sub-name {{_ 'public-desc'}}
template(name="boardChangeVisibilityPopup")
@@ -162,26 +175,26 @@ template(name="boardChangeWatchPopup")
li
with "watching"
a.js-select-watch
| 👁️
i.fa.fa-eye
| {{_ 'watching'}}
if watchCheck
| ✅
i.fa.fa-check
span.sub-name {{_ 'watching-info'}}
li
with "tracking"
a.js-select-watch
| 🔔
i.fa.fa-bell
| {{_ 'tracking'}}
if watchCheck
| ✅
i.fa.fa-check
span.sub-name {{_ 'tracking-info'}}
li
with "muted"
a.js-select-watch
| 🔕
i.fa.fa-bell-slash
| {{_ 'muted'}}
if watchCheck
| ✅
i.fa.fa-check
span.sub-name {{_ 'muted-info'}}
template(name="boardChangeViewPopup")
@@ -189,40 +202,47 @@ template(name="boardChangeViewPopup")
li
with "board-view-swimlanes"
a.js-open-swimlanes-view
| 🏊
| {{_ 'board-view-swimlanes'}}
i.fa.fa-th-large
| {{_ 'swimlanes'}}
if $eq Utils.boardView "board-view-swimlanes"
| ✅
i.fa.fa-check
li
with "board-view-lists"
a.js-open-lists-view
| 📋
i.fa.fa-trello
| {{_ 'board-view-lists'}}
if $eq Utils.boardView "board-view-lists"
| ✅
i.fa.fa-check
li
with "board-view-cal"
a.js-open-cal-view
| 📅
i.fa.fa-calendar
| {{_ 'board-view-cal'}}
if $eq Utils.boardView "board-view-cal"
| ✅
i.fa.fa-check
li
with "board-view-gantt"
a.js-open-gantt-view
i.fa.fa-bar-chart
| {{_ 'board-view-gantt'}}
if $eq Utils.boardView "board-view-gantt"
i.fa.fa-check
template(name="createBoard")
form
label
| {{_ 'title'}}
input.js-new-board-title(type="text" placeholder="{{_ 'bucket-example'}}" autofocus required)
if visibilityMenuIsOpen.get
if visibilityMenuIsOpen
+boardVisibilityList
else
p.quiet
if $eq visibility.get 'public'
span 🌐
if $eq visibility 'public'
span.fa.fa-globe.colorful
= " "
| {{{_ 'board-public-info'}}}
else
span 🔒
span.fa.fa-lock.colorful
= " "
| {{{_ 'board-private-info'}}}
a.js-change-visibility {{_ 'change'}}.
@@ -242,16 +262,75 @@ template(name="createBoardPopup")
label
| {{_ 'title'}}
input.js-new-board-title(type="text" placeholder="{{_ 'bucket-example'}}" autofocus required)
if visibilityMenuIsOpen.get
if visibilityMenuIsOpen
+boardVisibilityList
else
p.quiet
if $eq visibility.get 'public'
span 🌐
if $eq visibility 'public'
span.fa.fa-globe.colorful
= " "
| {{{_ 'board-public-info'}}}
else
span 🔒
span.fa.fa-lock.colorful
= " "
| {{{_ 'board-private-info'}}}
a.js-change-visibility {{_ 'change'}}.
a.flex.js-toggle-add-template-container
.materialCheckBox#add-template-container
span {{_ 'add-template-container'}}
input.primary.wide(type="submit" value="{{_ 'create'}}")
span.quiet
| {{_ 'or'}}
a.js-import-board {{_ 'import'}}
span.quiet
| /
a.js-board-template {{_ 'template'}}
template(name="headerBarCreateBoardPopup")
form
label
| {{_ 'title'}}
input.js-new-board-title(type="text" placeholder="{{_ 'bucket-example'}}" autofocus required)
if visibilityMenuIsOpen
+boardVisibilityList
else
p.quiet
if $eq visibility 'public'
span.fa.fa-globe.colorful
= " "
| {{{_ 'board-public-info'}}}
else
span.fa.fa-lock.colorful
= " "
| {{{_ 'board-private-info'}}}
a.js-change-visibility {{_ 'change'}}.
a.flex.js-toggle-add-template-container
.materialCheckBox#add-template-container
span {{_ 'add-template-container'}}
input.primary.wide(type="submit" value="{{_ 'create'}}")
span.quiet
| {{_ 'or'}}
a.js-import-board {{_ 'import'}}
span.quiet
| /
a.js-board-template {{_ 'template'}}
// New popup for Template Container creation; shares the same form content
template(name="createTemplateContainerPopup")
form
label
| {{_ 'title'}}
input.js-new-board-title(type="text" placeholder="{{_ 'bucket-example'}}" autofocus required)
if visibilityMenuIsOpen
+boardVisibilityList
else
p.quiet
if $eq visibility 'public'
span.fa.fa-globe.colorful
= " "
| {{{_ 'board-public-info'}}}
else
span.fa.fa-lock.colorful
= " "
| {{{_ 'board-private-info'}}}
a.js-change-visibility {{_ 'change'}}.
@@ -267,19 +346,30 @@ template(name="createBoardPopup")
a.js-board-template {{_ 'template'}}
//template(name="listsortPopup")
// h2
// | {{_ 'list-sort-by'}}
// hr
// ul.pop-over-list
// each value in allowedSortValues
// li
// a.js-sort-by(name="{{value.name}}")
// if $eq sortby value.name
// | {{#if $eq Direction "fa-arrow-up"}}⬆️{{else}}⬇️{{/if}}
// | {{_ value.label }}{{_ value.shortLabel}}
// if $eq sortby value.name
// | ✅
//
h2
//
| {{_ 'list-sort-by'}}
//
hr
//
ul.pop-over-list
//
each value in allowedSortValues
//
li
//
a.js-sort-by(name="{{value.name}}")
//
if $eq sortby value.name
//
| {{#if $eq Direction "fa-arrow-up"}}⬆️{{else}}⬇️{{/if}}
//
| {{_ value.label }}{{_ value.shortLabel}}
//
if $eq sortby value.name
//
i.fa.fa-check
template(name="boardChangeTitlePopup")
form
label
@@ -299,21 +389,21 @@ template(name="cardsSortPopup")
ul.pop-over-list
li
a.js-sort-due
| 📅
i.fa.fa-calendar
| {{_ 'due-date'}}
hr
li
a.js-sort-title
| 🔤
i.fa.fa-sort-alpha-asc
| {{_ 'title-alphabetically'}}
hr
li
a.js-sort-created-desc
| ⬇️
i.fa.fa-arrow-down
| {{_ 'created-at-newest-first'}}
hr
li
a.js-sort-created-asc
| ⬆️
i.fa.fa-arrow-up
| {{_ 'created-at-oldest-first'}}
+365 -334
View File
@@ -1,6 +1,15 @@
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';
@@ -9,7 +18,8 @@ const UPCLS = 'fa-sort-up';
const sortCardsBy = new ReactiveVar('');
Template.boardChangeTitlePopup.events({
submit(event, templateInstance) {
async submit(event, templateInstance) {
event.preventDefault();
const newTitle = templateInstance
.$('.js-board-name')
.val()
@@ -19,22 +29,22 @@ Template.boardChangeTitlePopup.events({
.val()
.trim();
if (newTitle) {
this.rename(newTitle);
this.setDescription(newDesc);
const board = Utils.getCurrentBoard();
if (board) {
await board.rename(newTitle);
await board.setDescription(newDesc);
}
Popup.back();
}
event.preventDefault();
},
});
BlazeComponent.extendComponent({
Template.boardHeaderBar.helpers({
watchLevel() {
const currentBoard = Utils.getCurrentBoard();
return currentBoard && currentBoard.getWatchLevel(Meteor.userId());
},
isStarred() {
const boardId = Session.get('currentBoard');
const user = ReactiveCache.getCurrentUser();
@@ -46,119 +56,7 @@ BlazeComponent.extendComponent({
const currentBoard = Utils.getCurrentBoard();
return currentBoard && currentBoard.stars >= 2;
},
/*
showSort() {
return ReactiveCache.getCurrentUser().hasSortBy();
},
directionClass() {
return this.currentDirection() === -1 ? DOWNCLS : UPCLS;
},
changeDirection() {
const direction = 0 - this.currentDirection() === -1 ? '-' : '';
Meteor.call('setListSortBy', direction + this.currentListSortBy());
},
currentDirection() {
return ReactiveCache.getCurrentUser().getListSortByDirection();
},
currentListSortBy() {
return ReactiveCache.getCurrentUser().getListSortBy();
},
listSortShortDesc() {
return `list-label-short-${this.currentListSortBy()}`;
},
*/
events() {
return [
{
'click .js-edit-board-title': Popup.open('boardChangeTitle'),
'click .js-star-board'() {
ReactiveCache.getCurrentUser().toggleBoardStar(Session.get('currentBoard'));
},
'click .js-open-board-menu': Popup.open('boardMenu'),
'click .js-change-visibility': Popup.open('boardChangeVisibility'),
'click .js-watch-board': Popup.open('boardChangeWatch'),
'click .js-open-archived-board'() {
Modal.open('archivedBoards');
},
'click .js-toggle-board-view': Popup.open('boardChangeView'),
'click .js-toggle-sidebar'() {
console.log('Hamburger menu clicked');
// Use the same approach as keyboard shortcuts
if (typeof Sidebar !== 'undefined' && Sidebar && typeof Sidebar.toggle === 'function') {
console.log('Using Sidebar.toggle()');
Sidebar.toggle();
} else {
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') {
console.log('Using Blaze helper Sidebar.toggle()');
sidebar.toggle();
}
}
}
},
'click .js-open-filter-view'() {
if (Sidebar) {
Sidebar.setView('filter');
} else {
console.warn('Sidebar not available for setView');
}
},
'click .js-sort-cards': Popup.open('cardsSort'),
/*
'click .js-open-sort-view'(evt) {
const target = evt.target;
if (target.tagName === 'I') {
// click on the text, popup choices
this.changeDirection();
} else {
// change the sort order
Popup.open('listsort')(evt);
}
},
*/
'click .js-filter-reset'(event) {
event.stopPropagation();
if (Sidebar) {
Sidebar.setView();
} else {
console.warn('Sidebar not available for setView');
}
Filter.reset();
},
'click .js-sort-reset'() {
Session.set('sortBy', '');
},
'click .js-open-search-view'() {
if (Sidebar) {
Sidebar.setView('search');
} else {
console.warn('Sidebar not available for setView');
}
},
'click .js-multiselection-activate'() {
const currentCard = Utils.getCurrentCardId();
MultiSelection.activate();
if (currentCard) {
MultiSelection.add(currentCard);
}
},
'click .js-multiselection-reset'(event) {
event.stopPropagation();
MultiSelection.disable();
},
'click .js-log-in'() {
FlowRouter.go('atSignIn');
},
},
];
},
}).register('boardHeaderBar');
Template.boardHeaderBar.helpers({
boardView() {
return Utils.boardView();
},
@@ -170,7 +68,7 @@ Template.boardHeaderBar.helpers({
if (!sortBy) {
return '🃏'; // Card icon when nothing is selected
}
// Determine which sort option is active based on sortBy object
if (sortBy.dueAt) {
return '📅'; // Due date icon
@@ -179,11 +77,111 @@ Template.boardHeaderBar.helpers({
} else if (sortBy.createdAt) {
return sortBy.createdAt === 1 ? '⬆️' : '⬇️'; // Up/down arrow based on direction
}
return '🃏'; // Default card icon
},
});
Template.boardHeaderBar.events({
'click .js-edit-board-title': Popup.open('boardChangeTitle'),
'click .js-star-board'() {
const boardId = Session.get('currentBoard');
if (boardId) {
Meteor.call('toggleBoardStar', boardId);
}
},
'click .js-open-board-menu': Popup.open('boardMenu'),
'click .js-change-visibility': Popup.open('boardChangeVisibility'),
'click .js-watch-board': Popup.open('boardChangeWatch'),
'click .js-open-archived-board'() {
Modal.open('archivedBoards');
},
'click .js-toggle-board-view': Popup.open('boardChangeView'),
'click .js-toggle-sidebar'() {
if (process.env.DEBUG === 'true') {
console.log('Hamburger menu clicked');
}
// Use the same approach as keyboard shortcuts
const sidebar = getSidebarInstance();
if (sidebar && typeof sidebar.toggle === 'function') {
if (process.env.DEBUG === 'true') {
console.log('Using Sidebar.toggle()');
}
sidebar.toggle();
} else {
if (process.env.DEBUG === 'true') {
console.warn('Sidebar not available, trying alternative approach');
}
// Try to trigger the sidebar through the global Blaze helper
if (typeof Blaze !== 'undefined' && Blaze._globalHelpers && Blaze._globalHelpers.Sidebar) {
const blazeSidebar = Blaze._globalHelpers.Sidebar();
if (blazeSidebar && typeof blazeSidebar.toggle === 'function') {
if (process.env.DEBUG === 'true') {
console.log('Using Blaze helper Sidebar.toggle()');
}
blazeSidebar.toggle();
}
}
}
},
'click .js-open-filter-view'() {
const sidebar = getSidebarInstance();
if (sidebar) {
sidebar.setView('filter');
} else {
console.warn('Sidebar not available for setView');
}
},
'click .js-sort-cards': Popup.open('cardsSort'),
/*
'click .js-open-sort-view'(evt) {
const target = evt.target;
if (target.tagName === 'I') {
// click on the text, popup choices
this.changeDirection();
} else {
// change the sort order
Popup.open('listsort')(evt);
}
},
*/
'click .js-filter-reset'(event) {
event.stopPropagation();
const sidebar = getSidebarInstance();
if (sidebar) {
sidebar.setView();
} else {
console.warn('Sidebar not available for setView');
}
Filter.reset();
},
'click .js-sort-reset'() {
Session.set('sortBy', '');
},
'click .js-open-search-view'() {
const sidebar = getSidebarInstance();
if (sidebar) {
sidebar.setView('search');
} else {
console.warn('Sidebar not available for setView');
}
},
'click .js-multiselection-activate'() {
const currentCard = Utils.getCurrentCardId();
MultiSelection.activate();
if (currentCard) {
MultiSelection.add(currentCard);
}
},
'click .js-multiselection-reset'(event) {
event.stopPropagation();
MultiSelection.disable();
},
'click .js-log-in'() {
FlowRouter.go('atSignIn');
},
});
Template.boardChangeViewPopup.events({
'click .js-open-lists-view'() {
Utils.setBoardView('board-view-lists');
@@ -197,196 +195,235 @@ Template.boardChangeViewPopup.events({
Utils.setBoardView('board-view-cal');
Popup.back();
},
});
const CreateBoard = BlazeComponent.extendComponent({
template() {
return 'createBoard';
},
onCreated() {
this.visibilityMenuIsOpen = new ReactiveVar(false);
this.visibility = new ReactiveVar('private');
this.boardId = new ReactiveVar('');
Meteor.subscribe('tableVisibilityModeSettings');
},
notAllowPrivateVisibilityOnly(){
return !TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue;
},
visibilityCheck() {
return this.currentData() === this.visibility.get();
},
setVisibility(visibility) {
this.visibility.set(visibility);
this.visibilityMenuIsOpen.set(false);
},
toggleVisibilityMenu() {
this.visibilityMenuIsOpen.set(!this.visibilityMenuIsOpen.get());
},
toggleAddTemplateContainer() {
$('#add-template-container').toggleClass('is-checked');
},
onSubmit(event) {
event.preventDefault();
const title = this.find('.js-new-board-title').value;
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'));
this.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: this.boardId.get(),
sort: 1,
type: 'template-container',
}),
// Insert the list templates swimlane
Swimlanes.insert(
{
// title: TAPi18n.__('list-templates-swimlane'),
title: 'List Templates',
boardId: this.boardId.get(),
sort: 2,
type: 'template-container',
},
);
// Insert the board templates swimlane
Swimlanes.insert(
{
//title: TAPi18n.__('board-templates-swimlane'),
title: 'Board Templates',
boardId: this.boardId.get(),
sort: 3,
type: 'template-container',
},
);
Utils.goBoardId(this.boardId.get());
} else {
const visibility = this.visibility.get();
this.boardId.set(
Boards.insert({
title,
permission: visibility,
migrationVersion: 1, // Latest version - no migration needed
}),
);
Swimlanes.insert({
title: 'Default',
boardId: this.boardId.get(),
});
Utils.goBoardId(this.boardId.get());
}
},
events() {
return [
{
'click .js-select-visibility'() {
this.setVisibility(this.currentData());
},
'click .js-change-visibility': this.toggleVisibilityMenu,
'click .js-import': Popup.open('boardImportBoard'),
submit: this.onSubmit,
'click .js-import-board': Popup.open('chooseBoardSource'),
'click .js-board-template': Popup.open('searchElement'),
'click .js-toggle-add-template-container': this.toggleAddTemplateContainer,
},
];
},
}).register('createBoardPopup');
(class HeaderBarCreateBoard extends CreateBoard {
onSubmit(event) {
super.onSubmit(event);
// Immediately star boards crated with the headerbar popup.
ReactiveCache.getCurrentUser().toggleBoardStar(this.boardId.get());
}
}.register('headerBarCreateBoardPopup'));
BlazeComponent.extendComponent({
notAllowPrivateVisibilityOnly(){
return !TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue;
},
visibilityCheck() {
const currentBoard = Utils.getCurrentBoard();
return this.currentData() === currentBoard.permission;
},
selectBoardVisibility() {
const currentBoard = Utils.getCurrentBoard();
const visibility = this.currentData();
currentBoard.setVisibility(visibility);
'click .js-open-gantt-view'() {
Utils.setBoardView('board-view-gantt');
Popup.back();
},
});
events() {
return [
{
'click .js-select-visibility': this.selectBoardVisibility,
},
];
// Shared setup for all create board popups
function setupCreateBoardState(tpl) {
tpl.visibilityMenuIsOpen = new ReactiveVar(false);
tpl.visibility = new ReactiveVar('private');
tpl.boardId = new ReactiveVar('');
Meteor.subscribe('tableVisibilityModeSettings');
}
function createBoardHelpers() {
return {
visibilityMenuIsOpen() {
return Template.instance().visibilityMenuIsOpen.get();
},
visibility() {
return Template.instance().visibility.get();
},
notAllowPrivateVisibilityOnly() {
return !TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue;
},
visibilityCheck() {
return Template.currentData() === Template.instance().visibility.get();
},
};
}
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) {
tpl.boardId.set(
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' },
],
}),
);
// Assign to space if one was selected
const spaceId = Session.get('createBoardInWorkspace');
if (spaceId) {
Meteor.call('assignBoardToWorkspace', tpl.boardId.get(), spaceId, (err) => {
if (err) console.error('Error assigning board to space:', err);
});
Session.set('createBoardInWorkspace', null); // Clear after use
}
FlowRouter.go('board', { id: tpl.boardId.get(), slug });
} else {
const visibility = tpl.visibility.get();
tpl.boardId.set(
await Meteor.callAsync('createBoardWithInitialSwimlanes', {
title,
slug,
permission: visibility,
migrationVersion: 1,
swimlanes: [{ title: 'Default' }],
}),
);
// Assign to space if one was selected
const spaceId = Session.get('createBoardInWorkspace');
if (spaceId) {
Meteor.call('assignBoardToWorkspace', tpl.boardId.get(), spaceId, (err) => {
if (err) console.error('Error assigning board to space:', err);
});
Session.set('createBoardInWorkspace', null); // Clear after use
}
FlowRouter.go('board', { id: tpl.boardId.get(), slug });
}
}
function createBoardEvents() {
return {
'click .js-select-visibility'(event, tpl) {
tpl.visibility.set(Template.currentData());
tpl.visibilityMenuIsOpen.set(false);
},
'click .js-change-visibility'(event, tpl) {
tpl.visibilityMenuIsOpen.set(!tpl.visibilityMenuIsOpen.get());
},
'click .js-import': Popup.open('boardImportBoard'),
async 'submit'(event, tpl) {
await createBoardSubmit(tpl, event);
},
'click .js-import-board': Popup.open('chooseBoardSource'),
'click .js-board-template': Popup.open('searchElement'),
'click .js-toggle-add-template-container'() {
$('#add-template-container').toggleClass('is-checked');
},
};
}
// createBoard (non-popup version)
Template.createBoard.onCreated(function () {
setupCreateBoardState(this);
});
Template.createBoard.helpers(createBoardHelpers());
Template.createBoard.events(createBoardEvents());
// createBoardPopup
Template.createBoardPopup.onCreated(function () {
setupCreateBoardState(this);
});
Template.createBoardPopup.helpers(createBoardHelpers());
Template.createBoardPopup.events(createBoardEvents());
// createTemplateContainerPopup
Template.createTemplateContainerPopup.onCreated(function () {
setupCreateBoardState(this);
});
Template.createTemplateContainerPopup.onRendered(function () {
// Always pre-check the template container checkbox for this popup
$('#add-template-container').addClass('is-checked');
});
Template.createTemplateContainerPopup.helpers(createBoardHelpers());
Template.createTemplateContainerPopup.events(createBoardEvents());
// headerBarCreateBoardPopup
Template.headerBarCreateBoardPopup.onCreated(function () {
setupCreateBoardState(this);
});
Template.headerBarCreateBoardPopup.helpers(createBoardHelpers());
Template.headerBarCreateBoardPopup.events({
'click .js-select-visibility'(event, tpl) {
tpl.visibility.set(Template.currentData());
tpl.visibilityMenuIsOpen.set(false);
},
}).register('boardChangeVisibilityPopup');
'click .js-change-visibility'(event, tpl) {
tpl.visibilityMenuIsOpen.set(!tpl.visibilityMenuIsOpen.get());
},
'click .js-import': Popup.open('boardImportBoard'),
async submit(event, tpl) {
await createBoardSubmit(tpl, event);
// Immediately star boards created with the headerbar popup.
await ReactiveCache.getCurrentUser().toggleBoardStar(tpl.boardId.get());
},
'click .js-import-board': Popup.open('chooseBoardSource'),
'click .js-board-template': Popup.open('searchElement'),
'click .js-toggle-add-template-container'() {
$('#add-template-container').toggleClass('is-checked');
},
});
BlazeComponent.extendComponent({
Template.boardChangeVisibilityPopup.onCreated(function () {
Meteor.subscribe('tableVisibilityModeSettings');
});
Template.boardChangeVisibilityPopup.helpers({
notAllowPrivateVisibilityOnly(){
return !TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue;
},
visibilityCheck() {
const currentBoard = Utils.getCurrentBoard();
return this === currentBoard.permission;
},
});
Template.boardChangeVisibilityPopup.events({
'click .js-select-visibility'() {
const currentBoard = Utils.getCurrentBoard();
const visibility = Template.currentData();
if (typeof visibility === 'string') {
currentBoard.setVisibility(visibility);
Popup.back();
}
},
});
Template.boardChangeWatchPopup.helpers({
watchLevel() {
const currentBoard = Utils.getCurrentBoard();
return currentBoard.getWatchLevel(Meteor.userId());
},
watchCheck() {
return this.currentData() === this.watchLevel();
const currentBoard = Utils.getCurrentBoard();
return this === currentBoard.getWatchLevel(Meteor.userId());
},
});
events() {
return [
{
'click .js-select-watch'() {
const level = this.currentData();
Meteor.call(
'watch',
'board',
Session.get('currentBoard'),
level,
(err, ret) => {
if (!err && ret) Popup.back();
},
);
Template.boardChangeWatchPopup.events({
'click .js-select-watch'() {
const level = Template.currentData();
if (typeof level === 'string') {
Meteor.call(
'watch',
'board',
Session.get('currentBoard'),
level,
(err, ret) => {
if (!err && ret) Popup.back();
},
},
];
);
}
},
}).register('boardChangeWatchPopup');
});
/*
BlazeComponent.extendComponent({
// BlazeComponent.extendComponent was removed - this code is unused.
// Original listsortPopup component:
// {
onCreated() {
//this.sortBy = new ReactiveVar();
////this.sortDirection = new ReactiveVar();
@@ -454,46 +491,40 @@ BlazeComponent.extendComponent({
},
];
},
}).register('listsortPopup');
// }.register('listsortPopup');
*/
BlazeComponent.extendComponent({
events() {
return [
{
'click .js-sort-due'() {
const sortBy = {
dueAt: 1,
};
Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('due-date'));
Popup.back();
},
'click .js-sort-title'() {
const sortBy = {
title: 1,
};
Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('title'));
Popup.back();
},
'click .js-sort-created-asc'() {
const sortBy = {
createdAt: 1,
};
Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('date-created-newest-first'));
Popup.back();
},
'click .js-sort-created-desc'() {
const sortBy = {
createdAt: -1,
};
Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('date-created-oldest-first'));
Popup.back();
},
},
];
Template.cardsSortPopup.events({
'click .js-sort-due'() {
const sortBy = {
dueAt: 1,
};
Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('due-date'));
Popup.back();
},
}).register('cardsSortPopup');
'click .js-sort-title'() {
const sortBy = {
title: 1,
};
Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('title'));
Popup.back();
},
'click .js-sort-created-asc'() {
const sortBy = {
createdAt: 1,
};
Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('date-created-newest-first'));
Popup.back();
},
'click .js-sort-created-desc'() {
const sortBy = {
createdAt: -1,
};
Session.set('sortBy', sortBy);
sortCardsBy.set(TAPi18n.__('date-created-oldest-first'));
Popup.back();
},
});
+533 -10
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 {
@@ -8,6 +8,339 @@
padding: 1vh 0;
}
/* Two-column layout for All Boards */
.boards-layout {
display: grid;
grid-template-columns: 260px 1fr;
gap: 16px;
}
.boards-left-menu {
border-right: 1px solid #e0e0e0;
padding-right: 12px;
}
.boards-left-menu ul.menu {
list-style: none;
padding: 0;
margin: 0 0 12px 0;
}
.boards-left-menu .menu-item {
margin: 4px 0;
}
.boards-left-menu .menu-item a {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 10px;
border-radius: 4px;
cursor: pointer;
}
.boards-left-menu .menu-item .menu-label {
flex: 1;
}
.boards-left-menu .menu-item .menu-count {
background: #ddd;
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: bold;
margin-left: 8px;
}
.boards-left-menu .menu-item.active a,
.boards-left-menu .menu-item a:hover {
background: #f0f0f0;
}
.boards-left-menu .menu-item.active .menu-count {
background: #bbb;
}
/* Drag-over state for menu items (for dropping boards on Remaining) */
.boards-left-menu .menu-item a.drag-over {
background: #d0e8ff;
border: 2px dashed #2196F3;
}
.workspaces-header {
display: flex;
align-items: center;
justify-content: space-between;
font-weight: bold;
margin-top: 12px;
}
.workspaces-header .js-add-space,
.workspaces-header .js-add-workspace {
text-decoration: none;
font-weight: bold;
border: none;
padding: 2px 8px;
border-radius: 4px;
margin-right: 10px;
}
.workspace-tree {
list-style: none;
padding-left: 10px;
}
.workspace-node {
margin: 2px 0;
position: relative;
}
.workspace-node-content {
display: flex;
align-items: center;
gap: 4px;
padding: 4px;
border-radius: 4px;
transition: background-color 0.2s;
}
.workspace-node.dragging > .workspace-node-content {
opacity: 0.5;
background: #e0e0e0;
}
.workspace-node.drag-over > .workspace-node-content {
background: #d0e8ff;
border: 2px dashed #2196F3;
}
.workspace-drag-handle {
cursor: grab;
color: #999;
font-size: 14px;
padding: 0 4px;
user-select: none;
}
.workspace-drag-handle:active {
cursor: grabbing;
}
.workspace-node .js-select-space {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
flex: 1;
text-decoration: none;
}
.workspace-node .workspace-icon {
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 16px;
line-height: 1;
vertical-align: middle;
}
.workspace-node .workspace-icon .workspace-icon-value,
.workspace-node .workspace-icon .workspace-icon-value p {
display: inline;
margin: 0;
margin-top: 12px;
}
.workspace-node .workspace-icon .emoji-icon {
margin-top: 0px;
}
.workspace-node .workspace-name {
flex: 1;
}
.workspace-node .workspace-count {
background: #ddd;
padding: 2px 6px;
border-radius: 10px;
font-size: 11px;
font-weight: bold;
min-width: 20px;
text-align: center;
}
.workspace-node .js-edit-space,
.workspace-node .js-add-subspace {
padding: 2px 6px;
border-radius: 3px;
cursor: pointer;
text-decoration: none;
font-size: 14px;
opacity: 0.6;
transition: opacity 0.2s;
}
.workspace-node .js-edit-space:hover,
.workspace-node .js-add-subspace:hover {
opacity: 1;
background: #e0e0e0;
}
.workspace-node .js-open-workspace-menu {
padding: 2px 6px;
border-radius: 3px;
cursor: pointer;
text-decoration: none;
font-size: 14px;
opacity: 0.7;
transition: opacity 0.2s;
}
.workspace-node .js-open-workspace-menu:hover {
opacity: 1;
background: #e0e0e0;
}
.workspace-actions-form label,
.workspace-subspace-form label {
display: block;
font-weight: 600;
margin-bottom: 8px;
}
.workspace-actions-form input[type='text'],
.workspace-subspace-form input[type='text'] {
width: 100%;
margin-top: 4px;
}
.workspace-actions-form .workspace-color-label {
margin-bottom: 6px;
}
.workspace-actions-form .workspace-color-list {
margin-bottom: 10px;
}
.workspace-delete-block {
display: flex;
flex-direction: column;
gap: 8px;
}
.workspace-delete-confirm {
display: flex;
align-items: center;
gap: 6px;
font-weight: 600;
}
.workspace-node.active > .workspace-node-content .js-select-space,
.workspace-node > .workspace-node-content:hover .js-select-space {
background: #f0f0f0;
}
.workspace-node.active .workspace-count {
background: #bbb;
}
.boards-right-grid {
min-height: 200px;
}
.boards-path-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 12px 16px;
margin-bottom: 16px;
background: #f5f5f5;
border-radius: 6px;
font-size: 16px;
font-weight: 500;
}
.boards-path-header .path-left {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
}
.boards-path-header .multiselection-hint {
background: #FFF3CD;
color: #856404;
padding: 4px 12px;
border-radius: 4px;
font-size: 13px;
font-weight: normal;
border: 1px solid #FFE69C;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.boards-path-header .path-right {
display: flex;
align-items: center;
gap: 8px;
}
.boards-path-header .path-icon {
font-size: 18px;
}
.boards-path-header .path-text {
color: #333;
}
.boards-path-header .board-header-btn {
padding: 6px 12px;
background: #fff;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
gap: 6px;
font-size: 14px;
transition: all 0.2s;
}
.boards-path-header .board-header-btn:hover {
background: #f0f0f0;
border-color: #bbb;
}
.boards-path-header .board-header-btn.emphasis {
background: #2196F3;
color: #fff;
border-color: #2196F3;
font-weight: bold;
box-shadow: 0 2px 8px rgba(33, 150, 243, 0.5);
transform: scale(1.05);
}
.boards-path-header .board-header-btn.emphasis:hover {
background: #1976D2;
box-shadow: 0 3px 12px rgba(33, 150, 243, 0.7);
}
.boards-path-header .board-header-btn-close {
padding: 4px 10px;
background: #f44336;
color: #000;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
margin-left: 10px; /* Extra space between MultiSelection toggle and Remove Filter */
}
.boards-path-header .board-header-btn-close:hover {
background: #d32f2f;
}
.zoom-controls {
display: flex;
align-items: center;
@@ -106,23 +439,35 @@
.board-list li.starred .is-star-active,
.board-list li.starred .is-not-star-active {
opacity: 1;
color: #ffd700;
}
/* Show star icon on hover even for non-starred boards */
.board-list li:hover .is-star-active,
.board-list li:hover .is-not-star-active {
opacity: 1;
}
.board-list .board-list-item {
overflow: hidden;
background-color: #999;
background-color: inherit; /* Inherit board color from parent li.js-board */
color: #f6f6f6;
min-height: 100px;
font-size: 16px;
line-height: 22px;
border-radius: 3px;
border-radius: 0; /* No border-radius - parent .js-board has it */
display: block;
font-weight: 700;
padding: 8px;
margin: 8px;
padding: 36px 8px 32px 8px; /* Top padding for drag handle, bottom for checkbox */
margin: 0; /* No margin - moved to parent .js-board */
position: relative;
text-decoration: none;
word-wrap: break-word;
}
.board-list .board-list-item > .js-open-board {
text-decoration: none;
color: inherit;
display: block;
}
.board-list .board-list-item.template-container {
border: 4px solid #fff;
}
@@ -150,13 +495,27 @@
.board-list .js-add-board .label {
font-weight: normal;
line-height: 56px;
min-height: 100px;
display: flex;
align-items: center;
justify-content: center;
background-color: #999; /* Darker background for better text contrast */
border-radius: 3px;
padding: 36px 8px 32px 8px;
color: #fff; /* White text */
}
.board-list .js-add-board :hover {
background-color: #939393;
.board-list .js-add-board .label i {
color: #fff; /* White icon */
}
.board-list .js-add-board .label:hover {
background-color: #808080; /* Even darker on hover */
}
.board-list .js-add-board .label:hover i {
color: #fff; /* Keep icon white on hover */
}
.board-list .is-star-active,
.board-list .is-not-star-active {
bottom: 0;
top: 0;
font-size: 14px;
height: 18px;
line-height: 18px;
@@ -164,7 +523,6 @@
padding: 9px 9px;
position: absolute;
right: 0;
top: 0;
transition-duration: 0.15s;
transition-property: color, font-size, background;
}
@@ -238,6 +596,107 @@
.board-list li:hover a .is-not-star-active {
opacity: 1;
}
/* Board drag handle - always visible and positioned at top */
.board-list .board-handle {
position: absolute;
padding: 4px 6px;
top: 4px;
left: 50%;
transform: translateX(-50%);
font-size: 14px;
color: #fff;
background: rgba(0,0,0,0.4);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
transition: background-color 0.2s ease;
cursor: grab;
opacity: 1;
user-select: none;
}
.board-list .board-handle:active {
cursor: grabbing;
}
.board-list .board-handle:hover {
background: rgba(255, 255, 0, 0.8) !important;
color: #000;
}
/* Multiselection checkbox on board items */
.board-list .board-list-item .multi-selection-checkbox {
position: absolute !important;
bottom: 4px !important;
left: 4px !important;
top: auto !important;
width: 24px;
height: 24px;
border: 3px solid #fff;
background: rgba(0,0,0,0.5);
border-radius: 4px;
cursor: pointer;
z-index: 11;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
box-shadow: 0 2px 4px rgba(0,0,0,0.3);
transform: none !important;
margin: 0 !important;
}
.board-list .board-list-item .multi-selection-checkbox:hover {
background: rgba(0,0,0,0.7);
transform: scale(1.15) !important;
box-shadow: 0 3px 6px rgba(0,0,0,0.5);
}
.board-list .board-list-item .multi-selection-checkbox.is-checked {
background: #3cb500;
border-color: #3cb500;
box-shadow: 0 2px 8px rgba(60, 181, 0, 0.6);
width: 24px !important;
height: 24px !important;
top: auto !important;
left: 4px !important;
transform: none !important;
border-radius: 4px !important;
}
.board-list .board-list-item .multi-selection-checkbox.is-checked::after {
content: '✓';
color: #fff;
font-size: 16px;
font-weight: bold;
}
/* Grey checkboxes when grey icons setting is enabled */
body.grey-icons-enabled .board-list .board-list-item .multi-selection-checkbox.is-checked {
background: #7a7a7a;
border-color: #7a7a7a;
box-shadow: 0 2px 8px rgba(122, 122, 122, 0.6);
}
body.grey-icons-enabled .board-list.is-multiselection-active .js-board.is-checked {
outline: 4px solid #7a7a7a;
box-shadow: 0 4px 12px rgba(122, 122, 122, 0.4);
}
.board-list.is-multiselection-active .js-board.is-checked {
outline: 4px solid #3cb500;
outline-offset: -4px;
box-shadow: 0 4px 12px rgba(60, 181, 0, 0.4);
}
/* Visual hint when multiselection is active */
.board-list.is-multiselection-active .board-list-item {
border: 2px dashed rgba(33, 150, 243, 0.3);
}
.board-backgrounds-list .board-background-select {
box-sizing: border-box;
display: block;
@@ -271,8 +730,19 @@
}
.board-backgrounds-list .board-background-select .background-box i.fa-check {
font-size: 25px;
color: #fff;
color: #3cb500;
}
/* Grey check icons when grey icons setting is enabled */
body.grey-icons-enabled .board-backgrounds-list .board-background-select .background-box i.fa-check {
color: #7a7a7a;
}
/* Prevent Grey Icons from affecting checkmarks in background color list */
body.grey-icons-enabled .checkmark-no-grey {
filter: none !important;
-webkit-filter: none !important;
}
/* Mobile view styles - applied when isMiniScreen is true (iPhone, etc.) */
.board-list.mobile-view {
height: calc(100vh - 120px);
@@ -739,9 +1209,62 @@
#resetBtn {
display: inline;
}
#resetBtn.filter-reset-btn {
background: #f44336;
color: #000;
border: none;
border-radius: 4px;
padding: 6px 12px;
cursor: pointer;
font-size: 14px;
display: inline-flex;
align-items: center;
gap: 6px;
transition: background 0.2s;
}
#resetBtn.filter-reset-btn:hover {
background: #d32f2f;
}
#resetBtn.filter-reset-btn .reset-icon {
font-size: 14px;
}
.js-board {
display: block;
background-color: #999; /* Default gray background if no color class is applied */
border-radius: 3px; /* Rounded corners for board items */
overflow: hidden; /* Ensure children respect rounded corners */
margin: 8px; /* Space between board items */
}
/* Reset background for add-board button */
.js-add-board {
background-color: transparent !important;
margin: 8px !important; /* Keep margin for add-board */
}
/* Apply board colors to li.js-board parent instead of just the link */
.board-list .board-color-nephritis { background-color: #27ae60; }
.board-list .board-color-pomegranate { background-color: #c0392b; }
.board-list .board-color-belize { background-color: #2980b9; }
.board-list .board-color-wisteria { background-color: #8e44ad; }
.board-list .board-color-midnight { background-color: #2c3e50; }
.board-list .board-color-pumpkin { background-color: #e67e22; }
.board-list .board-color-moderatepink { background-color: #cd5a91; }
.board-list .board-color-strongcyan { background-color: #00aecc; }
.board-list .board-color-limegreen { background-color: #4bbf6b; }
.board-list .board-color-dark { background-color: #2c3e51; }
.board-list .board-color-relax { background-color: #27ae61; }
.board-list .board-color-corteza { background-color: #568ba2; }
.board-list .board-color-clearblue { background-color: #3498db; }
.board-list .board-color-natural { background-color: #596557; }
.board-list .board-color-modern { background-color: #2a80b8; }
.board-list .board-color-moderndark { background-color: #2a2a2a; }
.board-list .board-color-exodark { background-color: #222; }
.minicard-members {
padding: 6px 0 6px 8px;
width: 100%;
+246 -146
View File
@@ -2,158 +2,258 @@ template(name="boardList")
.wrapper
.board-list-header
ul.AllBoardTeamsOrgs
li.AllBoardTeams
if userHasTeams
select.js-AllBoardTeams#jsAllBoardTeams("multiple")
option(value="-1") {{_ 'teams'}} :
each teamsDatas
option(value="{{teamId}}") {{_ teamDisplayName}}
.boards-layout
// Left menu
.boards-left-menu
ul.menu
li(class="menu-item {{#if isSelectedMenu 'starred'}}active{{/if}}")
a.js-select-menu(data-type="starred")
span.menu-label
span.emoji-icon
i.fa.fa-star
| {{_ 'allboards.starred'}}
span.menu-count {{menuItemCount 'starred'}}
li(class="menu-item {{#if isSelectedMenu 'templates'}}active{{/if}}")
a.js-select-menu(data-type="templates")
span.menu-label
span.emoji-icon
i.fa.fa-clipboard
| {{_ 'allboards.templates'}}
span.menu-count {{menuItemCount 'templates'}}
li(class="menu-item {{#if isSelectedMenu 'remaining'}}active{{/if}}")
a.js-select-menu(data-type="remaining")
span.menu-label
span.emoji-icon
i.fa.fa-folder
| {{_ 'allboards.remaining'}}
span.menu-count {{menuItemCount 'remaining'}}
.workspaces-header
span
span.emoji-icon
i.fa.fa-folder-open
| {{_ 'allboards.workspaces'}}
a.js-add-workspace(title="{{_ 'allboards.add-workspace'}}") +
// Workspaces tree
+workspaceTree(nodes=workspacesTree selectedWorkspaceId=selectedWorkspaceId)
li.AllBoardOrgs
if userHasOrgs
select.js-AllBoardOrgs#jsAllBoardOrgs("multiple")
option(value="-1") {{_ 'organizations'}} :
each orgsDatas
option(value="{{orgId}}") {{orgDisplayName}}
// Existing filter by orgs/teams (kept)
ul.AllBoardTeamsOrgs
li.AllBoardTeams
if userHasTeams
select.js-AllBoardTeams#jsAllBoardTeams("multiple")
option(value="-1") {{_ 'teams'}} :
each teamsDatas
option(value="{{teamId}}") {{_ teamDisplayName}}
//li.AllBoardTemplates
// if userHasTemplates
// select.js-AllBoardTemplates#jsAllBoardTemplates("multiple")
// option(value="-1") {{_ 'templates'}} :
// each templatesDatas
// option(value="{{templateId}}") {{_ templateDisplayName}}
li.AllBoardOrgs
if userHasOrgs
select.js-AllBoardOrgs#jsAllBoardOrgs("multiple")
option(value="-1") {{_ 'organizations'}} :
each orgsDatas
option(value="{{orgId}}") {{orgDisplayName}}
li.AllBoardBtns
div.AllBoardButtonsContainer
if userHasOrgsOrTeams
i.fa.fa-filter
input#filterBtn(type="button" value="{{_ 'filter'}}")
input#resetBtn(type="button" value="{{_ 'filter-clear'}}")
li.AllBoardBtns
div.AllBoardButtonsContainer
if userHasOrgsOrTeams
span.emoji-icon
i.fa.fa-search
input#filterBtn(type="button" value="{{_ 'filter'}}")
button#resetBtn.filter-reset-btn
span.reset-icon
span.emoji-icon
i.fa.fa-times-thin
span {{_ 'filter-clear'}}
ul.board-list.clearfix.js-boards(class="{{#if isMiniScreen}}mobile-view{{/if}}")
li.js-add-board
a.board-list-item.label(title="{{_ 'add-board'}}")
| {{_ 'add-board'}}
each boards
li(class="{{_id}}" class="{{#if isStarred}}starred{{/if}}" class=colorClass).js-board
if isInvited
.board-list-item
span.details
span.board-list-item-name= title
i.fa.js-star-board(
class="fa-star{{#if isStarred}} is-star-active{{else}}-o{{/if}}"
title="{{_ 'star-board-title'}}")
p.board-list-item-desc {{_ 'just-invited'}}
button.js-accept-invite.primary {{_ 'accept'}}
button.js-decline-invite {{_ 'decline'}}
else
if $eq type "template-container"
a.js-open-board.template-container.board-list-item(href="{{pathFor 'board' id=_id slug=slug}}")
span.details
span.board-list-item-name(title="{{_ 'template-container'}}")
+viewer
= title
i.fa.js-star-board(
class="fa-star{{#if isStarred}} is-star-active{{else}}-o{{/if}}"
title="{{_ 'star-board-title'}}")
p.board-list-item-desc
+viewer
= description
if hasSpentTimeCards
i.fa.js-has-spenttime-cards(
class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}"
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
i.fa.board-handle(
class="fa-arrows"
title="{{_ 'drag-board'}}")
if isSandstorm
i.fa.js-clone-board(
class="fa-clone"
title="{{_ 'duplicate-board'}}")
i.fa.js-archive-board(
class="fa-archive"
title="{{_ 'archive-board'}}")
else if isAdministrable
i.fa.js-clone-board(
class="fa-clone"
title="{{_ 'duplicate-board'}}")
i.fa.js-archive-board(
class="fa-archive"
title="{{_ 'archive-board'}}")
else if currentUser.isAdmin
i.fa.js-clone-board(
class="fa-clone"
title="{{_ 'duplicate-board'}}")
i.fa.js-archive-board(
class="fa-archive"
title="{{_ 'archive-board'}}")
// Right boards grid
.boards-right-grid
.boards-path-header
.path-left
span.path-icon.emoji-icon {{currentMenuPath.icon}}
span.path-text {{currentMenuPath.text}}
if BoardMultiSelection.isActive
span.multiselection-hint
span.emoji-icon
i.fa.fa-thumb-tack
| {{_ 'multi-selection-active'}}
.path-right
if canModifyBoards
if hasBoardsSelected
button.js-archive-selected-boards.board-header-btn
span.emoji-icon
i.fa.fa-archive
span {{_ 'archive-board'}}
button.js-duplicate-selected-boards.board-header-btn
span.emoji-icon
i.fa.fa-clipboard
span {{_ 'duplicate-board'}}
a.board-header-btn.js-multiselection-activate(
title="{{#if BoardMultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}}"
class="{{#if BoardMultiSelection.isActive}}emphasis{{/if}}")
span.emoji-icon
i.fa.fa-check-square-o
if BoardMultiSelection.isActive
a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}")
span.emoji-icon
i.fa.fa-times
ul.board-list.clearfix.js-boards(class="{{#if isMiniScreen}}mobile-view{{/if}} {{#if BoardMultiSelection.isActive}}is-multiselection-active{{/if}}")
li.js-add-board
if isSelectedMenu 'templates'
a.board-list-item.label(title="{{_ 'add-template-container'}}")
span.emoji-icon
i.fa.fa-plus
| &nbsp;{{_ 'add-template-container'}}
else
a.js-open-board.board-list-item(href="{{pathFor 'board' id=_id slug=slug}}")
span.details
span.board-list-item-name(title="{{_ 'board-drag-drop-reorder-or-click-open'}}")
+viewer
= title
unless currentSetting.hideBoardMemberList
if allowsBoardMemberList
.minicard-members
each member in boardMembers _id
a.name
+userAvatar(userId=member noRemove=true)
unless currentSetting.hideCardCounterList
if allowsCardCounterList
.minicard-lists.flex.flex-wrap
each list in boardLists _id
.item
| {{ list }}
a.js-star-board(
class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}"
title="{{_ 'star-board-title'}}")
| {{#if isStarred}}⭐{{else}}☆{{/if}}
p.board-list-item-desc
+viewer
= description
if hasSpentTimeCards
i.fa.js-has-spenttime-cards(
class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}"
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
i.fa.board-handle(
class="fa-arrows"
title="{{_ 'drag-board'}}")
if isSandstorm
a.js-clone-board(
class="fa-clone"
title="{{_ 'duplicate-board'}}")
| 📋
a.js-archive-board(
class="fa-archive"
title="{{_ 'archive-board'}}")
| 📦
else if isAdministrable
a.js-clone-board(
class="fa-clone"
title="{{_ 'duplicate-board'}}")
| 📋
a.js-archive-board(
class="fa-archive"
title="{{_ 'archive-board'}}")
| 📦
else if currentUser.isAdmin
a.js-clone-board(
class="fa-clone"
title="{{_ 'duplicate-board'}}")
| 📋
a.js-archive-board(
class="fa-archive"
title="{{_ 'archive-board'}}")
| 📦
a.board-list-item.label(title="{{_ 'add-board'}}")
span.emoji-icon
i.fa.fa-plus
| &nbsp;{{_ 'add-board'}}
each boards
li.js-board(class="{{_id}} {{#if isStarred}}starred{{/if}} {{colorClass}} {{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}", draggable="true")
if isInvited
.board-list-item
if BoardMultiSelection.isActive
.materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection(
class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}")
span.details
span.board-list-item-name= title
span.js-star-board(
class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}"
title="{{_ 'star-board-title'}}")
span.emoji-icon
| {{#if isStarred}}⭐{{else}}☆{{/if}}
p.board-list-item-desc {{_ 'just-invited'}}
button.js-accept-invite.primary {{_ 'accept'}}
button.js-decline-invite {{_ 'decline'}}
else
if $eq type "template-container"
.template-container.board-list-item
if BoardMultiSelection.isActive
.materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection(
class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}")
span.board-handle(title="{{_ 'drag-board'}}")
span.emoji-icon
i.fa.fa-arrows
a.js-open-board(href="{{pathFor 'board' id=_id slug=slug}}")
span.details
span.board-list-item-name(title="{{_ 'template-container'}}")
+viewer
= title
p.board-list-item-desc
+viewer
= description
if hasSpentTimeCards
span.js-has-spenttime-cards(
class="{{#if hasOvertimeCards}}has-overtime-card-active{{else}}no-overtime-card-active{{/if}}"
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
span.emoji-icon
i.fa.fa-clock-o
span.js-star-board(
class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}"
title="{{_ 'star-board-title'}}")
span.emoji-icon
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
else
.board-list-item
if BoardMultiSelection.isActive
.materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection(
class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}")
span.board-handle(title="{{_ 'drag-board'}}")
span.emoji-icon
i.fa.fa-arrows
a.js-open-board(href="{{pathFor 'board' id=_id slug=slug}}")
span.details
span.board-list-item-name(title="{{_ 'board-drag-drop-reorder-or-click-open'}}")
+viewer
= title
unless currentSetting.hideBoardMemberList
if allowsBoardMemberList
.minicard-members
each member in boardMembers _id
a.name
+userAvatar(userId=member noRemove=true)
unless currentSetting.hideCardCounterList
if allowsCardCounterList
.minicard-lists.flex.flex-wrap
each list in boardLists _id
.item
| {{ list }}
p.board-list-item-desc
+viewer
= description
if hasSpentTimeCards
span.js-has-spenttime-cards(
class="{{#if hasOvertimeCards}}has-overtime-card-active{{else}}no-overtime-card-active{{/if}}"
title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}")
span.emoji-icon
i.fa.fa-clock-o
a.js-star-board(
class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}"
title="{{_ 'star-board-title'}}")
span.emoji-icon
i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}")
template(name="boardListHeaderBar")
h1 {{_ title }}
//.board-header-btns.right
// a.board-header-btn.js-open-archived-board
// i.fa.fa-archive
// span {{_ 'archives'}}
// a.board-header-btn(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}")
// i.fa.fa-clone
// span {{_ 'templates'}}
//
a.board-header-btn.js-open-archived-board
//
i.fa.fa-archive
//
span {{_ 'archives'}}
//
a.board-header-btn(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}")
//
i.fa.fa-clone
//
span {{_ 'templates'}}
// Recursive template for workspaces tree
template(name="workspaceTree")
if nodes
ul.workspace-tree.js-workspace-tree
each nodes
li.workspace-node(class="{{#if $eq id selectedWorkspaceId}}active{{/if}}" data-workspace-id="{{id}}" draggable="true")
.workspace-node-content
span.workspace-drag-handle
span.emoji-icon
i.fa.fa-arrows
a.js-select-workspace.js-select-space(data-id="{{id}}")
span.workspace-icon
if icon
span.workspace-icon-value(style="{{#if iconColor}}color: {{iconColor}};{{/if}}")
+viewer
= icon
else
span.emoji-icon(style="{{#if iconColor}}color: {{iconColor}};{{/if}}")
i.fa.fa-folder
span.workspace-name= name
a.js-open-workspace-menu(data-id="{{id}}" title="{{_ 'allboards.workspace-menu'}}")
span.emoji-icon
i.fa.fa-bars
span.workspace-count {{workspaceCount id}}
if children
+workspaceTree(nodes=children selectedWorkspaceId=selectedWorkspaceId)
template(name="workspaceActionsPopup")
form.workspace-actions-form.js-workspace-actions-form(data-id="{{id}}")
label
| {{_ 'allboards.edit-workspace-name'}}
input.js-workspace-name(type="text" value="{{workspaceName}}" required)
label
| {{_ 'allboards.edit-workspace-icon'}}
input.js-workspace-icon(type="text" value="{{workspaceIcon}}")
input.primary.wide(type="submit" value="{{_ 'save'}}")
hr
form.workspace-subspace-form.js-workspace-subspace-form(data-id="{{id}}")
label
| {{_ 'allboards.add-subworkspace-prompt'}}
input.js-subworkspace-name(type="text")
button.primary.wide(type="submit") {{_ 'allboards.add-subworkspace'}}
hr
.workspace-delete-block
span.js-workspace-delete-error(style="color: #c00; display: block; margin: 4px 0;")
button.js-delete-workspace.negate.wide(type="button" data-id="{{id}}") {{_ 'delete'}}
File diff suppressed because it is too large. Load diff
+43
View File
@@ -0,0 +1,43 @@
.calendar-view .fc {
--fc-button-text-color: #333;
--fc-button-bg-color: #f5f5f5;
--fc-button-border-color: rgba(0, 0, 0, 0.2);
--fc-button-hover-bg-color: #e6e6e6;
--fc-button-hover-border-color: rgba(0, 0, 0, 0.25);
--fc-button-active-bg-color: #d9d9d9;
--fc-button-active-border-color: rgba(0, 0, 0, 0.3);
}
.calendar-view .fc .fc-button-primary {
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),
0 1px 2px rgba(0, 0, 0, 0.05);
background-image: linear-gradient(to bottom, #fff 0%, #e6e6e6 100%);
}
.calendar-view .fc .fc-button-primary:focus,
.calendar-view .fc .fc-button-primary:not(:disabled).fc-button-active,
.calendar-view .fc .fc-button-primary:not(:disabled):active {
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15),
0 1px 2px rgba(0, 0, 0, 0.05);
}
.calendar-create-close {
min-height: auto !important;
min-width: auto !important;
width: 32px;
height: 32px;
padding: 0 !important;
border: 0 !important;
background: transparent !important;
box-shadow: none !important;
color: #666 !important;
opacity: 0.8;
}
.calendar-create-close:hover,
.calendar-create-close:focus {
background: transparent !important;
color: #111 !important;
opacity: 1;
}
+2 -1
View File
@@ -3,6 +3,7 @@ template(name="miniboard")
class="minicard-{{colorClass}}")
.minicard-title
.handle
.fa.fa-arrows
span.drag-handle(title="{{_ 'dragBoard'}}")
i.fa.fa-arrows
+viewer
= title
@@ -164,32 +164,32 @@
margin: 5px 0;
padding: 10px;
}
.original-positions-header {
flex-direction: column;
align-items: stretch;
gap: 8px;
}
.original-positions-header .btn {
justify-content: center;
}
.original-positions-filters .btn-group {
justify-content: center;
}
.original-position-item-header {
flex-wrap: wrap;
gap: 6px;
}
.entity-name {
flex: 1;
min-width: 0;
word-break: break-word;
}
.original-position-item-details {
margin-left: 0;
margin-top: 8px;
@@ -203,60 +203,60 @@
border-color: #4a5568;
color: #e2e8f0;
}
.original-positions-content {
background-color: #1a202c;
border-color: #4a5568;
}
.original-position-item {
background-color: #2d3748;
border-color: #4a5568;
color: #e2e8f0;
}
.original-position-item:hover {
background-color: #4a5568;
border-color: #718096;
}
.original-position-item-header {
color: #e2e8f0;
}
.original-position-item-header i {
color: #a0aec0;
}
.entity-name {
color: #e2e8f0;
}
.entity-id {
color: #a0aec0;
}
.original-position-description {
color: #e2e8f0;
}
.original-title {
background-color: #4a5568;
color: #a0aec0;
}
.original-title strong {
color: #e2e8f0;
}
.original-position-date {
color: #a0aec0;
}
.no-original-positions {
color: #a0aec0;
}
.no-original-positions i {
color: #718096;
}
@@ -1,82 +0,0 @@
<template name="originalPositionsView">
<div class="original-positions-view">
<div class="original-positions-header">
<button class="btn btn-sm btn-outline-secondary" onclick="{{toggleOriginalPositions}}">
<i class="fa fa-history"></i>
{{#if isShowingOriginalPositions}}Hide{{else}}Show{{/if}} Original Positions
</button>
{{#if isShowingOriginalPositions}}
<button class="btn btn-sm btn-outline-primary" onclick="{{refreshHistory}}">
<i class="fa fa-refresh"></i> Refresh
</button>
{{/if}}
</div>
{{#if isShowingOriginalPositions}}
<div class="original-positions-content">
{{#if isLoading}}
<div class="original-positions-loading">
<i class="fa fa-spinner fa-spin"></i> Loading original positions...
</div>
{{else}}
<div class="original-positions-filters">
<div class="btn-group btn-group-sm" role="group">
<button type="button"
class="btn {{#if isFilterType 'all'}}btn-primary{{else}}btn-outline-secondary{{/if}}"
onclick="{{setFilterType 'all'}}">
All
</button>
<button type="button"
class="btn {{#if isFilterType 'swimlane'}}btn-primary{{else}}btn-outline-secondary{{/if}}"
onclick="{{setFilterType 'swimlane'}}">
<i class="fa fa-bars"></i> Swimlanes
</button>
<button type="button"
class="btn {{#if isFilterType 'list'}}btn-primary{{else}}btn-outline-secondary{{/if}}"
onclick="{{setFilterType 'list'}}">
<i class="fa fa-columns"></i> Lists
</button>
<button type="button"
class="btn {{#if isFilterType 'card'}}btn-primary{{else}}btn-outline-secondary{{/if}}"
onclick="{{setFilterType 'card'}}">
<i class="fa fa-sticky-note"></i> Cards
</button>
</div>
</div>
<div class="original-positions-list">
{{#each getFilteredHistory}}
<div class="original-position-item">
<div class="original-position-item-header">
<i class="fa {{getEntityTypeIcon entityType}}"></i>
<span class="entity-type">{{getEntityTypeLabel entityType}}</span>
<span class="entity-name">{{getEntityDisplayName this}}</span>
<span class="entity-id">({{entityId}})</span>
</div>
<div class="original-position-item-details">
<div class="original-position-description">
{{getEntityOriginalPositionDescription this}}
</div>
{{#if originalTitle}}
<div class="original-title">
<strong>Original title:</strong> {{originalTitle}}
</div>
{{/if}}
<div class="original-position-date">
<small class="text-muted">Created: {{formatDate createdAt}}</small>
</div>
</div>
</div>
{{else}}
<div class="no-original-positions">
<i class="fa fa-info-circle"></i>
No original position data available for this board.
</div>
{{/each}}
</div>
{{/if}}
</div>
{{/if}}
</div>
</template>
@@ -0,0 +1,57 @@
template(name="originalPositionsView")
.original-positions-view
.original-positions-header
button.btn.btn-sm.btn-outline-secondary.js-toggle-original-positions
i.fa.fa-history
if isShowingOriginalPositions
| Hide Original Positions
else
| Show Original Positions
if isShowingOriginalPositions
button.btn.btn-sm.btn-outline-primary.js-refresh-history
i.fa.fa-refresh
| Refresh
if isShowingOriginalPositions
.original-positions-content
if isLoading
.original-positions-loading
i.fa.fa-spinner.fa-spin
| Loading original positions...
else
.original-positions-filters
.btn-group.btn-group-sm(role="group")
button.btn.js-filter-type(type="button" class="{{#if isFilterType 'all'}}btn-primary{{else}}btn-outline-secondary{{/if}}" data-filter-type="all")
| All
button.btn.js-filter-type(type="button" class="{{#if isFilterType 'swimlane'}}btn-primary{{else}}btn-outline-secondary{{/if}}" data-filter-type="swimlane")
i.fa.fa-bars
| Swimlanes
button.btn.js-filter-type(type="button" class="{{#if isFilterType 'list'}}btn-primary{{else}}btn-outline-secondary{{/if}}" data-filter-type="list")
i.fa.fa-columns
| Lists
button.btn.js-filter-type(type="button" class="{{#if isFilterType 'card'}}btn-primary{{else}}btn-outline-secondary{{/if}}" data-filter-type="card")
i.fa.fa-sticky-note
| Cards
.original-positions-list
each getFilteredHistory
.original-position-item
.original-position-item-header
i.fa(class="{{getEntityTypeIcon entityType}}")
span.entity-type {{getEntityTypeLabel entityType}}
span.entity-name {{getEntityDisplayName this}}
span.entity-id ({{entityId}})
.original-position-item-details
.original-position-description
| {{getEntityOriginalPositionDescription this}}
if originalTitle
.original-title
strong Original title:
| {{originalTitle}}
.original-position-date
small.text-muted Created: {{formatDate createdAt}}
else
.no-original-positions
i.fa.fa-info-circle
| No original position data available for this board.
@@ -1,99 +1,78 @@
import { BlazeComponent } from 'meteor/peerlibrary:blaze-components';
import { ReactiveVar } from 'meteor/reactive-var';
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import './originalPositionsView.html';
/**
* Component to display original positions for all entities on a board
*/
class OriginalPositionsViewComponent extends BlazeComponent {
onCreated() {
super.onCreated();
this.showOriginalPositions = new ReactiveVar(false);
this.boardHistory = new ReactiveVar([]);
this.isLoading = new ReactiveVar(false);
this.filterType = new ReactiveVar('all'); // 'all', 'swimlane', 'list', 'card'
}
onRendered() {
super.onRendered();
this.loadBoardHistory();
}
Template.originalPositionsView.onCreated(function () {
this.showOriginalPositions = new ReactiveVar(false);
this.boardHistory = new ReactiveVar([]);
this.isLoading = new ReactiveVar(false);
this.filterType = new ReactiveVar('all'); // 'all', 'swimlane', 'list', 'card'
loadBoardHistory() {
const tpl = this;
this.loadBoardHistory = function () {
const boardId = Session.get('currentBoard');
if (!boardId) return;
this.isLoading.set(true);
tpl.isLoading.set(true);
Meteor.call('positionHistory.getBoardHistory', boardId, (error, result) => {
this.isLoading.set(false);
tpl.isLoading.set(false);
if (error) {
console.error('Error loading board history:', error);
this.boardHistory.set([]);
tpl.boardHistory.set([]);
} else {
this.boardHistory.set(result);
tpl.boardHistory.set(result);
}
});
}
};
});
toggleOriginalPositions() {
this.showOriginalPositions.set(!this.showOriginalPositions.get());
}
Template.originalPositionsView.onRendered(function () {
this.loadBoardHistory();
});
Template.originalPositionsView.helpers({
isShowingOriginalPositions() {
return this.showOriginalPositions.get();
}
return Template.instance().showOriginalPositions.get();
},
isLoading() {
return this.isLoading.get();
}
return Template.instance().isLoading.get();
},
getBoardHistory() {
return this.boardHistory.get();
}
return Template.instance().boardHistory.get();
},
getFilteredHistory() {
const history = this.getBoardHistory();
const filterType = this.filterType.get();
const tpl = Template.instance();
const history = tpl.boardHistory.get();
const filterType = tpl.filterType.get();
if (filterType === 'all') {
return history;
}
return history.filter(item => item.entityType === filterType);
}
},
getSwimlanesHistory() {
return this.getBoardHistory().filter(item => item.entityType === 'swimlane');
}
getListsHistory() {
return this.getBoardHistory().filter(item => item.entityType === 'list');
}
getCardsHistory() {
return this.getBoardHistory().filter(item => item.entityType === 'card');
}
setFilterType(type) {
this.filterType.set(type);
}
getFilterType() {
return this.filterType.get();
}
isFilterType(type) {
return Template.instance().filterType.get() === type;
},
getEntityDisplayName(entity) {
const position = entity.originalPosition || {};
return position.title || `Entity ${entity.entityId}`;
}
},
getEntityOriginalPositionDescription(entity) {
const position = entity.originalPosition || {};
let description = `Position: ${position.sort || 0}`;
if (entity.entityType === 'list' && entity.originalSwimlaneId) {
description += ` in swimlane ${entity.originalSwimlaneId}`;
} else if (entity.entityType === 'card') {
@@ -104,9 +83,9 @@ class OriginalPositionsViewComponent extends BlazeComponent {
description += ` in list ${entity.originalListId}`;
}
}
return description;
}
},
getEntityTypeIcon(entityType) {
switch (entityType) {
@@ -119,7 +98,7 @@ class OriginalPositionsViewComponent extends BlazeComponent {
default:
return 'fa-question';
}
}
},
getEntityTypeLabel(entityType) {
switch (entityType) {
@@ -132,17 +111,24 @@ class OriginalPositionsViewComponent extends BlazeComponent {
default:
return 'Unknown';
}
}
},
formatDate(date) {
return new Date(date).toLocaleString();
}
},
});
refreshHistory() {
this.loadBoardHistory();
}
}
Template.originalPositionsView.events({
'click .js-toggle-original-positions'(evt, tpl) {
tpl.showOriginalPositions.set(!tpl.showOriginalPositions.get());
},
OriginalPositionsViewComponent.register('originalPositionsView');
'click .js-refresh-history'(evt, tpl) {
tpl.loadBoardHistory();
},
export default OriginalPositionsViewComponent;
'click .js-filter-type'(evt, tpl) {
const type = evt.currentTarget.dataset.filterType;
tpl.filterType.set(type);
},
});
+40 -37
View File
@@ -55,6 +55,12 @@
flex-direction: row;
align-items: center;
}
.attachment-actions a {
margin-left: 16px;
}
.attachment-actions a:first-child {
margin-left: 0;
}
.add-attachment {
display: flex;
align-items: center;
@@ -106,6 +112,9 @@
color: white;
cursor: pointer;
font-size: 4em;
position: absolute;
right: 50px;
top: 16px;
}
/* Upload progress indicators for drag-and-drop uploads */
@@ -241,10 +250,6 @@
.js-card-details.is-dragging-over {
border: 2px dashed #007bff !important;
background: rgba(0, 123, 255, 0.05) !important;
}
top: 0;
right: 8px;
position: absolute;
}
.attachment-arrow {
font-size: 4em;
@@ -253,6 +258,20 @@
align-self: center;
margin: 0 20px;
}
#prev-attachment {
font-size: 4em;
color: white;
cursor: pointer;
align-self: center;
margin-left: 70px;
}
#next-attachment {
font-size: 4em;
color: white;
cursor: pointer;
align-self: center;
margin-right: 70px;
}
#viewer-content {
display: flex;
justify-content: center;
@@ -266,6 +285,13 @@
max-width: 100%;
max-height: 100%;
}
#video-viewer {
max-width: 100%;
max-height: 100%;
}
#audio-viewer {
max-width: 100%;
}
#pdf-viewer {
width: 40vw;
height: 100%;
@@ -300,9 +326,19 @@
}
#prev-attachment {
left: 0;
position: absolute;
bottom: 2.2em;
font-size: 1.6em;
padding: 16px;
margin-left: 0;
}
#next-attachment {
right: 0;
position: absolute;
bottom: 2.2em;
font-size: 1.6em;
padding: 16px;
margin-right: 0;
}
#pdf-viewer {
width: 100%;
@@ -336,36 +372,3 @@
margin-top: 10px;
}
}
/* Attachment migration styles */
.attachment-item.migrating {
position: relative;
opacity: 0.7;
}
.attachment-migration-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.9);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 10;
border-radius: 4px;
}
.migration-spinner {
font-size: 24px;
color: #007cba;
margin-bottom: 8px;
}
.migration-text {
font-size: 12px;
color: #666;
text-align: center;
}
+20 -20
View File
@@ -34,10 +34,11 @@ template(name="attachmentViewer")
#viewer-overlay.hidden
#viewer-top-bar
span#attachment-name
a#viewer-close ❌
a#viewer-close
i.fa.fa-times-thin
#viewer-container
| ◀️
i.fa.fa-caret-left#prev-attachment
#viewer-content
img#image-viewer.hidden
video#video-viewer.hidden(controls="true")
@@ -45,7 +46,7 @@ template(name="attachmentViewer")
object#pdf-viewer.hidden(type="application/pdf")
span.pdf-preview-error {{_ 'preview-pdf-not-supported' }}
object#txt-viewer.hidden(type="text/plain")
| ▶️
i.fa.fa-caret-right#next-attachment
template(name="attachmentGallery")
@@ -53,7 +54,7 @@ template(name="attachmentGallery")
if canModifyCard
a.attachment-item.add-attachment.js-add-attachment
| ➕
i.fa.fa-plus
each attachments
@@ -85,24 +86,27 @@ 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
if currentUser.isBoardMember
unless currentUser.isCommentOnly
unless currentUser.isWorker
a.js-rename(title="{{_ 'rename'}}")
| ✏️
i.fa.fa-pencil-square-o
a.js-confirm-delete(title="{{_ 'delete'}}")
| 🗑️
i.fa.fa-trash
a.js-open-attachment-menu(data-attachment-link="{{link}}", title="{{_ 'attachmentActionsPopup-title'}}")
| ☰
i.fa.fa-bars
// Migration spinner overlay
if isAttachmentMigrating _id
.attachment-migration-overlay
.migration-spinner
| ⚙️
i.fa.fa-cog.fa-spin
.migration-text {{_ 'migrating-attachment'}}
template(name="attachmentActionsPopup")
@@ -110,16 +114,12 @@ template(name="attachmentActionsPopup")
li
if isImage
a(class="{{#if isCover}}js-remove-cover{{else}}js-add-cover{{/if}}")
| 📖
| 🖼️
if isCover
| {{_ 'remove-cover'}}
else
| {{_ 'add-cover'}}
i.fa.fa-picture-o
| {{#if isCover}}{{_ 'remove-cover'}}{{else}}{{_ 'add-cover'}}{{/if}}
if currentUser.isBoardAdmin
if isImage
a(class="{{#if isBackgroundImage}}js-remove-background-image{{else}}js-add-background-image{{/if}}")
| 🖼️
i.fa.fa-picture-o
if isBackgroundImage
| {{_ 'remove-background-image'}}
else
@@ -127,19 +127,19 @@ template(name="attachmentActionsPopup")
if $neq versions.original.storage "fs"
a.js-move-storage-fs
| ▶️
i.fa.fa-arrow-right
| {{_ 'attachment-move-storage-fs'}}
if $neq versions.original.storage "gridfs"
if versions.original.storage
a.js-move-storage-gridfs
| ▶️
i.fa.fa-arrow-right
| {{_ 'attachment-move-storage-gridfs'}}
if $neq versions.original.storage "s3"
if versions.original.storage
a.js-move-storage-s3
| ▶️
i.fa.fa-arrow-right
| {{_ 'attachment-move-storage-s3'}}
template(name="attachmentRenamePopup")
+101 -88
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,
);
@@ -495,9 +514,9 @@ Template.previewClipboardImagePopup.events({
},
});
BlazeComponent.extendComponent({
Template.attachmentActionsPopup.helpers({
isCover() {
const ret = ReactiveCache.getCard(this.data().meta.cardId).coverId == this.data()._id;
const ret = ReactiveCache.getCard(this.meta.cardId).coverId == this._id;
return ret;
},
isBackgroundImage() {
@@ -505,78 +524,72 @@ BlazeComponent.extendComponent({
//return currentBoard.backgroundImageURL === $(".attachment-thumbnail-img").attr("src");
return false;
},
events() {
return [
{
'click .js-add-cover'() {
ReactiveCache.getCard(this.data().meta.cardId).setCover(this.data()._id);
Popup.back();
},
'click .js-remove-cover'() {
ReactiveCache.getCard(this.data().meta.cardId).unsetCover();
Popup.back();
},
'click .js-add-background-image'() {
const currentBoard = Utils.getCurrentBoard();
currentBoard.setBackgroundImageURL(attachmentActionsLink);
Utils.setBackgroundImage(attachmentActionsLink);
Popup.back();
event.preventDefault();
},
'click .js-remove-background-image'() {
const currentBoard = Utils.getCurrentBoard();
currentBoard.setBackgroundImageURL("");
Utils.setBackgroundImage("");
Popup.back();
Utils.reload();
event.preventDefault();
},
'click .js-move-storage-fs'() {
Meteor.call('moveAttachmentToStorage', this.data()._id, "fs");
Popup.back();
},
'click .js-move-storage-gridfs'() {
Meteor.call('moveAttachmentToStorage', this.data()._id, "gridfs");
Popup.back();
},
'click .js-move-storage-s3'() {
Meteor.call('moveAttachmentToStorage', this.data()._id, "s3");
Popup.back();
},
}
]
}
}).register('attachmentActionsPopup');
});
BlazeComponent.extendComponent({
Template.attachmentActionsPopup.events({
'click .js-add-cover'() {
ReactiveCache.getCard(this.meta.cardId).setCover(this._id);
Popup.back();
},
'click .js-remove-cover'() {
ReactiveCache.getCard(this.meta.cardId).unsetCover();
Popup.back();
},
'click .js-add-background-image'(event) {
const currentBoard = Utils.getCurrentBoard();
currentBoard.setBackgroundImageURL(attachmentActionsLink);
Utils.setBackgroundImage(attachmentActionsLink);
Popup.back();
event.preventDefault();
},
'click .js-remove-background-image'(event) {
const currentBoard = Utils.getCurrentBoard();
currentBoard.setBackgroundImageURL("");
Utils.setBackgroundImage("");
Popup.back();
Utils.reload();
event.preventDefault();
},
'click .js-move-storage-fs'() {
Meteor.call('moveAttachmentToStorage', this._id, "fs");
Popup.back();
},
'click .js-move-storage-gridfs'() {
Meteor.call('moveAttachmentToStorage', this._id, "gridfs");
Popup.back();
},
'click .js-move-storage-s3'() {
Meteor.call('moveAttachmentToStorage', this._id, "s3");
Popup.back();
},
});
Template.attachmentRenamePopup.helpers({
getNameWithoutExtension() {
const ret = this.data().name.replace(new RegExp("\." + this.data().extension + "$"), "");
const ret = this.name.replace(new RegExp("\." + this.extension + "$"), "");
return ret;
},
events() {
return [
{
'keydown input.js-edit-attachment-name'(evt) {
// enter = save
if (evt.keyCode === 13) {
this.find('button[type=submit]').click();
}
},
'click button.js-submit-edit-attachment-name'(event) {
// save button pressed
event.preventDefault();
const name = this.$('.js-edit-attachment-name')[0]
.value
.trim() + this.data().extensionWithDot;
if (name === sanitizeText(name)) {
Meteor.call('renameAttachment', this.data()._id, name);
}
Popup.back();
},
}
]
}
}).register('attachmentRenamePopup');
});
Template.attachmentRenamePopup.events({
'keydown input.js-edit-attachment-name'(evt, tpl) {
// enter = save
if (evt.keyCode === 13) {
tpl.find('button[type=submit]').click();
}
},
'click button.js-submit-edit-attachment-name'(event, tpl) {
// save button pressed
event.preventDefault();
const name = tpl.$('.js-edit-attachment-name')[0]
.value
.trim() + this.extensionWithDot;
if (name === sanitizeText(name)) {
Meteor.call('renameAttachment', this._id, name);
}
Popup.back();
},
});
// Template helpers for attachment migration status
Template.registerHelper('attachmentMigrationStatus', function(attachmentId) {
+21 -5
View File
@@ -6,10 +6,10 @@ template(name="cardCustomFieldsPopup")
span.full-name
= name
if hasCustomField
| ✅
i.fa.fa-check
hr
a.quiet-button.full.js-settings
| ⚙️
i.fa.fa-cog
span {{_ 'settings'}}
template(name="cardCustomField")
@@ -55,10 +55,11 @@ template(name="cardCustomField-number")
template(name="cardCustomField-checkbox")
.js-checklist-item.checklist-item(class="{{#if data.value }}is-checked{{/if}}")
if canModifyCard
.check-box-container
.check-box.materialCheckBox(class="{{#if data.value }}is-checked{{/if}}")
span.check-box-unicode
i.fa(class="{{#if data.value}}fa-check-square{{else}}fa-square-o{{/if}}")
else
.materialCheckBox(class="{{#if data.value }}is-checked{{/if}}")
span.check-box-unicode
i.fa(class="{{#if data.value}}fa-check-square{{else}}fa-square-o{{/if}}")
template(name="cardCustomField-currency")
if canModifyCard
@@ -98,6 +99,21 @@ template(name="cardCustomField-date")
b
| {{showWeek}}
template(name="cardCustomField-datePopup")
.datepicker-container
form.edit-date
.fields
.left
label(for="date") {{_ 'date'}}
input.js-date-field#date(type="date" name="date" value=showDate autofocus)
.right
label(for="time") {{_ 'time'}}
input.js-time-field#time(type="time" name="time" value=showTime)
if error.get
.warning {{_ error.get}}
button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}}
template(name="cardCustomField-dropdown")
if canModifyCard
+inlinedForm(classNames="js-card-customfield-dropdown")
+242 -254
View File
@@ -1,33 +1,41 @@
import { TAPi18n } from '/imports/i18n';
import { DatePicker } from '/client/lib/datepicker';
import {
setupDatePicker,
datePickerRendered,
datePickerHelpers,
datePickerEvents,
} from '/client/lib/datepicker';
import { ReactiveCache } from '/imports/reactiveCache';
import {
formatDateTime,
formatDate,
import {
formatDateTime,
formatDate,
formatDateByUserPreference,
formatTime,
getISOWeek,
isValidDate,
isBefore,
isAfter,
isSame,
add,
subtract,
startOf,
endOf,
format,
parseDate,
now,
createDate,
fromNow,
calendar
formatTime,
getISOWeek,
isValidDate,
isBefore,
isAfter,
isSame,
add,
subtract,
startOf,
endOf,
format,
parseDate,
now,
createDate,
fromNow,
calendar
} from '/imports/lib/dateUtils';
import Cards from '/models/cards';
import { CustomFieldStringTemplate } from '/client/lib/customFields'
import { getCurrentCardFromContext } from '/client/lib/currentCard';
import { EscapeActions } from '/client/lib/escapeActions';
import { getSidebarInstance } from '/client/features/sidebar/service';
Template.cardCustomFieldsPopup.helpers({
hasCustomField() {
const card = Utils.getCurrentCard();
const card = getCurrentCardFromContext();
if (!card) return false;
const customFieldId = this._id;
return card.customFieldIndex(customFieldId) > -1;
},
@@ -35,317 +43,297 @@ Template.cardCustomFieldsPopup.helpers({
Template.cardCustomFieldsPopup.events({
'click .js-select-field'(event) {
const card = Utils.getCurrentCard();
const card = getCurrentCardFromContext();
if (!card) return;
const customFieldId = this._id;
card.toggleCustomField(customFieldId);
event.preventDefault();
},
'click .js-settings'(event) {
EscapeActions.executeUpTo('detailsPane');
Sidebar.setView('customFields');
const sidebar = getSidebarInstance();
if (sidebar) {
sidebar.setView('customFields');
}
event.preventDefault();
},
});
// cardCustomField
const CardCustomField = BlazeComponent.extendComponent({
Template.cardCustomField.helpers({
getTemplate() {
return `cardCustomField-${this.data().definition.type}`;
},
onCreated() {
const self = this;
self.card = Utils.getCurrentCard();
self.customFieldId = this.data()._id;
return `cardCustomField-${this.definition.type}`;
},
});
CardCustomField.register('cardCustomField');
Template.cardCustomField.onCreated(function () {
this.card = getCurrentCardFromContext();
this.customFieldId = Template.currentData()._id;
});
// cardCustomField-text
(class extends CardCustomField {
onCreated() {
super.onCreated();
}
Template['cardCustomField-text'].onCreated(function () {
this.card = getCurrentCardFromContext();
this.customFieldId = Template.currentData()._id;
});
events() {
return [
{
'submit .js-card-customfield-text'(event) {
event.preventDefault();
const value = this.currentComponent().getValue();
this.card.setCustomField(this.customFieldId, value);
},
},
];
}
}.register('cardCustomField-text'));
Template['cardCustomField-text'].events({
'submit .js-card-customfield-text'(event, tpl) {
event.preventDefault();
const value = tpl.currentComponent ? tpl.currentComponent().getValue() : tpl.$('textarea').val();
tpl.card.setCustomField(tpl.customFieldId, value);
},
});
// cardCustomField-number
(class extends CardCustomField {
onCreated() {
super.onCreated();
}
Template['cardCustomField-number'].onCreated(function () {
this.card = getCurrentCardFromContext();
this.customFieldId = Template.currentData()._id;
});
events() {
return [
{
'submit .js-card-customfield-number'(event) {
event.preventDefault();
const value = parseInt(this.find('input').value, 10);
this.card.setCustomField(this.customFieldId, value);
},
},
];
}
}.register('cardCustomField-number'));
Template['cardCustomField-number'].events({
'submit .js-card-customfield-number'(event, tpl) {
event.preventDefault();
const value = parseInt(tpl.find('input').value, 10);
tpl.card.setCustomField(tpl.customFieldId, value);
},
});
// cardCustomField-checkbox
(class extends CardCustomField {
onCreated() {
super.onCreated();
}
Template['cardCustomField-checkbox'].onCreated(function () {
this.card = getCurrentCardFromContext();
this.customFieldId = Template.currentData()._id;
});
toggleItem() {
this.card.setCustomField(this.customFieldId, !this.data().value);
}
events() {
return [
{
'click .js-checklist-item .check-box-container': this.toggleItem,
},
];
}
}.register('cardCustomField-checkbox'));
Template['cardCustomField-checkbox'].events({
'click .js-checklist-item .check-box-unicode'(event, tpl) {
tpl.card.setCustomField(tpl.customFieldId, !Template.currentData().value);
},
'click .js-checklist-item .check-box-container'(event, tpl) {
tpl.card.setCustomField(tpl.customFieldId, !Template.currentData().value);
},
});
// cardCustomField-currency
(class extends CardCustomField {
onCreated() {
super.onCreated();
this.currencyCode = this.data().definition.settings.currencyCode;
}
Template['cardCustomField-currency'].onCreated(function () {
this.card = getCurrentCardFromContext();
this.customFieldId = Template.currentData()._id;
this.currencyCode = Template.currentData().definition.settings.currencyCode;
});
Template['cardCustomField-currency'].helpers({
formattedValue() {
const locale = TAPi18n.getLanguage();
const tpl = Template.instance();
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: this.currencyCode,
}).format(this.data().value);
}
currency: tpl.currencyCode,
}).format(this.value);
},
});
events() {
return [
{
'submit .js-card-customfield-currency'(event) {
event.preventDefault();
// To allow input separated by comma, the comma is replaced by a period.
const value = Number(this.find('input').value.replace(/,/i, '.'), 10);
this.card.setCustomField(this.customFieldId, value);
},
},
];
}
}.register('cardCustomField-currency'));
Template['cardCustomField-currency'].events({
'submit .js-card-customfield-currency'(event, tpl) {
event.preventDefault();
// To allow input separated by comma, the comma is replaced by a period.
const value = Number(tpl.find('input').value.replace(/,/i, '.'), 10);
tpl.card.setCustomField(tpl.customFieldId, value);
},
});
// cardCustomField-date
(class extends CardCustomField {
onCreated() {
super.onCreated();
const self = this;
self.date = ReactiveVar();
self.now = ReactiveVar(now());
window.setInterval(() => {
self.now.set(now());
}, 60000);
Template['cardCustomField-date'].onCreated(function () {
this.card = getCurrentCardFromContext();
this.customFieldId = Template.currentData()._id;
const self = this;
self.date = ReactiveVar();
self.now = ReactiveVar(now());
window.setInterval(() => {
self.now.set(now());
}, 60000);
self.autorun(() => {
self.date.set(new Date(self.data().value));
});
}
self.autorun(() => {
self.date.set(new Date(Template.currentData().value));
});
});
Template['cardCustomField-date'].helpers({
showWeek() {
return getISOWeek(this.date.get()).toString();
}
return getISOWeek(Template.instance().date.get()).toString();
},
showWeekOfYear() {
const user = ReactiveCache.getCurrentUser();
if (!user) {
// For non-logged-in users, week of year is not shown
return false;
}
return user.isShowWeekOfYear();
}
},
showDate() {
const currentUser = ReactiveCache.getCurrentUser();
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
return formatDateByUserPreference(this.date.get(), dateFormat, true);
}
return formatDateByUserPreference(Template.instance().date.get(), dateFormat, true);
},
showISODate() {
return this.date.get().toISOString();
}
return Template.instance().date.get().toISOString();
},
classes() {
const tpl = Template.instance();
if (
isBefore(this.date.get(), this.now.get(), 'minute') &&
isBefore(this.now.get(), this.data().value, 'minute')
isBefore(tpl.date.get(), tpl.now.get(), 'minute') &&
isBefore(tpl.now.get(), this.value, 'minute')
) {
return 'current';
}
return '';
}
},
showTitle() {
return `${TAPi18n.__('card-start-on')} ${this.date.get().toLocaleString()}`;
}
return `${TAPi18n.__('card-start-on')} ${Template.instance().date.get().toLocaleString()}`;
},
});
events() {
return [
{
'click .js-edit-date': Popup.open('cardCustomField-date'),
},
];
}
}.register('cardCustomField-date'));
Template['cardCustomField-date'].events({
'click .js-edit-date': Popup.open('cardCustomField-date'),
});
// cardCustomField-datePopup
(class extends DatePicker {
onCreated() {
super.onCreated();
const self = this;
self.card = Utils.getCurrentCard();
self.customFieldId = this.data()._id;
this.data().value && this.date.set(new Date(this.data().value));
}
Template['cardCustomField-datePopup'].onCreated(function () {
const data = Template.currentData();
setupDatePicker(this, {
initialDate: data.value ? data.value : undefined,
});
// Override card and store customFieldId for store/delete callbacks
this.datePicker.card = getCurrentCardFromContext();
this.customFieldId = data._id;
});
_storeDate(date) {
this.card.setCustomField(this.customFieldId, date);
}
Template['cardCustomField-datePopup'].onRendered(function () {
datePickerRendered(this);
});
_deleteDate() {
this.card.setCustomField(this.customFieldId, '');
}
}.register('cardCustomField-datePopup'));
Template['cardCustomField-datePopup'].helpers(datePickerHelpers());
Template['cardCustomField-datePopup'].events(datePickerEvents({
storeDate(date) {
this.datePicker.card.setCustomField(this.customFieldId, date);
},
deleteDate() {
this.datePicker.card.setCustomField(this.customFieldId, '');
},
}));
// cardCustomField-dropdown
(class extends CardCustomField {
onCreated() {
super.onCreated();
this._items = this.data().definition.settings.dropdownItems;
this.items = this._items.slice(0);
this.items.unshift({
_id: '',
name: TAPi18n.__('custom-field-dropdown-none'),
});
}
Template['cardCustomField-dropdown'].onCreated(function () {
this.card = getCurrentCardFromContext();
this.customFieldId = Template.currentData()._id;
this._items = Template.currentData().definition.settings.dropdownItems;
this.items = this._items.slice(0);
this.items.unshift({
_id: '',
name: TAPi18n.__('custom-field-dropdown-none'),
});
});
Template['cardCustomField-dropdown'].helpers({
items() {
return Template.instance().items;
},
selectedItem() {
const selected = this._items.find(item => {
return item._id === this.data().value;
const tpl = Template.instance();
const selected = tpl._items.find(item => {
return item._id === this.value;
});
return selected
? selected.name
: TAPi18n.__('custom-field-dropdown-unknown');
}
},
});
events() {
return [
{
'submit .js-card-customfield-dropdown'(event) {
event.preventDefault();
const value = this.find('select').value;
this.card.setCustomField(this.customFieldId, value);
},
},
];
}
}.register('cardCustomField-dropdown'));
Template['cardCustomField-dropdown'].events({
'submit .js-card-customfield-dropdown'(event, tpl) {
event.preventDefault();
const value = tpl.find('select').value;
tpl.card.setCustomField(tpl.customFieldId, value);
},
});
// cardCustomField-stringtemplate
class CardCustomFieldStringTemplate extends CardCustomField {
onCreated() {
super.onCreated();
this.customField = new CustomFieldStringTemplate(this.data().definition);
this.stringtemplateItems = new ReactiveVar(this.data().value ?? []);
}
Template['cardCustomField-stringtemplate'].onCreated(function () {
this.card = getCurrentCardFromContext();
this.customFieldId = Template.currentData()._id;
this.customField = new CustomFieldStringTemplate(Template.currentData().definition);
this.stringtemplateItems = new ReactiveVar(Template.currentData().value ?? []);
});
Template['cardCustomField-stringtemplate'].helpers({
formattedValue() {
const ret = this.customField.getFormattedValue(this.data().value);
const tpl = Template.instance();
const ret = tpl.customField.getFormattedValue(this.value);
return ret;
}
},
stringtemplateItems() {
return Template.instance().stringtemplateItems.get();
},
});
getItems() {
return Array.from(this.findAll('input'))
.map(input => input.value)
.filter(value => !!value.trim());
}
Template['cardCustomField-stringtemplate'].events({
'submit .js-card-customfield-stringtemplate'(event, tpl) {
event.preventDefault();
const items = tpl.stringtemplateItems.get();
tpl.card.setCustomField(tpl.customFieldId, items);
},
events() {
return [
{
'submit .js-card-customfield-stringtemplate'(event) {
event.preventDefault();
const items = this.stringtemplateItems.get();
this.card.setCustomField(this.customFieldId, items);
},
'keydown .js-card-customfield-stringtemplate-item'(event, tpl) {
if (event.keyCode === 13) {
event.preventDefault();
'keydown .js-card-customfield-stringtemplate-item'(event) {
if (event.keyCode === 13) {
event.preventDefault();
if (event.target.value.trim() || event.metaKey || event.ctrlKey) {
const inputLast = tpl.find('input.last');
if (event.target.value.trim() || event.metaKey || event.ctrlKey) {
const inputLast = this.find('input.last');
let items = Array.from(tpl.findAll('input'))
.map(input => input.value)
.filter(value => !!value.trim());
let items = this.getItems();
if (event.target === inputLast) {
inputLast.value = '';
} else if (event.target.nextSibling === inputLast) {
inputLast.focus();
} else {
event.target.blur();
if (event.target === inputLast) {
inputLast.value = '';
} else if (event.target.nextSibling === inputLast) {
inputLast.focus();
} else {
event.target.blur();
const idx = Array.from(tpl.findAll('input')).indexOf(
event.target,
);
items.splice(idx + 1, 0, '');
const idx = Array.from(this.findAll('input')).indexOf(
event.target,
);
items.splice(idx + 1, 0, '');
Tracker.afterFlush(() => {
const element = tpl.findAll('input')[idx + 1];
element.focus();
element.value = '';
});
}
Tracker.afterFlush(() => {
const element = this.findAll('input')[idx + 1];
element.focus();
element.value = '';
});
}
tpl.stringtemplateItems.set(items);
}
if (event.metaKey || event.ctrlKey) {
tpl.find('button[type=submit]').click();
}
}
},
this.stringtemplateItems.set(items);
}
if (event.metaKey || event.ctrlKey) {
this.find('button[type=submit]').click();
}
}
},
'blur .js-card-customfield-stringtemplate-item'(event, tpl) {
if (
!event.target.value.trim() ||
event.target === tpl.find('input.last')
) {
const items = Array.from(tpl.findAll('input'))
.map(input => input.value)
.filter(value => !!value.trim());
tpl.stringtemplateItems.set(items);
tpl.find('input.last').value = '';
}
},
'blur .js-card-customfield-stringtemplate-item'(event) {
if (
!event.target.value.trim() ||
event.target === this.find('input.last')
) {
const items = this.getItems();
this.stringtemplateItems.set(items);
this.find('input.last').value = '';
}
},
'click .js-close-inlined-form'(event) {
this.stringtemplateItems.set(this.data().value ?? []);
},
},
];
}
}
CardCustomFieldStringTemplate.register('cardCustomField-stringtemplate');
'click .js-close-inlined-form'(event, tpl) {
tpl.stringtemplateItems.set(Template.currentData().value ?? []);
},
});
+53 -20
View File
@@ -8,39 +8,72 @@
.card-date.is-active {
background-color: #b3b3b3;
}
.card-date.current,
.card-date.almost-due,
.card-date.due,
.card-date.long-overdue {
/* Date status colors - red = overdue, amber = due soon, no shade = not due */
.card-date.overdue {
background-color: #ff4444; /* Red for overdue */
color: #fff;
}
.card-date.overdue:hover,
.card-date.overdue.is-active {
background-color: #cc3333;
}
.card-date.due-soon {
background-color: #ffaa00; /* Amber for due soon */
color: #000;
}
.card-date.due-soon:hover,
.card-date.due-soon.is-active {
background-color: #e69900;
}
.card-date.not-due {
/* No special background - uses default date type colors */
}
.card-date.current {
background-color: #5ba639;
background-color: #5ba639; /* Green for current/active */
color: #fff;
}
.card-date.current:hover,
.card-date.current.is-active {
background-color: #46802c;
}
.card-date.almost-due {
background-color: #edc909;
.card-date.completed {
background-color: #90ee90; /* Light green for completed */
color: #000;
}
.card-date.almost-due:hover,
.card-date.almost-due.is-active {
background-color: #bc9f07;
.card-date.completed:hover,
.card-date.completed.is-active {
background-color: #7dd87d;
}
.card-date.due {
background-color: #fa3f00;
.card-date.completed-early {
background-color: #4caf50; /* Green for completed early */
color: #fff;
}
.card-date.due:hover,
.card-date.due.is-active {
background-color: #c73200;
.card-date.completed-early:hover,
.card-date.completed-early.is-active {
background-color: #45a049;
}
.card-date.long-overdue {
background-color: #fd5d47;
.card-date.completed-late {
background-color: #ff9800; /* Orange for completed late */
color: #fff;
}
.card-date.long-overdue:hover,
.card-date.long-overdue.is-active {
background-color: #fd3e24;
.card-date.completed-late:hover,
.card-date.completed-late.is-active {
background-color: #f57c00;
}
.card-date.completed-on-time {
background-color: #2196f3; /* Blue for completed on time */
color: #fff;
}
.card-date.completed-on-time:hover,
.card-date.completed-on-time.is-active {
background-color: #1976d2;
}
/* Date type specific colors */
+186 -28
View File
@@ -14,6 +14,70 @@ template(name="dateBadge")
b
| {{showWeek}}
template(name="cardReceivedDate")
if canModifyCard
a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
else
a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
template(name="cardStartDate")
if canModifyCard
a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
else
a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
template(name="cardDueDate")
if canModifyCard
a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
else
a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
template(name="cardEndDate")
if canModifyCard
a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
else
a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
template(name="dateCustomField")
a(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
@@ -22,6 +86,46 @@ template(name="dateCustomField")
b
| {{showWeek}}
template(name="cardCustomFieldDate")
a(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
template(name="voteEndDate")
if canModifyCard
a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
else
a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
template(name="pokerEndDate")
if canModifyCard
a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
else
a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
time(datetime="{{showISODate}}")
| {{showDate}}
if showWeekOfYear
b
| {{showWeek}}
template(name="minicardReceivedDate")
if canModifyCard
a.js-edit-date.card-date.received-date(title="{{_ 'card-received'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}")
@@ -95,37 +199,91 @@ template(name="minicardCustomFieldDate")
| {{showWeek}}
template(name="editCardReceivedDatePopup")
form.edit-card-received-date
.datepicker
.clear-date
a.js-clear-date {{_ 'clear'}}
.datepicker-actions
button.primary.wide.left(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right {{_ 'delete'}}
.datepicker-container
form.edit-date
.fields
.left
label(for="date") {{_ 'date'}}
input.js-date-field#date(type="date" name="date" value=showDate autofocus)
.right
label(for="time") {{_ 'time'}}
input.js-time-field#time(type="time" name="time" value=showTime)
if error.get
.warning {{_ error.get}}
button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}}
template(name="editCardStartDatePopup")
form.edit-card-start-date
.datepicker
.clear-date
a.js-clear-date {{_ 'clear'}}
.datepicker-actions
button.primary.wide.left(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right {{_ 'delete'}}
.datepicker-container
form.edit-date
.fields
.left
label(for="date") {{_ 'date'}}
input.js-date-field#date(type="date" name="date" value=showDate autofocus)
.right
label(for="time") {{_ 'time'}}
input.js-time-field#time(type="time" name="time" value=showTime)
if error.get
.warning {{_ error.get}}
button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}}
template(name="editCardDueDatePopup")
form.edit-card-due-date
.datepicker
.clear-date
a.js-clear-date {{_ 'clear'}}
.datepicker-actions
button.primary.wide.left(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right {{_ 'delete'}}
.datepicker-container
form.edit-date
.fields
.left
label(for="date") {{_ 'date'}}
input.js-date-field#date(type="date" name="date" value=showDate autofocus)
.right
label(for="time") {{_ 'time'}}
input.js-time-field#time(type="time" name="time" value=showTime)
if error.get
.warning {{_ error.get}}
button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}}
template(name="editCardEndDatePopup")
form.edit-card-end-date
.datepicker
.clear-date
a.js-clear-date {{_ 'clear'}}
.datepicker-actions
button.primary.wide.left(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right {{_ 'delete'}}
.datepicker-container
form.edit-date
.fields
.left
label(for="date") {{_ 'date'}}
input.js-date-field#date(type="date" name="date" value=showDate autofocus)
.right
label(for="time") {{_ 'time'}}
input.js-time-field#time(type="time" name="time" value=showTime)
if error.get
.warning {{_ error.get}}
button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}}
template(name="editVoteEndDatePopup")
.datepicker-container
form.edit-date
.fields
.left
label(for="date") {{_ 'date'}}
input.js-date-field#date(type="date" name="date" value=showDate autofocus)
.right
label(for="time") {{_ 'time'}}
input.js-time-field#time(type="time" name="time" value=showTime)
if error.get
.warning {{_ error.get}}
button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}}
template(name="editPokerEndDatePopup")
.datepicker-container
form.edit-date
.fields
.left
label(for="date") {{_ 'date'}}
input.js-date-field#date(type="date" name="date" value=showDate autofocus)
.right
label(for="time") {{_ 'time'}}
input.js-time-field#time(type="time" name="time" value=showTime)
if error.get
.warning {{_ error.get}}
button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}}
button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}}
File diff suppressed because it is too large. Load diff
+20 -28
View File
@@ -1,37 +1,29 @@
const descriptionFormIsOpen = new ReactiveVar(false);
BlazeComponent.extendComponent({
onDestroyed() {
descriptionFormIsOpen.set(false);
$('.note-popover').hide();
},
Template.descriptionForm.onDestroyed(function () {
descriptionFormIsOpen.set(false);
$('.note-popover').hide();
});
Template.descriptionForm.helpers({
descriptionFormIsOpen() {
return descriptionFormIsOpen.get();
},
});
getInput() {
return this.$('.js-new-description-input');
Template.descriptionForm.events({
async 'submit .js-card-description'(event, tpl) {
event.preventDefault();
const description = tpl.currentComponent ? tpl.currentComponent().getValue() : tpl.$('textarea').val();
await this.setDescription(description);
},
events() {
return [
{
'submit .js-card-description'(event) {
event.preventDefault();
const description = this.currentComponent().getValue();
this.data().setDescription(description);
},
// Pressing Ctrl+Enter should submit the form
'keydown form textarea'(evt) {
if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) {
const submitButton = this.find('button[type=submit]');
if (submitButton) {
submitButton.click();
}
}
},
},
];
// Pressing Ctrl+Enter should submit the form
'keydown form textarea'(evt, tpl) {
if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) {
const submitButton = tpl.find('button[type=submit]');
if (submitButton) {
submitButton.click();
}
}
},
}).register('descriptionForm');
});
+296 -38
View File
@@ -1,23 +1,25 @@
/* Date Format Selector */
.card-details-item-date-format {
margin-bottom: 10px;
margin-bottom: 12px;
}
.card-details-item-date-format .card-details-item-title {
font-size: 14px;
font-size: 15px;
font-weight: bold;
margin-bottom: 5px;
margin-bottom: 6px;
color: #333;
letter-spacing: 0.03em;
}
.card-details-item-date-format .js-date-format-selector {
width: 100%;
padding: 8px;
padding: 9px 10px;
border: 1px solid #ddd;
border-radius: 4px;
border-radius: 5px;
background-color: #fff;
font-size: 14px;
font-size: 15px;
cursor: pointer;
transition: border-color 0.15s, box-shadow 0.15s;
}
.card-details-item-date-format .js-date-format-selector:focus {
@@ -27,18 +29,18 @@
}
.assignee {
border-radius: 3px;
display: block;
position: relative;
float: left;
height: 30px;
width: 30px;
margin: .3vh;
height: clamp(24px, 3.5vw, 36px);
width: clamp(24px, 3.5vw, 36px);
margin: 0.3vh;
cursor: pointer;
user-select: none;
z-index: 1;
text-decoration: none;
border-radius: 50%;
box-shadow: 0 1px 2px 0 rgba(0,0,0,0.04);
}
.assignee .avatar {
overflow: hidden;
@@ -51,12 +53,18 @@
background-color: #dbdbdb;
color: #444;
position: absolute;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.assignee .avatar.avatar-image {
object-fit: cover;
object-position: center;
height: 100%;
width: 100%;
display: block;
}
.assignee .assignee-presence-status {
background-color: #b3b3b3;
@@ -67,7 +75,6 @@
position: absolute;
right: -1px;
bottom: -1px;
border: 1px solid #fff;
z-index: 15;
}
.assignee .assignee-presence-status.active {
@@ -91,6 +98,7 @@
align-items: center;
justify-content: center;
box-shadow: 0 0 0 2px #bfbfbf inset;
transition: box-shadow 0.12s;
}
.assignee.add-assignee:hover,
.assignee.add-assignee.is-active {
@@ -102,22 +110,83 @@
background-color: rgba(0,0,0,0.875);
color: #fff;
border-radius: 0.7vw;
font-size: 0.98em;
}
.card-details {
padding: 0;
flex-shrink: 0;
flex-basis: min(600px, 80vw);
will-change: flex-basis;
overflow-y: scroll;
overflow-y: auto;
overflow-x: hidden;
background: #f7f7f7;
border-radius: bottom 0.4vw;
border-radius: 0 0 0.4vw 0.4vw;
z-index: 30;
animation: flexGrowIn 0.1s;
box-shadow: 0 0 0.9vh 0 #b3b3b3;
transition: flex-basis 0.1s;
transition: flex-basis 0.1s, box-shadow 0.15s;
box-sizing: border-box;
}
/* Desktop mode: position card below board header */
body.desktop-mode .card-details:not(.card-details-popup) {
position: fixed;
width: auto;
max-width: 800px;
flex-basis: auto;
border-radius: 8px;
z-index: 2001;
}
/* Default position for first card or when dragged */
body.desktop-mode .card-details:not(.card-details-popup):not([style*="left"]):not([style*="top"]) {
top: 8px;
left: 20px;
right: 20px;
bottom: 20px;
}
/* Stagger positions for multiple cards using nth-of-type */
body.desktop-mode .card-details:not(.card-details-popup):nth-of-type(1) {
top: 8px;
left: 20px;
}
body.desktop-mode .card-details:not(.card-details-popup):nth-of-type(2) {
top: 38px;
left: 50px;
}
body.desktop-mode .card-details:not(.card-details-popup):nth-of-type(3) {
top: 68px;
left: 80px;
}
body.desktop-mode .card-details:not(.card-details-popup):nth-of-type(4) {
top: 98px;
left: 110px;
}
body.desktop-mode .card-details:not(.card-details-popup):nth-of-type(5) {
top: 128px;
left: 140px;
}
/* For expanded cards, set dimensions */
body.desktop-mode .card-details:not(.card-details-popup):not(.card-details-collapsed) {
right: 20px;
bottom: 20px;
}
/* Collapsed card state - hide content and set height to title row only */
.card-details.card-details-collapsed .card-details-canvas > *:not(.card-details-header) {
display: none !important;
}
.card-details.card-details-collapsed {
height: auto !important;
bottom: auto !important;
overflow: visible;
}
body.desktop-mode .card-details.card-details-collapsed {
bottom: auto !important;
}
.card-details .mCustomScrollBox {
padding-left: 0;
}
@@ -127,18 +196,47 @@
}
.card-details .card-details-header {
margin: 0 -20px 5px;
padding: 7px 20px;
padding: 8px 20px;
background: #ededed;
border-bottom: 1px solid #dbdbdb;
position: sticky;
top: 0px;
z-index: 500;
display: flow-root;
min-height: 44px;
}
.card-details .card-details-header .card-number {
color: #b3b3b3;
display: inline-block;
margin-right: 5px;
margin-right: 6px;
}
/* Collapse toggle triangle */
.card-details .card-details-header .card-collapse-toggle {
float: left;
font-size: 20px;
padding: 7px 10px;
margin-left: -10px;
margin-right: 5px;
cursor: pointer;
user-select: none;
color: #000;
vertical-align: middle;
line-height: 1.2;
}
.card-details .card-details-header .card-drag-handle {
font-size: 20px;
padding: 8px 10px;
margin-right: 10px;
cursor: move;
user-select: none;
display: inline-block;
float: right;
vertical-align: middle;
line-height: 1.2;
}
.card-details .card-details-header .close-card-details,
.card-details .card-details-header .maximize-card-details,
.card-details .card-details-header .minimize-card-details,
@@ -156,11 +254,19 @@
font-size: 24px;
padding: 5px 10px 5px 10px;
margin-right: -8px;
cursor: pointer;
user-select: none;
vertical-align: middle;
line-height: 1.2;
transition: color 0.13s;
}
.card-details .card-details-header .close-card-details-mobile-web {
.card-details .card-details-header .close-card-details-mobile-web,
.card-details .card-details-header .card-mobile-desktop-toggle {
font-size: 24px;
padding: 5px;
margin-right: 40px;
margin-right: 5px;
cursor: pointer;
user-select: none;
}
.card-details .card-details-header .card-copy-button {
font-size: 17px;
@@ -175,12 +281,44 @@
.card-details .card-details-header .card-details-menu {
font-size: 17px;
padding: 10px;
vertical-align: middle;
line-height: 1.2;
}
.card-details .card-details-header .card-details-menu-mobile-web {
font-size: 17px;
padding: 10px;
margin-right: 30px;
}
.card-details .card-details-header .card-mobile-desktop-toggle,
.card-details .card-details-header .card-zoom-in,
.card-details .card-details-header .card-zoom-out {
font-size: 24px;
padding: 5px 10px 5px 10px;
margin-right: 5px;
cursor: pointer;
user-select: none;
float: right;
}
/* Unify all card text to match title size */
.card-details {
font-size: 1em;
}
.card-details p,
.card-details span,
.card-details div,
.card-details a,
.card-details label,
.card-details input,
.card-details textarea,
.card-details select,
.card-details button,
.card-details .card-details-item-title,
.card-details .card-label,
.card-details .viewer {
font-size: inherit;
line-height: 1.5;
}
.card-details .card-details-header .card-details-watch {
font-size: 17px;
padding-left: 7px;
@@ -188,9 +326,13 @@
}
.card-details .card-details-header .card-details-title {
font-weight: bold;
font-size: 1.33em;
font-size: 1.35em;
margin: 7px 0 0;
padding: 0;
display: inline-block;
vertical-align: middle;
line-height: 1.3;
letter-spacing: 0.01em;
}
.card-details .card-details-header .linked-card-location {
font-style: italic;
@@ -205,10 +347,10 @@
margin-bottom: 10px;
}
.card-details .card-details-header form.inlined-form .copied-tooltip {
padding: 0px 10px;
padding: 0 10px;
}
.card-details .card-details-header .card-details-list {
font-size: 0.85em;
font-size: 0.9em;
margin-bottom: 3px;
}
.card-details .card-details-header .card-details-list a.card-details-list-title {
@@ -218,7 +360,7 @@
display: inline-block;
background: #e6e6e6;
border-radius: 3px;
padding: 0px 5px;
padding: 0 5px;
}
.card-details .card-details-header .copied-tooltip {
margin-right: 10px;
@@ -229,11 +371,13 @@
}
.card-details .card-description textarea {
min-height: 100px;
resize: vertical;
}
.card-details .card-details-items {
display: flex;
flex-wrap: wrap;
margin: 15px 0;
gap: 0.5em;
}
.card-details .card-details-items .card-details-item {
margin-right: 0.5em;
@@ -284,15 +428,28 @@
position: fixed;
resize: both;
}
/* Override for mobile mode even on larger screens */
body.mobile-mode .card-details {
width: 100vw !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
height: 100vh !important;
max-height: 100vh !important;
resize: none !important;
}
.card-details-maximized {
padding: 0;
flex-shrink: 0;
flex-basis: calc(100% - 20px);
will-change: flex-basis;
overflow-y: scroll;
overflow-x: scroll;
overflow-y: auto;
overflow-x: auto;
background: #f7f7f7;
border-radius: bottom 3px;
border-radius: 0 0 3px 3px;
z-index: 100;
animation: flexGrowIn 0.1s;
box-shadow: 0 0 7px 0 #b3b3b3;
@@ -335,19 +492,52 @@ input[type="submit"].attachment-add-link-submit {
}
@media screen and (max-width: 800px) {
.card-details {
width: calc(100% - 1px);
padding: 0px 20px 0px 20px;
margin: 0px;
width: 100% !important;
padding: 0 !important;
margin: 0 !important;
transition: none;
overflow-y: revert;
overflow-x: revert;
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
z-index: 100 !important;
height: 100vh !important;
max-height: 100vh !important;
border-radius: 0 !important;
box-shadow: none !important;
}
/* Ensure card details are above everything on mobile */
body.mobile-mode .card-details {
z-index: 100 !important;
width: 100vw !important;
left: 0 !important;
right: 0 !important;
}
.card-details .card-details-canvas {
width: 100%;
padding-left: 0px;
padding: 0 15px;
}
.card-details .card-details-header .close-card-details {
margin-right: 0px;
display: block !important;
}
.card-details .card-details-header .close-card-details-mobile-web {
display: block !important;
margin-right: 5px !important;
}
.card-details .card-details-header .card-mobile-desktop-toggle {
display: block !important;
margin-right: 5px !important;
}
.card-details .card-details-header .card-mobile-desktop-toggle {
display: block !important;
margin-right: 5px !important;
}
.card-details .card-details-header .card-details-menu {
margin-right: 40px;
@@ -373,6 +563,62 @@ input[type="submit"].attachment-add-link-submit {
.pop-over > .content-wrapper > .popup-container-depth-0 .card-details-header {
margin: 0;
}
/* iPhone mobile: enlarge header buttons and increase spacing */
body.mobile-mode.iphone-device .card-details .card-details-header {
padding-right: 16px;
}
body.mobile-mode.iphone-device .card-details .card-details-header .close-card-details,
body.mobile-mode.iphone-device .card-details .card-details-header .maximize-card-details,
body.mobile-mode.iphone-device .card-details .card-details-header .minimize-card-details,
body.mobile-mode.iphone-device .card-details .card-details-header .card-details-menu-mobile-web,
body.mobile-mode.iphone-device .card-details .card-details-header .card-copy-mobile-button,
body.mobile-mode.iphone-device .card-details .card-details-header .card-mobile-desktop-toggle,
body.mobile-mode.iphone-device .card-details .card-details-header .card-zoom-in,
body.mobile-mode.iphone-device .card-details .card-details-header .card-zoom-out {
font-size: 2em !important; /* 2x bigger */
padding: 0.3em !important;
margin-right: 0.75em !important; /* 2x space compared to default */
margin-left: 0 !important;
}
/* Avoid clipping of the close button on the right edge */
body.mobile-mode.iphone-device .card-details .card-details-header .close-card-details {
margin-right: 0.75em !important;
}
/* Enlarge the header title too */
body.mobile-mode.iphone-device .card-details .card-details-header .card-details-title {
font-size: 1.2em !important;
font-weight: bold;
}
}
/* Mobile mode styles - apply when body has mobile-mode class regardless of screen size */
body.mobile-mode .card-details {
width: 100vw !important;
padding: 0px !important;
margin: 0px !important;
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
z-index: 100 !important;
height: 100vh !important;
max-height: 100vh !important;
border-radius: 0 !important;
box-shadow: none !important;
overflow-y: auto !important;
overflow-x: hidden !important;
-webkit-overflow-scrolling: touch;
}
body.mobile-mode .card-details .card-details-canvas {
width: 100% !important;
padding: 0 15px !important;
}
body.mobile-mode .card-details .card-details-header .close-card-details,
body.mobile-mode .card-details .card-details-header .close-card-details-mobile-web {
display: block !important;
}
.card-details-white {
background: #fff !important;
@@ -481,13 +727,15 @@ input[type="submit"].attachment-add-link-submit {
.vote-title {
display: flex;
justify-content: space-between;
align-items: center;
}
.vote-title .js-edit-date {
align-self: baseline;
margin-left: 5px;
align-self: flex-start;
margin-left: 6px;
}
.vote-result {
display: flex;
gap: 6px;
}
.js-show-positive-votes {
cursor: pointer;
@@ -498,29 +746,33 @@ input[type="submit"].attachment-add-link-submit {
.poker-title {
display: flex;
justify-content: space-between;
align-items: center;
}
.poker-title .js-edit-date {
align-self: baseline;
margin-left: 5px;
align-self: flex-start;
margin-left: 6px;
}
.poker-result {
display: flex;
flex-flow: row wrap;
flex-wrap: wrap;
gap: 7px;
}
.js-show-positive-poker-votes {
cursor: pointer;
}
.poker-deck {
display: grid;
flex-direction: column;
grid-auto-flow: row;
text-align: center;
gap: 6px;
}
.poker-card-result {
width: 32px;
width: 34px;
font-size: 1em;
font-weight: bold;
padding: 4px 2px 4px 2px;
padding: 4px 2px;
cursor: default;
border-radius: 3px;
}
.winner {
font-weight: bold;
@@ -531,6 +783,7 @@ input[type="submit"].attachment-add-link-submit {
}
.responsive-table {
overflow-x: auto;
width: 100%;
}
.poker-table {
display: table;
@@ -593,11 +846,15 @@ input[type="submit"].attachment-add-link-submit {
margin: auto;
margin-right: 10px;
width: 100px;
border-radius: 2px;
padding: 3px 6px;
}
.estimation-add button {
display: inline-block;
float: right;
margin: auto;
border-radius: 2px;
padding: 3px 10px;
}
.poker-card {
width: 48px;
@@ -616,6 +873,7 @@ input[type="submit"].attachment-add-link-submit {
text-align: center;
position: relative;
cursor: pointer;
transition: box-shadow 0.12s;
}
.poker-card .inner {
display: table-cell;
+354 -171
View File
@@ -5,48 +5,64 @@ template(name="cardDetails")
+attachmentViewer
section.card-details.js-card-details.nodragscroll(class='{{#if cardMaximized}}card-details-maximized{{/if}}' class='{{#if isPopup}}card-details-popup{{/if}}' class='{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}'): .card-details-canvas
section.card-details.js-card-details.nodragscroll(class='{{#if cardMaximized}}card-details-maximized{{/if}}' class='{{#if isPopup}}card-details-popup{{/if}}' class='{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}' class='{{#if cardCollapsed}}card-details-collapsed{{/if}}'): .card-details-canvas
.card-details-header(class='{{#if colorClass}}card-details-{{colorClass}}{{/if}}')
+inlinedForm(classNames="js-card-details-title")
+editCardTitleForm
else
unless isMiniScreen
unless isPopup
a.close-card-details.js-close-card-details(title="{{_ 'close-card'}}")
| ❌
if canModifyCard
if cardMaximized
a.minimize-card-details.js-minimize-card-details(title="{{_ 'minimize-card'}}")
| 🔽
else
a.maximize-card-details.js-maximize-card-details(title="{{_ 'maximize-card'}}")
| 🔼
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'}}")
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
a.card-details-menu.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
| ☰
a.card-copy-button.js-copy-link(
id="cardURL_copy"
class="fa-link"
title="{{_ 'copy-card-link-to-clipboard'}}"
href="{{ originRelativeUrl }}"
)
span.copied-tooltip {{_ 'copied'}}
span.card-drag-handle.js-card-drag-handle(title="Drag card")
i.fa.fa-arrows
span.copied-tooltip {{_ 'copied'}}
else
unless isPopup
a.close-card-details.js-close-card-details(title="{{_ 'close-card'}}")
| ❌
if canModifyCard
a.card-details-menu-mobile-web.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
| ☰
a.card-copy-mobile-button.js-copy-link(
id="cardURL_copy"
class="fa-link"
title="{{_ 'copy-card-link-to-clipboard'}}"
href="{{ originRelativeUrl }}"
)
span.copied-tooltip {{_ 'copied'}}
a.close-card-details.js-close-card-details(title="{{_ 'close-card'}}")
i.fa.fa-times-thin
a.card-zoom-out.js-card-zoom-out(title="{{_ 'zoom-out'}}")
i.fa.fa-search-minus
a.card-zoom-in.js-card-zoom-in(title="{{_ 'zoom-in'}}")
i.fa.fa-search-plus
a.card-mobile-desktop-toggle.js-card-mobile-desktop-toggle(title="{{_ 'mobile-desktop-toggle'}}")
if mobileMode
i.fa.fa-desktop
else
i.fa.fa-mobile
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-mobile-web.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}")
i.fa.fa-bars
a.card-copy-mobile-button.js-copy-link(
id="cardURL_copy"
title="{{_ 'copy-card-link-to-clipboard'}}"
href="{{ originRelativeUrl }}"
)
span.emoji-icon
i.fa.fa-link
span.copied-tooltip {{_ 'copied'}}
h2.card-details-title.js-card-title(
class="{{#if canModifyCard}}js-open-inlined-form is-editable{{/if}}")
class="{{#if canModifyCard}}js-open-inlined-form is-editable{{else}}js-card-title-drag-handle{{/if}}")
+viewer
if currentBoard.allowsCardNumber
span.card-number
@@ -54,7 +70,7 @@ template(name="cardDetails")
= getTitle
if isWatching
i.card-details-watch
| 👁️
i.fa.fa-eye
.card-details-path
each parentList
| &nbsp; &gt; &nbsp;
@@ -76,7 +92,7 @@ template(name="cardDetails")
if hasActiveUploads
.card-details-upload-progress
.upload-progress-header
| 📤
i.fa.fa-upload
span {{_ 'uploading-files'}} ({{uploadCount}})
each uploads
.upload-progress-item(class="{{#if $eq status 'error'}}upload-error{{/if}}")
@@ -85,11 +101,11 @@ template(name="cardDetails")
.upload-progress-fill(style="width: {{progress}}%")
if $eq status 'error'
.upload-progress-error
| ⚠️
i.fa.fa-exclamation-triangle
span {{_ 'upload-failed'}}
else if $eq status 'completed'
.upload-progress-success
| ✅
i.fa.fa-check
span {{_ 'upload-completed'}}
.card-details-left
@@ -98,7 +114,7 @@ template(name="cardDetails")
if currentBoard.allowsLabels
.card-details-item.card-details-item-labels
h3.card-details-item-title
| 🏷️
i.fa.fa-tags
| {{_ 'labels'}}
a(class="{{#if canModifyCard}}js-add-labels{{else}}is-disabled{{/if}}" title="{{_ 'card-labels-title'}}")
each labels
@@ -108,14 +124,14 @@ template(name="cardDetails")
if canModifyCard
unless currentUser.isWorker
a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}")
| ➕
i.fa.fa-plus
if currentBoard.hasAnyAllowsDate
hr
.card-details-item.card-details-item-date-format
h3.card-details-item-title
| 📅
i.fa.fa-calendar
| {{_ 'date-format'}}
.card-details-item-content
select.js-date-format-selector
@@ -126,7 +142,7 @@ template(name="cardDetails")
if currentBoard.allowsReceivedDate
.card-details-item.card-details-item-received
h3.card-details-item-title
| 📥
i.fa.fa-sign-out
| {{_ 'card-received'}}
if getReceived
+cardReceivedDate
@@ -134,12 +150,12 @@ template(name="cardDetails")
if canModifyCard
unless currentUser.isWorker
a.card-label.add-label.js-received-date
| ➕
i.fa.fa-plus
if currentBoard.allowsStartDate
.card-details-item.card-details-item-start
h3.card-details-item-title
| 🚀
i.fa.fa-hourglass-start
| {{_ 'card-start'}}
if getStart
+cardStartDate
@@ -147,12 +163,12 @@ template(name="cardDetails")
if canModifyCard
unless currentUser.isWorker
a.card-label.add-label.js-start-date
| ➕
i.fa.fa-plus
if currentBoard.allowsDueDate
.card-details-item.card-details-item-due
h3.card-details-item-title
| ⏰
i.fa.fa-clock-o
| {{_ 'card-due'}}
if getDue
+cardDueDate
@@ -160,12 +176,12 @@ template(name="cardDetails")
if canModifyCard
unless currentUser.isWorker
a.card-label.add-label.js-due-date
| ➕
i.fa.fa-plus
if currentBoard.allowsEndDate
.card-details-item.card-details-item-end
h3.card-details-item-title
| 🏁
i.fa.fa-hourglass-end
| {{_ 'card-end'}}
if getEnd
+cardEndDate
@@ -173,7 +189,7 @@ template(name="cardDetails")
if canModifyCard
unless currentUser.isWorker
a.card-label.add-label.js-end-date
| ➕
i.fa.fa-plus
if currentBoard.hasAnyAllowsUser
hr
@@ -181,7 +197,7 @@ template(name="cardDetails")
if currentBoard.allowsCreator
.card-details-item.card-details-item-creator
h3.card-details-item-title
| 👤
i.fa.fa-user
| {{_ 'creator'}}
+userAvatar(userId=userId noRemove=true)
@@ -191,7 +207,7 @@ template(name="cardDetails")
if currentBoard.allowsMembers
.card-details-item.card-details-item-members
h3.card-details-item-title
| 👤s
i.fa.fa-users
| {{_ 'members'}}
each userId in getMembers
+userAvatar(userId=userId cardId=_id)
@@ -199,30 +215,30 @@ template(name="cardDetails")
if canModifyCard
unless currentUser.isWorker
a.member.add-member.card-details-item-add-button.js-add-members(title="{{_ 'card-members-title'}}")
| ➕
i.fa.fa-plus
//if assigneeSelected
if currentBoard.allowsAssignee
.card-details-item.card-details-item-assignees
h3.card-details-item-title
| 👤
i.fa.fa-user
| {{_ 'assignee'}}
each userId in getAssignees
+userAvatar(userId=userId cardId=_id assignee=true)
| {{! XXX Hack to hide syntaxic coloration /// }}
if canModifyCard
a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}")
| ➕
i.fa.fa-plus
if currentUser.isWorker
unless assigneeSelected
a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}")
| ➕
i.fa.fa-plus
//.card-details-items
if currentBoard.allowsRequestedBy
.card-details-item.card-details-item-name
h3.card-details-item-title
| 🛒
i.fa.fa-shopping-cart
| {{_ 'requested-by'}}
if canModifyCard
unless currentUser.isWorker
@@ -242,7 +258,7 @@ template(name="cardDetails")
if currentBoard.allowsAssignedBy
.card-details-item.card-details-item-name
h3.card-details-item-title
| 👤-plus
i.fa.fa-user-plus
| {{_ 'assigned-by'}}
if canModifyCard
unless currentUser.isWorker
@@ -265,7 +281,7 @@ template(name="cardDetails")
if currentBoard.allowsCardSortingByNumber
.card-details-item.card-details-sort-order
h3.card-details-item-title
| 🔢
i.fa.fa-sort-numeric-asc
| {{_ 'sort'}}
if canModifyCard
+inlinedForm(classNames="js-card-details-sort")
@@ -278,7 +294,7 @@ template(name="cardDetails")
if currentBoard.allowsShowLists
.card-details-item.card-details-show-lists
h3.card-details-item-title
| 📋
i.fa.fa-list
| {{_ 'list'}}
select.js-select-card-details-lists(disabled="{{#unless canModifyCard}}disabled{{/unless}}")
each currentBoard.lists
@@ -304,7 +320,7 @@ template(name="cardDetails")
hr
.card-details-item.card-details-item-customfield
h3.card-details-item-title
| 📋-alt
i.fa.fa-list
= definition.name
+cardCustomField
@@ -322,7 +338,7 @@ template(name="cardDetails")
.vote-title
div.flex
h3
| 👍
i.fa.fa-thumbs-up
| {{_ 'vote-question'}}
if getVoteEnd
+voteEndDate
@@ -334,13 +350,14 @@ template(name="cardDetails")
.card-label.card-label-green {{ voteCountPositive }}
.card-label.card-label-red {{ voteCountNegative }}
unless ($and currentBoard.isPublic voteAllowNonBoardMembers )
.card-label.card-label-gray {{ voteCount }} {{_ 'r-of' }} {{ currentBoard.activeMembers.length }}
.card-label.card-label-gray
| {{ voteCount }} {{_ 'r-of' }} {{ currentBoard.activeMembers.length }}
+viewer
= getVoteQuestion
if showVotingButtons
button.card-details-green.js-vote.js-vote-positive(class="{{#if voteState}}voted{{/if}}")
if voteState
| 👍
i.fa.fa-thumbs-up
| {{_ 'vote-for-it'}}
button.card-details-red.js-vote.js-vote-negative(class="{{#if $eq voteState false}}voted{{/if}}")
if $eq voteState false
@@ -352,7 +369,7 @@ template(name="cardDetails")
.poker-title
div.flex
h3
| 👍
i.fa.fa-thumbs-up
| {{_ 'poker-question'}}
if getPokerEnd
+pokerEndDate
@@ -360,59 +377,60 @@ template(name="cardDetails")
.poker-result
if expiredPoker
unless ($and currentBoard.isPublic pokerAllowNonBoardMembers )
.card-label.card-label-gray {{ pokerCount }} {{_ 'r-of' }} {{ currentBoard.activeMembers.length }}
.card-label.card-label-gray
| {{ pokerCount }} {{_ 'r-of' }} {{ currentBoard.activeMembers.length }}
if showPlanningPokerButtons
.poker-result
.poker-deck
.poker-card
span.inner.js-poker.js-poker-vote-one(class="{{#if $eq pokerState 'one'}}poker-voted{{/if}}") {{_ 'poker-one'}}
if $eq pokerState "one"
| ✅
i.fa.fa-check
.poker-deck
.poker-card
span.inner.js-poker.js-poker-vote-two(class="{{#if $eq pokerState 'two'}}poker-voted{{/if}}") {{_ 'poker-two'}}
if $eq pokerState "two"
| ✅
i.fa.fa-check
.poker-deck
.poker-card
span.inner.js-poker.js-poker-vote-three(class="{{#if $eq pokerState 'three'}}poker-voted{{/if}}") {{_ 'poker-three'}}
if $eq pokerState "three"
| ✅
i.fa.fa-check
.poker-deck
.poker-card
span.inner.js-poker.js-poker-vote-five(class="{{#if $eq pokerState 'five'}}poker-voted{{/if}}") {{_ 'poker-five'}}
if $eq pokerState "five"
| ✅
i.fa.fa-check
.poker-deck
.poker-card
span.inner.js-poker.js-poker-vote-eight(class="{{#if $eq pokerState 'eight'}}poker-voted{{/if}}") {{_ 'poker-eight'}}
if $eq pokerState "eight"
| ✅
i.fa.fa-check
.poker-deck
.poker-card
span.inner.js-poker.js-poker-vote-thirteen(class="{{#if $eq pokerState 'thirteen'}}poker-voted{{/if}}") {{_ 'poker-thirteen'}}
if $eq pokerState "thirteen"
| ✅
i.fa.fa-check
.poker-deck
.poker-card
span.inner.js-poker.js-poker-vote-twenty(class="{{#if $eq pokerState 'twenty'}}poker-voted{{/if}}") {{_ 'poker-twenty'}}
if $eq pokerState "twenty"
| ✅
i.fa.fa-check
.poker-deck
.poker-card
span.inner.js-poker.js-poker-vote-forty(class="{{#if $eq pokerState 'forty'}}poker-voted{{/if}}") {{_ 'poker-forty'}}
if $eq pokerState "forty"
| ✅
i.fa.fa-check
.poker-deck
.poker-card
span.inner.js-poker.js-poker-vote-one-hundred(class="{{#if $eq pokerState 'oneHundred'}}poker-voted{{/if}}") {{_ 'poker-oneHundred'}}
if $eq pokerState "oneHundred"
| ✅
i.fa.fa-check
.poker-deck
.poker-card
span.inner.js-poker.js-poker-vote-unsure(class="{{#if $eq pokerState 'unsure'}}poker-voted{{/if}}") {{_ 'poker-unsure'}}
if $eq pokerState "unsure"
| ✅
i.fa.fa-check
if currentUser.isBoardAdmin
button.card-details-blue.js-poker-finish(class="{{#if $eq voteState false}}poker-voted{{/if}}") {{_ 'poker-finish'}}
@@ -542,7 +560,7 @@ template(name="cardDetails")
button.card-details-red.js-poker-replay(class="{{#if $eq voteState false}}voted{{/if}}") {{_ 'poker-replay'}}
div.estimation-add
button.js-poker-estimation
| ➕
i.fa.fa-plus
| {{_ 'set-estimation'}}
input(type=text,autofocus value=getPokerEstimation,id="pokerEstimation")
@@ -552,7 +570,7 @@ template(name="cardDetails")
if currentBoard.allowsDescriptionTitle
hr
h3.card-details-item-title
| 📝
i.fa.fa-file-text-o
| {{_ 'description'}}
if currentBoard.allowsDescriptionText
+inlinedCardDescription(classNames="card-description js-card-description")
@@ -563,7 +581,7 @@ template(name="cardDetails")
else
if currentBoard.allowsDescriptionText
a.js-open-inlined-form(title="{{_ 'edit'}}" value=title)
| ✏️
i.fa.fa-pencil-square-o
a.js-open-inlined-form(title="{{_ 'edit'}}" value=title)
if getDescription
+viewer
@@ -593,7 +611,7 @@ template(name="cardDetails")
if currentBoard.allowsAttachments
hr
h3.card-details-item-title
| 📎
i.fa.fa-paperclip
| {{_ 'attachments'}}
if Meteor.settings.public.attachmentsUploadMaxSize
| {{_ 'max-upload-filesize'}} {{Meteor.settings.public.attachmentsUploadMaxSize}}
@@ -609,22 +627,24 @@ template(name="cardDetails")
unless currentUser.isNoComments
.comment-title
h3.card-details-item-title
| 💬
i.fa.fa-comment-o
| {{_ 'comments'}}
if currentBoard.allowsComments
if currentUser.isBoardMember
unless currentUser.isNoComments
+commentForm
unless currentUser.isReadOnly
unless currentUser.isReadAssignedOnly
+commentForm
+comments
hr
.card-details-right
unless currentUser.isNoComments
if currentUser.isBoardAdmin
.activity-title
h3.card-details-item-title
| 📜
i.fa.fa-history
| {{ _ 'activities'}}
if currentUser.isBoardMember
.material-toggle-switch(title="{{_ 'show-activities'}}")
@@ -634,7 +654,7 @@ template(name="cardDetails")
input.toggle-switch(type="checkbox" id="toggleShowActivitiesCard")
label.toggle-label(for="toggleShowActivitiesCard")
unless currentUser.isNoComments
if currentUser.isBoardAdmin
if isLoaded.get
if isLinkedCard
+activities(card=this mode="linkedcard")
@@ -675,10 +695,10 @@ template(name="cardDetailsActionsPopup")
li
a.js-toggle-watch-card
if isWatching
| 👁️
i.fa.fa-eye
| {{_ 'unwatch'}}
else
| 👁️-slash
i.fa.fa-eye
| {{_ 'watch'}}
hr
if canModifyCard
@@ -689,16 +709,16 @@ template(name="cardDetailsActionsPopup")
//li: a.js-attachments {{_ 'card-edit-attachments'}}
li
a.js-start-voting
| 👍
i.fa.fa-thumbs-up
| {{_ 'card-edit-voting'}}
li
a.js-start-planning-poker
| 👍
i.fa.fa-thumbs-up
| {{_ 'card-edit-planning-poker'}}
if currentUser.isBoardAdmin
li
a.js-custom-fields
| 📋-alt
i.fa.fa-list
| {{_ 'card-edit-custom-fields'}}
//li: a.js-received-date {{_ 'editCardReceivedDatePopup-title'}}
//li: a.js-start-date {{_ 'editCardStartDatePopup-title'}}
@@ -706,114 +726,275 @@ template(name="cardDetailsActionsPopup")
//li: a.js-end-date {{_ 'editCardEndDatePopup-title'}}
li
a.js-spent-time
| 🕐
i.fa.fa-clock-o
| {{_ 'editCardSpentTimePopup-title'}}
li
a.js-set-card-color
| 🎨
i.fa.fa-paint-brush
| {{_ 'setCardColorPopup-title'}}
li
a.js-toggle-show-list-on-minicard
if showListOnMinicard
| 👁️
i.fa.fa-eye
| {{_ 'hide-list-on-minicard'}}
else
| 👁️-slash
i.fa.fa-eye
| {{_ 'show-list-on-minicard'}}
hr
if canModifyCard
hr
else
unless currentUser.isReadOnly
unless currentUser.isReadAssignedOnly
hr
ul.pop-over-list
li
a.js-export-card
| 📤
i.fa.fa-upload
| {{_ 'export-card'}}
hr
ul.pop-over-list
li
a.js-move-card-to-top
| ⬆️
| {{_ 'moveCardToTop-title'}}
li
a.js-move-card-to-bottom
| ⬇️
| {{_ 'moveCardToBottom-title'}}
hr
ul.pop-over-list
if currentUser.isBoardAdmin
li
a.js-move-card
| ➡️
| {{_ 'moveCardPopup-title'}}
unless currentUser.isWorker
li
a.js-copy-card
| 📋
| {{_ 'copyCardPopup-title'}}
unless currentUser.isWorker
ul.pop-over-list
li
a.js-copy-checklist-cards
| 📋
| 📋
| {{_ 'copyManyCardsPopup-title'}}
unless archived
hr
ul.pop-over-list
li
a.js-archive
| ➡️
| 📦
| {{_ 'archive-card'}}
unless canModifyCard
unless currentUser.isReadOnly
unless currentUser.isReadAssignedOnly
hr
ul.pop-over-list
li
a.js-move-card-to-top
i.fa.fa-arrow-up
| {{_ 'moveCardToTop-title'}}
li
a.js-move-card-to-bottom
i.fa.fa-arrow-down
| {{_ 'moveCardToBottom-title'}}
hr
ul.pop-over-list
if currentUser.isBoardAdmin
li
a.js-move-card
i.fa.fa-arrow-right
| {{_ 'moveCardPopup-title'}}
unless currentUser.isWorker
li
a.js-copy-card
i.fa.fa-clipboard
| {{_ 'copyCardPopup-title'}}
unless currentUser.isWorker
ul.pop-over-list
li
a.js-copy-checklist-cards
i.fa.fa-copy
| {{_ 'copyManyCardsPopup-title'}}
unless archived
hr
ul.pop-over-list
li
a.js-archive
i.fa.fa-archive
| {{_ 'archive-card'}}
hr
ul.pop-over-list
li
a.js-more
span.emoji-icon
i.fa.fa-link
| {{_ 'cardMorePopup-title'}}
if canModifyCard
hr
ul.pop-over-list
li
a.js-more
| 🔗
| {{_ 'cardMorePopup-title'}}
a.js-move-card-to-top
i.fa.fa-arrow-up
| {{_ 'moveCardToTop-title'}}
li
a.js-move-card-to-bottom
i.fa.fa-arrow-down
| {{_ 'moveCardToBottom-title'}}
hr
ul.pop-over-list
if currentUser.isBoardAdmin
li
a.js-move-card
i.fa.fa-arrow-right
| {{_ 'moveCardPopup-title'}}
unless currentUser.isWorker
li
a.js-copy-card
i.fa.fa-clipboard
| {{_ 'copyCardPopup-title'}}
unless currentUser.isWorker
ul.pop-over-list
li
a.js-copy-checklist-cards
i.fa.fa-copy
| {{_ 'copyManyCardsPopup-title'}}
unless archived
hr
ul.pop-over-list
li
a.js-archive
i.fa.fa-archive
| {{_ 'archive-card'}}
hr
ul.pop-over-list
li
a.js-more
span.emoji-icon
i.fa.fa-link
| {{_ 'cardMorePopup-title'}}
template(name="exportCardPopup")
ul.pop-over-list
li
a(href="{{exportUrlCardPDF}}",, download="{{exportFilenameCardPDF}}")
| 📤
a(href="{{exportUrlCardPDF}}", download="{{exportFilenameCardPDF}}")
i.fa.fa-file-pdf-o
| {{_ 'export-card-pdf'}}
hr
p.quiet {{_ 'export-card-excel-fields'}}
ul.pop-over-list
each excelExportFields
li(class="{{#if checked}}active{{/if}}")
a.js-excel-field-toggle(data-field="{{field}}" href="#")
i.fa.fa-check
| {{_ label}}
ul.pop-over-list
li
a(href="{{exportUrlCardExcel}}", download="{{exportFilenameCardExcel}}")
i.fa.fa-file-excel-o
| {{_ 'download'}}
template(name="moveCardPopup")
+copyAndMoveCard
label(for='move-card-title') {{_ 'title'}}:
input#move-card-title.full-line(type="text" value="{{getTitle}}" autofocus)
unless currentUser.isWorker
label {{_ 'boards'}}:
select.js-select-boards(autofocus)
each boards
option(value="{{_id}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
label {{_ 'swimlanes'}}:
select.js-select-swimlanes
each swimlanes
option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{add @index 1}}. {{isTitleDefault title}}
label {{_ 'lists'}}:
select.js-select-lists
each lists
option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
label {{_ 'cards'}}:
select.js-select-cards
each cards
option(value="{{_id}}" selected="{{#if isDialogOptionCardId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
div
input(type="radio" name="position" value="above" checked id="position-above" style="display: inline")
label(for="position-above") {{_ 'above-selected-card'}}
div
input(type="radio" name="position" value="below" id="position-below" style="display: inline")
label(for="position-below") {{_ 'below-selected-card'}}
.edit-controls.clearfix
button.primary.confirm.js-done {{_ 'done'}}
template(name="copyCardPopup")
label(for='copy-card-title') {{_ 'title'}}:
textarea#copy-card-title.minicard-composer-textarea.js-card-title(autofocus)
= getTitle
+copyAndMoveCard
unless currentUser.isWorker
label {{_ 'boards'}}:
select.js-select-boards(autofocus)
each boards
option(value="{{_id}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
label {{_ 'swimlanes'}}:
select.js-select-swimlanes
each swimlanes
option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{add @index 1}}. {{isTitleDefault title}}
label {{_ 'lists'}}:
select.js-select-lists
each lists
option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
label {{_ 'cards'}}:
select.js-select-cards
each cards
option(value="{{_id}}" selected="{{#if isDialogOptionCardId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
div
input(type="radio" name="position" value="above" checked id="position-above" style="display: inline")
label(for="position-above") {{_ 'above-selected-card'}}
div
input(type="radio" name="position" value="below" id="position-below" style="display: inline")
label(for="position-below") {{_ 'below-selected-card'}}
.edit-controls.clearfix
button.primary.confirm.js-done {{_ 'done'}}
template(name="copyManyCardsPopup")
label(for='copy-checklist-cards-title') {{_ 'copyManyCardsPopup-instructions'}}:
textarea#copy-card-title.minicard-composer-textarea.js-card-title(autofocus)
| {{_ 'copyManyCardsPopup-format'}}
+copyAndMoveCard
unless currentUser.isWorker
label {{_ 'boards'}}:
select.js-select-boards(autofocus)
each boards
option(value="{{_id}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
label {{_ 'swimlanes'}}:
select.js-select-swimlanes
each swimlanes
option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{add @index 1}}. {{isTitleDefault title}}
label {{_ 'lists'}}:
select.js-select-lists
each lists
option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
label {{_ 'cards'}}:
select.js-select-cards
each cards
option(value="{{_id}}" selected="{{#if isDialogOptionCardId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
div
input(type="radio" name="position" value="above" checked id="position-above" style="display: inline")
label(for="position-above") {{_ 'above-selected-card'}}
div
input(type="radio" name="position" value="below" id="position-below" style="display: inline")
label(for="position-below") {{_ 'below-selected-card'}}
.edit-controls.clearfix
button.primary.confirm.js-done {{_ 'done'}}
template(name="convertChecklistItemToCardPopup")
label(for='convert-checklist-item-to-card-title') {{_ 'title'}}:
textarea#copy-card-title.minicard-composer-textarea.js-card-title(autofocus)
= item.title
+copyAndMoveCard
template(name="copyAndMoveCard")
unless currentUser.isWorker
label {{_ 'boards'}}:
select.js-select-boards(autofocus)
each boards
option(value="{{_id}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{title}}
option(value="{{_id}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
label {{_ 'swimlanes'}}:
select.js-select-swimlanes
each swimlanes
option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{title}}
option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{add @index 1}}. {{isTitleDefault title}}
label {{_ 'lists'}}:
select.js-select-lists
each lists
option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{title}}
option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
label {{_ 'cards'}}:
select.js-select-cards
each cards
option(value="{{_id}}" selected="{{#if isDialogOptionCardId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
div
input(type="radio" name="position" value="above" checked id="position-above" style="display: inline")
label(for="position-above") {{_ 'above-selected-card'}}
div
input(type="radio" name="position" value="below" id="position-below" style="display: inline")
label(for="position-below") {{_ 'below-selected-card'}}
.edit-controls.clearfix
button.primary.confirm.js-done {{_ 'done'}}
@@ -824,13 +1005,13 @@ template(name="cardMembersPopup")
each members
li.item(class="{{#if isCardMember}}active{{/if}}")
a.name.js-select-member(href="#")
+userAvatar(userId=user._id)
+userAvatar(userId=userId)
span.full-name
= user.profile.fullname
| (<span class="username">{{ user.username }}</span>)
= userData.profile.fullname
if userData.username
| (#{userData.username})
if isCardMember
| ✅
i.fa.fa-check
template(name="cardAssigneesPopup")
input.card-assignees-filter(type="text" placeholder="{{_ 'search'}}")
unless currentUser.isWorker
@@ -838,12 +1019,13 @@ template(name="cardAssigneesPopup")
each members
li.item(class="{{#if isCardAssignee}}active{{/if}}")
a.name.js-select-assignee(href="#")
+userAvatar(userId=user._id)
+userAvatar(userId=userId)
span.full-name
= user.profile.fullname
| (<span class="username">{{ user.username }}</span>)
= userData.profile.fullname
if userData.username
| (#{userData.username})
if isCardAssignee
| ✅
i.fa.fa-check
if currentUser.isWorker
ul.pop-over-list.js-card-assignee-list
li.item(class="{{#if currentUser.isCardAssignee}}active{{/if}}")
@@ -851,10 +1033,10 @@ template(name="cardAssigneesPopup")
+userAvatar(userId=currentUser._id)
span.full-name
= currentUser.profile.fullname
| (<span class="username">{{ currentUser.username }}</span>)
if currentUser.username
| (#{currentUser.username})
if currentUser.isCardAssignee
| ✅
i.fa.fa-check
template(name="cardAssigneePopup")
.board-assignee-menu
.mini-profile-info
@@ -877,7 +1059,7 @@ template(name="cardMorePopup")
span.clearfix
span {{_ 'link-card'}}
= ' '
| {{#if board.isPublic}}🌐{{else}}🔒{{/if}}
i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}")
input.inline-input(type="text" id="cardURL" readonly value="{{ originRelativeUrl }}" autofocus="autofocus")
button.js-copy-card-link-to-clipboard(class="btn" id="clipboard") {{_ 'copy-card-link-to-clipboard'}}
.copied-tooltip {{_ 'copied'}}
@@ -909,19 +1091,20 @@ template(name="cardMorePopup")
option(value="{{_id}}") {{title}}
br
| {{_ 'added'}}
span.date(title=card.createdAt) {{ moment createdAt 'LLL' }}
span.date(title=card.createdAt) {{ displayDate createdAt 'LLL' }}
if currentUser.isBoardAdmin
a.js-delete(title="{{_ 'card-delete-notice'}}") {{_ 'delete'}}
template(name="setCardColorPopup")
form.edit-label
.palette-colors: each colors
unless $eq color 'white'
span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
if(isSelected color)
| ✅
button.primary.confirm.js-submit {{_ 'save'}}
button.js-remove-color.negate.wide.right {{_ 'unset-color'}}
.palette-colors
each colors
unless $eq color 'white'
span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
if(isSelected color)
i.fa.fa-check
button.primary.confirm.js-submit {{_ 'save'}}
button.js-remove-color.negate.wide.right {{_ 'unset-color'}}
template(name="cardDeletePopup")
p {{_ "card-delete-pop"}}
@@ -953,12 +1136,12 @@ template(name="cardStartVotingPopup")
.materialCheckBox#vote-public(name="vote-public" class="{{#if votePublic}}is-checked{{/if}}")
span {{_ 'vote-public'}}
.check-div.flex
| ⏰
i.fa.fa-clock-o
a.js-end-date
span
| {{_ 'card-end'}}
unless getVoteEnd
| ➕
i.fa.fa-plus
if getVoteEnd
+voteEndDate
@@ -999,12 +1182,12 @@ template(name="cardStartPlanningPokerPopup")
.materialCheckBox#poker-allow-non-members(name="poker-allow-non-members" class="{{#if pokerAllowNonBoardMembers}}is-checked{{/if}}")
span {{_ 'allowNonBoardMembers'}}
.check-div.flex
| ⏰
i.fa.fa-clock-o
a.js-end-date
span
| {{_ 'card-end'}}
unless getPokerEnd
| ➕
i.fa.fa-plus
if getPokerEnd
+pokerEndDate
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -1,4 +1,4 @@
template(name="editCardSpentTime")
template(name="editCardSpentTimePopup")
.edit-card-time
form.edit-time
.fields
@@ -13,7 +13,7 @@ template(name="editCardSpentTime")
button.primary.wide.left.js-submit-time(type="submit") {{_ 'save'}}
button.js-delete-time.negate.wide.right {{_ 'delete'}}
template(name="timeBadge")
template(name="cardSpentTime")
if canModifyCard
a.js-edit-time.card-time(title="{{_ 'time'}}" class="{{#if getIsOvertime}}card-label-red{{else}}card-label-green{{/if}}")
| ⏱️ {{showTime}}
+73 -67
View File
@@ -1,85 +1,91 @@
import { TAPi18n } from '/imports/i18n';
import Cards from '/models/cards';
import { getCurrentCardIdFromContext } from '/client/lib/currentCard';
BlazeComponent.extendComponent({
template() {
return 'editCardSpentTime';
function getCardId() {
return getCurrentCardIdFromContext();
}
Template.editCardSpentTimePopup.onCreated(function () {
this.error = new ReactiveVar('');
this.card = Cards.findOne(getCardId());
});
Template.editCardSpentTimePopup.helpers({
error() {
return Template.instance().error;
},
onCreated() {
this.error = new ReactiveVar('');
this.card = this.data();
card() {
return Cards.findOne(getCardId());
},
toggleOvertime() {
this.card.setIsOvertime(!this.card.getIsOvertime());
getIsOvertime() {
const card = Cards.findOne(getCardId());
return card?.getIsOvertime ? card.getIsOvertime() : false;
},
});
Template.editCardSpentTimePopup.events({
//TODO : need checking this portion
'submit .edit-time'(evt, tpl) {
evt.preventDefault();
const card = Cards.findOne(getCardId());
if (!card) return;
const spentTime = parseFloat(evt.target.time.value);
let isOvertime = false;
if ($('#overtime').attr('class').indexOf('is-checked') >= 0) {
isOvertime = true;
}
if (spentTime >= 0) {
card.setSpentTime(spentTime);
card.setIsOvertime(isOvertime);
Popup.back();
} else {
tpl.error.set('invalid-time');
evt.target.time.focus();
}
},
'click .js-delete-time'(evt) {
evt.preventDefault();
const card = Cards.findOne(getCardId());
if (!card) return;
card.setSpentTime(null);
card.setIsOvertime(false);
Popup.back();
},
'click a.js-toggle-overtime'(evt) {
const card = Cards.findOne(getCardId());
if (!card) return;
card.setIsOvertime(!card.getIsOvertime());
$('#overtime .materialCheckBox').toggleClass('is-checked');
$('#overtime').toggleClass('is-checked');
},
storeTime(spentTime, isOvertime) {
this.card.setSpentTime(spentTime);
this.card.setIsOvertime(isOvertime);
},
deleteTime() {
this.card.setSpentTime(null);
this.card.setIsOvertime(false);
},
events() {
return [
{
//TODO : need checking this portion
'submit .edit-time'(evt) {
evt.preventDefault();
});
const spentTime = parseFloat(evt.target.time.value);
//const isOvertime = this.card.getIsOvertime();
let isOvertime = false;
if ($('#overtime').attr('class').indexOf('is-checked') >= 0) {
isOvertime = true;
}
if (spentTime >= 0) {
this.storeTime(spentTime, isOvertime);
Popup.back();
} else {
this.error.set('invalid-time');
evt.target.time.focus();
}
},
'click .js-delete-time'(evt) {
evt.preventDefault();
this.deleteTime();
Popup.back();
},
'click a.js-toggle-overtime': this.toggleOvertime,
},
];
},
}).register('editCardSpentTimePopup');
BlazeComponent.extendComponent({
template() {
return 'timeBadge';
},
onCreated() {
const self = this;
self.time = ReactiveVar();
},
Template.cardSpentTime.helpers({
showTitle() {
if (this.data().getIsOvertime()) {
const card = Cards.findOne(this._id) || this;
if (card.getIsOvertime && card.getIsOvertime()) {
return `${TAPi18n.__(
'overtime',
)} ${this.data().getSpentTime()} ${TAPi18n.__('hours')}`;
} else {
)} ${card.getSpentTime()} ${TAPi18n.__('hours')}`;
} else if (card.getSpentTime) {
return `${TAPi18n.__(
'card-spent',
)} ${this.data().getSpentTime()} ${TAPi18n.__('hours')}`;
)} ${card.getSpentTime()} ${TAPi18n.__('hours')}`;
}
return '';
},
showTime() {
return this.data().getSpentTime();
const card = Cards.findOne(this._id) || this;
return card.getSpentTime ? card.getSpentTime() : '';
},
events() {
return [
{
'click .js-edit-time': Popup.open('editCardSpentTime'),
},
];
getIsOvertime() {
const card = Cards.findOne(this._id) || this;
return card.getIsOvertime ? card.getIsOvertime() : false;
},
}).register('cardSpentTime');
});
Template.cardSpentTime.events({
'click .js-edit-time': Popup.open('editCardSpentTime'),
});
+4 -2
View File
@@ -37,10 +37,12 @@ textarea.js-edit-checklist-item {
.checklist-progress-bar-container .checklist-progress-bar {
width: 80%;
height: 10px;
background-color: #e0e0e0;
border-radius: 16px;
}
.checklist-progress-bar-container .checklist-progress-bar .checklist-progress {
color: #fff !important;
background-color: #2196f3 !important;
color: #fff;
background-color: #666;
padding: 0.01em 16px;
border-radius: 16px;
height: 100%;
+25 -19
View File
@@ -1,14 +1,15 @@
template(name="checklists")
.checklists-title
h3.card-details-item-title
| ✅
i.fa.fa-check
| {{_ 'checklists'}}
if canModifyCard
+inlinedForm(autoclose=false classNames="js-add-checklist" cardId = cardId position="top")
+inlinedForm(autoclose=false classNames="js-add-checklist" cardId = cardId
position="top")
+addChecklistItemForm
else
a.add-checklist-top.js-open-inlined-form(title="{{_ 'add-checklist'}}")
| ➕
i.fa.fa-plus
if currentUser.isBoardMember
.material-toggle-switch(title="{{_ 'hide-finished-checklist'}}")
//span.toggle-switch-title
@@ -28,7 +29,7 @@ template(name="checklists")
+addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=false)
else
a.add-checklist.js-open-inlined-form(title="{{_ 'add-checklist'}}")
| ➕
i.fa.fa-plus
template(name="checklistDetail")
.js-checklist.checklist.nodragscroll
@@ -38,7 +39,7 @@ template(name="checklistDetail")
.checklist-title
span
if canModifyCard
a.checklist-details-menu.js-open-checklist-details-menu(title="{{_ 'checklistActionsPopup-title'}}")
a.fa.fa-navicon.checklist-details-menu.js-open-checklist-details-menu(title="{{_ 'checklistActionsPopup-title'}}")
if canModifyCard
h4.title.js-open-inlined-form.is-editable
@@ -62,13 +63,17 @@ template(name="checklistDeletePopup")
p {{_ 'confirm-checklist-delete-popup'}}
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
template(name="checklistItemDeletePopup")
p {{_ 'confirm-checklist-item-delete-popup'}}
button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
template(name="addChecklistItemForm")
a(title="{{_ 'copy-text-to-clipboard'}}")
a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}")
span.copied-tooltip {{_ 'copied'}}
textarea.js-add-checklist-item(rows='1' autofocus)
.edit-controls.clearfix
button.primary.confirm.js-submit-add-checklist-item-form(type="submit") {{_ 'save'}}
a.js-close-inlined-form(title="{{_ 'close-add-checklist-item'}}")
a.fa.fa-times-thin.js-close-inlined-form(title="{{_ 'close-add-checklist-item'}}")
if showNewlineBecomesNewChecklistItem
.material-toggle-switch(title="{{_ 'newlineBecomesNewChecklistItem'}}")
input.toggle-switch(type="checkbox" id="toggleNewlineBecomesNewChecklistItem")
@@ -81,7 +86,7 @@ template(name="addChecklistItemForm")
| {{_ 'originOrder'}}
template(name="editChecklistItemForm")
a(title="{{_ 'copy-text-to-clipboard'}}")
a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}")
span.copied-tooltip {{_ 'copied'}}
textarea.js-edit-checklist-item(rows='1' autofocus dir="auto")
if $eq type 'item'
@@ -90,12 +95,12 @@ template(name="editChecklistItemForm")
= checklist.title
.edit-controls.clearfix
button.primary.confirm.js-submit-edit-checklist-item-form(type="submit") {{_ 'save'}}
a.js-close-inlined-form(title="{{_ 'close-edit-checklist-item'}}")
span(title=createdAt) {{ moment createdAt }}
a.fa.fa-times-thin.js-close-inlined-form(title="{{_ 'close-edit-checklist-item'}}")
span(title=createdAt) {{ displayDate createdAt }}
if canModifyCard
a.js-delete-checklist-item {{_ "delete"}}...
a.js-convert-checklist-item-to-card
| 📋
i.fa.fa-copy
| {{_ 'convertChecklistItemToCardPopup-title'}}
template(name="checklistItems")
@@ -105,7 +110,7 @@ template(name="checklistItems")
+addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=true position="top")
else
a.add-checklist-item.js-open-inlined-form(title="{{_ 'add-checklist-item'}}")
| ➕
i.fa.fa-plus
.checklist-items.js-checklist-items
each item in checklist.items
+inlinedForm(classNames="js-edit-checklist-item" item = item checklist = checklist)
@@ -117,7 +122,7 @@ template(name="checklistItems")
+addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=true)
else
a.add-checklist-item.js-open-inlined-form(title="{{_ 'add-checklist-item'}}")
| ➕
i.fa.fa-plus
template(name='checklistItemDetail')
.js-checklist-item.checklist-item(class="{{#if item.isFinished }}is-checked{{#if checklist.hideCheckedChecklistItems}} invisible{{/if}}{{/if}}{{#if checklist.hideAllChecklistItems}} is-checked invisible{{/if}}"
@@ -125,7 +130,8 @@ template(name='checklistItemDetail')
if canModifyCard
.check-box-container
.check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
span.fa.checklistitem-handle(class="fa-arrows" title="{{_ 'dragChecklistItem'}}")
if isTouchScreenOrShowDesktopDragHandles
span.fa.checklistitem-handle(class="fa-arrows" title="{{_ 'dragChecklistItem'}}")
.item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}")
+viewer
= item.title
@@ -139,16 +145,16 @@ template(name="checklistActionsPopup")
ul.pop-over-list
li
a.js-delete-checklist.delete-checklist
| 🗑️
i.fa.fa-trash
| {{_ "delete"}} ...
a.js-move-checklist.move-checklist
| ➡️
i.fa.fa-arrow-right
| {{_ "moveChecklist"}} ...
a.js-copy-checklist.copy-checklist
| 📋
i.fa.fa-copy
| {{_ "copyChecklist"}} ...
a.js-hide-checked-checklist-items
| 🙈
i.fa.fa-eye-slash
| {{_ "hideCheckedChecklistItems"}} ...
.material-toggle-switch(title="{{_ 'hide-checked-items'}}")
if checklist.hideCheckedChecklistItems
@@ -157,7 +163,7 @@ template(name="checklistActionsPopup")
input.toggle-switch(type="checkbox" id="toggleHideCheckedChecklistItems_{{checklist._id}}")
label.toggle-label(for="toggleHideCheckedChecklistItems_{{checklist._id}}")
a.js-hide-all-checklist-items
| 🚫
i.fa.fa-ban
| {{_ "hideAllChecklistItems"}} ...
.material-toggle-switch(title="{{_ 'hideAllChecklistItems'}}")
if checklist.hideAllChecklistItems
+283 -242
View File
@@ -2,10 +2,15 @@ import { ReactiveCache } from '/imports/reactiveCache';
import { TAPi18n } from '/imports/i18n';
import Cards from '/models/cards';
import Boards from '/models/boards';
import { DialogWithBoardSwimlaneListCard } from '/client/lib/dialogWithBoardSwimlaneListCard';
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();
const { calculateIndexData, capitalize } = Utils;
// SubsManager removed for Meteor 3 migration
const { calculateIndexData } = Utils;
function initSorting(items) {
items.sortable({
@@ -45,55 +50,66 @@ function initSorting(items) {
});
}
BlazeComponent.extendComponent({
onRendered() {
const self = this;
self.itemsDom = this.$('.js-checklist-items');
initSorting(self.itemsDom);
self.itemsDom.mousedown(function (evt) {
evt.stopPropagation();
});
Template.checklistDetail.onRendered(function () {
const tpl = this;
tpl.itemsDom = this.$('.js-checklist-items');
initSorting(tpl.itemsDom);
tpl.itemsDom.mousedown(function (evt) {
evt.stopPropagation();
});
function userIsMember() {
return ReactiveCache.getCurrentUser()?.isBoardMember();
}
function userIsMember() {
return ReactiveCache.getCurrentUser()?.isBoardMember();
}
// Disable sorting if the current user is not a board member
self.autorun(() => {
const $itemsDom = $(self.itemsDom);
if ($itemsDom.data('uiSortable') || $itemsDom.data('sortable')) {
$(self.itemsDom).sortable('option', 'disabled', !userIsMember());
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
$(self.itemsDom).sortable({
handle: 'span.fa.checklistitem-handle',
});
}
// Disable sorting if the current user is not a board member
tpl.autorun(() => {
const $itemsDom = $(tpl.itemsDom);
if ($itemsDom.data('uiSortable') || $itemsDom.data('sortable')) {
$(tpl.itemsDom).sortable('option', 'disabled', !userIsMember());
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
$(tpl.itemsDom).sortable({
handle: 'span.fa.checklistitem-handle',
});
}
});
},
}
});
});
Template.checklistDetail.helpers({
/** returns the finished percent of the checklist */
finishedPercent() {
const ret = this.data().checklist.finishedPercent();
const ret = this.checklist.finishedPercent();
return ret;
},
}).register('checklistDetail');
});
BlazeComponent.extendComponent({
addChecklist(event) {
Template.checklists.helpers({
checklists() {
const card = ReactiveCache.getCard(this.cardId);
if (!card || typeof card.checklists !== 'function') {
return [];
}
const ret = card.checklists();
return ret;
},
});
Template.checklists.events({
'click .js-open-checklist-details-menu': Popup.open('checklistActions'),
'submit .js-add-checklist'(event, tpl) {
event.preventDefault();
const textarea = this.find('textarea.js-add-checklist-item');
const textarea = tpl.find('textarea.js-add-checklist-item');
const title = textarea.value.trim();
let cardId = this.currentData().cardId;
let cardId = Template.currentData().cardId;
const card = ReactiveCache.getCard(cardId);
//if (card.isLinked()) cardId = card.linkedId;
if (card.isLinkedCard()) {
cardId = card.linkedId;
}
let sortIndex;
let checklistItemIndex;
if (this.currentData().position === 'top') {
if (Template.currentData().position === 'top') {
sortIndex = Utils.calculateIndexData(null, card.firstChecklist()).base;
checklistItemIndex = 0;
} else {
@@ -107,35 +123,60 @@ BlazeComponent.extendComponent({
title,
sort: sortIndex,
});
this.closeAllInlinedForms();
tpl.$('.js-close-inlined-form').click();
setTimeout(() => {
this.$('.add-checklist-item')
tpl.$('.add-checklist-item')
.eq(checklistItemIndex)
.click();
}, 100);
}
},
addChecklistItem(event) {
'submit .js-edit-checklist-title'(event, tpl) {
event.preventDefault();
const textarea = this.find('textarea.js-add-checklist-item');
const newlineBecomesNewChecklistItem = this.find('input#toggleNewlineBecomesNewChecklistItem');
const newlineBecomesNewChecklistItemOriginOrder = this.find('input#toggleNewlineBecomesNewChecklistItemOriginOrder');
const textarea = tpl.find('textarea.js-edit-checklist-item');
const title = textarea.value.trim();
const checklist = this.currentData().checklist;
const checklist = Template.currentData().checklist;
checklist.setTitle(title);
},
'submit .js-add-checklist-item'(event, tpl) {
event.preventDefault();
const textarea = tpl.find('textarea.js-add-checklist-item');
if (!textarea) {
return;
}
const newlineBecomesNewChecklistItem = tpl.find('input#toggleNewlineBecomesNewChecklistItem');
const newlineBecomesNewChecklistItemOriginOrder = tpl.find('input#toggleNewlineBecomesNewChecklistItemOriginOrder');
const title = textarea.value.trim();
const currentData = Template.currentData() || {};
let resolvedData = currentData;
let checklist = currentData.checklist;
if (!checklist) {
const form = event.currentTarget?.closest
? event.currentTarget.closest('form')
: $(event.target).closest('form').get(0);
const formData = form ? Blaze.getData(form) : null;
if (formData) {
resolvedData = formData;
}
checklist = formData?.checklist;
}
if (!checklist) {
return;
}
if (title) {
let checklistItems = [title];
if (newlineBecomesNewChecklistItem.checked) {
if (newlineBecomesNewChecklistItem?.checked) {
checklistItems = title.split('\n').map(_value => _value.trim());
if (this.currentData().position === 'top') {
if (newlineBecomesNewChecklistItemOriginOrder.checked === false) {
if (resolvedData.position === 'top') {
if (newlineBecomesNewChecklistItemOriginOrder?.checked === false) {
checklistItems = checklistItems.reverse();
}
}
}
let addIndex;
let sortIndex;
if (this.currentData().position === 'top') {
if (resolvedData.position === 'top') {
sortIndex = Utils.calculateIndexData(null, checklist.firstItem()).base;
addIndex = -1;
} else {
@@ -156,33 +197,33 @@ BlazeComponent.extendComponent({
textarea.value = '';
textarea.focus();
},
deleteItem() {
const checklist = this.currentData().checklist;
const item = this.currentData().item;
if (checklist && item && item._id) {
ChecklistItems.remove(item._id);
'submit .js-edit-checklist-item'(event, tpl) {
event.preventDefault();
const textarea = tpl.find('textarea.js-edit-checklist-item');
const title = textarea.value.trim();
const formData = Blaze.getData(event.currentTarget) || Blaze.getData(event.target);
const item = formData?.item;
if (item) {
item.setTitle(title);
}
},
editChecklist(event) {
event.preventDefault();
const textarea = this.find('textarea.js-edit-checklist-item');
const title = textarea.value.trim();
const checklist = this.currentData().checklist;
checklist.setTitle(title);
'click .js-convert-checklist-item-to-card': Popup.open('convertChecklistItemToCard'),
'click .js-delete-checklist-item': Popup.afterConfirm('checklistItemDelete', function () {
Popup.back();
const item = this?.item || this;
if (item && item._id) {
ChecklistItems.remove(item._id);
}
}),
// add and delete checklist / checklist-item
'click .js-open-inlined-form'(event, tpl) {
tpl.$('.js-close-inlined-form').click();
},
editChecklistItem(event) {
'click #toggleHideFinishedChecklist'(event) {
event.preventDefault();
const textarea = this.find('textarea.js-edit-checklist-item');
const title = textarea.value.trim();
const item = this.currentData().item;
item.setTitle(title);
Template.currentData().card.toggleHideFinishedChecklist();
},
pressKey(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) {
@@ -191,201 +232,201 @@ BlazeComponent.extendComponent({
$form.find('button[type=submit]').click();
}
},
});
focusChecklistItem(event) {
// If a new checklist is created, pre-fill the title and select it.
const checklist = this.currentData().checklist;
if (!checklist) {
const textarea = event.target;
textarea.value = capitalize(TAPi18n.__('r-checklist'));
textarea.select();
}
},
// NOTE: boardsSwimlanesAndLists template was removed from jade but JS was left behind.
// This is dead code — the template no longer exists in any jade file.
/** closes all inlined forms (checklist and checklist-item input fields) */
closeAllInlinedForms() {
this.$('.js-close-inlined-form').click();
},
Template.addChecklistItemForm.onRendered(function () {
autosize(this.$('textarea.js-add-checklist-item'));
});
events() {
return [
{
'click .js-open-checklist-details-menu': Popup.open('checklistActions'),
'submit .js-add-checklist': this.addChecklist,
'submit .js-edit-checklist-title': this.editChecklist,
'submit .js-add-checklist-item': this.addChecklistItem,
'submit .js-edit-checklist-item': this.editChecklistItem,
'click .js-convert-checklist-item-to-card': Popup.open('convertChecklistItemToCard'),
'click .js-delete-checklist-item': this.deleteItem,
'focus .js-add-checklist-item': this.focusChecklistItem,
// add and delete checklist / checklist-item
'click .js-open-inlined-form': this.closeAllInlinedForms,
'click #toggleHideFinishedChecklist'(event) {
event.preventDefault();
this.data().card.toggleHideFinishedChecklist();
},
keydown: this.pressKey,
},
];
},
}).register('checklists');
Template.addChecklistItemForm.events({
'click a.fa.fa-copy'(event, tpl) {
const $editor = tpl.$('textarea');
const promise = Utils.copyTextToClipboard($editor[0].value);
BlazeComponent.extendComponent({
onCreated() {
subManager.subscribe('board', Session.get('currentBoard'), false);
this.selectedBoardId = new ReactiveVar(Session.get('currentBoard'));
},
boards() {
const ret = ReactiveCache.getBoards(
{
archived: false,
'members.userId': Meteor.userId(),
_id: { $ne: ReactiveCache.getCurrentUser().getTemplatesBoardId() },
},
{
sort: { sort: 1 /* boards default sorting */ },
},
);
return ret;
},
swimlanes() {
const board = ReactiveCache.getBoard(this.selectedBoardId.get());
return board.swimlanes();
},
aBoardLists() {
const board = ReactiveCache.getBoard(this.selectedBoardId.get());
return board.lists();
},
events() {
return [
{
'change .js-select-boards'(event) {
this.selectedBoardId.set($(event.currentTarget).val());
subManager.subscribe('board', this.selectedBoardId.get(), false);
},
},
];
},
}).register('boardsSwimlanesAndLists');
Template.checklists.helpers({
checklists() {
const card = ReactiveCache.getCard(this.cardId);
const ret = card.checklists();
return ret;
const $tooltip = tpl.$('.copied-tooltip');
Utils.showCopied(promise, $tooltip);
},
});
BlazeComponent.extendComponent({
onRendered() {
autosize(this.$('textarea.js-add-checklist-item'));
Template.checklistActionsPopup.events({
'click .js-delete-checklist': Popup.afterConfirm('checklistDelete', function () {
Popup.back(2);
const checklist = this.checklist;
if (checklist && checklist._id) {
Checklists.remove(checklist._id);
}
}),
'click .js-move-checklist': Popup.open('moveChecklist'),
'click .js-copy-checklist': Popup.open('copyChecklist'),
'click .js-hide-checked-checklist-items'(event) {
event.preventDefault();
Template.currentData().checklist.toggleHideCheckedChecklistItems();
Popup.back();
},
events() {
return [
{
'click a.fa.fa-copy'(event) {
const $editor = this.$('textarea');
const promise = Utils.copyTextToClipboard($editor[0].value);
const $tooltip = this.$('.copied-tooltip');
Utils.showCopied(promise, $tooltip);
},
}
];
}
}).register('addChecklistItemForm');
BlazeComponent.extendComponent({
events() {
return [
{
'click .js-delete-checklist': Popup.afterConfirm('checklistDelete', function () {
Popup.back(2);
const checklist = this.checklist;
if (checklist && checklist._id) {
Checklists.remove(checklist._id);
}
}),
'click .js-move-checklist': Popup.open('moveChecklist'),
'click .js-copy-checklist': Popup.open('copyChecklist'),
'click .js-hide-checked-checklist-items'(event) {
event.preventDefault();
this.data().checklist.toggleHideCheckedChecklistItems();
Popup.back();
},
'click .js-hide-all-checklist-items'(event) {
event.preventDefault();
this.data().checklist.toggleHideAllChecklistItems();
Popup.back();
},
}
]
}
}).register('checklistActionsPopup');
BlazeComponent.extendComponent({
onRendered() {
autosize(this.$('textarea.js-edit-checklist-item'));
'click .js-hide-all-checklist-items'(event) {
event.preventDefault();
Template.currentData().checklist.toggleHideAllChecklistItems();
Popup.back();
},
events() {
return [
{
'click a.fa.fa-copy'(event) {
const $editor = this.$('textarea');
const promise = Utils.copyTextToClipboard($editor[0].value);
});
const $tooltip = this.$('.copied-tooltip');
Utils.showCopied(promise, $tooltip);
},
}
];
}
}).register('editChecklistItemForm');
Template.editChecklistItemForm.onRendered(function () {
autosize(this.$('textarea.js-edit-checklist-item'));
});
Template.editChecklistItemForm.events({
'click a.fa.fa-copy'(event, tpl) {
const $editor = tpl.$('textarea');
const promise = Utils.copyTextToClipboard($editor[0].value);
const $tooltip = tpl.$('.copied-tooltip');
Utils.showCopied(promise, $tooltip);
},
});
Template.checklistItemDetail.helpers({
});
BlazeComponent.extendComponent({
toggleItem() {
const checklist = this.currentData().checklist;
const item = this.currentData().item;
Template.checklistItemDetail.events({
'click .js-checklist-item .check-box-container'() {
const checklist = Template.currentData().checklist;
const item = Template.currentData().item;
if (checklist && item && item._id) {
item.toggleItem();
}
},
events() {
return [
{
'click .js-checklist-item .check-box-container': this.toggleItem,
},
];
});
/**
* Helper to find the dialog instance from a parent popup template.
* copyAndMoveChecklist is included inside moveChecklistPopup / copyChecklistPopup,
* so we traverse up the view hierarchy to find the parent template's dialog.
*/
function getParentDialog(tpl) {
let view = tpl.view.parentView;
while (view) {
if (view.templateInstance && view.templateInstance() && view.templateInstance().dialog) {
return view.templateInstance().dialog;
}
view = view.parentView;
}
return null;
}
/** Shared helpers for copyAndMoveChecklist sub-template */
Template.copyAndMoveChecklist.helpers({
boards() {
const dialog = getParentDialog(Template.instance());
return dialog ? dialog.boards() : [];
},
}).register('checklistItemDetail');
swimlanes() {
const dialog = getParentDialog(Template.instance());
return dialog ? dialog.swimlanes() : [];
},
lists() {
const dialog = getParentDialog(Template.instance());
return dialog ? dialog.lists() : [];
},
cards() {
const dialog = getParentDialog(Template.instance());
return dialog ? dialog.cards() : [];
},
isDialogOptionBoardId(boardId) {
const dialog = getParentDialog(Template.instance());
return dialog ? dialog.isDialogOptionBoardId(boardId) : false;
},
isDialogOptionSwimlaneId(swimlaneId) {
const dialog = getParentDialog(Template.instance());
return dialog ? dialog.isDialogOptionSwimlaneId(swimlaneId) : false;
},
isDialogOptionListId(listId) {
const dialog = getParentDialog(Template.instance());
return dialog ? dialog.isDialogOptionListId(listId) : false;
},
isDialogOptionCardId(cardId) {
const dialog = getParentDialog(Template.instance());
return dialog ? dialog.isDialogOptionCardId(cardId) : false;
},
isTitleDefault(title) {
const dialog = getParentDialog(Template.instance());
return dialog ? dialog.isTitleDefault(title) : title;
},
});
/**
* Helper: register standard card dialog events on a checklist popup template.
* Events bubble up from the copyAndMoveChecklist sub-template to the parent popup.
*/
function registerChecklistDialogEvents(templateName) {
Template[templateName].events({
async 'click .js-done'(event, tpl) {
const dialog = tpl.dialog;
const boardSelect = tpl.$('.js-select-boards')[0];
const boardId = boardSelect.options[boardSelect.selectedIndex].value;
const listSelect = tpl.$('.js-select-lists')[0];
const listId = listSelect.options[listSelect.selectedIndex].value;
const swimlaneSelect = tpl.$('.js-select-swimlanes')[0];
const swimlaneId = swimlaneSelect.options[swimlaneSelect.selectedIndex].value;
const cardSelect = tpl.$('.js-select-cards')[0];
const cardId = cardSelect.options.length > 0
? cardSelect.options[cardSelect.selectedIndex].value
: null;
const options = { boardId, swimlaneId, listId, cardId };
try {
await dialog.setDone(cardId, options);
} catch (e) {
console.error('Error in card dialog operation:', e);
}
Popup.back(2);
},
'change .js-select-boards'(event, tpl) {
tpl.dialog.getBoardData($(event.currentTarget).val());
},
'change .js-select-swimlanes'(event, tpl) {
tpl.dialog.selectedSwimlaneId.set($(event.currentTarget).val());
tpl.dialog.setFirstListId();
},
'change .js-select-lists'(event, tpl) {
tpl.dialog.selectedListId.set($(event.currentTarget).val());
tpl.dialog.selectedCardId.set('');
},
'change .js-select-cards'(event, tpl) {
tpl.dialog.selectedCardId.set($(event.currentTarget).val());
},
});
}
/** Move Checklist Dialog */
(class extends DialogWithBoardSwimlaneListCard {
getDialogOptions() {
const ret = ReactiveCache.getCurrentUser().getMoveChecklistDialogOptions();
return ret;
}
setDone(cardId, options) {
ReactiveCache.getCurrentUser().setMoveChecklistDialogOption(this.currentBoardId, options);
this.data().checklist.move(cardId);
}
}).register('moveChecklistPopup');
Template.moveChecklistPopup.onCreated(function () {
this.dialog = new BoardSwimlaneListCardDialog(this, {
getDialogOptions() {
return ReactiveCache.getCurrentUser().getMoveChecklistDialogOptions();
},
async setDone(cardId, options) {
ReactiveCache.getCurrentUser().setMoveChecklistDialogOption(this.currentBoardId, options);
await Template.currentData().checklist.move(cardId);
},
});
});
registerChecklistDialogEvents('moveChecklistPopup');
/** Copy Checklist Dialog */
(class extends DialogWithBoardSwimlaneListCard {
getDialogOptions() {
const ret = ReactiveCache.getCurrentUser().getCopyChecklistDialogOptions();
return ret;
}
setDone(cardId, options) {
ReactiveCache.getCurrentUser().setCopyChecklistDialogOption(this.currentBoardId, options);
this.data().checklist.copy(cardId);
}
}).register('copyChecklistPopup');
Template.copyChecklistPopup.onCreated(function () {
this.dialog = new BoardSwimlaneListCardDialog(this, {
getDialogOptions() {
return ReactiveCache.getCurrentUser().getCopyChecklistDialogOptions();
},
async setDone(cardId, options) {
ReactiveCache.getCurrentUser().setCopyChecklistDialogOption(this.currentBoardId, options);
await Template.currentData().checklist.copy(cardId);
},
});
});
registerChecklistDialogEvents('copyChecklistPopup');
@@ -0,0 +1,6 @@
template(name='inlinedCardDescription')
if isOpen.get
form.inlined-form.js-inlined-form(id=id class=classNames)
+Template.contentBlock
else
+Template.elseBlock
+6 -2
View File
@@ -223,9 +223,13 @@
.card-label-edit-button:hover {
background: #dbdbdb;
}
ul.edit-labels-pop-over span.fa.label-handle {
ul.edit-labels-pop-over span.label-handle {
padding-right: 10px;
display: inline-block;
width: 1.2em;
text-align: center;
color: #999;
}
ul.edit-labels-pop-over span.fa.label-handle + .card-label {
ul.edit-labels-pop-over span.label-handle + .card-label {
max-width: 180px;
}
+3 -4
View File
@@ -6,7 +6,7 @@ template(name="formLabel")
.palette-colors: each labels
span.card-label.palette-color.js-palette-color(class="card-label-{{color}}")
if(isSelected color)
| ✅
i.fa.fa-check
template(name="createLabelPopup")
form.create-label
@@ -28,8 +28,7 @@ template(name="cardLabelsPopup")
ul.edit-labels-pop-over
each board.labels
li.js-card-label-item
a.card-label-edit-button.js-edit-label
| ✏️
a.card-label-edit-button.fa.fa-pencil.js-edit-label
if isTouchScreenOrShowDesktopDragHandles
span.fa.label-handle(class="fa-arrows" title="{{_ 'dragLabel'}}")
span.card-label.card-label-selectable.js-select-label.card-label-wrapper(class="card-label-{{color}}"
@@ -37,5 +36,5 @@ template(name="cardLabelsPopup")
+viewer
= name
if(isLabelSelected ../_id)
| ✅
i.card-label-selectable-icon.fa.fa-check
a.quiet-button.full.js-add-label {{_ 'label-create'}}
+106 -120
View File
@@ -1,119 +1,117 @@
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;
});
BlazeComponent.extendComponent({
onCreated() {
this.currentColor = new ReactiveVar(this.data().color);
},
const getFallbackLabelColor = () => {
if (Array.isArray(labelColors) && labelColors.length > 0) {
return labelColors[0];
}
return 'green';
};
Template.formLabel.onCreated(function () {
const initialColor = this.data?.color || getFallbackLabelColor();
this.currentColor = new ReactiveVar(initialColor);
});
Template.formLabel.helpers({
labels() {
return labelColors.map(color => ({ color, name: '' }));
const colors = Array.isArray(labelColors) ? labelColors : [getFallbackLabelColor()];
return colors.map(color => ({ color, name: '' }));
},
isSelected(color) {
return this.currentColor.get() === color;
return Template.instance().currentColor.get() === color;
},
});
events() {
return [
{
'click .js-palette-color'() {
this.currentColor.set(this.currentData().color);
},
},
];
Template.formLabel.events({
'click .js-palette-color'(event, tpl) {
const paletteData = Blaze.getData(event.currentTarget);
const selectedColor = paletteData?.color || Template.currentData()?.color || getFallbackLabelColor();
tpl.currentColor.set(selectedColor);
},
}).register('formLabel');
});
Template.createLabelPopup.helpers({
// This is the default color for a new label. We search the first color that
// is not already used in the board (although it's not a problem if two
// labels have the same color).
defaultColor() {
const labels = Utils.getCurrentBoard().labels;
const usedColors = _.pluck(labels, 'color');
const availableColors = _.difference(labelColors, usedColors);
return availableColors.length > 1 ? availableColors[0] : labelColors[0];
const board = Utils.getCurrentBoard();
const colors = Array.isArray(labelColors) ? labelColors : [getFallbackLabelColor()];
const labels = Array.isArray(board?.labels) ? board.labels : [];
const usedColors = labels.map(l => l.color);
const availableColors = colors.filter(c => !usedColors.includes(c));
return availableColors.length > 0 ? availableColors[0] : colors[0];
},
});
BlazeComponent.extendComponent({
onRendered() {
const itemsSelector = 'li.js-card-label-item:not(.placeholder)';
const $labels = this.$('.edit-labels-pop-over');
Template.cardLabelsPopup.onRendered(function () {
const tpl = this;
const itemsSelector = 'li.js-card-label-item:not(.placeholder)';
const $labels = tpl.$('.edit-labels-pop-over');
$labels.sortable({
connectWith: '.edit-labels-pop-over',
tolerance: 'pointer',
appendTo: '.edit-labels-pop-over',
helper(element, currentItem) {
let ret = currentItem.clone();
if (currentItem.closest('.popup-container-depth-0').length == 0)
{ // only set css transform at every sub-popup, not at the main popup
const content = currentItem.closest('.content')[0]
const offsetLeft = content.offsetLeft;
const offsetTop = $('.pop-over > .header').height() * -1;
ret.css("transform", `translate(${offsetLeft}px, ${offsetTop}px)`);
}
return ret;
},
distance: 7,
items: itemsSelector,
placeholder: 'card-label-wrapper placeholder',
start(evt, ui) {
ui.helper.css('z-index', 1000);
ui.placeholder.height(ui.helper.height());
EscapeActions.clickExecute(evt.target, 'inlinedForm');
},
stop(evt, ui) {
const newLabelOrderOnlyIds = ui.item.parent().children().toArray().map(_element => Blaze.getData(_element)._id)
const card = Blaze.getData(this);
card.board().setNewLabelOrder(newLabelOrderOnlyIds);
},
});
// Disable drag-dropping if the current user is not a board member or is comment only
this.autorun(() => {
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
$labels.sortable({
handle: '.label-handle',
});
$labels.sortable({
connectWith: '.edit-labels-pop-over',
tolerance: 'pointer',
appendTo: '.edit-labels-pop-over',
helper(element, currentItem) {
let ret = currentItem.clone();
if (currentItem.closest('.popup-container-depth-0').length == 0)
{ // only set css transform at every sub-popup, not at the main popup
const content = currentItem.closest('.content')[0]
const offsetLeft = content.offsetLeft;
const offsetTop = $('.pop-over > .header').height() * -1;
ret.css("transform", `translate(${offsetLeft}px, ${offsetTop}px)`);
}
});
return ret;
},
distance: 7,
items: itemsSelector,
placeholder: 'card-label-wrapper placeholder',
start(evt, ui) {
ui.helper.css('z-index', 1000);
ui.placeholder.height(ui.helper.height());
EscapeActions.clickExecute(evt.target, 'inlinedForm');
},
stop(evt, ui) {
const newLabelOrderOnlyIds = ui.item.parent().children().toArray().map(_element => Blaze.getData(_element)._id)
const card = Blaze.getData(this);
card.board().setNewLabelOrder(newLabelOrderOnlyIds);
},
});
// Disable drag-dropping if the current user is not a board member or is comment only
tpl.autorun(() => {
if (Utils.isTouchScreenOrShowDesktopDragHandles()) {
$labels.sortable({
handle: '.label-handle',
});
}
});
});
Template.cardLabelsPopup.helpers({
isLabelSelected(cardId) {
return (ReactiveCache.getCard(cardId).labelIds || []).includes(this._id);
},
events() {
return [
{
'click .js-select-label'(event) {
const card = this.data();
const labelId = this.currentData()._id;
card.toggleLabel(labelId);
event.preventDefault();
},
'click .js-edit-label': Popup.open('editLabel'),
'click .js-add-label': Popup.open('createLabel'),
}
];
}
}).register('cardLabelsPopup');
});
Template.cardLabelsPopup.events({
});
Template.formLabel.events({
'click .js-palette-color'(event) {
const $this = $(event.currentTarget);
// hide selected ll colors
$('.js-palette-select').addClass('hide');
// show select color
$this.find('.js-palette-select').removeClass('hide');
'click .js-select-label'(event) {
const card = Template.currentData();
const labelId = this._id;
card.toggleLabel(labelId);
event.preventDefault();
},
'click .js-edit-label': Popup.open('editLabel'),
'click .js-add-label': Popup.open('createLabel'),
});
Template.createLabelPopup.events({
@@ -121,23 +119,20 @@ Template.createLabelPopup.events({
'submit .create-label'(event, templateInstance) {
event.preventDefault();
const board = Utils.getCurrentBoard();
if (!board) {
return;
}
const name = templateInstance
.$('#labelName')
.val()
.trim();
// Find the selected color by looking for the palette color that contains the checkmark
let selectedColor = null;
templateInstance.$('.js-palette-color').each(function() {
if ($(this).text().includes('✅')) {
selectedColor = Blaze.getData(this).color;
return false; // break out of loop
}
});
if (selectedColor) {
board.addLabel(name, selectedColor);
}
const selectedColorIcon = templateInstance.find('.js-palette-color .fa-check');
const selectedPaletteNode = selectedColorIcon?.closest
? selectedColorIcon.closest('.js-palette-color')
: null;
const selectedColorData = selectedPaletteNode && Blaze.getData(selectedPaletteNode);
const color = selectedColorData?.color || getFallbackLabelColor();
board.addLabel(name, color);
Popup.back();
},
});
@@ -151,29 +146,20 @@ Template.editLabelPopup.events({
'submit .edit-label'(event, templateInstance) {
event.preventDefault();
const board = Utils.getCurrentBoard();
if (!board) {
return;
}
const name = templateInstance
.$('#labelName')
.val()
.trim();
// Find the selected color by looking for the palette color that contains the checkmark
let selectedColor = null;
templateInstance.$('.js-palette-color').each(function() {
if ($(this).text().includes('✅')) {
selectedColor = Blaze.getData(this).color;
return false; // break out of loop
}
});
if (selectedColor) {
board.editLabel(this._id, name, selectedColor);
}
const selectedColorIcon = templateInstance.find('.js-palette-color .fa-check');
const selectedPaletteNode = selectedColorIcon?.closest
? selectedColorIcon.closest('.js-palette-color')
: null;
const selectedColorData = selectedPaletteNode && Blaze.getData(selectedPaletteNode);
const color = selectedColorData?.color || getFallbackLabelColor();
board.editLabel(this._id, name, color);
Popup.back();
},
});
Template.cardLabelsPopup.helpers({
isLabelSelected(cardId) {
return _.contains(ReactiveCache.getCard(cardId).labelIds, this._id);
},
});
+160 -18
View File
@@ -45,9 +45,10 @@
}
.minicard-details-menu-with-handle {
float: right;
font-size: clamp(14px, 3vw, 18px);
padding-right: 4vw;
padding-left: 0.7vw;
font-size: clamp(14px, 3vw, 18px);
padding: 0;
z-index: 1;
}
.minicard-details-menu {
float: right;
@@ -97,6 +98,7 @@
}
.minicard .minicard-labels {
float: none;
margin-right: 6vw;
}
.minicard .minicard-labels .minicard-label {
width: clamp(12px, 1.5vw, 16px);
@@ -111,6 +113,7 @@
}
.minicard .minicard-custom-fields {
display: block;
margin-right: 6vw;
}
.minicard .minicard-custom-field {
display: flex;
@@ -133,18 +136,25 @@
width: clamp(20px, 2.5vw, 28px);
height: clamp(20px, 2.5vw, 28px);
position: absolute;
right: 0.7vw;
top: 0.7vh;
right: 0vw;
top: 4vh;
display: none;
z-index: 1;
}
@media only screen {
.minicard .handle {
display: block;
}
}
.minicard .handle .fa-arrows {
.minicard .handle .drag-handle {
font-size: clamp(16px, 3vw, 20px);
color: #ccc;
display: inline-block;
width: 1.4em;
text-align: center;
}
.minicard .minicard-title {
margin-right: 1.5vw;
}
.minicard .minicard-title .card-number {
color: #b3b3b3;
@@ -164,6 +174,10 @@
display: flex;
flex-direction: row;
flex-wrap: wrap;
position: relative;
z-index: 5;
margin-right: 6vw;
clear: both;
}
.minicard .date {
margin-right: 0.4vw;
@@ -229,19 +243,74 @@
background-color: #ff9999;
}
/* Font Awesome icons in minicard dates */
.minicard .card-date i.fa {
margin-right: 0.3vw;
font-size: 0.9em;
vertical-align: middle;
/* Date status colors for minicards - matching cardDate.css */
.minicard .card-date.overdue {
background-color: #ff4444 !important; /* Red for overdue */
color: #fff !important;
}
.minicard .card-date.overdue:hover,
.minicard .card-date.overdue.is-active {
background-color: #cc3333 !important;
}
/* Font Awesome icons in minicard spent time */
.minicard .card-time i.fa {
margin-right: 0.3vw;
font-size: 0.9em;
vertical-align: middle;
.minicard .card-date.due-soon {
background-color: #ffaa00 !important; /* Amber for due soon */
color: #000 !important;
}
.minicard .card-date.due-soon:hover,
.minicard .card-date.due-soon.is-active {
background-color: #e69900 !important;
}
.minicard .card-date.not-due {
/* No special background - uses default date type colors */
}
.minicard .card-date.current {
background-color: #5ba639 !important; /* Green for current/active */
color: #fff !important;
}
.minicard .card-date.current:hover,
.minicard .card-date.current.is-active {
background-color: #46802c !important;
}
.minicard .card-date.completed {
background-color: #90ee90 !important; /* Light green for completed */
color: #000 !important;
}
.minicard .card-date.completed:hover,
.minicard .card-date.completed.is-active {
background-color: #7dd87d !important;
}
.minicard .card-date.completed-early {
background-color: #4caf50 !important; /* Green for completed early */
color: #fff !important;
}
.minicard .card-date.completed-early:hover,
.minicard .card-date.completed-early.is-active {
background-color: #45a049 !important;
}
.minicard .card-date.completed-late {
background-color: #ff9800 !important; /* Orange for completed late */
color: #fff !important;
}
.minicard .card-date.completed-late:hover,
.minicard .card-date.completed-late.is-active {
background-color: #f57c00 !important;
}
.minicard .card-date.completed-on-time {
background-color: #2196f3 !important; /* Blue for completed on time */
color: #fff !important;
}
.minicard .card-date.completed-on-time:hover,
.minicard .card-date.completed-on-time.is-active {
background-color: #1976d2 !important;
}
.minicard .badges {
float: left;
margin-top: 1vh;
@@ -673,7 +742,80 @@
gap: 0.3vw;
}
.minicard-list-name i.fa {
font-size: 0.8em;
opacity: 0.7;
/* Checklist display on minicard */
.minicard-checklist {
width: 100%;
margin-top: 0.5vh;
margin-bottom: 0.5vh;
padding: 0.3vh 0.5vw;
background-color: rgba(255, 255, 255, 0.8);
border-radius: 0.3vw;
border: 1px solid #e0e0e0;
}
.minicard-checklist .checklist-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.3vh;
}
.minicard-checklist .checklist-title {
font-size: 0.8em;
font-weight: bold;
color: #4d4d4d;
flex: 1;
}
.minicard-checklist .checklist-menu {
font-size: 1.2em;
color: #666;
cursor: pointer;
padding: 0 0.3vw;
border-radius: 0.2vw;
transition: background-color 0.2s;
}
.minicard-checklist .checklist-menu:hover {
background-color: rgba(0, 0, 0, 0.1);
}
.minicard-checklist .checklist-item {
font-size: 0.75em;
color: #666;
margin-bottom: 0.2vh;
display: flex;
align-items: flex-start;
gap: 0.3vw;
line-height: 1.2;
cursor: pointer;
padding: 0.2vh 0;
border-radius: 0.2vw;
transition: background-color 0.2s;
}
.minicard-checklist .checklist-item:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.minicard-checklist .checklist-item.is-checked {
text-decoration: line-through;
color: #999;
}
.minicard-checklist .checklist-item .check-box-unicode {
flex-shrink: 0;
font-size: 0.8em;
margin-top: 0.1vh;
transition: transform 0.2s;
}
.minicard-checklist .checklist-item:hover .check-box-unicode {
transform: scale(1.1);
}
.minicard-checklist .checklist-item .item-title {
flex: 1;
word-wrap: break-word;
overflow-wrap: break-word;
}
+77 -99
View File
@@ -3,22 +3,29 @@ template(name="minicard")
class="{{#if isLinkedCard}}linked-card{{/if}}"
class="{{#if isLinkedBoard}}linked-board{{/if}}"
class="{{#if colorClass}}minicard-{{colorClass}}{{/if}}")
if canMoveCard
if isTouchScreenOrShowDesktopDragHandles
.handle
i.fa.fa-arrows
if canModifyCard
a.minicard-details-menu-with-handle.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}") ☰
.handle
| ↕️
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
@@ -30,7 +37,7 @@ template(name="minicard")
if hasActiveUploads
.minicard-upload-progress
.upload-progress-header
| 📤
i.fa.fa-upload
span {{_ 'uploading-files'}} ({{uploadCount}})
each uploads
.upload-progress-item(class="{{#if $eq status 'error'}}upload-error{{/if}}")
@@ -39,11 +46,11 @@ template(name="minicard")
.upload-progress-fill(style="width: {{progress}}%")
if $eq status 'error'
.upload-progress-error
| ⚠️
i.fa.fa-warning
span {{_ 'upload-failed'}}
else if $eq status 'completed'
.upload-progress-success
| ✅
i.fa.fa-check
span {{_ 'upload-completed'}}
.minicard-title
@@ -55,26 +62,30 @@ template(name="minicard")
| {{ parentCardName }}
if isLinkedBoard
a.js-linked-link
span.linked-icon | 📁
span.linked-icon
i.fa.fa-folder
else if isLinkedCard
a.js-linked-link
span.linked-icon | 🃏
span.linked-icon
i.fa.fa-id-card
if getArchived
span.linked-icon.linked-archived | 📦
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
@@ -141,45 +152,54 @@ template(name="minicard")
if canModifyCard
if comments.length
.badge(title="{{_ 'card-comments-title' comments.length }}")
span.badge-icon.badge-comment.badge-text | 💬
span.badge-icon.badge-comment.badge-text
i.fa.fa-comment-o
= ' '
= comments.length
//span.badge-comment.badge-text
//| {{_ 'comment'}}
//|
{{_ 'comment'}}
if getDescription
unless currentBoard.allowsDescriptionTextOnMinicard
.badge.badge-state-image-only(title=getDescription)
span.badge-icon | 📝
span.badge-icon
i.fa.fa-file-text-o
if getVoteQuestion
.badge.badge-state-image-only(title=getVoteQuestion)
span.badge-icon(class="{{#if voteState}}text-green{{/if}}") | 👍
span.badge-icon(class="{{#if voteState}}text-green{{/if}}")
i.fa.fa-thumbs-up
span.badge-text {{ voteCountPositive }}
span.badge-icon(class="{{#if $eq voteState false}}text-red{{/if}}") | 👎
span.badge-icon(class="{{#if $eq voteState false}}text-red{{/if}}")
i.fa.fa-thumbs-down
span.badge-text {{ voteCountNegative }}
if getPokerQuestion
.badge.badge-state-image-only(title=getPokerQuestion)
span.badge-icon(class="{{#if pokerState}}text-green{{/if}}") | ✅
span.badge-icon(class="{{#if pokerState}}text-green{{/if}}")
i.fa.fa-check-square
if expiredPoker
span.badge-text {{ getPokerEstimation }}
if attachments.length
if currentBoard.allowsBadgeAttachmentOnMinicard
.badge
span.badge-icon | 📎
span.badge-icon
i.fa.fa-paperclip
span.badge-text= attachments.length
if checklists.length
.badge(class="{{#if checklistFinished}}is-finished{{/if}}")
span.badge-icon | ☑️
span.badge-text.check-list-text {{checklistFinishedCount}}/{{checklistItemCount}}
if allSubtasks.count
.badge
span.badge-icon | 🌐
span.badge-text.check-list-text {{subtasksFinishedCount}}/{{allSubtasksCount}}
//{{subtasksFinishedCount}}/{{subtasksCount}} does not work because when a subtaks is archived, the count goes down
if 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
span.badge-icon | 🔢
span.badge-icon
i.fa.fa-sort-numeric-asc
span.badge-text.check-list-sort {{ sort }}
if shouldShowChecklistAtMinicard
each shouldShowChecklistAtMinicard
+minicardChecklist(checklist=. card=..)
if currentBoard.allowsDescriptionTextOnMinicard
if getDescription
.minicard-description
@@ -187,7 +207,7 @@ template(name="minicard")
| {{ getDescription }}
if shouldShowListOnMinicard
.minicard-list-name
| 📋
i.fa.fa-list
| {{ listName }}
if $eq 'subtext-with-full-path' currentBoard.presentParentTask
.parent-subtext
@@ -201,55 +221,13 @@ template(name="editCardSortOrderPopup")
.edit-controls.clearfix
button.primary.confirm.js-submit-edit-card-sort-popup(type="submit") {{_ 'save'}}
template(name="minicardDetailsActionsPopup")
ul.pop-over-list
if canModifyCard
li
a.js-move-card
| ➡️
| {{_ 'moveCardPopup-title'}}
li
a.js-copy-card
| 📋
| {{_ 'copyCardPopup-title'}}
hr
li
a.js-archive
| ➡️
| 📦
| {{_ 'archive-card'}}
hr
li
a.js-move-card-to-top
| ⬆️
| {{_ 'moveCardToTop-title'}}
li
a.js-move-card-to-bottom
| ⬇️
| {{_ 'moveCardToBottom-title'}}
hr
li
a.js-add-labels
| 🏷️
| {{_ 'card-edit-labels'}}
li
a.js-due-date
| 📥
| {{_ 'editCardDueDatePopup-title'}}
li
a.js-set-card-color
| 🎨
| {{_ 'setCardColorPopup-title'}}
li
a.js-link
| 🔗
| {{_ 'link-card'}}
li
a.js-toggle-watch-card
if isWatching
| 👁️
| {{_ 'unwatch'}}
else
| 👁️-slash
| {{_ 'watch'}}
template(name="minicardChecklist")
.minicard-checklist
.checklist-header
.checklist-title= checklist.title
if canModifyCard
a.checklist-menu.js-open-checklist-menu(title="{{_ 'checklistActionsPopup-title'}}")
i.fa.fa-bars
each visibleItems
+checklistItemDetail(item = . checklist = checklist card = card)
+195 -170
View File
@@ -3,18 +3,15 @@ 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')
// });
BlazeComponent.extendComponent({
template() {
return 'minicard';
},
Template.minicard.helpers({
formattedCurrencyCustomFieldValue(definition) {
const customField = this.data()
const customField = this
.customFieldsWD()
.find(f => f._id === definition._id);
const customFieldTrueValue =
@@ -28,7 +25,7 @@ BlazeComponent.extendComponent({
},
formattedStringtemplateCustomFieldValue(definition) {
const customField = this.data()
const customField = this
.customFieldsWD()
.find(f => f._id === definition._id);
@@ -41,7 +38,7 @@ BlazeComponent.extendComponent({
showCreatorOnMinicard() {
// cache "board" to reduce the mini-mongodb access
const board = this.data().board();
const board = this.board();
let ret = false;
if (board) {
ret = board.allowsCreatorOnMinicard ?? false;
@@ -49,128 +46,55 @@ BlazeComponent.extendComponent({
return ret;
},
isWatching() {
const card = this.currentData();
return card.findWatcher(Meteor.userId());
return this.findWatcher(Meteor.userId());
},
showMembers() {
// cache "board" to reduce the mini-mongodb access
const board = this.data().board();
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.data().board();
const board = this.board();
let ret = false;
if (board) {
ret =
board.allowsAssignee === null ||
board.allowsAssignee === undefined ||
board.allowsAssignee
;
ret = board.allowsAssigneeOnMinicard ?? false;
}
return ret;
},
/** opens the card label popup only if clicked onto a label
* <li> this is necessary to have the data context of the minicard.
* if .js-card-label is used at click event, then only the data context of the label itself is available at this.currentData()
*/
cardLabelsPopup(event) {
if (this.find('.js-card-label:hover')) {
Popup.open("cardLabels")(event, {dataContextIfCurrentDataIsUndefined: this.currentData()});
}
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;
},
events() {
return [
{
'click .js-linked-link'() {
if (this.data().isLinkedCard()) Utils.goCardId(this.data().linkedId);
else if (this.data().isLinkedBoard())
Utils.goBoardId(this.data().linkedId);
},
'click .js-toggle-minicard-label-text'() {
if (window.localStorage.getItem('hiddenMinicardLabelText')) {
window.localStorage.removeItem('hiddenMinicardLabelText'); //true
} else {
window.localStorage.setItem('hiddenMinicardLabelText', 'true'); //true
}
},
'click span.badge-icon.fa.fa-sort, click span.badge-text.check-list-sort' : Popup.open("editCardSortOrder"),
'click .minicard-labels' : this.cardLabelsPopup,
'click .js-open-minicard-details-menu': Popup.open('minicardDetailsActions'),
// Drag and drop file upload handlers
'dragover .minicard'(event) {
// Only prevent default for file drags to avoid interfering with sortable
const dataTransfer = event.originalEvent.dataTransfer;
if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) {
event.preventDefault();
event.stopPropagation();
}
},
'dragenter .minicard'(event) {
const dataTransfer = event.originalEvent.dataTransfer;
if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) {
event.preventDefault();
event.stopPropagation();
const card = this.data();
const board = card.board();
// Only allow drag-and-drop if user can modify card and board allows attachments
if (Utils.canModifyCard() && board && board.allowsAttachments) {
$(event.currentTarget).addClass('is-dragging-over');
}
}
},
'dragleave .minicard'(event) {
const dataTransfer = event.originalEvent.dataTransfer;
if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) {
event.preventDefault();
event.stopPropagation();
$(event.currentTarget).removeClass('is-dragging-over');
}
},
'drop .minicard'(event) {
const dataTransfer = event.originalEvent.dataTransfer;
if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) {
event.preventDefault();
event.stopPropagation();
$(event.currentTarget).removeClass('is-dragging-over');
const card = this.data();
const board = card.board();
// Check permissions
if (!Utils.canModifyCard() || !board || !board.allowsAttachments) {
return;
}
// Check if this is a file drop (not a card reorder)
if (!dataTransfer.files || dataTransfer.files.length === 0) {
return;
}
const files = dataTransfer.files;
if (files && files.length > 0) {
handleFileUpload(card, files);
}
}
},
}
];
},
}).register('minicard');
Template.minicard.helpers({
hiddenMinicardLabelText() {
const currentUser = ReactiveCache.getCurrentUser();
if (currentUser) {
@@ -181,15 +105,23 @@ Template.minicard.helpers({
return false;
}
},
cover() {
if (!this.coverId) return null;
const attachment = ReactiveCache.getAttachment(this.coverId);
if (!attachment) return null;
const coverId = this.coverId;
return {
link() {
return `/cdn/storage/attachments/${coverId}`;
},
};
},
// XXX resolve this nasty hack for https://github.com/veliovgroup/Meteor-Files/issues/763
sess() {
return Meteor.connection && Meteor.connection._lastSessionId
? Meteor.connection._lastSessionId
: null;
},
isWatching() {
return this.findWatcher(Meteor.userId());
},
// Upload progress helpers
hasActiveUploads() {
return uploadProgressManager.hasActiveUploads(this._id);
@@ -209,68 +141,161 @@ Template.minicard.helpers({
// Show list name if either:
// 1. Board-wide setting is enabled, OR
// 2. This specific card has the setting enabled
const currentBoard = this.currentBoard;
const currentBoard = this.board();
if (!currentBoard) return false;
return currentBoard.allowsShowListsOnMinicard || this.showListOnMinicard;
},
shouldShowChecklistAtMinicard() {
// Return checklists that should be shown on minicard
const currentBoard = this.board();
if (!currentBoard) return [];
const checklists = this.checklists();
const visibleChecklists = [];
checklists.forEach(checklist => {
// Show checklist if either:
// 1. Board-wide setting is enabled, OR
// 2. This specific checklist has the setting enabled
if (currentBoard.allowsChecklistAtMinicard || checklist.showChecklistAtMinicard) {
visibleChecklists.push(checklist);
}
});
return visibleChecklists;
}
});
BlazeComponent.extendComponent({
events() {
return [
{
'keydown input.js-edit-card-sort-popup'(evt) {
// enter = save
if (evt.keyCode === 13) {
this.find('button[type=submit]').click();
}
},
'click button.js-submit-edit-card-sort-popup'(event) {
// save button pressed
event.preventDefault();
const sort = this.$('.js-edit-card-sort-popup')[0]
.value
.trim();
if (!Number.isNaN(sort)) {
let card = this.data();
card.move(card.boardId, card.swimlaneId, card.listId, sort);
Popup.back();
}
},
Template.minicard.events({
'click .js-linked-link'() {
if (this.isLinkedCard()) Utils.goCardId(this.linkedId);
else if (this.isLinkedBoard())
Utils.goBoardId(this.linkedId);
},
'click .js-toggle-minicard-label-text'() {
if (window.localStorage.getItem('hiddenMinicardLabelText')) {
window.localStorage.removeItem('hiddenMinicardLabelText'); //true
} else {
window.localStorage.setItem('hiddenMinicardLabelText', 'true'); //true
}
},
'click span.badge-icon.fa.fa-sort, click span.badge-text.check-list-sort' : Popup.open("editCardSortOrder"),
'click .minicard-labels'(event, tpl) {
if (tpl.find('.js-card-label:hover')) {
Popup.open("cardLabels")(event, {dataContextIfCurrentDataIsUndefined: Template.currentData()});
}
},
'click .js-open-minicard-details-menu'(event, tpl) {
event.preventDefault();
event.stopPropagation();
const card = Template.currentData();
Popup.open('cardDetailsActions').call({currentData: () => card}, event);
},
// Drag and drop file upload handlers
'dragover .minicard'(event) {
// Only prevent default for file drags to avoid interfering with sortable
const dataTransfer = event.originalEvent.dataTransfer;
if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) {
event.preventDefault();
event.stopPropagation();
}
},
'dragenter .minicard'(event) {
const dataTransfer = event.originalEvent.dataTransfer;
if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) {
event.preventDefault();
event.stopPropagation();
const card = this;
const board = card.board();
// Only allow drag-and-drop if user can modify card and board allows attachments
if (Utils.canModifyCard() && board && board.allowsAttachments) {
$(event.currentTarget).addClass('is-dragging-over');
}
]
}
}).register('editCardSortOrderPopup');
}
},
'dragleave .minicard'(event) {
const dataTransfer = event.originalEvent.dataTransfer;
if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) {
event.preventDefault();
event.stopPropagation();
$(event.currentTarget).removeClass('is-dragging-over');
}
},
'drop .minicard'(event) {
const dataTransfer = event.originalEvent.dataTransfer;
if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) {
event.preventDefault();
event.stopPropagation();
$(event.currentTarget).removeClass('is-dragging-over');
Template.minicardDetailsActionsPopup.events({
'click .js-due-date': Popup.open('editCardDueDate'),
'click .js-move-card': Popup.open('moveCard'),
'click .js-copy-card': Popup.open('copyCard'),
'click .js-set-card-color': Popup.open('setCardColor'),
'click .js-add-labels': Popup.open('cardLabels'),
'click .js-link': Popup.open('linkCard'),
'click .js-move-card-to-top'(event) {
event.preventDefault();
const minOrder = this.getMinSort();
this.move(this.boardId, this.swimlaneId, this.listId, minOrder - 1);
Popup.back();
const card = this;
const board = card.board();
// Check permissions
if (!Utils.canModifyCard() || !board || !board.allowsAttachments) {
return;
}
// Check if this is a file drop (not a card reorder)
if (!dataTransfer.files || dataTransfer.files.length === 0) {
return;
}
const files = dataTransfer.files;
if (files && files.length > 0) {
handleFileUpload(card, files);
}
}
},
'click .js-move-card-to-bottom'(event) {
event.preventDefault();
const maxOrder = this.getMaxSort();
this.move(this.boardId, this.swimlaneId, this.listId, maxOrder + 1);
Popup.back();
},
'click .js-archive': Popup.afterConfirm('cardArchive', function () {
Popup.close();
this.archive();
Utils.goBoardId(this.boardId);
}),
'click .js-toggle-watch-card'() {
const currentCard = this;
const level = currentCard.findWatcher(Meteor.userId()) ? null : 'watching';
Meteor.call('watch', 'card', currentCard._id, level, (err, ret) => {
if (!err && ret) Popup.back();
});
Template.minicardChecklist.helpers({
visibleItems() {
const checklist = this.checklist || this;
const items = checklist.items();
return items.filter(item => {
// Hide finished items if hideCheckedChecklistItems is true
if (item.isFinished && checklist.hideCheckedChecklistItems) {
return false;
}
// Hide all items if hideAllChecklistItems is true
if (checklist.hideAllChecklistItems) {
return false;
}
return true;
});
},
});
Template.minicardChecklist.events({
'click .js-open-checklist-menu'(event) {
const data = Template.currentData();
const checklist = data.checklist || data;
const card = data.card || this;
const context = { currentData: () => ({ checklist, card }) };
Popup.open('checklistActions').call(context, event);
},
});
Template.editCardSortOrderPopup.events({
'keydown input.js-edit-card-sort-popup'(evt, tpl) {
// enter = save
if (evt.keyCode === 13) {
tpl.find('button[type=submit]').click();
}
},
'click button.js-submit-edit-card-sort-popup'(event, tpl) {
// save button pressed
event.preventDefault();
const sort = tpl.$('.js-edit-card-sort-popup')[0]
.value
.trim();
if (!Number.isNaN(sort)) {
let card = this;
card.move(card.boardId, card.swimlaneId, card.listId, sort);
Popup.back();
}
},
});
+6 -6
View File
@@ -11,9 +11,9 @@ template(name="resultCard")
= getBoard.title
else
.broken-cards-null
| NULL
| {{_ 'no-name'}}
if getBoard.archived
| 📦
i.fa.fa-archive
li.result-card-context.result-card-context-separator
= ' '
| {{_ 'context-separator'}}
@@ -25,9 +25,9 @@ template(name="resultCard")
= getSwimlane.title
else
.broken-cards-null
| NULL
| {{_ 'no-name'}}
if getSwimlane.archived
| 📦
i.fa.fa-archive
li.result-card-context.result-card-context-separator
= ' '
| {{_ 'context-separator'}}
@@ -39,6 +39,6 @@ template(name="resultCard")
= getList.title
else
.broken-cards-null
| NULL
| {{_ 'no-name'}}
if getList.archived
| 📦
i.fa.fa-archive
+54 -22
View File
@@ -1,35 +1,67 @@
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 '#';
},
});
BlazeComponent.extendComponent({
clickOnMiniCard(evt) {
evt.preventDefault();
const this_ = this;
const cardId = this.currentData()._id;
const boardId = this.currentData().boardId;
Template.resultCard.events({
'click .js-minicard'(event) {
event.preventDefault();
const cardId = Template.currentData()._id;
const boardId = Template.currentData().boardId;
Meteor.subscribe('popupCardData', cardId, {
onReady() {
Session.set('popupCardId', cardId);
Session.set('popupCardBoardId', boardId);
this_.cardDetailsPopup(evt);
if (!Popup.isOpen()) {
Popup.open("cardDetails")(event);
}
},
});
},
cardDetailsPopup(event) {
if (!Popup.isOpen()) {
Popup.open("cardDetails")(event);
}
},
events() {
return [
{
'click .js-minicard': this.clickOnMiniCard,
},
];
},
}).register('resultCard');
});
+43 -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;
@@ -87,6 +97,15 @@ textarea.js-edit-subtask-item {
top: 0;
bottom: -600px;
right: 0;
z-index: 15;
}
/* Fix for mobile Safari: ensure this doesn't block card interaction */
@media screen and (max-width: 800px) {
#card-details-overlay {
z-index: 15;
pointer-events: none;
}
}
.subtasks {
background: #f7f7f7;
@@ -127,6 +146,25 @@ textarea.js-edit-subtask-item {
border-bottom: 2px solid #3cb500;
border-right: 2px solid #3cb500;
}
/* Unicode checkbox icons styling */
.subtasks-item .check-box-unicode {
font-size: 1.3em;
margin-right: 8px;
cursor: pointer;
display: inline-block;
vertical-align: middle;
line-height: 1;
}
/* Grey checkmarks when grey icons setting is enabled */
body.grey-icons-enabled .subtasks-item .check-box.is-checked {
border-bottom: 2px solid #7a7a7a;
border-right: 2px solid #7a7a7a;
}
body.grey-icons-enabled .subtasks-item .check-box-unicode {
filter: grayscale(100%);
-webkit-filter: grayscale(100%);
opacity: 0.85;
}
.subtasks-item .item-title {
flex: 1;
padding-left: 10px;
@@ -147,6 +185,7 @@ textarea.js-edit-subtask-item {
display: inline-block;
}
.subtask-details-menu {
float: right;
flex-shrink: 0;
padding: 6px 10px 6px 10px;
order: 2;
}
+17 -11
View File
@@ -1,6 +1,6 @@
template(name="subtasks")
h3.card-details-item-title
| 🌐
i.fa.fa-globe
| {{_ 'subtasks'}}
if currentUser.isBoardAdmin
if toggleDeleteDialog.get
@@ -16,7 +16,7 @@ template(name="subtasks")
+addSubtaskItemForm
else
a.js-open-inlined-form(title="{{_ 'add-subtask'}}")
| ➕
i.fa.fa-plus
template(name="subtaskDetail")
.js-subtasks.subtask
@@ -26,7 +26,7 @@ template(name="subtaskDetail")
.subtask-title
span
if canModifyCard
a.subtask-details-menu.js-open-subtask-details-menu(title="{{_ 'subtaskActionsPopup-title'}}")
a.fa.fa-navicon.subtask-details-menu.js-open-subtask-details-menu(title="{{_ 'subtaskActionsPopup-title'}}")
if canModifyCard
h2.title.js-open-inlined-form.is-editable
+viewer
@@ -40,7 +40,7 @@ template(name="addSubtaskItemForm")
textarea.js-add-subtask-item(rows='1' autofocus dir="auto")
.edit-controls.clearfix
button.primary.confirm.js-submit-add-subtask-item-form(type="submit") {{_ 'save'}}
a.js-close-inlined-form
a.fa.fa-times-thin.js-close-inlined-form(title="{{_ 'close-add-checklist-item'}}")
template(name="editSubtaskItemForm")
textarea.js-edit-subtask-item(rows='1' autofocus dir="auto")
@@ -50,8 +50,8 @@ template(name="editSubtaskItemForm")
= subtask.title
.edit-controls.clearfix
button.primary.confirm.js-submit-edit-subtask-item-form(type="submit") {{_ 'save'}}
a.js-close-inlined-form
span(title=createdAt) {{ moment createdAt }}
a.fa.fa-times-thin.js-close-inlined-form(title="{{_ 'close-edit-checklist-item'}}")
span(title=createdAt) {{ displayDate createdAt }}
if canModifyCard
if currentUser.isBoardAdmin
a.js-delete-subtask-item {{_ "delete"}}...
@@ -68,18 +68,20 @@ template(name="subtasksItems")
+addSubtaskItemForm
else
a.add-subtask-item.js-open-inlined-form
| ➕
i.fa.fa-plus
| {{_ 'add-subtask-item'}}...
template(name='subtaskItemDetail')
.js-subtasks-item.subtasks-item
if canModifyCard
.check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
span.check-box-unicode
i.fa(class="{{#if item.isFinished}}fa-check-square{{else}}fa-square-o{{/if}}")
.item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}")
+viewer
= item.title
else
.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}")
span.check-box-unicode
i.fa(class="{{#if item.isFinished}}fa-check-square{{else}}fa-square-o{{/if}}")
.item-title(class="{{#if item.isFinished }}is-checked{{/if}}")
+viewer
= item.title
@@ -92,10 +94,14 @@ template(name="subtaskActionsPopup")
ul.pop-over-list
li
a.js-view-subtask(title="{{ subtask.title }}")
| 👁️
i.fa.fa-eye
| {{_ "view-it"}}
li
a.js-go-to-subtask-board
i.fa.fa-folder
| {{_ "go-to-board"}}
if currentUser.isBoardAdmin
a.js-delete-subtask.delete-subtask
| 🗑️
i.fa.fa-trash
| {{_ "delete"}} ...
+100 -75
View File
@@ -1,16 +1,35 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import Cards from '/models/cards';
import { Filter } from '/client/lib/filter';
BlazeComponent.extendComponent({
addSubtask(event) {
Template.subtasks.events({
'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 = this.find('textarea.js-add-subtask-item');
const textarea = tpl.find('textarea.js-add-subtask-item');
const title = textarea.value.trim();
const cardId = this.currentData().cardId;
const cardId = Template.currentData().cardId;
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({
@@ -18,20 +37,22 @@ BlazeComponent.extendComponent({
_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
? targetBoard.getDefaultSwimline()._id
: targetSwimlane._id;
const nextCardNumber = targetBoard.getNextCardNumber();
const nextCardNumber = await targetBoard.getNextCardNumber();
if (title) {
const _id = Cards.insert({
const _id = await Cards.insertAsync({
title,
parentId: cardId,
members: [],
@@ -42,7 +63,7 @@ BlazeComponent.extendComponent({
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
@@ -52,7 +73,7 @@ BlazeComponent.extendComponent({
Filter.addException(_id);
setTimeout(() => {
this.$('.add-subtask-item')
tpl.$('.add-subtask-item')
.last()
.click();
}, 100);
@@ -60,27 +81,20 @@ BlazeComponent.extendComponent({
textarea.value = '';
textarea.focus();
},
deleteSubtask() {
const subtask = this.currentData().subtask;
if (subtask && subtask._id) {
subtask.archive();
}
},
isBoardAdmin() {
return ReactiveCache.getCurrentUser().isBoardAdmin();
},
editSubtask(event) {
'submit .js-edit-subtask-title'(event, tpl) {
event.preventDefault();
const textarea = this.find('textarea.js-edit-subtask-item');
const textarea = tpl.find('textarea.js-edit-subtask-item');
const title = textarea.value.trim();
const subtask = this.currentData().subtask;
const subtask = Template.currentData().subtask;
subtask.setTitle(title);
},
pressKey(event) {
async 'click .js-delete-subtask-item'() {
const subtask = Template.currentData().subtask;
if (subtask && subtask._id) {
await subtask.archive();
}
},
keydown(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) {
@@ -89,53 +103,66 @@ BlazeComponent.extendComponent({
$form.find('button[type=submit]').click();
}
},
});
events() {
return [
{
'click .js-open-subtask-details-menu': Popup.open('subtaskActions'),
'submit .js-add-subtask': this.addSubtask,
'submit .js-edit-subtask-title': this.editSubtask,
'click .js-delete-subtask-item': this.deleteSubtask,
keydown: this.pressKey,
},
];
},
}).register('subtasks');
Template.subtasks.onCreated(function () {
this.toggleDeleteDialog = new ReactiveVar(false);
});
BlazeComponent.extendComponent({
// ...
}).register('subtaskItemDetail');
BlazeComponent.extendComponent({
Template.subtasks.helpers({
isBoardAdmin() {
return ReactiveCache.getCurrentUser().isBoardAdmin();
},
events() {
return [
{
'click .js-view-subtask'(event) {
if ($(event.target).hasClass('js-view-subtask')) {
const subtask = this.currentData().subtask;
const board = subtask.board();
FlowRouter.go('card', {
boardId: board._id,
slug: board.slug,
cardId: subtask._id,
});
}
},
'click .js-delete-subtask' : Popup.afterConfirm('subtaskDelete', function () {
Popup.back(2);
const subtask = this.subtask;
if (subtask && subtask._id) {
subtask.archive();
}
}),
}
]
}
}).register('subtaskActionsPopup');
toggleDeleteDialog() {
return Template.instance().toggleDeleteDialog;
},
});
Template.subtaskItemDetail.events({
async 'click .js-subtasks-item .check-box-unicode'() {
const item = Template.currentData().item;
if (item && item._id) {
await item.toggleItem();
}
},
});
Template.subtaskActionsPopup.helpers({
isBoardAdmin() {
return ReactiveCache.getCurrentUser().isBoardAdmin();
},
});
Template.subtaskActionsPopup.events({
'click .js-view-subtask'(event) {
if ($(event.target).hasClass('js-view-subtask')) {
const subtask = Template.currentData().subtask;
const board = subtask.board();
FlowRouter.go('card', {
boardId: board._id,
slug: board.slug,
cardId: subtask._id,
swimlaneId: subtask.swimlaneId,
listId: subtask.listId,
});
}
},
'click .js-go-to-subtask-board'() {
const subtask = Template.currentData().subtask;
const board = subtask.board();
if (board) {
Popup.close();
FlowRouter.go('board', { id: board._id, slug: board.slug });
}
},
'click .js-delete-subtask' : Popup.afterConfirm('subtaskDelete', async function () {
Popup.back(2);
const subtask = this.subtask;
if (subtask && subtask._id) {
await subtask.archive();
}
}),
});
Template.editSubtaskItemForm.helpers({
user() {
@@ -145,5 +172,3 @@ Template.editSubtaskItemForm.helpers({
return ReactiveCache.getCurrentUser().isBoardAdmin();
},
});
@@ -81,11 +81,11 @@
font-size: 11px;
padding: 6px;
}
.original-position-details {
padding: 4px 6px;
}
.original-position-moved,
.original-position-unchanged {
padding: 3px 5px;
@@ -99,24 +99,24 @@
border-color: #4a5568;
color: #e2e8f0;
}
.original-position-moved {
background-color: #744210;
border-color: #b7791f;
color: #fbd38d;
}
.original-position-unchanged {
background-color: #22543d;
border-color: #38a169;
color: #9ae6b4;
}
.original-title {
color: #a0aec0;
border-color: #4a5568;
}
.original-title strong {
color: #e2e8f0;
}
@@ -1,29 +0,0 @@
<template name="originalPosition">
<div class="original-position-info">
{{#if isLoading}}
<div class="original-position-loading">
<i class="fa fa-spinner fa-spin"></i> Loading original position...
</div>
{{else if showOriginalPosition}}
<div class="original-position-details">
{{#if hasMovedFromOriginal}}
<div class="original-position-moved">
<i class="fa fa-info-circle"></i>
<span class="original-position-text">{{getOriginalPositionDescription}}</span>
</div>
{{else}}
<div class="original-position-unchanged">
<i class="fa fa-check-circle"></i>
<span class="original-position-text">In original position</span>
</div>
{{/if}}
{{#if getOriginalTitle}}
<div class="original-title">
<strong>Original title:</strong> {{getOriginalTitle}}
</div>
{{/if}}
</div>
{{/if}}
</div>
</template>
@@ -0,0 +1,19 @@
template(name="originalPosition")
.original-position-info
if isLoading
.original-position-loading
| ⏳ Loading original position...
else
if showOriginalPosition
.original-position-details
if hasMovedFromOriginal
.original-position-moved
span.original-position-text ℹ️ {{getOriginalPositionDescription}}
else
.original-position-unchanged
span.original-position-text ✅ In original position
if getOriginalTitle
.original-title
strong Original title:
| {{getOriginalTitle}}
+44 -48
View File
@@ -1,71 +1,71 @@
import { BlazeComponent } from 'meteor/peerlibrary:blaze-components';
import { ReactiveVar } from 'meteor/reactive-var';
import { Meteor } from 'meteor/meteor';
import { Template } from 'meteor/templating';
import './originalPosition.html';
/**
* Component to display original position information for swimlanes, lists, and cards
*/
class OriginalPositionComponent extends BlazeComponent {
onCreated() {
super.onCreated();
this.originalPosition = new ReactiveVar(null);
this.isLoading = new ReactiveVar(false);
this.hasMoved = new ReactiveVar(false);
this.autorun(() => {
const data = this.data();
if (data && data.entityId && data.entityType) {
this.loadOriginalPosition(data.entityId, data.entityType);
}
});
}
loadOriginalPosition(entityId, entityType) {
this.isLoading.set(true);
Template.originalPosition.onCreated(function () {
this.originalPosition = new ReactiveVar(null);
this.isLoading = new ReactiveVar(false);
this.hasMoved = new ReactiveVar(false);
const tpl = this;
function loadOriginalPosition(entityId, entityType) {
tpl.isLoading.set(true);
const methodName = `positionHistory.get${entityType.charAt(0).toUpperCase() + entityType.slice(1)}OriginalPosition`;
Meteor.call(methodName, entityId, (error, result) => {
this.isLoading.set(false);
tpl.isLoading.set(false);
if (error) {
console.error('Error loading original position:', error);
this.originalPosition.set(null);
tpl.originalPosition.set(null);
} else {
this.originalPosition.set(result);
tpl.originalPosition.set(result);
// Check if the entity has moved
const movedMethodName = `positionHistory.has${entityType.charAt(0).toUpperCase() + entityType.slice(1)}Moved`;
Meteor.call(movedMethodName, entityId, (movedError, movedResult) => {
if (!movedError) {
this.hasMoved.set(movedResult);
tpl.hasMoved.set(movedResult);
}
});
}
});
}
this.autorun(() => {
const data = Template.currentData();
if (data && data.entityId && data.entityType) {
loadOriginalPosition(data.entityId, data.entityType);
}
});
});
Template.originalPosition.helpers({
getOriginalPosition() {
return this.originalPosition.get();
}
return Template.instance().originalPosition.get();
},
isLoading() {
return this.isLoading.get();
}
return Template.instance().isLoading.get();
},
hasMovedFromOriginal() {
return this.hasMoved.get();
}
return Template.instance().hasMoved.get();
},
getOriginalPositionDescription() {
const position = this.getOriginalPosition();
const position = Template.instance().originalPosition.get();
if (!position) return 'No original position data';
if (position.originalPosition) {
const entityType = this.data().entityType;
const data = Template.currentData();
const entityType = data.entityType;
let description = `Original position: ${position.originalPosition.sort || 0}`;
if (entityType === 'list' && position.originalSwimlaneId) {
description += ` in swimlane ${position.originalSwimlaneId}`;
} else if (entityType === 'card') {
@@ -76,23 +76,19 @@ class OriginalPositionComponent extends BlazeComponent {
description += ` in list ${position.originalListId}`;
}
}
return description;
}
return 'No original position data';
}
},
getOriginalTitle() {
const position = this.getOriginalPosition();
const position = Template.instance().originalPosition.get();
return position ? position.originalTitle : '';
}
},
showOriginalPosition() {
return this.getOriginalPosition() !== null;
}
}
OriginalPositionComponent.register('originalPosition');
export default OriginalPositionComponent;
return Template.instance().originalPosition.get() !== null;
},
});
+16
View File
@@ -0,0 +1,16 @@
import {
setupDatePicker,
datePickerRendered,
datePickerHelpers,
datePickerEvents,
} from '/client/lib/datepicker';
Template.datepicker.onCreated(function () {
setupDatePicker(this);
});
Template.datepicker.onRendered(function () {
datePickerRendered(this);
});
Template.datepicker.helpers(datePickerHelpers());
+29 -13
View File
@@ -130,8 +130,8 @@ textarea.editor {
}
input[type="submit"],
button {
background: #cfcfcf;
background: linear-gradient(#cfcfcf, #c2c2c2);
background: #000;
background: linear-gradient(#000, #000);
border: none;
cursor: pointer;
display: inline-block;
@@ -139,6 +139,7 @@ button {
line-height: 1.3;
padding: 1vh 2.5vw;
text-align: center;
color: #fff;
}
input[type="submit"] .wide,
button .wide {
@@ -149,14 +150,16 @@ input[type="submit"]:hover,
button:hover,
input[type="submit"]:focus,
button:focus {
background: #c2c2c2;
background: linear-gradient(#c2c2c2, #b5b5b5);
background: #222;
background: linear-gradient(#222, #222);
color: #fff;
}
input[type="submit"]:active,
button:active {
background: #b5b5b5;
background: linear-gradient(#b5b5b5, #a8a8a8);
box-shadow: inset 0 3px 6px rgba(0,0,0,0.1);
background: #111;
background: linear-gradient(#111, #111);
box-shadow: inset 0 3px 6px rgba(0,0,0,0.3);
color: #fff;
}
input[type="submit"]:active:hover,
button:active:hover,
@@ -183,6 +186,12 @@ input[type="submit"].primary:active,
button.primary:active {
background: #01628c;
}
input[type="submit"].negate,
button.negate {
background: #eb5a46;
box-shadow: 0 1px 0 #4d4d4d;
color: #fff;
}
input[type="submit"].negate:hover,
button.negate:hover,
input[type="submit"].negate:focus,
@@ -217,10 +226,10 @@ input[type="submit"]:disabled:active,
input[type="button"].disabled:active,
button.disabled:active,
.button.disabled:active {
background: #cfcfcf;
background: #555;
cursor: default;
box-shadow: none;
color: #a8a8a8;
color: #999;
}
fieldset {
border: 1px solid #bfbfbf;
@@ -315,11 +324,18 @@ textarea::-moz-placeholder {
margin-right: 6px;
border-top: 2px solid transparent;
border-left: 2px solid transparent;
border-bottom: 2px solid #3cb500;
border-right: 2px solid #3cb500;
transform: rotate(40deg);
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
transform-origin: 100% 100%;
}
/* Grey checkmarks when grey icons setting is enabled */
body.grey-icons-enabled .materialCheckBox.is-checked {
border-bottom: 2px solid #7a7a7a;
border-right: 2px solid #7a7a7a;
}
.button-link {
background: #fff;
background: linear-gradient(#fff, #f5f5f5);
@@ -393,12 +409,12 @@ textarea::-moz-placeholder {
.button-link.setting.disabled.primary,
.button-link.setting.disabled.primary:hover,
.button-link.setting.disabled.primary:active {
background: #cfcfcf;
border-color: #c2c2c2;
border-bottom-color: #b5b5b5;
background: #555;
border-color: #444;
border-bottom-color: #333;
cursor: default;
box-shadow: none;
color: #a8a8a8;
color: #999;
}
.button-link.setting .label {
color: #222;
+6 -5
View File
@@ -1,6 +1,7 @@
template(name='inlinedForm')
if isOpen.get
form.inlined-form.js-inlined-form(id=id class=classNames)
+Template.contentBlock
else
+Template.elseBlock
.js-inlined-form-wrapper(style='display: contents;' data-position=position)
if isOpen.get
form.inlined-form.js-inlined-form(id=id class=classNames)
+Template.contentBlock
else
+Template.elseBlock
+203
View File
@@ -0,0 +1,203 @@
/* Gantt chart cell background colors for Received, Start, Due, End (matching cardDetails) */
.ganttview-received {
background-color: #dbdbdb !important;
color: #000 !important;
font-size: 18px !important;
font-weight: bold !important;
}
.ganttview-start {
background-color: #90ee90 !important;
color: #000 !important;
font-size: 18px !important;
font-weight: bold !important;
}
.ganttview-due {
background-color: #ffd700 !important;
color: #000 !important;
font-size: 18px !important;
font-weight: bold !important;
}
.ganttview-end {
background-color: #ffb3b3 !important;
color: #000 !important;
font-size: 18px !important;
font-weight: bold !important;
}
/* Gantt View Styles */
.gantt-view {
width: 100%;
height: auto;
overflow: visible;
background-color: #fff;
}
.gantt-view.swimlane {
background-color: #fff;
padding: 10px;
}
.gantt-container {
overflow-x: auto;
overflow-y: visible;
background-color: #fff;
display: block;
width: 100%;
}
.gantt-container table,
.gantt-table {
border-collapse: collapse;
width: 100%;
min-width: 800px;
border: 2px solid #666;
font-family: sans-serif;
font-size: 13px;
background-color: #fff;
}
.gantt-container thead {
background-color: #e8e8e8;
border-bottom: 2px solid #666;
font-weight: bold;
position: sticky;
top: 0;
z-index: 10;
}
.gantt-container thead th,
.gantt-container thead tr > td:first-child {
border-right: 2px solid #666;
padding: 4px; /* half of 8px */
width: 100px; /* half of 200px */
text-align: left;
font-weight: bold;
background-color: #e8e8e8;
min-width: 100px; /* half of 200px */
}
.gantt-container thead td {
border-right: 1px solid #999;
padding: 2px 1px; /* half */
text-align: center;
background-color: #f5f5f5;
font-size: 11px;
min-width: 15px; /* half of 30px */
font-weight: bold;
height: auto;
line-height: 1.2;
white-space: normal;
word-break: break-word;
}
.gantt-container tbody tr {
border-bottom: 1px solid #999;
height: 32px;
}
.gantt-container tbody tr:hover {
background-color: #f9f9f9;
}
.gantt-container tbody tr:hover td {
background-color: #f9f9f9 !important;
}
.gantt-container tbody td {
border-right: 1px solid #ccc;
padding: 1px; /* half */
text-align: center;
min-width: 15px; /* half of 30px */
height: 32px;
vertical-align: middle;
line-height: 28px;
background-color: #ffffff;
font-size: 18px;
font-weight: bold;
}
.gantt-container tbody td:nth-child(even) {
background-color: #fafafa;
}
.gantt-container tbody td:first-child {
border-right: 2px solid #666;
padding: 4px; /* half of 8px */
font-weight: 500;
cursor: pointer;
background-color: #fafafa !important;
text-align: left;
width: 100px; /* half of 200px */
min-width: 100px; /* half of 200px */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
height: auto;
line-height: normal;
}
.gantt-container tbody td:first-child:hover {
background-color: #f0f0f0 !important;
text-decoration: underline;
}
.js-gantt-task-cell {
cursor: pointer;
}
.js-gantt-date-icon {
cursor: pointer;
}
.gantt-container .ganttview-weekend {
background-color: #efefef;
}
.gantt-container .ganttview-today {
background-color: #fcf8e3;
border-right: 2px solid #ffb347;
}
/* Task bar styling - VERY VISIBLE */
.gantt-container tbody td.ganttview-block {
background-color: #4CAF50 !important;
color: #fff !important;
font-size: 18px !important;
font-weight: bold !important;
padding: 2px !important;
border-radius: 2px;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.gantt-container table {
font-size: 11px;
}
.gantt-container thead td {
min-width: 20px;
padding: 2px;
}
.gantt-container tbody td {
min-width: 20px;
padding: 1px;
height: 20px;
}
.gantt-container tbody td:first-child {
width: 100px;
font-size: 12px;
}
}
/* Print styles */
@media print {
.gantt-container {
overflow: visible;
}
.gantt-container table {
page-break-inside: avoid;
}
}
+27
View File
@@ -0,0 +1,27 @@
//- Gantt Chart View Template
template(name="ganttView")
link(rel="stylesheet" href="/client/components/gantt/gantt.css")
link(rel="stylesheet" href="/client/components/gantt/ganttCard.css")
.gantt-view
h2 {{_ 'board-view-gantt'}}
if hasSelectedCard
+ganttCard(selectedCard)
each weeks
table.gantt-table
thead
tr
th {{_ 'task'}} {{_ 'predicate-week'}} {{week}}
each weekDays this
th
| {{formattedDate .}} {{weekdayLabel .}}
tbody
each cardsInWeek this
tr(data-card-id="{{cardId .}}")
td.js-gantt-task-cell
a.js-gantt-card-title(href="#")
+viewer
| {{cardTitle .}}
each weekDays ..
td(class="{{cellClasses .. .}}" data-card-id="{{cardId ..}}" data-date-type="{{cellContentClass .. .}}")
| {{cellContent .. .}}
+221
View File
@@ -0,0 +1,221 @@
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
import Cards from '/models/cards';
// Add click handler to ganttView for card titles
Template.ganttView.events({
'click .js-gantt-card-title'(event, template) {
event.preventDefault();
// Get card ID from the closest row's data attribute
const $row = template.$(event.currentTarget).closest('tr');
const cardId = $row.data('card-id');
if (cardId) {
template.selectedCardId.set(cardId);
}
},
});
import { Template } from 'meteor/templating';
import { Utils } from '/client/lib/utils';
// Blaze template helpers for ganttView
function getISOWeekInfo(d) {
const date = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
const dayNum = date.getUTCDay() || 7;
date.setUTCDate(date.getUTCDate() + 4 - dayNum);
const yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
const week = Math.ceil((((date - yearStart) / 86400000) + 1) / 7);
return { year: date.getUTCFullYear(), week };
}
function startOfISOWeek(d) {
const date = new Date(d);
const day = date.getDay() || 7;
if (day !== 1) date.setDate(date.getDate() - (day - 1));
date.setHours(0,0,0,0);
return date;
}
Template.ganttView.helpers({
weeks() {
const board = Utils.getCurrentBoard();
if (!board) return [];
const cards = Cards.find({ boardId: board._id }, { sort: { startAt: 1, dueAt: 1 } }).fetch();
const weeksMap = new Map();
const relevantCards = cards.filter(c => c.receivedAt || c.startAt || c.dueAt || c.endAt);
relevantCards.forEach(card => {
['receivedAt','startAt','dueAt','endAt'].forEach(field => {
if (card[field]) {
const dt = new Date(card[field]);
const info = getISOWeekInfo(dt);
const key = `${info.year}-W${info.week}`;
if (!weeksMap.has(key)) {
weeksMap.set(key, { year: info.year, week: info.week, start: startOfISOWeek(dt) });
}
}
});
});
return Array.from(weeksMap.values()).sort((a,b) => a.start - b.start);
},
weekDays(week) {
const weekStart = new Date(week.start);
return Array.from({length:7}, (_,i) => {
const d = new Date(weekStart);
d.setDate(d.getDate() + i);
d.setHours(0,0,0,0);
return d;
});
},
weekdayLabel(day) {
const weekdayKeys = ['monday','tuesday','wednesday','thursday','friday','saturday','sunday'];
return TAPi18n.__(weekdayKeys[day.getDay() === 0 ? 6 : day.getDay() - 1]);
},
formattedDate(day) {
const currentUser = ReactiveCache.getCurrentUser && ReactiveCache.getCurrentUser();
const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
return formatDateByUserPreference(day, dateFormat, false);
},
cardsInWeek(week) {
const board = Utils.getCurrentBoard();
if (!board) return [];
const cards = Cards.find({ boardId: board._id }).fetch();
return cards.filter(card => {
return ['receivedAt','startAt','dueAt','endAt'].some(field => {
if (card[field]) {
const dt = new Date(card[field]);
const info = getISOWeekInfo(dt);
return info.week === week.week && info.year === week.year;
}
return false;
});
});
},
cardTitle(card) {
return card.title;
},
cardId(card) {
return card._id;
},
cardUrl(card) {
if (!card) return '#';
const board = ReactiveCache.getBoard(card.boardId);
if (!board) return '#';
return FlowRouter.path('card', {
boardId: card.boardId,
slug: board.slug,
cardId: card._id,
});
},
cellContentClass(card, day) {
const cardDates = {
receivedAt: card.receivedAt ? new Date(card.receivedAt) : null,
startAt: card.startAt ? new Date(card.startAt) : null,
dueAt: card.dueAt ? new Date(card.dueAt) : null,
endAt: card.endAt ? new Date(card.endAt) : null,
};
if (cardDates.receivedAt && cardDates.receivedAt.toDateString() === day.toDateString()) return 'ganttview-received';
if (cardDates.startAt && cardDates.startAt.toDateString() === day.toDateString()) return 'ganttview-start';
if (cardDates.dueAt && cardDates.dueAt.toDateString() === day.toDateString()) return 'ganttview-due';
if (cardDates.endAt && cardDates.endAt.toDateString() === day.toDateString()) return 'ganttview-end';
return '';
},
cellContent(card, day) {
const cardDates = {
receivedAt: card.receivedAt ? new Date(card.receivedAt) : null,
startAt: card.startAt ? new Date(card.startAt) : null,
dueAt: card.dueAt ? new Date(card.dueAt) : null,
endAt: card.endAt ? new Date(card.endAt) : null,
};
if (cardDates.receivedAt && cardDates.receivedAt.toDateString() === day.toDateString()) return '📥';
if (cardDates.startAt && cardDates.startAt.toDateString() === day.toDateString()) return '🚀';
if (cardDates.dueAt && cardDates.dueAt.toDateString() === day.toDateString()) return '⏰';
if (cardDates.endAt && cardDates.endAt.toDateString() === day.toDateString()) return '🏁';
return '';
},
isToday(day) {
const today = new Date();
return day.toDateString() === today.toDateString();
},
isWeekend(day) {
const idx = day.getDay();
return idx === 0 || idx === 6;
},
hasSelectedCard() {
return Template.instance().selectedCardId.get() !== null;
},
selectedCard() {
const cardId = Template.instance().selectedCardId.get();
return cardId ? ReactiveCache.getCard(cardId) : null;
},
cellClasses(card, day) {
// Get the base class from cellContentClass logic
const cardDates = {
receivedAt: card.receivedAt ? new Date(card.receivedAt) : null,
startAt: card.startAt ? new Date(card.startAt) : null,
dueAt: card.dueAt ? new Date(card.dueAt) : null,
endAt: card.endAt ? new Date(card.endAt) : null,
};
let classes = '';
if (cardDates.receivedAt && cardDates.receivedAt.toDateString() === day.toDateString()) classes = 'ganttview-received';
else if (cardDates.startAt && cardDates.startAt.toDateString() === day.toDateString()) classes = 'ganttview-start';
else if (cardDates.dueAt && cardDates.dueAt.toDateString() === day.toDateString()) classes = 'ganttview-due';
else if (cardDates.endAt && cardDates.endAt.toDateString() === day.toDateString()) classes = 'ganttview-end';
// Add conditional classes
const today = new Date();
if (day.toDateString() === today.toDateString()) classes += ' ganttview-today';
const idx = day.getDay();
if (idx === 0 || idx === 6) classes += ' ganttview-weekend';
if (classes.trim()) classes += ' js-gantt-date-icon';
return classes.trim();
}
});
Template.ganttView.onCreated(function() {
this.selectedCardId = new ReactiveVar(null);
// Provide properties expected by cardDetails component
this.showOverlay = new ReactiveVar(false);
this.mouseHasEnterCardDetails = false;
});
// Blaze onRendered logic for ganttView
Template.ganttView.onRendered(function() {
const self = this;
this.autorun(() => {
// If you have legacy imperative rendering, keep it here
if (typeof renderGanttChart === 'function') {
renderGanttChart();
}
});
// Add click handler for date cells (Received, Start, Due, End)
this.$('.gantt-table').on('click', '.js-gantt-date-icon', function(e) {
e.preventDefault();
e.stopPropagation();
const $cell = self.$(this);
const cardId = $cell.data('card-id');
let dateType = $cell.data('date-type');
// Remove 'ganttview-' prefix to match popup map
if (typeof dateType === 'string' && dateType.startsWith('ganttview-')) {
dateType = dateType.replace('ganttview-', '');
}
const popupMap = {
received: 'editCardReceivedDate',
start: 'editCardStartDate',
due: 'editCardDueDate',
end: 'editCardEndDate',
};
const popupName = popupMap[dateType];
if (!popupName || typeof Popup === 'undefined' || typeof Popup.open !== 'function') return;
const card = ReactiveCache.getCard(cardId);
if (!card) return;
const openFn = Popup.open(popupName);
openFn.call({ currentData: () => card }, e, { dataContextIfCurrentDataIsUndefined: card });
});
});
import markdownit from 'markdown-it';
import { TAPi18n } from '/imports/i18n';
import { formatDateByUserPreference } from '/imports/lib/dateUtils';
import { ReactiveCache } from '/imports/reactiveCache';
const md = markdownit({ breaks: true, linkify: true });
+47
View File
@@ -0,0 +1,47 @@
.gantt-card-wrapper {
background: white;
border: 1px solid #ddd;
border-radius: 5px;
margin: 1rem 0;
padding: 1rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.gantt-card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
border-bottom: 1px solid #eee;
padding-bottom: 0.5rem;
}
.gantt-card-header h3 {
margin: 0;
color: #333;
}
.close-button {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: #666;
padding: 0;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.close-button:hover {
background-color: #f0f0f0;
color: #333;
}
.gantt-card-content {
max-height: 400px;
overflow-y: auto;
}
+2
View File
@@ -0,0 +1,2 @@
template(name="ganttCard")
+cardDetails(selectedCard)
+41
View File
@@ -0,0 +1,41 @@
Template.ganttCard.onCreated(function () {
// Provide the expected parent component properties for cardDetails
this.showOverlay = new ReactiveVar(false);
this.mouseHasEnterCardDetails = false;
});
Template.ganttCard.helpers({
selectedCard() {
// The selected card is now passed as a parameter to the component
return Template.currentData();
},
});
Template.ganttCard.events({
'click .js-close-card-details'(event) {
event.preventDefault();
// Find the ganttView template instance and clear selectedCardId
let view = Blaze.currentView;
while (view) {
if (view.templateInstance && view.templateInstance().selectedCardId) {
view.templateInstance().selectedCardId.set(null);
break;
}
view = view.parentView;
}
},
});
// Add click handler to ganttView for card titles
Template.ganttView.events({
'click .js-gantt-card-title'(event, template) {
event.preventDefault();
// Get card ID from the closest row's data attribute
const $row = template.$(event.currentTarget).closest('tr');
const cardId = $row.data('card-id');
if (cardId) {
template.selectedCardId.set(cardId);
}
},
});
+7 -7
View File
@@ -1,7 +1,7 @@
template(name="importHeaderBar")
h1
a.back-btn(href="{{pathFor 'home'}}")
i.fa.fa-chevron-left
i.fa-arrow-left
| {{_ title}}
template(name="import")
@@ -56,17 +56,17 @@ template(name="importMapMembersAddPopup")
p
| {{_ 'import-user-select'}}
.js-map-member
+EasySearch.Input(index=searchIndex)
input.js-search-member-input(type="text" placeholder="{{_ 'search-users'}}")
ul.pop-over-list
+EasySearch.Each(index=searchIndex)
each searchResults
li.item.js-member-item
a.name.js-select-import(title="{{profile.fullname}} ({{username}})" data-id="{{__originalId}}")
+userAvatar(userId=__originalId)
a.name.js-select-import(title="{{profile.fullname}} ({{username}})" data-id="{{_id}}")
+userAvatar(userId=_id)
span.full-name
= profile.fullname
| (<span class="username">{{username}}</span>)
+EasySearch.IfSearching(index=searchIndex)
if searching.get
+spinner
+EasySearch.IfNoResults(index=searchIndex)
if noResults.get
.manage-member-section
p.quiet {{_ 'no-results'}}
+234 -168
View File
@@ -1,40 +1,71 @@
import { ReactiveCache } from '/imports/reactiveCache';
import { trelloGetMembersToMap } from './trelloMembersMapper';
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');
BlazeComponent.extendComponent({
Template.importHeaderBar.helpers({
title() {
return `import-board-title-${Session.get('importSource')}`;
},
}).register('importHeaderBar');
});
BlazeComponent.extendComponent({
onCreated() {
this.error = new ReactiveVar('');
this.steps = ['importTextarea', 'importMapMembers'];
this._currentStepIndex = new ReactiveVar(0);
this.importedData = new ReactiveVar();
this.membersToMap = new ReactiveVar([]);
this.importSource = Session.get('importSource');
},
// Helper to find the closest ancestor template instance by name
function findParentTemplateInstance(childTemplateInstance, parentTemplateName) {
let view = childTemplateInstance.view;
while (view) {
if (view.name === `Template.${parentTemplateName}` && view.templateInstance) {
return view.templateInstance();
}
view = view.parentView;
}
return null;
}
currentTemplate() {
return this.steps[this._currentStepIndex.get()];
},
function _prepareAdditionalData(dataObject) {
const importSource = Session.get('importSource');
let membersToMap;
switch (importSource) {
case 'trello':
membersToMap = trelloGetMembersToMap(dataObject);
break;
case 'wekan':
membersToMap = wekanGetMembersToMap(dataObject);
break;
case 'csv':
membersToMap = csvGetMembersToMap(dataObject);
break;
}
return membersToMap;
}
nextStep() {
Template.import.onCreated(function () {
this.error = new ReactiveVar('');
this.steps = ['importTextarea', 'importMapMembers'];
this._currentStepIndex = new ReactiveVar(0);
this.importedData = new ReactiveVar();
this.membersToMap = new ReactiveVar([]);
this.importSource = Session.get('importSource');
this.nextStep = () => {
const nextStepIndex = this._currentStepIndex.get() + 1;
if (nextStepIndex >= this.steps.length) {
this.finishImport();
} else {
this._currentStepIndex.set(nextStepIndex);
}
},
};
importData(evt, dataSource) {
this.setError = (error) => {
this.error.set(error);
};
this.importData = (evt, dataSource) => {
evt.preventDefault();
const input = this.find('.js-import-json').value;
if (dataSource === 'csv') {
@@ -42,7 +73,7 @@ BlazeComponent.extendComponent({
const ret = Papa.parse(csv);
if (ret && ret.data && ret.data.length) this.importedData.set(ret.data);
else throw new Meteor.Error('error-csv-schema');
const membersToMap = this._prepareAdditionalData(ret.data);
const membersToMap = _prepareAdditionalData(ret.data);
this.membersToMap.set(membersToMap);
this.nextStep();
} else {
@@ -50,7 +81,7 @@ BlazeComponent.extendComponent({
const dataObject = JSON.parse(input);
this.setError('');
this.importedData.set(dataObject);
const membersToMap = this._prepareAdditionalData(dataObject);
const membersToMap = _prepareAdditionalData(dataObject);
// store members data and mapping in Session
// (we go deep and 2-way, so storing in data context is not a viable option)
this.membersToMap.set(membersToMap);
@@ -59,13 +90,9 @@ BlazeComponent.extendComponent({
this.setError('error-json-malformed');
}
}
},
};
setError(error) {
this.error.set(error);
},
finishImport() {
this.finishImport = () => {
const additionalData = {};
const membersMapping = this.membersToMap.get();
if (membersMapping) {
@@ -93,44 +120,27 @@ BlazeComponent.extendComponent({
FlowRouter.go('board', {
id: res,
slug: title,
})
});
//Utils.goBoardId(res);
}
},
);
},
};
});
_prepareAdditionalData(dataObject) {
const importSource = Session.get('importSource');
let membersToMap;
switch (importSource) {
case 'trello':
membersToMap = trelloGetMembersToMap(dataObject);
break;
case 'wekan':
membersToMap = wekanGetMembersToMap(dataObject);
break;
case 'csv':
membersToMap = csvGetMembersToMap(dataObject);
break;
}
return membersToMap;
Template.import.helpers({
error() {
return Template.instance().error;
},
_screenAdditionalData() {
return 'mapMembers';
},
}).register('import');
BlazeComponent.extendComponent({
template() {
return 'importTextarea';
currentTemplate() {
return Template.instance().steps[Template.instance()._currentStepIndex.get()];
},
});
Template.importTextarea.helpers({
instruction() {
return `import-board-instruction-${Session.get('importSource')}`;
},
importPlaceHolder() {
const importSource = Session.get('importSource');
if (importSource === 'csv') {
@@ -139,81 +149,37 @@ BlazeComponent.extendComponent({
return 'import-json-placeholder';
}
},
});
events() {
return [
{
submit(evt) {
return this.parentComponent().importData(
evt,
Session.get('importSource'),
);
},
},
];
Template.importTextarea.events({
submit(evt, tpl) {
const importTpl = findParentTemplateInstance(tpl, 'import');
if (importTpl) {
return importTpl.importData(evt, Session.get('importSource'));
}
},
}).register('importTextarea');
});
BlazeComponent.extendComponent({
onCreated() {
this.usersLoaded = new ReactiveVar(false);
// Module-level reference so popup children can access importMapMembers methods
let _importMapMembersTpl = null;
this.autorun(() => {
const handle = this.subscribe(
'user-miniprofile',
this.members().map(member => {
return member.username;
}),
);
Tracker.nonreactive(() => {
Tracker.autorun(() => {
if (
handle.ready() &&
!this.usersLoaded.get() &&
this.members().length
) {
this._refreshMembers(
this.members().map(member => {
if (!member.wekanId) {
let user = ReactiveCache.getUser({ username: member.username });
if (!user) {
user = ReactiveCache.getUser({ importUsernames: member.username });
}
if (user) {
// eslint-disable-next-line no-console
// console.log('found username:', user.username);
member.wekanId = user._id;
}
}
return member;
}),
);
}
this.usersLoaded.set(handle.ready());
});
});
});
},
Template.importMapMembers.onCreated(function () {
_importMapMembersTpl = this;
this.usersLoaded = new ReactiveVar(false);
members() {
return this.parentComponent().membersToMap.get();
},
this.members = () => {
const importTpl = findParentTemplateInstance(this, 'import');
return importTpl ? importTpl.membersToMap.get() : [];
};
_refreshMembers(listOfMembers) {
return this.parentComponent().membersToMap.set(listOfMembers);
},
this._refreshMembers = (listOfMembers) => {
const importTpl = findParentTemplateInstance(this, 'import');
if (importTpl) {
importTpl.membersToMap.set(listOfMembers);
}
};
/**
* Will look into the list of members to import for the specified memberId,
* then set its property to the supplied value.
* If unset is true, it will remove the property from the rest of the list as well.
*
* use:
* - memberId = null to use selected member
* - value = null to unset a property
* - unset = true to ensure property is only set on 1 member at a time
*/
_setPropertyForMember(property, value, memberId, unset = false) {
this._setPropertyForMember = (property, value, memberId, unset = false) => {
const listOfMembers = this.members();
let finder = null;
if (memberId) {
@@ -239,17 +205,13 @@ BlazeComponent.extendComponent({
});
// Session.get gives us a copy, we have to set it back so it sticks
this._refreshMembers(listOfMembers);
},
};
setSelectedMember(memberId) {
this.setSelectedMember = (memberId) => {
return this._setPropertyForMember('selected', true, memberId, true);
},
};
/**
* returns the member with specified id,
* or the selected member if memberId is not specified
*/
getMember(memberId = null) {
this.getMember = (memberId = null) => {
const allMembers = this.members();
let finder = null;
if (memberId) {
@@ -258,59 +220,163 @@ BlazeComponent.extendComponent({
finder = user => user.selected;
}
return allMembers.find(finder);
},
};
mapSelectedMember(wekanId) {
this.mapSelectedMember = (wekanId) => {
return this._setPropertyForMember('wekanId', wekanId, null);
},
};
unmapMember(memberId) {
this.unmapMember = (memberId) => {
return this._setPropertyForMember('wekanId', null, memberId);
},
};
onSubmit(evt) {
this.autorun(() => {
const handle = this.subscribe(
'user-miniprofile',
this.members().map(member => {
return member.username;
}),
);
Tracker.nonreactive(() => {
Tracker.autorun(() => {
if (
handle.ready() &&
!this.usersLoaded.get() &&
this.members().length
) {
this._refreshMembers(
this.members().map(member => {
if (!member.wekanId) {
let user = ReactiveCache.getUser({ username: member.username });
if (!user) {
user = ReactiveCache.getUser({ importUsernames: member.username });
}
if (user) {
member.wekanId = user._id;
}
}
return member;
}),
);
}
this.usersLoaded.set(handle.ready());
});
});
});
});
Template.importMapMembers.onDestroyed(function () {
if (_importMapMembersTpl === this) {
_importMapMembersTpl = null;
}
});
Template.importMapMembers.helpers({
usersLoaded() {
return Template.instance().usersLoaded;
},
members() {
return Template.instance().members();
},
});
Template.importMapMembers.events({
submit(evt, tpl) {
evt.preventDefault();
this.parentComponent().nextStep();
const importTpl = findParentTemplateInstance(tpl, 'import');
if (importTpl) {
importTpl.nextStep();
}
},
events() {
return [
{
submit: this.onSubmit,
'click .js-select-member'(evt) {
const memberToMap = this.currentData();
if (memberToMap.wekan) {
// todo xxx ask for confirmation?
this.unmapMember(memberToMap.id);
} else {
this.setSelectedMember(memberToMap.id);
Popup.open('importMapMembersAdd')(evt);
}
},
},
];
'click .js-select-member'(evt, tpl) {
const memberToMap = Template.currentData();
if (memberToMap.wekan) {
// todo xxx ask for confirmation?
tpl.unmapMember(memberToMap.id);
} else {
tpl.setSelectedMember(memberToMap.id);
Popup.open('importMapMembersAdd')(evt);
}
},
}).register('importMapMembers');
});
BlazeComponent.extendComponent({
onRendered() {
this.find('.js-map-member input').focus();
},
// Global reactive variables for import member popup
const importMemberPopupState = {
searching: new ReactiveVar(false),
searchResults: new ReactiveVar([]),
noResults: new ReactiveVar(false),
searchTimeout: null,
};
onSelectUser() {
Popup.getOpenerComponent(5).mapSelectedMember(this.currentData().__originalId);
Template.importMapMembersAddPopup.onCreated(function () {
this.searching = importMemberPopupState.searching;
this.searchResults = importMemberPopupState.searchResults;
this.noResults = importMemberPopupState.noResults;
this.searchTimeout = null;
this.searching.set(false);
this.searchResults.set([]);
this.noResults.set(false);
});
Template.importMapMembersAddPopup.onRendered(function () {
this.find('.js-search-member-input').focus();
});
Template.importMapMembersAddPopup.onDestroyed(function () {
if (this.searchTimeout) {
clearTimeout(this.searchTimeout);
}
this.searching.set(false);
});
function importPerformSearch(tpl, query) {
if (!query || query.length < 2) {
tpl.searchResults.set([]);
tpl.noResults.set(false);
return;
}
tpl.searching.set(true);
tpl.noResults.set(false);
const results = UserSearchIndex.search(query, { limit: 20 }).fetch();
tpl.searchResults.set(results);
tpl.searching.set(false);
if (results.length === 0) {
tpl.noResults.set(true);
}
}
Template.importMapMembersAddPopup.events({
'click .js-select-import'(event, tpl) {
if (_importMapMembersTpl) {
_importMapMembersTpl.mapSelectedMember(Template.currentData().__originalId);
}
Popup.back();
},
'keyup .js-search-member-input'(event, tpl) {
const query = event.target.value.trim();
events() {
return [
{
'click .js-select-import': this.onSelectUser,
},
];
if (tpl.searchTimeout) {
clearTimeout(tpl.searchTimeout);
}
tpl.searchTimeout = setTimeout(() => {
importPerformSearch(tpl, query);
}, 300);
},
}).register('importMapMembersAddPopup');
});
Template.importMapMembersAddPopup.helpers({
searchIndex: () => UserSearchIndex,
})
searchResults() {
return importMemberPopupState.searchResults.get();
},
searching() {
return importMemberPopupState.searching;
},
noResults() {
return importMemberPopupState.noResults;
},
});
+11
View File
@@ -0,0 +1,11 @@
template(name="basicTabs")
.basicTabs-container(class="{{name}}")
ul.tabs-list
each tabs
li.tab-item(class="{{isActiveTab slug}} {{class}}" data-tab="{{slug}}") {{name}}
.tabs-content-container
+Template.contentBlock
template(name="tabContent")
section.tabs-content(class="{{isActiveTab slug}}" data-tab="{{slug}}")
+Template.contentBlock
Loaded 100 of 1189 files, more files were not shown because too many files have changed in this diff. Show more